Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ END TEMPLATE-->

### New features

* Add batched Box2 / Box2Rotated drawing methods to Clyde WorldHandle.
* Completion filter now works by Contains instead of StartsWith

### Bugfixes
Expand Down
50 changes: 50 additions & 0 deletions Robust.Client/Graphics/Clyde/Clyde.RenderHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ internal sealed class RenderHandle : IRenderHandle
{
private readonly Clyde _clyde;
private readonly IEntityManager _entities;
private readonly ClydeTexture _whiteClydeTexture;
private readonly Box2 _whiteUv;

public DrawingHandleScreen DrawingHandleScreen { get; }
public DrawingHandleWorld DrawingHandleWorld { get; }
Expand All @@ -29,6 +31,8 @@ public RenderHandle(Clyde clyde, IEntityManager entities)
_entities = entities;

var white = _clyde.GetStockTexture(ClydeStockTexture.White);
_whiteClydeTexture = ExtractTexture(white, null, out var whiteBounds);
_whiteUv = WorldTextureBoundsToUV(_whiteClydeTexture, whiteBounds);
DrawingHandleScreen = new DrawingHandleScreenImpl(white, this);
DrawingHandleWorld = new DrawingHandleWorldImpl(white, this);
}
Expand Down Expand Up @@ -93,6 +97,30 @@ public void DrawTextureWorld(Texture texture, Vector2 bl, Vector2 br, Vector2 tl
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
}

public void DrawTextureWorldBatch(Texture texture, ReadOnlySpan<WorldTextureRect> rects, Color modulate)
{
var clydeTexture = ExtractTexture(texture, null, out var csr);
var sr = WorldTextureBoundsToUV(clydeTexture, csr);
_clyde.DrawTextureBatch(clydeTexture.TextureId, rects, modulate, in sr);
}

public void DrawTextureWorldBatchUnmodulated(Texture texture, ReadOnlySpan<WorldTextureRect> rects)
{
var clydeTexture = ExtractTexture(texture, null, out var csr);
var sr = WorldTextureBoundsToUV(clydeTexture, csr);
_clyde.DrawTextureBatchUnmodulated(clydeTexture.TextureId, rects, in sr);
}

public void DrawRectWorldBatch(ReadOnlySpan<WorldRect> rects, Color modulate)
{
_clyde.DrawRectBatch(_whiteClydeTexture.TextureId, rects, modulate, in _whiteUv);
}

public void DrawRectWorldBatchUnmodulated(ReadOnlySpan<WorldRect> rects)
{
_clyde.DrawRectBatchUnmodulated(_whiteClydeTexture.TextureId, rects, in _whiteUv);
}

internal static Box2 WorldTextureBoundsToUV(ClydeTexture texture, UIBox2 csr)
{
var (w, h) = texture.Size;
Expand Down Expand Up @@ -488,6 +516,16 @@ public override void DrawRect(Box2 rect, Color color, bool filled = true)
}
}

public override void DrawRects(ReadOnlySpan<WorldRect> rects)
{
_renderHandle.DrawRectWorldBatch(rects, Modulate);
}

public override void DrawRectsUnmodulated(ReadOnlySpan<WorldRect> rects)
{
_renderHandle.DrawRectWorldBatchUnmodulated(rects);
}

public override void DrawRect(in Box2Rotated rect, Color color, bool filled = true)
{
if (filled)
Expand Down Expand Up @@ -539,6 +577,18 @@ public override void DrawTextureRectRegion(Texture texture, in Box2Rotated quad,
quad.TopLeft, quad.TopRight, color, in subRegion);
}

/// <inheritdoc />
public override void DrawTextureRects(Texture texture, ReadOnlySpan<WorldTextureRect> rects)
{
_renderHandle.DrawTextureWorldBatch(texture, rects, Modulate);
}

/// <inheritdoc />
public override void DrawTextureRectsUnmodulated(Texture texture, ReadOnlySpan<WorldTextureRect> rects)
{
_renderHandle.DrawTextureWorldBatchUnmodulated(texture, rects);
}

public override void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, Texture texture,
ReadOnlySpan<DrawVertexUV2DColor> vertices)
{
Expand Down
183 changes: 179 additions & 4 deletions Robust.Client/Graphics/Clyde/Clyde.Rendering.cs
Original file line number Diff line number Diff line change
Expand Up @@ -620,16 +620,191 @@ private void DrawTexture(ClydeHandle texture, Vector2 bl, Vector2 br, Vector2 tl

// TODO: split batch if necessary.
var vIdx = BatchVertexIndex;
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, new Vector2(0, 0), modulate);
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, new Vector2(1, 0), modulate);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, new Vector2(1, 1), modulate);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, new Vector2(0, 1), modulate);
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, Vector2.Zero, modulate);
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, Vector2.UnitX, modulate);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, Vector2.One, modulate);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, Vector2.UnitY, modulate);
BatchVertexIndex += 4;
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);

_debugStats.LastClydeDrawCalls += 1;
}

private void DrawTextureBatch(
ClydeHandle texture,
ReadOnlySpan<WorldTextureRect> rects,
Color modulate,
in Box2 texCoords)
{
DrawTextureBatch(texture, rects, modulate, in texCoords, true);
}

private void DrawTextureBatchUnmodulated(
ClydeHandle texture,
ReadOnlySpan<WorldTextureRect> rects,
in Box2 texCoords)
{
DrawTextureBatch(texture, rects, Color.White, in texCoords, false);
}

private void DrawTextureBatch(
ClydeHandle texture,
ReadOnlySpan<WorldTextureRect> rects,
Color modulate,
in Box2 texCoords,
bool applyModulation)
{
if (rects.Length == 0)
return;

// Avoids calling EnsureBatchSpaceAvailable / EnsureBatchState for each individual quad.
var primitiveType = GetQuadBatchPrimitiveType();
var indexCount = GetQuadBatchIndexCount();
var rectIndex = 0;

while (rectIndex < rects.Length)
{
var availableQuads = GetAvailableBatchQuads(indexCount);

if (availableQuads <= 0)
{
FlushBatchQueue();
continue;
}

EnsureBatchState(texture, true, primitiveType, _queuedShader);

availableQuads = GetAvailableBatchQuads(indexCount);

if (availableQuads <= 0)
{
FlushBatchQueue();
continue;
}

var count = Math.Min(availableQuads, rects.Length - rectIndex);

for (var i = 0; i < count; i++)
{
ref readonly var rect = ref rects[rectIndex + i];
var color = rect.Modulate ?? Color.White;

if (applyModulation)
color *= modulate;

// Can probably SIMD this more somehow but future concern.
var quad = rect.Quad;
var transform = quad.Transform * _currentMatrixModel;

var bl = Vector2.Transform(quad.Box.BottomLeft, transform);
var br = Vector2.Transform(quad.Box.BottomRight, transform);
var tr = Vector2.Transform(quad.Box.TopRight, transform);
var tl = tr + bl - br;

var vIdx = BatchVertexIndex;
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, Vector2.Zero, color);
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, Vector2.UnitX, color);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, Vector2.One, color);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, Vector2.UnitY, color);
BatchVertexIndex += 4;
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);
}

rectIndex += count;
_debugStats.LastClydeDrawCalls += count;
}
}

private void DrawRectBatch(
ClydeHandle texture,
ReadOnlySpan<WorldRect> rects,
Color modulate,
in Box2 texCoords)
{
DrawRectBatch(texture, rects, modulate, in texCoords, true);
}

private void DrawRectBatchUnmodulated(
ClydeHandle texture,
ReadOnlySpan<WorldRect> rects,
in Box2 texCoords)
{
DrawRectBatch(texture, rects, Color.White, in texCoords, false);
}

private void DrawRectBatch(
ClydeHandle texture,
ReadOnlySpan<WorldRect> rects,
Color modulate,
in Box2 texCoords,
bool applyModulation)
{
if (rects.Length == 0)
return;

var primitiveType = GetQuadBatchPrimitiveType();
var indexCount = GetQuadBatchIndexCount();
var rectIndex = 0;

while (rectIndex < rects.Length)
{
var availableQuads = GetAvailableBatchQuads(indexCount);

if (availableQuads <= 0)
{
FlushBatchQueue();
continue;
}

EnsureBatchState(texture, true, primitiveType, _queuedShader);

availableQuads = GetAvailableBatchQuads(indexCount);

if (availableQuads <= 0)
{
FlushBatchQueue();
continue;
}

var count = Math.Min(availableQuads, rects.Length - rectIndex);

for (var i = 0; i < count; i++)
{
ref readonly var rect = ref rects[rectIndex + i];
var color = rect.Color;

if (applyModulation)
color *= modulate;

var box = rect.Rect;

var bl = Vector2.Transform(box.BottomLeft, _currentMatrixModel);
var br = Vector2.Transform(box.BottomRight, _currentMatrixModel);
var tr = Vector2.Transform(box.TopRight, _currentMatrixModel);
var tl = tr + bl - br;

var vIdx = BatchVertexIndex;
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, Vector2.Zero, color);
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, Vector2.UnitX, color);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, Vector2.One, color);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, Vector2.UnitY, color);
BatchVertexIndex += 4;
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);
}

rectIndex += count;
_debugStats.LastClydeDrawCalls += count;
}
}

private int GetAvailableBatchQuads(int indexCount)
{
// The vertex size is so comically high you're probably never hitting it.
var availableVertices = Math.Max(0, BatchVertexData.Length - BatchVertexIndex - 1);
var availableIndices = Math.Max(0, BatchIndexData.Length - BatchIndexIndex);
return Math.Min(availableVertices / 4, availableIndices / indexCount);
}

private void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, ClydeHandle textureId,
ReadOnlySpan<ushort> indices, ReadOnlySpan<Vertex2D> vertices)
{
Expand Down
34 changes: 34 additions & 0 deletions Robust.Client/Graphics/Drawing/DrawingHandleWorld.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Numerics;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
Expand All @@ -22,6 +23,19 @@ protected DrawingHandleWorld(Texture white) : base(white)
/// <param name="filled">Is it filled with color, or just the border lines?</param>
public abstract void DrawRect(Box2 rect, Color color, bool filled = true);

/// <summary>
/// Draws multiple filled, untextured colored rectangles to the world. All rectangles use the current transform.
/// </summary>
/// <remarks>
/// This is the batched equivalent of repeated filled <see cref="DrawRect(Box2, Color, bool)"/> calls.
/// </remarks>
public abstract void DrawRects(ReadOnlySpan<WorldRect> rects);

/// <summary>
/// Draws multiple filled, untextured colored rectangles without multiplying by the handle modulation color.
/// </summary>
public abstract void DrawRectsUnmodulated(ReadOnlySpan<WorldRect> rects);

/// <summary>
/// Draws an untextured colored rectangle to the world.The coordinate system is right handed.
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
Expand Down Expand Up @@ -58,6 +72,16 @@ public abstract void DrawTextureRectRegion(Texture texture, Box2 quad,
public abstract void DrawTextureRectRegion(Texture texture, in Box2Rotated quad,
Color? modulate = null, UIBox2? subRegion = null);

/// <summary>
/// Draws multiple rotated rectangles for the same texture.
/// </summary>
public abstract void DrawTextureRects(Texture texture, ReadOnlySpan<WorldTextureRect> rects);

/// <summary>
/// Draws multiple rotated rectangles for the same texture without multiplying by the handle modulation color.
/// </summary>
public abstract void DrawTextureRectsUnmodulated(Texture texture, ReadOnlySpan<WorldTextureRect> rects);

private Box2 GetQuad(Texture texture, Vector2 position)
{
return Box2.FromDimensions(position, texture.Size / (float)Ppm);
Expand Down Expand Up @@ -133,4 +157,14 @@ public void DrawTextureRect(Texture texture, in Box2Rotated quad, Color? modulat
DrawTextureRectRegion(texture, in quad, modulate);
}
}

/// <summary>
/// A rotated rectangle for batched world texture drawing.
/// </summary>
public readonly record struct WorldTextureRect(Box2Rotated Quad, Color? Modulate = null);

/// <summary>
/// An axis-aligned rectangle for batched world rectangle drawing.
/// </summary>
public readonly record struct WorldRect(Box2 Rect, Color Color);
}
Loading