Optimise UBO processing for contiguous memory access

This commit is contained in:
Dan Balasescu
2024-02-26 15:06:02 +09:00
parent 71c82ea863
commit d6c85be456
7 changed files with 157 additions and 145 deletions

View File

@@ -36,16 +36,16 @@ namespace osu.Framework.Benchmarks
[Benchmark]
public int Read()
{
var reader = filledEventList.CreateReader();
var enumerator = filledEventList.CreateEnumerator();
int totalVertices = 0;
while (reader.Next())
while (enumerator.Next())
{
switch (reader.CurrentType())
switch (enumerator.CurrentType())
{
case RenderEventType.Flush:
ref FlushEvent e = ref reader.Current<FlushEvent>();
ref FlushEvent e = ref enumerator.Current<FlushEvent>();
totalVertices += e.VertexCount;
break;
}

View File

@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using osu.Framework.Graphics.Rendering.Deferred.Allocation;
using osu.Framework.Graphics.Rendering.Deferred.Events;
@@ -34,16 +35,6 @@ namespace osu.Framework.Graphics.Rendering.Deferred
where T : unmanaged, IRenderEvent
=> events.Add(createEvent(renderEvent));
/// <summary>
/// Replaces the current event referenced by the <see cref="EventListReader"/> with a new one.
/// </summary>
/// <param name="reader">The <see cref="EventListReader"/>.</param>
/// <param name="newEvent">The new render event.</param>
/// <typeparam name="T">The new event type.</typeparam>
public void ReplaceCurrent<T>(EventListReader reader, in T newEvent)
where T : unmanaged, IRenderEvent
=> events[reader.CurrentIndex()] = createEvent(newEvent);
private MemoryReference createEvent<T>(in T renderEvent)
where T : unmanaged, IRenderEvent
{
@@ -61,8 +52,83 @@ namespace osu.Framework.Graphics.Rendering.Deferred
/// <summary>
/// Creates a reader of this <see cref="EventList"/>.
/// </summary>
/// <returns>The <see cref="EventListReader"/>.</returns>
public EventListReader CreateReader()
=> new EventListReader(allocator, events);
/// <returns>The <see cref="Enumerator"/>.</returns>
public Enumerator CreateEnumerator()
=> new Enumerator(this);
/// <summary>
/// Reads an <see cref="EventList"/>. Semantically, this is very similar to <see cref="IEnumerator{T}"/>.
/// </summary>
internal ref struct Enumerator
{
private readonly EventList list;
private int eventIndex;
private Span<byte> eventData = Span<byte>.Empty;
public Enumerator(EventList list)
{
this.list = list;
eventIndex = 0;
}
/// <summary>
/// Advances to the next (or first) event in the list.
/// </summary>
/// <returns>Whether an event can be read.</returns>
public bool Next()
{
if (eventIndex < list.events.Count)
{
eventData = list.allocator.GetRegion(list.events[eventIndex]);
eventIndex++;
return true;
}
eventData = Span<byte>.Empty;
return false;
}
/// <summary>
/// Reads the current event type.
/// </summary>
/// <remarks>
/// Not valid for use if <see cref="Next"/> returns <c>false</c>.
/// </remarks>
public readonly ref RenderEventType CurrentType()
=> ref MemoryMarshal.AsRef<RenderEventType>(eventData);
/// <summary>
/// Reads the current event.
/// </summary>
/// <typeparam name="T">The expected event type.</typeparam>
/// <remarks>
/// Not valid for use if <see cref="Next"/> returns <c>false</c>.
/// </remarks>
public readonly ref T Current<T>()
where T : unmanaged, IRenderEvent
=> ref MemoryMarshal.AsRef<T>(eventData[1..]);
/// <summary>
/// Replaces the current event with a new one.
/// </summary>
/// <param name="newEvent">The new render event.</param>
/// <typeparam name="T">The new event type.</typeparam>
public void Replace<T>(T newEvent)
where T : unmanaged, IRenderEvent
{
if (Unsafe.SizeOf<T>() <= eventData.Length)
{
// Fast path where we can maintain contiguous data references.
eventData[0] = (byte)newEvent.Type;
Unsafe.WriteUnaligned(ref eventData[1], newEvent);
}
else
{
// Slow path.
eventData = list.allocator.GetRegion(list.events[eventIndex] = list.createEvent(newEvent));
}
}
}
}
}

View File

@@ -1,73 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using osu.Framework.Graphics.Rendering.Deferred.Allocation;
using osu.Framework.Graphics.Rendering.Deferred.Events;
namespace osu.Framework.Graphics.Rendering.Deferred
{
/// <summary>
/// Reads an <see cref="EventList"/>. Semantically, this is very similar to <see cref="IEnumerator{T}"/>.
/// </summary>
internal ref struct EventListReader
{
private readonly ResourceAllocator allocator;
private readonly List<MemoryReference> events = new List<MemoryReference>();
private int eventIndex;
private Span<byte> eventData = Span<byte>.Empty;
public EventListReader(ResourceAllocator allocator, List<MemoryReference> events)
{
this.allocator = allocator;
this.events = events;
eventIndex = 0;
}
/// <summary>
/// Advances to the next (or first) event in the list.
/// </summary>
/// <returns>Whether an event can be read.</returns>
public bool Next()
{
if (eventIndex < events.Count)
{
eventData = allocator.GetRegion(events[eventIndex]);
eventIndex++;
return true;
}
eventData = Span<byte>.Empty;
return false;
}
/// <summary>
/// Reads the current event type.
/// </summary>
/// <remarks>
/// Not valid for use if <see cref="Next"/> returns <c>false</c>.
/// </remarks>
public readonly ref RenderEventType CurrentType()
=> ref MemoryMarshal.AsRef<RenderEventType>(eventData);
/// <summary>
/// Reads the current event.
/// </summary>
/// <typeparam name="T">The expected event type.</typeparam>
/// <remarks>
/// Not valid for use if <see cref="Next"/> returns <c>false</c>.
/// </remarks>
public readonly ref T Current<T>()
where T : unmanaged, IRenderEvent
=> ref MemoryMarshal.AsRef<T>(eventData[1..]);
/// <summary>
/// The index of the current event in the list.
/// </summary>
public int CurrentIndex()
=> eventIndex - 1;
}
}

View File

@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using osu.Framework.Graphics.Rendering.Deferred.Allocation;
@@ -39,21 +38,21 @@ namespace osu.Framework.Graphics.Rendering.Deferred
if (string.IsNullOrEmpty(FrameworkEnvironment.DeferredRendererEventsOutputPath))
return;
EventListReader reader = context.RenderEvents.CreateReader();
EventList.Enumerator enumerator = context.RenderEvents.CreateEnumerator();
StringBuilder builder = new StringBuilder();
int indent = 0;
while (reader.Next())
while (enumerator.Next())
{
string info;
int indentChange = 0;
switch (reader.CurrentType())
switch (enumerator.CurrentType())
{
case RenderEventType.DrawNodeAction:
{
ref DrawNodeActionEvent e = ref reader.Current<DrawNodeActionEvent>();
ref DrawNodeActionEvent e = ref enumerator.Current<DrawNodeActionEvent>();
info = $"DrawNode.{e.Action} ({context.Dereference<DrawNode>(e.DrawNode)})";
@@ -73,7 +72,7 @@ namespace osu.Framework.Graphics.Rendering.Deferred
default:
{
info = $"{reader.CurrentType().ToString()}";
info = $"{enumerator.CurrentType().ToString()}";
break;
}
}
@@ -88,15 +87,15 @@ namespace osu.Framework.Graphics.Rendering.Deferred
private void processUploads()
{
EventListReader reader = context.RenderEvents.CreateReader();
EventList.Enumerator enumerator = context.RenderEvents.CreateEnumerator();
while (reader.Next())
while (enumerator.Next())
{
switch (reader.CurrentType())
switch (enumerator.CurrentType())
{
case RenderEventType.AddPrimitiveToBatch:
{
ref AddPrimitiveToBatchEvent e = ref reader.Current<AddPrimitiveToBatchEvent>();
ref AddPrimitiveToBatchEvent e = ref enumerator.Current<AddPrimitiveToBatchEvent>();
IDeferredVertexBatch batch = context.Dereference<IDeferredVertexBatch>(e.VertexBatch);
batch.Write(e.Memory);
break;
@@ -104,16 +103,16 @@ namespace osu.Framework.Graphics.Rendering.Deferred
case RenderEventType.SetUniformBufferData:
{
ref SetUniformBufferDataEvent e = ref reader.Current<SetUniformBufferDataEvent>();
ref SetUniformBufferDataEvent e = ref enumerator.Current<SetUniformBufferDataEvent>();
IDeferredUniformBuffer buffer = context.Dereference<IDeferredUniformBuffer>(e.Buffer);
UniformBufferReference range = buffer.Write(e.Memory);
context.RenderEvents.ReplaceCurrent(reader, new SetUniformBufferRangeEvent(e.Buffer, range));
UniformBufferReference range = buffer.Write(e.Data.Memory);
enumerator.Replace(e with { Data = new UniformBufferData(range) });
break;
}
case RenderEventType.SetShaderStorageBufferObjectData:
{
ref SetShaderStorageBufferObjectDataEvent e = ref reader.Current<SetShaderStorageBufferObjectDataEvent>();
ref SetShaderStorageBufferObjectDataEvent e = ref enumerator.Current<SetShaderStorageBufferObjectDataEvent>();
IDeferredShaderStorageBufferObject buffer = context.Dereference<IDeferredShaderStorageBufferObject>(e.Buffer);
buffer.Write(e.Index, e.Memory);
break;
@@ -127,74 +126,70 @@ namespace osu.Framework.Graphics.Rendering.Deferred
private void processEvents()
{
EventListReader reader = context.RenderEvents.CreateReader();
EventList.Enumerator enumerator = context.RenderEvents.CreateEnumerator();
while (reader.Next())
while (enumerator.Next())
{
switch (reader.CurrentType())
switch (enumerator.CurrentType())
{
case RenderEventType.SetFrameBuffer:
processEvent(reader.Current<SetFrameBufferEvent>());
processEvent(enumerator.Current<SetFrameBufferEvent>());
break;
case RenderEventType.ResizeFrameBuffer:
processEvent(reader.Current<ResizeFrameBufferEvent>());
processEvent(enumerator.Current<ResizeFrameBufferEvent>());
break;
case RenderEventType.SetShader:
processEvent(reader.Current<SetShaderEvent>());
processEvent(enumerator.Current<SetShaderEvent>());
break;
case RenderEventType.SetTexture:
processEvent(reader.Current<SetTextureEvent>());
processEvent(enumerator.Current<SetTextureEvent>());
break;
case RenderEventType.SetUniformBuffer:
processEvent(reader.Current<SetUniformBufferEvent>());
processEvent(enumerator.Current<SetUniformBufferEvent>());
break;
case RenderEventType.Clear:
processEvent(reader.Current<ClearEvent>());
processEvent(enumerator.Current<ClearEvent>());
break;
case RenderEventType.SetDepthInfo:
processEvent(reader.Current<SetDepthInfoEvent>());
processEvent(enumerator.Current<SetDepthInfoEvent>());
break;
case RenderEventType.SetScissor:
processEvent(reader.Current<SetScissorEvent>());
processEvent(enumerator.Current<SetScissorEvent>());
break;
case RenderEventType.SetScissorState:
processEvent(reader.Current<SetScissorStateEvent>());
processEvent(enumerator.Current<SetScissorStateEvent>());
break;
case RenderEventType.SetStencilInfo:
processEvent(reader.Current<SetStencilInfoEvent>());
processEvent(enumerator.Current<SetStencilInfoEvent>());
break;
case RenderEventType.SetViewport:
processEvent(reader.Current<SetViewportEvent>());
processEvent(enumerator.Current<SetViewportEvent>());
break;
case RenderEventType.SetBlend:
processEvent(reader.Current<SetBlendEvent>());
processEvent(enumerator.Current<SetBlendEvent>());
break;
case RenderEventType.SetBlendMask:
processEvent(reader.Current<SetBlendMaskEvent>());
processEvent(enumerator.Current<SetBlendMaskEvent>());
break;
case RenderEventType.Flush:
processEvent(reader.Current<FlushEvent>());
break;
case RenderEventType.SetUniformBufferRange:
processEvent(reader.Current<SetUniformBufferRangeEvent>());
processEvent(enumerator.Current<FlushEvent>());
break;
case RenderEventType.SetUniformBufferData:
Debug.Fail($"Should be replaced by {nameof(SetUniformBufferRangeEvent)} during upload.");
processEvent(enumerator.Current<SetUniformBufferDataEvent>());
break;
}
}
@@ -242,12 +237,12 @@ namespace osu.Framework.Graphics.Rendering.Deferred
private void processEvent(in FlushEvent e)
=> context.Dereference<IDeferredVertexBatch>(e.VertexBatch).Draw(e.VertexCount);
private void processEvent(in SetUniformBufferRangeEvent e)
private void processEvent(in SetUniformBufferDataEvent e)
{
IDeferredUniformBuffer buffer = context.Dereference<IDeferredUniformBuffer>(e.UniformBuffer);
IDeferredUniformBuffer buffer = context.Dereference<IDeferredUniformBuffer>(e.Buffer);
buffer.Activate(e.Range.Chunk);
graphics.SetUniformBufferOffset(buffer, (uint)e.Range.OffsetInChunk);
buffer.Activate(e.Data.Range.Chunk);
graphics.SetUniformBufferOffset(buffer, (uint)e.Data.Range.OffsetInChunk);
}
}
}

View File

@@ -24,6 +24,5 @@ namespace osu.Framework.Graphics.Rendering.Deferred.Events
Flush,
DrawNodeAction,
SetUniformBufferRange
}
}

View File

@@ -2,18 +2,55 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Runtime.InteropServices;
using osu.Framework.Graphics.Rendering.Deferred.Allocation;
namespace osu.Framework.Graphics.Rendering.Deferred.Events
{
internal readonly record struct SetUniformBufferDataEvent(ResourceReference Buffer, MemoryReference Memory) : IRenderEvent
internal readonly record struct SetUniformBufferDataEvent(ResourceReference Buffer, UniformBufferData Data) : IRenderEvent
{
public RenderEventType Type => RenderEventType.SetUniformBufferData;
public static SetUniformBufferDataEvent Create<T>(DeferredRenderer renderer, IDeferredUniformBuffer uniformBuffer, T data)
where T : unmanaged, IEquatable<T>
{
return new SetUniformBufferDataEvent(renderer.Context.Reference(uniformBuffer), renderer.Context.AllocateObject(data));
return new SetUniformBufferDataEvent(renderer.Context.Reference(uniformBuffer), new UniformBufferData(renderer.Context.AllocateObject(data)));
}
}
[StructLayout(LayoutKind.Explicit)]
internal readonly struct UniformBufferData : IEquatable<UniformBufferData>
{
[FieldOffset(0)]
public readonly MemoryReference Memory;
[FieldOffset(0)]
public readonly UniformBufferReference Range;
public UniformBufferData(MemoryReference memory)
{
Memory = memory;
}
public UniformBufferData(UniformBufferReference range)
{
Range = range;
}
public bool Equals(UniformBufferData other)
{
return Memory.Equals(other.Memory)
&& Range.Equals(other.Range);
}
public override bool Equals(object? obj)
{
return obj is UniformBufferData other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(Memory, Range);
}
}
}

View File

@@ -1,12 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Rendering.Deferred.Allocation;
namespace osu.Framework.Graphics.Rendering.Deferred.Events
{
internal readonly record struct SetUniformBufferRangeEvent(ResourceReference UniformBuffer, UniformBufferReference Range) : IRenderEvent
{
public RenderEventType Type => RenderEventType.SetUniformBufferRange;
}
}