mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
1298 lines
47 KiB
C#
1298 lines
47 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using OpenTK.Graphics.OpenGL;
|
|
using Robust.Client.GameObjects;
|
|
using Robust.Client.Graphics.ClientEye;
|
|
using Robust.Client.Graphics.Drawing;
|
|
using Robust.Client.Graphics.Overlays;
|
|
using Robust.Client.Graphics.Shaders;
|
|
using Robust.Client.Interfaces.Graphics;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Client.Utility;
|
|
using Robust.Shared.Interfaces.GameObjects;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Client.Graphics.Clyde
|
|
{
|
|
internal partial class Clyde
|
|
{
|
|
private RenderHandle _renderHandle;
|
|
|
|
/// <summary>
|
|
/// Are we current rendering screen space or world space? Some code works differently between the two.
|
|
/// </summary>
|
|
private CurrentSpace _currentSpace;
|
|
|
|
private bool _lightingReady;
|
|
|
|
/// <summary>
|
|
/// The current model matrix we would use.
|
|
/// Necessary since certain drawing operations mess with it still.
|
|
/// </summary>
|
|
private Matrix3 _currentModelMatrix = Matrix3.Identity;
|
|
|
|
// The amount of quads we can render with ushort indices, leaving open 65536 for primitive restart.
|
|
private const ushort MaxBatchQuads = (2 << 13) - 1; // In human terms: (2**16/4)-1 = 16383
|
|
|
|
private readonly Vertex2D[] BatchVertexData = new Vertex2D[MaxBatchQuads * 4];
|
|
|
|
// Need 5 indices per quad: 4 to draw the quad with triangle strips and another one as primitive restart.
|
|
private readonly ushort[] BatchIndexData = new ushort[MaxBatchQuads * 5];
|
|
|
|
private int BatchIndex;
|
|
|
|
private ClydeHandle? BatchingTexture;
|
|
|
|
// We break batching when modulate changes too.
|
|
// This simplifies the rendering and catches most cases for now.
|
|
// For example all the walls have a single color.
|
|
// Yes this can be optimized later.
|
|
private Color? BatchingModulate;
|
|
|
|
/// <summary>
|
|
/// If true, re-allocate buffer objects with BufferData instead of using BufferSubData.
|
|
/// </summary>
|
|
private bool _reallocateBuffers;
|
|
|
|
private ProjViewMatrices _currentMatrices;
|
|
|
|
private ClydeHandle _currentShader;
|
|
|
|
private float _renderTime;
|
|
|
|
private bool _isScissoring;
|
|
|
|
private readonly List<SpriteComponent> _sortingSpritesList = new List<SpriteComponent>();
|
|
|
|
public void Render()
|
|
{
|
|
var size = ScreenSize;
|
|
if (size.X == 0 || size.Y == 0)
|
|
return;
|
|
|
|
_debugStats.Reset();
|
|
|
|
// Basic pre-render busywork.
|
|
// Clear screen to black.
|
|
ClearFramebuffer(Color.Black);
|
|
|
|
// Update shared UBOs.
|
|
_updateUniformConstants();
|
|
|
|
_setSpace(CurrentSpace.ScreenSpace);
|
|
|
|
// Short path to render only the splash.
|
|
if (_drawingSplash)
|
|
{
|
|
_drawSplash(_renderHandle);
|
|
_flushRenderHandle(_renderHandle);
|
|
SwapBuffers();
|
|
return;
|
|
}
|
|
|
|
void RenderOverlays(OverlaySpace space)
|
|
{
|
|
using (DebugGroup($"Overlays: {space}"))
|
|
{
|
|
foreach (var overlay in _overlayManager.AllOverlays
|
|
.Where(o => o.Space == space)
|
|
.OrderBy(o => o.ZIndex))
|
|
{
|
|
overlay.ClydeRender(_renderHandle);
|
|
}
|
|
|
|
_flushRenderHandle(_renderHandle);
|
|
}
|
|
}
|
|
|
|
RenderOverlays(OverlaySpace.ScreenSpaceBelowWorld);
|
|
|
|
_setSpace(CurrentSpace.WorldSpace);
|
|
|
|
// Calculate world-space AABB for camera, to cull off-screen things.
|
|
var eye = _eyeManager.CurrentEye;
|
|
var worldBounds = Box2.CenteredAround(eye.Position.Position,
|
|
_screenSize / EyeManager.PIXELSPERMETER * eye.Zoom);
|
|
|
|
using (DebugGroup("Lights"))
|
|
{
|
|
_drawLights(worldBounds);
|
|
}
|
|
|
|
using (DebugGroup("Grids"))
|
|
{
|
|
_drawGrids(worldBounds);
|
|
}
|
|
|
|
using (DebugGroup("Entities"))
|
|
{
|
|
_sortingSpritesList.Clear();
|
|
var map = _eyeManager.CurrentMap;
|
|
|
|
// So we could calculate the correct size of the entities based on the contents of their sprite...
|
|
// Or we can just assume that no entity is larger than 10x10 and get a stupid easy check.
|
|
// TODO: Make this check more accurate.
|
|
var widerBounds = worldBounds.Enlarged(5);
|
|
|
|
foreach (var sprite in _componentManager.GetAllComponents<SpriteComponent>())
|
|
{
|
|
var entity = sprite.Owner;
|
|
if (!entity.Transform.IsMapTransform || entity.Transform.MapID != map ||
|
|
!widerBounds.Contains(entity.Transform.WorldPosition) || !sprite.Visible)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_sortingSpritesList.Add(sprite);
|
|
}
|
|
|
|
_sortingSpritesList.Sort((a, b) =>
|
|
{
|
|
var cmp = ((int) a.DrawDepth).CompareTo((int) b.DrawDepth);
|
|
if (cmp != 0)
|
|
{
|
|
return cmp;
|
|
}
|
|
|
|
cmp = a.RenderOrder.CompareTo(b.RenderOrder);
|
|
|
|
if (cmp != 0)
|
|
{
|
|
return cmp;
|
|
}
|
|
|
|
return a.Owner.Uid.CompareTo(b.Owner.Uid);
|
|
});
|
|
|
|
foreach (var sprite in _sortingSpritesList)
|
|
{
|
|
Vector2i roundedPos = default;
|
|
if (sprite.PostShader != null)
|
|
{
|
|
_renderHandle.UseRenderTarget(EntityPostRenderTarget);
|
|
_renderHandle.Clear(new Color());
|
|
// Calculate viewport so that the entity thinks it's drawing to the same position,
|
|
// which is necessary for light application,
|
|
// but it's ACTUALLY drawing into the center of the render target.
|
|
var spritePos = sprite.Owner.Transform.WorldPosition;
|
|
var screenPos = _eyeManager.WorldToScreen(spritePos);
|
|
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
|
|
var flippedPos = new Vector2i(roundedX, ScreenSize.Y - roundedY);
|
|
flippedPos -= EntityPostRenderTarget.Size / 2;
|
|
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, ScreenSize));
|
|
}
|
|
|
|
sprite.OpenGLRender(_renderHandle.DrawingHandleWorld);
|
|
|
|
if (sprite.PostShader != null)
|
|
{
|
|
_renderHandle.UseRenderTarget(null);
|
|
_renderHandle.Viewport(Box2i.FromDimensions(Vector2i.Zero, ScreenSize));
|
|
|
|
_renderHandle.UseShader(sprite.PostShader);
|
|
_renderHandle.SetSpace(CurrentSpace.ScreenSpace);
|
|
_renderHandle.SetModelTransform(Matrix3.Identity);
|
|
|
|
var rounded = roundedPos - EntityPostRenderTarget.Size / 2;
|
|
|
|
var box = UIBox2i.FromDimensions(rounded, EntityPostRenderTarget.Size);
|
|
|
|
_renderHandle.DrawTexture(EntityPostRenderTarget.Texture, box.BottomLeft,
|
|
box.TopRight, Color.White, null, 0);
|
|
|
|
_renderHandle.SetSpace(CurrentSpace.WorldSpace);
|
|
_renderHandle.UseShader(null);
|
|
}
|
|
}
|
|
|
|
_flushRenderHandle(_renderHandle);
|
|
}
|
|
|
|
RenderOverlays(OverlaySpace.WorldSpace);
|
|
|
|
_lightingReady = false;
|
|
|
|
_setSpace(CurrentSpace.ScreenSpace);
|
|
|
|
RenderOverlays(OverlaySpace.ScreenSpace);
|
|
|
|
using (DebugGroup("UI"))
|
|
{
|
|
_userInterfaceManager.Render(_renderHandle);
|
|
_flushRenderHandle(_renderHandle);
|
|
}
|
|
|
|
// And finally, swap those buffers!
|
|
SwapBuffers();
|
|
}
|
|
|
|
private void _drawSplash(IRenderHandle handle)
|
|
{
|
|
var texture = _resourceCache.GetResource<TextureResource>("/Textures/Logo/logo.png").Texture;
|
|
|
|
handle.DrawingHandleScreen.DrawTexture(texture, (ScreenSize - texture.Size) / 2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates uniform constants shared to all shaders, such as time and pixel size.
|
|
/// </summary>
|
|
private void _updateUniformConstants()
|
|
{
|
|
var constants = new UniformConstants(Vector2.One / ScreenSize, _renderTime);
|
|
_writeBuffer(UniformConstantsUBO, constants);
|
|
}
|
|
|
|
private ProjViewMatrices _screenMatrices()
|
|
{
|
|
var viewMatrixScreen = Matrix3.Identity;
|
|
|
|
// Screen projection matrix.
|
|
var projMatrixScreen = Matrix3.Identity;
|
|
projMatrixScreen.R0C0 = 2f / ScreenSize.X;
|
|
projMatrixScreen.R1C1 = -2f / ScreenSize.Y;
|
|
projMatrixScreen.R0C2 = -1;
|
|
projMatrixScreen.R1C2 = 1;
|
|
|
|
return new ProjViewMatrices(projMatrixScreen, viewMatrixScreen);
|
|
}
|
|
|
|
private ProjViewMatrices _worldMatrices()
|
|
{
|
|
var eye = _eyeManager.CurrentEye;
|
|
|
|
var toScreen = _eyeManager.WorldToScreen(eye.Position.Position);
|
|
// Round camera position to a screen pixel to avoid weird issues on odd screen sizes.
|
|
toScreen = ((float) Math.Floor(toScreen.X), (float) Math.Floor(toScreen.Y));
|
|
var cameraWorldAdjusted = _eyeManager.ScreenToMap(toScreen);
|
|
|
|
var viewMatrixWorld = Matrix3.Identity;
|
|
viewMatrixWorld.R0C0 = 1 / eye.Zoom.X;
|
|
viewMatrixWorld.R1C1 = 1 / eye.Zoom.Y;
|
|
viewMatrixWorld.R0C2 = -cameraWorldAdjusted.X / eye.Zoom.X;
|
|
viewMatrixWorld.R1C2 = -cameraWorldAdjusted.Y / eye.Zoom.Y;
|
|
|
|
var projMatrixWorld = Matrix3.Identity;
|
|
projMatrixWorld.R0C0 = EyeManager.PIXELSPERMETER * 2f / ScreenSize.X;
|
|
projMatrixWorld.R1C1 = EyeManager.PIXELSPERMETER * 2f / ScreenSize.Y;
|
|
|
|
return new ProjViewMatrices(projMatrixWorld, viewMatrixWorld);
|
|
}
|
|
|
|
private void _drawLights(Box2 worldBounds)
|
|
{
|
|
if (!_lightManager.Enabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var map = _eyeManager.CurrentMap;
|
|
|
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, LightRenderTarget.ObjectHandle.Handle);
|
|
var converted = Color.FromSrgb(new Color(0.1f, 0.1f, 0.1f));
|
|
GL.ClearColor(converted.R, converted.G, converted.B, 1);
|
|
GL.Clear(ClearBufferMask.ColorBufferBit);
|
|
|
|
var (lightW, lightH) = _lightMapSize();
|
|
GL.Viewport(0, 0, lightW, lightH);
|
|
|
|
_lightShader.Use();
|
|
|
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One);
|
|
|
|
var lastRange = float.NaN;
|
|
var lastPower = float.NaN;
|
|
var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN);
|
|
Texture lastMask = null;
|
|
|
|
foreach (var component in _componentManager.GetAllComponents<PointLightComponent>())
|
|
{
|
|
if (!component.Enabled || component.Owner.Transform.MapID != map)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var transform = component.Owner.Transform;
|
|
var lightPos = transform.WorldMatrix.Transform(component.Offset);
|
|
|
|
var lightBounds = Box2.CenteredAround(lightPos, Vector2.One * component.Radius * 2);
|
|
|
|
if (!lightBounds.Intersects(worldBounds))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Texture mask = null;
|
|
var rotation = Angle.Zero;
|
|
if (component.Mask != null)
|
|
{
|
|
mask = component.Mask;
|
|
rotation = component.Rotation;
|
|
|
|
if (component.MaskAutoRotate)
|
|
{
|
|
rotation += transform.WorldRotation;
|
|
}
|
|
}
|
|
|
|
var maskTexture = mask ?? Texture.White;
|
|
if (lastMask != maskTexture)
|
|
{
|
|
var maskHandle = _loadedTextures[((ClydeTexture) maskTexture).TextureId].OpenGLObject;
|
|
GL.ActiveTexture(TextureUnit.Texture0);
|
|
GL.BindTexture(TextureTarget.Texture2D, maskHandle.Handle);
|
|
lastMask = maskTexture;
|
|
_lightShader.SetUniformTexture("lightMask", TextureUnit.Texture0);
|
|
}
|
|
|
|
if (!FloatMath.CloseTo(lastRange, component.Radius))
|
|
{
|
|
lastRange = component.Radius;
|
|
_lightShader.SetUniform("lightRange", lastRange);
|
|
}
|
|
|
|
if (!FloatMath.CloseTo(lastPower, component.Energy))
|
|
{
|
|
lastPower = component.Energy;
|
|
_lightShader.SetUniform("lightPower", lastPower);
|
|
}
|
|
|
|
if (lastColor != component.Color)
|
|
{
|
|
lastColor = component.Color;
|
|
_lightShader.SetUniform("lightColor", lastColor);
|
|
}
|
|
|
|
_lightShader.SetUniform("lightCenter", lightPos);
|
|
|
|
var offset = new Vector2(component.Radius, component.Radius);
|
|
|
|
Matrix3 matrix;
|
|
if (mask == null)
|
|
{
|
|
matrix = Matrix3.Identity;
|
|
}
|
|
else
|
|
{
|
|
// Only apply rotation if a mask is said, because else it doesn't matter.
|
|
matrix = Matrix3.CreateRotation(rotation);
|
|
}
|
|
|
|
(matrix.R0C2, matrix.R1C2) = lightPos;
|
|
|
|
_drawQuad(-offset, offset, ref matrix, _lightShader);
|
|
}
|
|
|
|
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
|
|
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
|
GL.Viewport(0, 0, ScreenSize.X, ScreenSize.Y);
|
|
|
|
_lightingReady = true;
|
|
}
|
|
|
|
private void _setProjViewMatrices(in ProjViewMatrices matrices)
|
|
{
|
|
_currentMatrices = matrices;
|
|
_writeBuffer(ProjViewUBO, matrices);
|
|
}
|
|
|
|
private void _processCommandList(RenderCommandList list)
|
|
{
|
|
foreach (ref var command in list.RenderCommands)
|
|
{
|
|
switch (command.Type)
|
|
{
|
|
case RenderCommandType.Texture:
|
|
_drawCommandTexture(ref command.Texture);
|
|
_debugStats.LastClydeDrawCalls += 1;
|
|
break;
|
|
|
|
case RenderCommandType.Line:
|
|
_flushBatchBuffer();
|
|
_drawCommandLine(ref command.Line);
|
|
_debugStats.LastClydeDrawCalls += 1;
|
|
break;
|
|
|
|
case RenderCommandType.ModelMatrix:
|
|
_currentModelMatrix = command.ModelMatrix.Matrix;
|
|
break;
|
|
|
|
case RenderCommandType.Scissor:
|
|
_flushBatchBuffer();
|
|
var oldIsScissoring = _isScissoring;
|
|
_isScissoring = command.Scissor.EnableScissor;
|
|
if (_isScissoring)
|
|
{
|
|
if (!oldIsScissoring)
|
|
{
|
|
GL.Enable(EnableCap.ScissorTest);
|
|
}
|
|
|
|
ref var s = ref command.Scissor.Scissor;
|
|
// Don't forget to flip it, these coordinates have bottom left as origin.
|
|
GL.Scissor(s.Left, _screenSize.Y - s.Bottom, s.Width, s.Height);
|
|
}
|
|
else if (oldIsScissoring)
|
|
{
|
|
GL.Disable(EnableCap.ScissorTest);
|
|
}
|
|
|
|
break;
|
|
|
|
case RenderCommandType.ViewMatrix:
|
|
_flushBatchBuffer();
|
|
var matrices = new ProjViewMatrices(_currentMatrices, command.ViewMatrix.Matrix);
|
|
_setProjViewMatrices(matrices);
|
|
break;
|
|
|
|
case RenderCommandType.UseShader:
|
|
_flushBatchBuffer();
|
|
if (command.UseShader.Handle.Value == _currentShader.Value)
|
|
{
|
|
break;
|
|
}
|
|
|
|
_currentShader = (ClydeHandle) command.UseShader.Handle;
|
|
break;
|
|
|
|
case RenderCommandType.ResetViewMatrix:
|
|
_flushBatchBuffer();
|
|
_setSpace(_currentSpace);
|
|
break;
|
|
|
|
case RenderCommandType.SwitchSpace:
|
|
_flushBatchBuffer();
|
|
_setSpace(command.SwitchSpace.NewSpace);
|
|
break;
|
|
|
|
case RenderCommandType.RenderTarget:
|
|
_flushBatchBuffer();
|
|
if (command.RenderTarget.RenderTarget.Value == 0)
|
|
{
|
|
_popDebugGroupMaybe();
|
|
// Bind window framebuffer.
|
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
|
var (w, h) = ScreenSize;
|
|
GL.Viewport(0, 0, w, h);
|
|
}
|
|
else
|
|
{
|
|
_pushDebugGroupMaybe("fb");
|
|
var renderTarget = _renderTargets[command.RenderTarget.RenderTarget];
|
|
|
|
GL.BindFramebuffer(FramebufferTarget.Framebuffer, renderTarget.ObjectHandle.Handle);
|
|
var (w, h) = renderTarget.Size;
|
|
GL.Viewport(0, 0, w, h);
|
|
}
|
|
|
|
break;
|
|
|
|
case RenderCommandType.Viewport:
|
|
_flushBatchBuffer();
|
|
ref var vp = ref command.Viewport.Viewport;
|
|
GL.Viewport(vp.Left, vp.Bottom, vp.Width, vp.Height);
|
|
break;
|
|
|
|
case RenderCommandType.Clear:
|
|
_flushBatchBuffer();
|
|
ref var color = ref command.Clear.Color;
|
|
GL.ClearColor(color.R, color.G, color.B, color.A);
|
|
GL.Clear(ClearBufferMask.ColorBufferBit);
|
|
break;
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void _drawCommandTexture(ref RenderCommandTexture command)
|
|
{
|
|
if (BatchingTexture.HasValue)
|
|
{
|
|
DebugTools.Assert(BatchingModulate.HasValue);
|
|
if (BatchingTexture.Value != command.TextureId ||
|
|
!StrictColorEquality(BatchingModulate.Value, command.Modulate))
|
|
{
|
|
_flushBatchBuffer();
|
|
BatchingTexture = command.TextureId;
|
|
BatchingModulate = command.Modulate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BatchingTexture = command.TextureId;
|
|
BatchingModulate = command.Modulate;
|
|
}
|
|
|
|
var loadedTexture = _loadedTextures[BatchingTexture.Value];
|
|
UIBox2 sr;
|
|
if (command.HasSubRegion)
|
|
{
|
|
var (w, h) = loadedTexture.Size;
|
|
var csr = command.SubRegion;
|
|
if (_currentSpace == CurrentSpace.WorldSpace)
|
|
{
|
|
sr = new UIBox2(csr.Left / w, csr.Top / h, csr.Right / w, csr.Bottom / h);
|
|
}
|
|
else
|
|
{
|
|
sr = new UIBox2(csr.Left / w, csr.Bottom / h, csr.Right / w, csr.Top / h);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_currentSpace == CurrentSpace.WorldSpace)
|
|
{
|
|
sr = new UIBox2(0, 0, 1, 1);
|
|
}
|
|
else
|
|
{
|
|
sr = new UIBox2(0, 1, 1, 0);
|
|
}
|
|
}
|
|
|
|
Vector2 bl;
|
|
Vector2 br;
|
|
Vector2 tr;
|
|
Vector2 tl;
|
|
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
|
if (command.Angle == Angle.Zero)
|
|
{
|
|
bl = _currentModelMatrix.Transform(command.PositionA);
|
|
br = _currentModelMatrix.Transform(new Vector2(command.PositionB.X, command.PositionA.Y));
|
|
tr = _currentModelMatrix.Transform(command.PositionB);
|
|
tl = _currentModelMatrix.Transform(new Vector2(command.PositionA.X, command.PositionB.Y));
|
|
}
|
|
else
|
|
{
|
|
bl = _currentModelMatrix.Transform(command.Angle.RotateVec(command.PositionA));
|
|
br = _currentModelMatrix.Transform(
|
|
command.Angle.RotateVec(new Vector2(command.PositionB.X, command.PositionA.Y)));
|
|
tr = _currentModelMatrix.Transform(command.Angle.RotateVec(command.PositionB));
|
|
tl = _currentModelMatrix.Transform(
|
|
command.Angle.RotateVec(new Vector2(command.PositionA.X, command.PositionB.Y)));
|
|
}
|
|
|
|
var vIdx = BatchIndex * 4;
|
|
BatchVertexData[vIdx + 0] = new Vertex2D(bl, sr.BottomLeft);
|
|
BatchVertexData[vIdx + 1] = new Vertex2D(br, sr.BottomRight);
|
|
BatchVertexData[vIdx + 2] = new Vertex2D(tl, sr.TopLeft);
|
|
BatchVertexData[vIdx + 3] = new Vertex2D(tr, sr.TopRight);
|
|
var nIdx = BatchIndex * 5;
|
|
var tIdx = (ushort) (BatchIndex * 4);
|
|
BatchIndexData[nIdx + 0] = tIdx;
|
|
BatchIndexData[nIdx + 1] = (ushort) (tIdx + 1);
|
|
BatchIndexData[nIdx + 2] = (ushort) (tIdx + 2);
|
|
BatchIndexData[nIdx + 3] = (ushort) (tIdx + 3);
|
|
BatchIndexData[nIdx + 4] = ushort.MaxValue;
|
|
BatchIndex += 1;
|
|
if (BatchIndex >= MaxBatchQuads)
|
|
{
|
|
throw new NotImplementedException("Can't batch things this big yet sorry.");
|
|
}
|
|
}
|
|
|
|
private void _drawCommandLine(ref RenderCommandLine renderCommandLine)
|
|
{
|
|
var (program, loaded) = ActivateShaderInstance(_currentShader);
|
|
|
|
program.Use();
|
|
program.SetUniformMaybe(UniIModUV, new Vector4(0, 0, 1, 1));
|
|
program.SetUniformMaybe(UniIModulate, renderCommandLine.Color);
|
|
|
|
var white = _loadedTextures[((ClydeTexture) Texture.White).TextureId].OpenGLObject;
|
|
GL.ActiveTexture(TextureUnit.Texture0);
|
|
GL.BindTexture(TextureTarget.Texture2D, white.Handle);
|
|
|
|
GL.ActiveTexture(TextureUnit.Texture1);
|
|
if (_lightingReady && loaded.HasLighting)
|
|
{
|
|
var lightTexture = _loadedTextures[LightRenderTarget.Texture.TextureId].OpenGLObject;
|
|
GL.BindTexture(TextureTarget.Texture2D, lightTexture.Handle);
|
|
}
|
|
else
|
|
{
|
|
GL.BindTexture(TextureTarget.Texture2D, white.Handle);
|
|
}
|
|
|
|
program.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
|
program.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
|
|
|
|
var a = renderCommandLine.PositionA;
|
|
var b = renderCommandLine.PositionB;
|
|
|
|
GL.BindVertexArray(LineVAO.Handle);
|
|
var rectTransform = Matrix3.Identity;
|
|
(rectTransform.R0C0, rectTransform.R1C1) = b - a;
|
|
(rectTransform.R0C2, rectTransform.R1C2) = a;
|
|
rectTransform.Multiply(ref _currentModelMatrix);
|
|
program.SetUniformMaybe(UniIModelMatrix, rectTransform);
|
|
_debugStats.LastGLDrawCalls += 1;
|
|
GL.DrawArrays(PrimitiveType.Lines, 0, 2);
|
|
}
|
|
|
|
private void _drawQuad(Vector2 a, Vector2 b, ref Matrix3 modelMatrix, ShaderProgram program)
|
|
{
|
|
GL.BindVertexArray(QuadVAO.Handle);
|
|
var rectTransform = Matrix3.Identity;
|
|
(rectTransform.R0C0, rectTransform.R1C1) = b - a;
|
|
(rectTransform.R0C2, rectTransform.R1C2) = a;
|
|
rectTransform.Multiply(ref modelMatrix);
|
|
program.SetUniformMaybe(UniIModelMatrix, rectTransform);
|
|
|
|
_debugStats.LastGLDrawCalls += 1;
|
|
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flush the render handle, processing and re-pooling all the command lists.
|
|
/// </summary>
|
|
private void _flushRenderHandle(RenderHandle handle)
|
|
{
|
|
_processCommandList(handle.CommandList);
|
|
handle.CommandList.RenderCommands.Clear();
|
|
|
|
_flushBatchBuffer();
|
|
|
|
// Reset renderer state.
|
|
_currentShader = _defaultShader.Handle;
|
|
_currentModelMatrix = Matrix3.Identity;
|
|
_disableScissor();
|
|
}
|
|
|
|
private void _flushBatchBuffer()
|
|
{
|
|
if (!BatchingTexture.HasValue)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_debugStats.LastBatches += 1;
|
|
|
|
DebugTools.Assert(BatchingTexture.HasValue);
|
|
var loadedTexture = _loadedTextures[BatchingTexture.Value];
|
|
|
|
GL.BindVertexArray(BatchVAO.Handle);
|
|
|
|
_writeBuffer(BatchVBO, new Span<Vertex2D>(BatchVertexData, 0, BatchIndex * 4));
|
|
_writeBuffer(BatchEBO, new Span<ushort>(BatchIndexData, 0, BatchIndex * 5));
|
|
|
|
var (program, loaded) = ActivateShaderInstance(_currentShader);
|
|
|
|
GL.ActiveTexture(TextureUnit.Texture0);
|
|
GL.BindTexture(TextureTarget.Texture2D, loadedTexture.OpenGLObject.Handle);
|
|
|
|
GL.ActiveTexture(TextureUnit.Texture1);
|
|
if (_lightingReady && loaded.HasLighting)
|
|
{
|
|
var lightTexture = _loadedTextures[LightRenderTarget.Texture.TextureId].OpenGLObject;
|
|
GL.BindTexture(TextureTarget.Texture2D, lightTexture.Handle);
|
|
}
|
|
else
|
|
{
|
|
var white = _loadedTextures[((ClydeTexture) Texture.White).TextureId].OpenGLObject;
|
|
GL.BindTexture(TextureTarget.Texture2D, white.Handle);
|
|
}
|
|
|
|
program.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
|
program.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
|
|
|
|
// Model matrix becomes identity since it's built into the batch mesh.
|
|
program.SetUniformMaybe(UniIModelMatrix, Matrix3.Identity);
|
|
// Reset ModUV to ensure it's identity and doesn't touch anything.
|
|
program.SetUniformMaybe(UniIModUV, new Vector4(0, 0, 1, 1));
|
|
// Set modulate.
|
|
DebugTools.Assert(BatchingModulate.HasValue);
|
|
program.SetUniformMaybe(UniIModulate, BatchingModulate.Value);
|
|
program.SetUniformMaybe(UniITexturePixelSize, Vector2.One / loadedTexture.Size);
|
|
|
|
_debugStats.LastGLDrawCalls += 1;
|
|
GL.DrawElements(PrimitiveType.TriangleStrip, BatchIndex * 5, DrawElementsType.UnsignedShort, 0);
|
|
|
|
// Reset batch state.
|
|
BatchIndex = 0;
|
|
BatchingTexture = null;
|
|
BatchingModulate = null;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void _disableScissor()
|
|
{
|
|
if (_isScissoring)
|
|
{
|
|
GL.Disable(EnableCap.ScissorTest);
|
|
}
|
|
|
|
_isScissoring = false;
|
|
}
|
|
|
|
private void _setSpace(CurrentSpace newSpace)
|
|
{
|
|
_currentSpace = newSpace;
|
|
|
|
switch (newSpace)
|
|
{
|
|
case CurrentSpace.ScreenSpace:
|
|
_setProjViewMatrices(_screenMatrices());
|
|
break;
|
|
case CurrentSpace.WorldSpace:
|
|
_setProjViewMatrices(_worldMatrices());
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(newSpace), newSpace, null);
|
|
}
|
|
}
|
|
|
|
private void ClearFramebuffer(Color color)
|
|
{
|
|
GL.ClearColor(color.ConvertOpenTK());
|
|
GL.Clear(ClearBufferMask.ColorBufferBit);
|
|
}
|
|
|
|
// Uses either glBufferData or glBufferSubData depending on _reallocateBuffers.
|
|
private void _writeBuffer<T>(Buffer buffer, Span<T> data) where T : unmanaged
|
|
{
|
|
if (_reallocateBuffers)
|
|
{
|
|
buffer.Reallocate(data);
|
|
}
|
|
else
|
|
{
|
|
buffer.WriteSubData(data);
|
|
}
|
|
}
|
|
|
|
private void _writeBuffer<T>(Buffer buffer, in T data) where T : unmanaged
|
|
{
|
|
if (_reallocateBuffers)
|
|
{
|
|
buffer.Reallocate(data);
|
|
}
|
|
else
|
|
{
|
|
buffer.WriteSubData(data);
|
|
}
|
|
}
|
|
|
|
private (ShaderProgram, LoadedShader) ActivateShaderInstance(ClydeHandle handle)
|
|
{
|
|
var instance = _shaderInstances[handle];
|
|
var shader = _loadedShaders[instance.ShaderHandle];
|
|
var program = shader.Program;
|
|
|
|
program.Use();
|
|
|
|
if (shader.ActiveInstance == instance.ShaderHandle)
|
|
{
|
|
return (program, shader);
|
|
}
|
|
|
|
// Assign shader parameters to uniform since they may be dirty.
|
|
foreach (var (name, value) in instance.Parameters)
|
|
{
|
|
if (!program.HasUniform(name))
|
|
{
|
|
// Can happen if the GLSL compiler removes uniforms due to them being unused.
|
|
// Safe to just ignore them then I'd say.
|
|
continue;
|
|
}
|
|
|
|
switch (value)
|
|
{
|
|
case float f:
|
|
program.SetUniform(name, f);
|
|
break;
|
|
case Vector2 vector2:
|
|
program.SetUniform(name, vector2);
|
|
break;
|
|
case Vector3 vector3:
|
|
program.SetUniform(name, vector3);
|
|
break;
|
|
case Vector4 vector4:
|
|
program.SetUniform(name, vector4);
|
|
break;
|
|
case Color color:
|
|
program.SetUniform(name, color);
|
|
break;
|
|
case int i:
|
|
program.SetUniform(name, i);
|
|
break;
|
|
case Vector2i vector2I:
|
|
program.SetUniform(name, vector2I);
|
|
break;
|
|
case bool b:
|
|
program.SetUniform(name, b ? 1 : 0);
|
|
break;
|
|
case Matrix3 matrix3:
|
|
program.SetUniform(name, matrix3);
|
|
break;
|
|
case Matrix4 matrix4:
|
|
program.SetUniform(name, matrix4);
|
|
break;
|
|
default:
|
|
throw new InvalidOperationException($"Unable to handle shader parameter {name}: {value}");
|
|
}
|
|
}
|
|
|
|
return (program, shader);
|
|
}
|
|
|
|
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
|
|
private static bool StrictColorEquality(in Color a, in Color b)
|
|
{
|
|
return a.R == b.R && a.G == b.G && a.B == b.B && a.A == b.A;
|
|
}
|
|
|
|
private sealed class RenderHandle : IRenderHandle
|
|
{
|
|
private readonly Clyde _clyde;
|
|
public readonly RenderCommandList CommandList = new RenderCommandList();
|
|
|
|
public DrawingHandleScreen DrawingHandleScreen { get; }
|
|
public DrawingHandleWorld DrawingHandleWorld { get; }
|
|
|
|
public RenderHandle(Clyde clyde)
|
|
{
|
|
_clyde = clyde;
|
|
DrawingHandleScreen = new DrawingHandleScreenImpl(this);
|
|
DrawingHandleWorld = new DrawingHandleWorldImpl(this);
|
|
}
|
|
|
|
public void SetModelTransform(in Matrix3 matrix)
|
|
{
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.ModelMatrix;
|
|
|
|
command.ModelMatrix.Matrix = matrix;
|
|
}
|
|
|
|
public void SetViewTransform(in Matrix3 matrix)
|
|
{
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.ViewMatrix;
|
|
|
|
command.ViewMatrix.Matrix = matrix;
|
|
}
|
|
|
|
public void ResetViewTransform()
|
|
{
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.ResetViewMatrix;
|
|
}
|
|
|
|
public void DrawTexture(Texture texture, Vector2 a, Vector2 b, Color modulate, UIBox2? subRegion,
|
|
Angle angle)
|
|
{
|
|
switch (texture)
|
|
{
|
|
case AtlasTexture atlas:
|
|
{
|
|
texture = atlas.SourceTexture;
|
|
if (subRegion.HasValue)
|
|
{
|
|
var offset = atlas.SubRegion.TopLeft;
|
|
subRegion = new UIBox2(
|
|
subRegion.Value.TopLeft + offset,
|
|
subRegion.Value.BottomRight + offset);
|
|
}
|
|
else
|
|
{
|
|
subRegion = atlas.SubRegion;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.Texture;
|
|
|
|
var clydeTexture = (ClydeTexture) texture;
|
|
command.Texture.TextureId = clydeTexture.TextureId;
|
|
command.Texture.PositionA = a;
|
|
command.Texture.PositionB = b;
|
|
command.Texture.Angle = angle;
|
|
command.Texture.Modulate = modulate;
|
|
if (subRegion.HasValue)
|
|
{
|
|
command.Texture.SubRegion = subRegion.Value;
|
|
command.Texture.HasSubRegion = true;
|
|
}
|
|
else
|
|
{
|
|
command.Texture.HasSubRegion = false;
|
|
}
|
|
}
|
|
|
|
public void SetScissor(UIBox2i? scissorBox)
|
|
{
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.Scissor;
|
|
|
|
command.Scissor.EnableScissor = scissorBox.HasValue;
|
|
if (scissorBox.HasValue)
|
|
{
|
|
command.Scissor.Scissor = scissorBox.Value;
|
|
}
|
|
}
|
|
|
|
public void SetSpace(CurrentSpace space)
|
|
{
|
|
ref var commandWorldSpace = ref CommandList.RenderCommands.AllocAdd();
|
|
commandWorldSpace.Type = RenderCommandType.SwitchSpace;
|
|
commandWorldSpace.SwitchSpace.NewSpace = space;
|
|
}
|
|
|
|
public void DrawEntity(IEntity entity, Vector2 position, Vector2 scale)
|
|
{
|
|
if (entity.Deleted)
|
|
{
|
|
throw new ArgumentException("Tried to draw an entity has been deleted.", nameof(entity));
|
|
}
|
|
|
|
var sprite = entity.GetComponent<SpriteComponent>();
|
|
|
|
// Switch rendering to world space.
|
|
SetSpace(CurrentSpace.WorldSpace);
|
|
|
|
{
|
|
// Change view matrix to put entity where we need.
|
|
ref var commandViewMatrix = ref CommandList.RenderCommands.AllocAdd();
|
|
commandViewMatrix.Type = RenderCommandType.ViewMatrix;
|
|
|
|
var ofsX = position.X - _clyde.ScreenSize.X / 2f;
|
|
var ofsY = position.Y - _clyde.ScreenSize.Y / 2f;
|
|
ref var viewMatrix = ref commandViewMatrix.ViewMatrix.Matrix;
|
|
viewMatrix = Matrix3.Identity;
|
|
viewMatrix.R0C0 = scale.X;
|
|
viewMatrix.R1C1 = scale.Y;
|
|
viewMatrix.R0C2 = ofsX / EyeManager.PIXELSPERMETER;
|
|
viewMatrix.R1C2 = -ofsY / EyeManager.PIXELSPERMETER;
|
|
}
|
|
|
|
// Draw the entity.
|
|
sprite.OpenGLRender(DrawingHandleWorld, false);
|
|
|
|
// Reset to screen space
|
|
SetSpace(CurrentSpace.ScreenSpace);
|
|
}
|
|
|
|
public void DrawLine(Vector2 a, Vector2 b, Color color)
|
|
{
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.Line;
|
|
|
|
command.Line.PositionA = a;
|
|
command.Line.PositionB = b;
|
|
command.Line.Color = color;
|
|
}
|
|
|
|
public void UseShader(ShaderInstance shader)
|
|
{
|
|
if (shader != null && shader.Disposed)
|
|
{
|
|
throw new ArgumentException("Unable to use disposed shader instance.", nameof(shader));
|
|
}
|
|
|
|
var clydeShader = (ClydeShaderInstance) shader;
|
|
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.UseShader;
|
|
|
|
command.UseShader.Handle = clydeShader?.Handle ?? _clyde._defaultShader.Handle;
|
|
}
|
|
|
|
public void Viewport(Box2i viewport)
|
|
{
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.Viewport;
|
|
|
|
command.Viewport.Viewport = viewport;
|
|
}
|
|
|
|
public void UseRenderTarget(IRenderTarget renderTarget)
|
|
{
|
|
var target = (RenderTarget) renderTarget;
|
|
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.RenderTarget;
|
|
|
|
command.RenderTarget.RenderTarget = target?.Handle ?? new ClydeHandle(0);
|
|
}
|
|
|
|
public void Clear(Color color)
|
|
{
|
|
ref var command = ref CommandList.RenderCommands.AllocAdd();
|
|
command.Type = RenderCommandType.Clear;
|
|
|
|
command.Clear.Color = color;
|
|
}
|
|
|
|
private sealed class DrawingHandleScreenImpl : DrawingHandleScreen
|
|
{
|
|
private readonly RenderHandle _renderHandle;
|
|
|
|
public DrawingHandleScreenImpl(RenderHandle renderHandle)
|
|
{
|
|
_renderHandle = renderHandle;
|
|
}
|
|
|
|
public override void SetTransform(in Matrix3 matrix)
|
|
{
|
|
_renderHandle.SetModelTransform(matrix);
|
|
}
|
|
|
|
public override void UseShader(ShaderInstance shader)
|
|
{
|
|
_renderHandle.UseShader(shader);
|
|
}
|
|
|
|
public override void DrawCircle(Vector2 position, float radius, Color color)
|
|
{
|
|
// TODO: Implement this.
|
|
}
|
|
|
|
public override void DrawLine(Vector2 from, Vector2 to, Color color)
|
|
{
|
|
_renderHandle.DrawLine(from, to, color * Modulate);
|
|
}
|
|
|
|
public override void DrawRect(UIBox2 rect, Color color, bool filled = true)
|
|
{
|
|
if (filled)
|
|
{
|
|
DrawTextureRect(Texture.White, rect, color);
|
|
}
|
|
else
|
|
{
|
|
DrawLine(rect.TopLeft, rect.TopRight, color);
|
|
DrawLine(rect.TopRight, rect.BottomRight, color);
|
|
DrawLine(rect.BottomRight, rect.BottomLeft, color);
|
|
DrawLine(rect.BottomLeft, rect.TopLeft, color);
|
|
}
|
|
}
|
|
|
|
public override void DrawTextureRectRegion(Texture texture, UIBox2 rect, UIBox2? subRegion = null,
|
|
Color? modulate = null)
|
|
{
|
|
var color = (modulate ?? Color.White) * Modulate;
|
|
_renderHandle.DrawTexture(texture, rect.TopLeft, rect.BottomRight, color,
|
|
subRegion, 0);
|
|
}
|
|
}
|
|
|
|
private sealed class DrawingHandleWorldImpl : DrawingHandleWorld
|
|
{
|
|
private readonly RenderHandle _renderHandle;
|
|
|
|
public DrawingHandleWorldImpl(RenderHandle renderHandle)
|
|
{
|
|
_renderHandle = renderHandle;
|
|
}
|
|
|
|
public override void SetTransform(in Matrix3 matrix)
|
|
{
|
|
_renderHandle.SetModelTransform(matrix);
|
|
}
|
|
|
|
public override void UseShader(ShaderInstance shader)
|
|
{
|
|
_renderHandle.UseShader(shader);
|
|
}
|
|
|
|
public override void DrawCircle(Vector2 position, float radius, Color color)
|
|
{
|
|
// TODO: Implement this.
|
|
}
|
|
|
|
public override void DrawLine(Vector2 from, Vector2 to, Color color)
|
|
{
|
|
_renderHandle.DrawLine(from, to, color * Modulate);
|
|
}
|
|
|
|
public override void DrawRect(Box2 rect, Color color, bool filled = true)
|
|
{
|
|
if (filled)
|
|
{
|
|
DrawTextureRect(Texture.White, rect, color);
|
|
}
|
|
else
|
|
{
|
|
DrawLine(rect.TopLeft, rect.TopRight, color);
|
|
DrawLine(rect.TopRight, rect.BottomRight, color);
|
|
DrawLine(rect.BottomRight, rect.BottomLeft, color);
|
|
DrawLine(rect.BottomLeft, rect.TopLeft, color);
|
|
}
|
|
}
|
|
|
|
public override void DrawRect(in Box2Rotated rect, Color color, bool filled = true)
|
|
{
|
|
if (filled)
|
|
{
|
|
DrawTextureRect(Texture.White, rect, color);
|
|
}
|
|
else
|
|
{
|
|
DrawLine(rect.TopLeft, rect.TopRight, color);
|
|
DrawLine(rect.TopRight, rect.BottomRight, color);
|
|
DrawLine(rect.BottomRight, rect.BottomLeft, color);
|
|
DrawLine(rect.BottomLeft, rect.TopLeft, color);
|
|
}
|
|
}
|
|
|
|
public override void DrawTextureRectRegion(Texture texture, Box2 rect, UIBox2? subRegion = null,
|
|
Color? modulate = null)
|
|
{
|
|
var color = (modulate ?? Color.White) * Modulate;
|
|
|
|
_renderHandle.DrawTexture(texture, rect.BottomLeft, rect.TopRight, color, subRegion, 0);
|
|
}
|
|
|
|
public override void DrawTextureRectRegion(Texture texture, in Box2Rotated rect,
|
|
UIBox2? subRegion = null, Color? modulate = null)
|
|
{
|
|
var color = (modulate ?? Color.White) * Modulate;
|
|
|
|
_renderHandle.DrawTexture(texture, rect.Box.BottomLeft, rect.Box.TopRight, color, subRegion,
|
|
(float) rect.Rotation);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Use a tagged union to store all render commands.
|
|
// This significantly improves performance vs doing sum types via inheritance.
|
|
// Also means I don't have to declare a pool for every command type.
|
|
[StructLayout(LayoutKind.Explicit)]
|
|
private struct RenderCommand
|
|
{
|
|
[FieldOffset(0)] public RenderCommandType Type;
|
|
|
|
[FieldOffset(4)] public RenderCommandTexture Texture;
|
|
[FieldOffset(4)] public RenderCommandModelMatrix ModelMatrix;
|
|
[FieldOffset(4)] public RenderCommandViewMatrix ViewMatrix;
|
|
[FieldOffset(4)] public RenderCommandScissor Scissor;
|
|
[FieldOffset(4)] public RenderCommandUseShader UseShader;
|
|
[FieldOffset(4)] public RenderCommandLine Line;
|
|
[FieldOffset(4)] public RenderCommandSwitchSpace SwitchSpace;
|
|
[FieldOffset(4)] public RenderCommandRenderTarget RenderTarget;
|
|
[FieldOffset(4)] public RenderCommandViewport Viewport;
|
|
[FieldOffset(4)] public RenderCommandClear Clear;
|
|
}
|
|
|
|
private struct RenderCommandTexture
|
|
{
|
|
public ClydeHandle TextureId;
|
|
public bool HasSubRegion;
|
|
public UIBox2 SubRegion;
|
|
|
|
public Vector2 PositionA;
|
|
public Vector2 PositionB;
|
|
|
|
public Color Modulate;
|
|
public Angle Angle;
|
|
}
|
|
|
|
private struct RenderCommandModelMatrix
|
|
{
|
|
public Matrix3 Matrix;
|
|
}
|
|
|
|
private struct RenderCommandViewMatrix
|
|
{
|
|
public Matrix3 Matrix;
|
|
}
|
|
|
|
private struct RenderCommandScissor
|
|
{
|
|
public bool EnableScissor;
|
|
public UIBox2i Scissor;
|
|
}
|
|
|
|
private struct RenderCommandUseShader
|
|
{
|
|
public ClydeHandle Handle;
|
|
}
|
|
|
|
private struct RenderCommandLine
|
|
{
|
|
public Vector2 PositionA;
|
|
public Vector2 PositionB;
|
|
|
|
public Color Color;
|
|
}
|
|
|
|
private struct RenderCommandSwitchSpace
|
|
{
|
|
public CurrentSpace NewSpace;
|
|
}
|
|
|
|
private struct RenderCommandRenderTarget
|
|
{
|
|
public ClydeHandle RenderTarget;
|
|
}
|
|
|
|
private struct RenderCommandViewport
|
|
{
|
|
public Box2i Viewport;
|
|
}
|
|
|
|
private struct RenderCommandClear
|
|
{
|
|
public Color Color;
|
|
}
|
|
|
|
private enum RenderCommandType
|
|
{
|
|
Texture,
|
|
Line,
|
|
|
|
ModelMatrix,
|
|
ViewMatrix,
|
|
ResetViewMatrix,
|
|
SwitchSpace,
|
|
Viewport,
|
|
|
|
UseShader,
|
|
Scissor,
|
|
RenderTarget,
|
|
Clear
|
|
}
|
|
|
|
private enum CurrentSpace
|
|
{
|
|
ScreenSpace = 0,
|
|
WorldSpace = 1,
|
|
}
|
|
|
|
private struct PopDebugGroup : IDisposable
|
|
{
|
|
private readonly Clyde _clyde;
|
|
|
|
public PopDebugGroup(Clyde clyde)
|
|
{
|
|
_clyde = clyde;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_clyde._popDebugGroupMaybe();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A list of rendering commands to execute in order. Pooled.
|
|
/// </summary>
|
|
// ReSharper disable once ClassNeverInstantiated.Local
|
|
private class RenderCommandList
|
|
{
|
|
public readonly RefList<RenderCommand> RenderCommands = new RefList<RenderCommand>();
|
|
}
|
|
}
|
|
}
|