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 @@ -202,16 +202,35 @@ public bool ContainerOccluded
[ViewVariables(VVAccess.ReadWrite)] internal bool _inertUpdateQueued;

/// <summary>
/// Shader instance to use when drawing the final sprite to the world.
/// Primary shader instance to use when drawing the final sprite to the world.
/// Additional shaders in <see cref="PostShaderChain"/> are applied afterwards.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public ShaderInstance? PostShader
public ShaderInstance? PostShader { get; set; }

/// <summary>
/// Additional post-shaders applied in order after <see cref="PostShader"/>.
/// Use <see cref="SpriteSystem.AddPostShader"/> and <see cref="SpriteSystem.RemovePostShader"/>
/// to modify the chain.
/// </summary>
[ViewVariables]
public IReadOnlyList<ShaderInstance> PostShaderChain => _postShaderChain;

internal readonly List<ShaderInstance> _postShaderChain = new();

internal int PostShaderCount => _postShaderChain.Count + (PostShader == null ? 0 : 1);

internal ShaderInstance GetPostShader(int index)
{
get;
// This will get obsoleted, but I only want to mark it as obsolete when multi-shader support is added, so
// that people can use the appropriate method and don't migrate to an incorrect new method that wont
// be obsoleted.
set;
if (PostShader != null)
{
if (index == 0)
return PostShader;

index--;
}

return _postShaderChain[index];
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ in target
target.Comp.PostShader = source.Comp.PostShader is {Mutable: true}
? source.Comp.PostShader.Duplicate()
: source.Comp.PostShader;
target.Comp._postShaderChain.Clear();
foreach (var postShader in source.Comp._postShaderChain)
{
target.Comp._postShaderChain.Add(postShader.Mutable
? postShader.Duplicate()
: postShader);
}

target.Comp.RenderOrder = source.Comp.RenderOrder;
target.Comp.GranularLayersRendering = source.Comp.GranularLayersRendering;
Expand Down
23 changes: 23 additions & 0 deletions Robust.Client/GameObjects/EntitySystems/SpriteSystem.Setters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,27 @@ public void SetGranularLayersRendering(Entity<SpriteComponent?> sprite, bool val
_tree.QueueTreeUpdate(sprite!);
DirtyBounds(sprite!);
}

/// <summary>
/// Adds an additional post-shader to the end of a sprite's post-shader chain.
/// The primary <see cref="SpriteComponent.PostShader"/> is applied before this chain.
/// </summary>
public bool AddPostShader(Entity<SpriteComponent?> sprite, ShaderInstance shader)
{
if (!_query.Resolve(sprite.Owner, ref sprite.Comp) ||
sprite.Comp._postShaderChain.Contains(shader))
return false;

sprite.Comp._postShaderChain.Add(shader);
return true;
}

/// <summary>
/// Removes the first matching additional post-shader from a sprite's post-shader chain.
/// </summary>
public bool RemovePostShader(Entity<SpriteComponent?> sprite, ShaderInstance shader)
{
return _query.Resolve(sprite.Owner, ref sprite.Comp) &&
sprite.Comp._postShaderChain.Remove(shader);
}
}
80 changes: 64 additions & 16 deletions Robust.Client/Graphics/Clyde/Clyde.HLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world
var overlayIndex = 0;

RenderTexture? entityPostRenderTarget = null;
RenderTexture? entityPostRenderTarget2 = null;
bool flushed = false;
for (var i = 0; i < _drawingSpriteList.Count; i++)
{
Expand All @@ -317,7 +318,8 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world
}

Vector2i roundedPos = default;
if (entry.Sprite.PostShader != null)
var applyPostShaders = false;
if (entry.Sprite.PostShaderCount > 0)
{
// 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();
Expand All @@ -331,14 +333,17 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world
screenSpriteSize.Y++;

bool exit = false;
if (entry.Sprite.GetScreenTexture && entry.Sprite.PostShader != null)
if (entry.Sprite.GetScreenTexture)
{
FlushRenderQueue();
var tex = CopyScreenTexture(viewport.RenderTarget);
if (tex == null)
exit = true;
else
entry.Sprite.PostShader.SetParameter("SCREEN_TEXTURE", tex);
{
for (var shaderIndex = 0; shaderIndex < entry.Sprite.PostShaderCount; shaderIndex++)
entry.Sprite.GetPostShader(shaderIndex).SetParameter("SCREEN_TEXTURE", tex);
}
}

// check that sprite size is valid
Expand All @@ -365,6 +370,16 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world
name: nameof(entityPostRenderTarget));
}

if (entry.Sprite.PostShaderCount > 1 &&
(entityPostRenderTarget2 == null
|| entityPostRenderTarget2.Size.X < entityPostRenderTarget.Size.X
|| entityPostRenderTarget2.Size.Y < entityPostRenderTarget.Size.Y))
{
entityPostRenderTarget2 = CreateRenderTarget(entityPostRenderTarget.Size,
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
name: nameof(entityPostRenderTarget2));
}

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

Expand All @@ -379,34 +394,66 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world
if (entry.Sprite.RaiseShaderEvent)
_entityManager.EventBus.RaiseLocalEvent(entry.Uid,
new BeforePostShaderRenderEvent(entry.Sprite, viewport), false);

applyPostShaders = true;
}
}

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

if (entry.Sprite.PostShader != null && entityPostRenderTarget != null)
if (applyPostShaders && entityPostRenderTarget != null)
{
var oldProj = _currentMatrixProj;
var oldView = _currentMatrixView;
_renderHandle.SetModelTransform(Matrix3x2.Identity);

_renderHandle.UseRenderTarget(viewport.RenderTarget);
_renderHandle.Viewport(Box2i.FromDimensions(Vector2i.Zero, screenSize));
var source = entityPostRenderTarget;
for (var shaderIndex = 0; shaderIndex < entry.Sprite.PostShaderCount; shaderIndex++)
{
var finalShader = shaderIndex == entry.Sprite.PostShaderCount - 1;
Box2i box;

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

var rounded = roundedPos - entityPostRenderTarget.Size / 2;
var rounded = roundedPos - source.Size / 2;
box = Box2i.FromDimensions(rounded, source.Size);
}
else
{
DebugTools.Assert(entityPostRenderTarget2 != null);
var destination = ReferenceEquals(source, entityPostRenderTarget)
? entityPostRenderTarget2
: entityPostRenderTarget;

_renderHandle.UseRenderTarget(destination);
_renderHandle.Viewport(Box2i.FromDimensions(Vector2i.Zero, destination.Size));
_renderHandle.Clear(default, 0,
ClearBufferMask.ColorBufferBit | ClearBufferMask.StencilBufferBit);
CalcScreenMatrices(destination.Size, out var proj, out var view);
_renderHandle.SetProjView(proj, view);
box = Box2i.FromDimensions(Vector2i.Zero, source.Size);
}

var box = Box2i.FromDimensions(rounded, entityPostRenderTarget.Size);
_renderHandle.UseShader(entry.Sprite.GetPostShader(shaderIndex));
_renderHandle.DrawTextureScreen(source.Texture,
box.BottomLeft, box.BottomRight, box.TopLeft, box.TopRight,
Color.White, null);
_renderHandle.UseShader(null);

_renderHandle.DrawTextureScreen(entityPostRenderTarget.Texture,
box.BottomLeft, box.BottomRight, box.TopLeft, box.TopRight,
Color.White, null);
if (!finalShader)
{
source = ReferenceEquals(source, entityPostRenderTarget)
? entityPostRenderTarget2!
: entityPostRenderTarget;
}
}

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

Expand All @@ -424,6 +471,7 @@ private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 world

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

_debugStats.Entities += _drawingSpriteList.Count;
_drawingSpriteList.Clear();
Expand Down
Loading