Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ public ShaderInstance? PostShader
[DataField]
public bool RaiseShaderEvent;

/// <summary>
/// If true, this sprite is drawn after hard FOV has been applied to the viewport.
/// Useful for semi-transparent occluders like smoke, where FOV should appear behind the sprite instead of
/// on top of it.
/// </summary>
[DataField]
public bool DrawOverFov;

[ViewVariables] internal Dictionary<object, int> LayerMap { get; set; } = new();
[ViewVariables] internal List<Layer> Layers = new();

Expand Down Expand Up @@ -1120,7 +1128,8 @@ public sealed class Layer : ISpriteLayer, ISerializationHooks
/// </summary>
[ViewVariables] internal bool UnShaded;

[ViewVariables] public RSI? RSI
[ViewVariables]
public RSI? RSI
{
get => _rsi;
[Obsolete("Use SpriteSystem.LayerSetRsi() instead.")]
Expand All @@ -1137,7 +1146,8 @@ [ViewVariables] public RSI? RSI
}

internal RSI.StateId StateId;
[ViewVariables] public RSI.StateId State
[ViewVariables]
public RSI.StateId State
{
get => StateId;
[Obsolete("Use SpriteSystem.LayerSetRsiState() instead.")]
Expand Down Expand Up @@ -1637,7 +1647,7 @@ internal void GetLayerDrawMatrix(RsiDirection dir, out Matrix3x2 layerDrawMatrix
if (dir == RsiDirection.South || noRot)
layerDrawMatrix = LocalMatrix;
else
layerDrawMatrix = Matrix3x2.Multiply(_rsiDirectionMatrices[(int) dir], LocalMatrix);
layerDrawMatrix = Matrix3x2.Multiply(_rsiDirectionMatrices[(int)dir], LocalMatrix);
}

private static Matrix3x2[] _rsiDirectionMatrices = new Matrix3x2[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void SetAutoAnimateSync(SpriteComponent sprite, SpriteComponent.Layer lay
return;
}

layer.AnimationTimeLeft = (float) -(time % state.TotalDelay);
layer.AnimationTimeLeft = (float)-(time % state.TotalDelay);
layer.AnimationFrame = 0;
}

Expand Down Expand Up @@ -82,11 +82,12 @@ in target

target.Comp.IsInert = source.Comp.IsInert;
target.Comp.LayerMap = source.Comp.LayerMap.ShallowClone();
target.Comp.PostShader = source.Comp.PostShader is {Mutable: true}
target.Comp.PostShader = source.Comp.PostShader is { Mutable: true }
? source.Comp.PostShader.Duplicate()
: source.Comp.PostShader;

target.Comp.RenderOrder = source.Comp.RenderOrder;
target.Comp.DrawOverFov = source.Comp.DrawOverFov;
target.Comp.GranularLayersRendering = source.Comp.GranularLayersRendering;
target.Comp.Loop = source.Comp.Loop;

Expand All @@ -107,5 +108,5 @@ public void QueueUpdateIsInert(Entity<SpriteComponent> sprite)
}

[Obsolete("Use QueueUpdateIsInert")]
public void QueueUpdateInert(EntityUid uid, SpriteComponent sprite) => QueueUpdateIsInert(new (uid, sprite));
public void QueueUpdateInert(EntityUid uid, SpriteComponent sprite) => QueueUpdateIsInert(new(uid, sprite));
}
223 changes: 131 additions & 92 deletions Robust.Client/Graphics/Clyde/Clyde.HLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ internal partial class Clyde
public static float PostShadeScale = 1.25f;

private List<Overlay> _overlays = new();
private readonly List<int> _drawOverFovSprites = new();

public void Render()
{
Expand Down Expand Up @@ -297,6 +298,12 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world
{
ref var entry = ref _drawingSpriteList[indexList[i]];

if (entry.Sprite.DrawOverFov)
{
_drawOverFovSprites.Add(indexList[i]);
continue;
}

for (; overlayIndex < worldOverlays.Count; overlayIndex++)
{
var overlay = worldOverlays[overlayIndex];
Expand All @@ -316,118 +323,148 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world
RenderSingleWorldOverlay(overlay, viewport, OverlaySpace.WorldSpaceEntities, worldAABB, worldBounds);
}

Vector2i roundedPos = default;
if (entry.Sprite.PostShader != null)
DrawEntity(viewport, eye, spriteSystem, ref entry, ref entityPostRenderTarget, screenSize);
}

// draw remainder of overlays
for (; overlayIndex < worldOverlays.Count; overlayIndex++)
{
if (!flushed)
{
// get the size of the sprite on screen, scaled slightly to allow for shaders that increase the final sprite size.
var screenSpriteSize = (Vector2i)(entry.SpriteScreenBB.Size * PostShadeScale).Rounded();

// I'm not 100% sure why it works, but without it post-shader
// can be lower or upper by 1px than original sprite depending on sprite rotation or scale
// probably some rotation rounding error
if (screenSpriteSize.X % 2 != 0)
screenSpriteSize.X++;
if (screenSpriteSize.Y % 2 != 0)
screenSpriteSize.Y++;

bool exit = false;
if (entry.Sprite.GetScreenTexture && entry.Sprite.PostShader != null)
{
FlushRenderQueue();
var tex = CopyScreenTexture(viewport.RenderTarget);
if (tex == null)
exit = true;
else
entry.Sprite.PostShader.SetParameter("SCREEN_TEXTURE", tex);
}
FlushRenderQueue();
flushed = true;
}

// check that sprite size is valid
if (!exit && screenSpriteSize.X > 0 && screenSpriteSize.Y > 0)
RenderSingleWorldOverlay(worldOverlays[overlayIndex], viewport, OverlaySpace.WorldSpaceEntities, worldAABB, worldBounds);
}

ArrayPool<int>.Shared.Return(indexList);
entityPostRenderTarget?.DisposeDeferred();
FlushRenderQueue();
}

private void DrawEntity(
Viewport viewport,
IEye eye,
SpriteSystem spriteSystem,
ref SpriteData entry,
ref RenderTexture? entityPostRenderTarget,
Vector2i screenSize)
{
Vector2i roundedPos = default;
if (entry.Sprite.PostShader != null)
{
// get the size of the sprite on screen, scaled slightly to allow for shaders that increase the final sprite size.
var screenSpriteSize = (Vector2i)(entry.SpriteScreenBB.Size * PostShadeScale).Rounded();

// I'm not 100% sure why it works, but without it post-shader
// can be lower or upper by 1px than original sprite depending on sprite rotation or scale
// probably some rotation rounding error
if (screenSpriteSize.X % 2 != 0)
screenSpriteSize.X++;
if (screenSpriteSize.Y % 2 != 0)
screenSpriteSize.Y++;

bool exit = false;
if (entry.Sprite.GetScreenTexture)
{
FlushRenderQueue();
var tex = CopyScreenTexture(viewport.RenderTarget);
if (tex == null)
exit = true;
else
entry.Sprite.PostShader.SetParameter("SCREEN_TEXTURE", tex);
}

// check that sprite size is valid
if (!exit && screenSpriteSize.X > 0 && screenSpriteSize.Y > 0)
{
// This is really bare-bones render target re-use logic. One problem is that if it ever draws a
// single large entity in a frame, the render target may be way to big for every subsequent
// entity. But the vast majority of sprites are currently all 32x32, so it doesn't matter all that
// much.
//
// Also, if there are many differenty sizes, and they all happen to be drawn in order of
// increasing size, then this will still generate a whole bunch of render targets. So maybe
// iterate once _drawingSpriteList, check sprite sizes, and decide what render targets to create
// based off of that?
//
// TODO PERFORMANCE better renderTarget re-use / caching.

if (entityPostRenderTarget == null
|| entityPostRenderTarget.Size.X < screenSpriteSize.X
|| entityPostRenderTarget.Size.Y < screenSpriteSize.Y)
{
// This is really bare-bones render target re-use logic. One problem is that if it ever draws a
// single large entity in a frame, the render target may be way to big for every subsequent
// entity. But the vast majority of sprites are currently all 32x32, so it doesn't matter all that
// much.
//
// Also, if there are many differenty sizes, and they all happen to be drawn in order of
// increasing size, then this will still generate a whole bunch of render targets. So maybe
// iterate once _drawingSpriteList, check sprite sizes, and decide what render targets to create
// based off of that?
//
// TODO PERFORMANCE better renderTarget re-use / caching.

if (entityPostRenderTarget == null
|| entityPostRenderTarget.Size.X < screenSpriteSize.X
|| entityPostRenderTarget.Size.Y < screenSpriteSize.Y)
{
entityPostRenderTarget = CreateRenderTarget(screenSpriteSize,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
name: nameof(entityPostRenderTarget));
}

_renderHandle.UseRenderTarget(entityPostRenderTarget);
_renderHandle.Clear(default, 0, ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);

// 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.
roundedPos = (Vector2i) entry.SpriteScreenBB.Center;
var flippedPos = new Vector2i(roundedPos.X, screenSize.Y - roundedPos.Y);
flippedPos -= entityPostRenderTarget.Size / 2;
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));

if (entry.Sprite.RaiseShaderEvent)
_entityManager.EventBus.RaiseLocalEvent(entry.Uid,
new BeforePostShaderRenderEvent(entry.Sprite, viewport), false);
entityPostRenderTarget = CreateRenderTarget(screenSpriteSize,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
name: nameof(entityPostRenderTarget));
}

_renderHandle.UseRenderTarget(entityPostRenderTarget);
_renderHandle.Clear(default, 0, ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);

// 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.
roundedPos = (Vector2i)entry.SpriteScreenBB.Center;
var flippedPos = new Vector2i(roundedPos.X, screenSize.Y - roundedPos.Y);
flippedPos -= entityPostRenderTarget.Size / 2;
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));

if (entry.Sprite.RaiseShaderEvent)
_entityManager.EventBus.RaiseLocalEvent(entry.Uid,
new BeforePostShaderRenderEvent(entry.Sprite, viewport), false);
}
}

spriteSystem.RenderSprite(new(entry.Uid, entry.Sprite), _renderHandle.DrawingHandleWorld, eye.Rotation, entry.WorldRot, entry.WorldPos);
spriteSystem.RenderSprite(new(entry.Uid, entry.Sprite), _renderHandle.DrawingHandleWorld, eye.Rotation, entry.WorldRot, entry.WorldPos);

if (entry.Sprite.PostShader != null && entityPostRenderTarget != null)
{
var oldProj = _currentMatrixProj;
var oldView = _currentMatrixView;
if (entry.Sprite.PostShader == null || entityPostRenderTarget == null)
return;

_renderHandle.UseRenderTarget(viewport.RenderTarget);
_renderHandle.Viewport(Box2i.FromDimensions(Vector2i.Zero, screenSize));
var oldProj = _currentMatrixProj;
var oldView = _currentMatrixView;

_renderHandle.UseShader(entry.Sprite.PostShader);
CalcScreenMatrices(viewport.Size, out var proj, out var view);
_renderHandle.SetProjView(proj, view);
_renderHandle.SetModelTransform(Matrix3x2.Identity);
_renderHandle.UseRenderTarget(viewport.RenderTarget);
_renderHandle.Viewport(Box2i.FromDimensions(Vector2i.Zero, screenSize));

var rounded = roundedPos - entityPostRenderTarget.Size / 2;
_renderHandle.UseShader(entry.Sprite.PostShader);
CalcScreenMatrices(viewport.Size, out var proj, out var view);
_renderHandle.SetProjView(proj, view);
_renderHandle.SetModelTransform(Matrix3x2.Identity);

var box = Box2i.FromDimensions(rounded, entityPostRenderTarget.Size);
var rounded = roundedPos - entityPostRenderTarget.Size / 2;
var box = Box2i.FromDimensions(rounded, entityPostRenderTarget.Size);

_renderHandle.DrawTextureScreen(entityPostRenderTarget.Texture,
box.BottomLeft, box.BottomRight, box.TopLeft, box.TopRight,
Color.White, null);
_renderHandle.DrawTextureScreen(entityPostRenderTarget.Texture,
box.BottomLeft, box.BottomRight, box.TopLeft, box.TopRight,
Color.White, null);

_renderHandle.SetProjView(oldProj, oldView);
_renderHandle.UseShader(null);
}
}
_renderHandle.SetProjView(oldProj, oldView);
_renderHandle.UseShader(null);
}

// draw remainder of overlays
for (; overlayIndex < worldOverlays.Count; overlayIndex++)
{
if (!flushed)
{
FlushRenderQueue();
flushed = true;
}
private void DrawEntitiesOverFov(Viewport viewport, IEye eye)
{
if (_drawingSpriteList.Count == 0)
return;

RenderSingleWorldOverlay(worldOverlays[overlayIndex], viewport, OverlaySpace.WorldSpaceEntities, worldAABB, worldBounds);
var spriteSystem = _entityManager.System<SpriteSystem>();
var screenSize = viewport.Size;
RenderTexture? entityPostRenderTarget = null;

for (var i = 0; i < _drawOverFovSprites.Count; i++)
{
ref var entry = ref _drawingSpriteList[_drawOverFovSprites[i]];
DrawEntity(viewport, eye, spriteSystem, ref entry, ref entityPostRenderTarget, screenSize);
}

ArrayPool<int>.Shared.Return(indexList);
entityPostRenderTarget?.DisposeDeferred();
FlushRenderQueue();

_debugStats.Entities += _drawingSpriteList.Count;
_drawOverFovSprites.Clear();
_drawingSpriteList.Clear();
FlushRenderQueue();
}

private void DrawLoadingScreen(IRenderHandle handle)
Expand All @@ -437,7 +474,7 @@ private void DrawLoadingScreen(IRenderHandle handle)
_loadingScreenManager.DrawLoadingScreen(handle, ScreenSize);
}

private void RenderInRenderTarget(RenderTargetBase rt, Action a, Color? clearColor=default)
private void RenderInRenderTarget(RenderTargetBase rt, Action a, Color? clearColor = default)
{
// TODO: for the love of god all this state pushing/popping needs to be cleaned up.

Expand Down Expand Up @@ -556,6 +593,8 @@ private void RenderViewport(Viewport viewport)
if (_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
ApplyFovToBuffer(viewport, eye);
}

DrawEntitiesOverFov(viewport, eye);
}

_lightingReady = false;
Expand Down
Loading