mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-13 11:20:31 +00:00
Optimise UBO processing for contiguous memory access
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,5 @@ namespace osu.Framework.Graphics.Rendering.Deferred.Events
|
||||
Flush,
|
||||
|
||||
DrawNodeAction,
|
||||
SetUniformBufferRange
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user