From b822e090efe336a6245af87af86e2cb502880b92 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:05:21 +1000 Subject: [PATCH] Update postshaders for multi-shader support (#44735) --- .../Graphics/ContentPostShaderIds.cs | 25 +++ Content.Client/Holopad/HolopadSystem.cs | 24 ++- .../Components/InteractionOutlineComponent.cs | 7 +- Content.Client/Interaction/DragDropSystem.cs | 53 ++++-- .../Movement/Systems/FloorOcclusionSystem.cs | 12 +- .../Outline/InteractionOutlineSystem.cs | 178 +++++++++++++----- Content.Client/Outline/TargetOutlineSystem.cs | 21 +-- Content.Client/Stealth/StealthSystem.cs | 22 ++- Resources/Textures/Shaders/hcut.swsl | 2 +- Resources/Textures/Shaders/outline.swsl | 11 +- 10 files changed, 256 insertions(+), 99 deletions(-) create mode 100644 Content.Client/Graphics/ContentPostShaderIds.cs diff --git a/Content.Client/Graphics/ContentPostShaderIds.cs b/Content.Client/Graphics/ContentPostShaderIds.cs new file mode 100644 index 0000000000..d1e2276332 --- /dev/null +++ b/Content.Client/Graphics/ContentPostShaderIds.cs @@ -0,0 +1,25 @@ +namespace Content.Client.Graphics; + +public static class ContentPostShaderIds +{ + public const string Stealth = "stealth"; + public const string FloorOcclusion = "floor-occlusion"; + public const string Holopad = "holopad"; + public const string InteractionOutline = "interaction-outline"; + public const string TargetOutline = "target-outline"; + public const string DragDropOutline = "drag-drop-outline"; + + public static readonly string[] BeforeOutlines = + { + InteractionOutline, + TargetOutline, + DragDropOutline, + }; + + public static readonly string[] AfterBaseEffects = + { + Stealth, + FloorOcclusion, + Holopad, + }; +} diff --git a/Content.Client/Holopad/HolopadSystem.cs b/Content.Client/Holopad/HolopadSystem.cs index 2f22c66d4d..fbcd0573b9 100644 --- a/Content.Client/Holopad/HolopadSystem.cs +++ b/Content.Client/Holopad/HolopadSystem.cs @@ -1,3 +1,4 @@ +using Content.Client.Graphics; using Content.Shared.Chat.TypingIndicator; using Content.Shared.Holopad; using Robust.Client.GameObjects; @@ -30,10 +31,10 @@ public sealed partial class HolopadSystem : SharedHolopadSystem private void OnShaderRender(Entity entity, ref BeforePostShaderRenderEvent ev) { - if (ev.Sprite.PostShader == null) + if (ev.Id != ContentPostShaderIds.Holopad) return; - UpdateHologramSprite(entity, entity.Comp.LinkedEntity); + UpdateHologramSprite(entity, entity.Comp.LinkedEntity, ev.Shader); } private void OnTypingChanged(TypingChangedEvent ev, EntitySessionEventArgs args) @@ -50,7 +51,7 @@ public sealed partial class HolopadSystem : SharedHolopadSystem RaiseNetworkEvent(netEv); } - private void UpdateHologramSprite(EntityUid hologram, EntityUid? target) + private void UpdateHologramSprite(EntityUid hologram, EntityUid? target, ShaderInstance? shader = null) { // Get required components if (!TryComp(hologram, out var hologramSprite) || @@ -110,15 +111,19 @@ public sealed partial class HolopadSystem : SharedHolopadSystem hologramSprite.LayerSetShader(i, "unshaded"); } - UpdateHologramShader(hologram, hologramSprite, holopadhologram); + UpdateHologramShader(hologram, hologramSprite, holopadhologram, shader); } - private void UpdateHologramShader(EntityUid uid, SpriteComponent sprite, HolopadHologramComponent holopadHologram) + private void UpdateHologramShader( + EntityUid uid, + SpriteComponent sprite, + HolopadHologramComponent holopadHologram, + ShaderInstance? shader = null) { // Find the texture height of the largest layer float texHeight = sprite.AllLayers.Max(x => x.PixelSize.Y); - var instance = ProtoMan.Index(holopadHologram.ShaderName).InstanceUnique(); + var instance = shader ?? ProtoMan.Index(holopadHologram.ShaderName).InstanceUnique(); instance.SetParameter("color1", new Vector3(holopadHologram.Color1.R, holopadHologram.Color1.G, holopadHologram.Color1.B)); instance.SetParameter("color2", new Vector3(holopadHologram.Color2.R, holopadHologram.Color2.G, holopadHologram.Color2.B)); instance.SetParameter("alpha", holopadHologram.Alpha); @@ -126,7 +131,10 @@ public sealed partial class HolopadSystem : SharedHolopadSystem instance.SetParameter("texHeight", texHeight); instance.SetParameter("t", (float)_timing.CurTime.TotalSeconds * holopadHologram.ScrollRate); - sprite.PostShader = instance; - sprite.RaiseShaderEvent = true; + _sprite.SetPostShader((uid, sprite), new SpriteComponent.PostShaderArgs(ContentPostShaderIds.Holopad, instance) + { + RaiseShaderEvent = true, + Before = ContentPostShaderIds.BeforeOutlines, + }); } } diff --git a/Content.Client/Interactable/Components/InteractionOutlineComponent.cs b/Content.Client/Interactable/Components/InteractionOutlineComponent.cs index 838c782e9a..a2e144b86e 100644 --- a/Content.Client/Interactable/Components/InteractionOutlineComponent.cs +++ b/Content.Client/Interactable/Components/InteractionOutlineComponent.cs @@ -1,4 +1,9 @@ namespace Content.Client.Interactable.Components; [RegisterComponent] -public sealed partial class InteractionOutlineComponent : Component; +public sealed partial class InteractionOutlineComponent : Component +{ + public bool InRange; + public int LastRenderScale; + public bool Active; +} diff --git a/Content.Client/Interaction/DragDropSystem.cs b/Content.Client/Interaction/DragDropSystem.cs index c38bf138ad..f276e2f9c9 100644 --- a/Content.Client/Interaction/DragDropSystem.cs +++ b/Content.Client/Interaction/DragDropSystem.cs @@ -1,6 +1,7 @@ using System.Numerics; using Content.Client.CombatMode; using Content.Client.Gameplay; +using Content.Client.Graphics; using Content.Client.Outline; using Content.Shared.ActionBlocker; using Content.Shared.CCVar; @@ -101,7 +102,8 @@ public sealed partial class DragDropSystem : SharedDragDropSystem private ShaderInstance? _dropTargetInRangeShader; private ShaderInstance? _dropTargetOutOfRangeShader; - private readonly List _highlightedSprites = new(); + private readonly HashSet _highlightedSprites = new(); + private readonly HashSet _nextHighlightedSprites = new(); public override void Initialize() { @@ -422,8 +424,7 @@ public sealed partial class DragDropSystem : SharedDragDropSystem // highlights the possible targets which are visible // and able to be dropped on by the current dragged entity - // remove current highlights - RemoveHighlights(); + _nextHighlightedSprites.Clear(); // find possible targets on screen even if not reachable // TODO: Duplicated in SpriteSystem and TargetOutlineSystem. Should probably be cached somewhere for a frame? @@ -455,32 +456,52 @@ public sealed partial class DragDropSystem : SharedDragDropSystem && _interactionSystem.InRangeUnobstructed(user.Value, entity); } - if (inRangeSprite.PostShader != null && - inRangeSprite.PostShader != _dropTargetInRangeShader && - inRangeSprite.PostShader != _dropTargetOutOfRangeShader) - { - continue; - } - // highlight depending on whether its in or out of range - inRangeSprite.PostShader = valid.Value ? _dropTargetInRangeShader : _dropTargetOutOfRangeShader; + SetDragDropPostShader((entity, inRangeSprite), valid.Value ? _dropTargetInRangeShader! : _dropTargetOutOfRangeShader!); inRangeSprite.RenderOrder = EntityManager.CurrentTick.Value; - _highlightedSprites.Add(inRangeSprite); + _nextHighlightedSprites.Add(inRangeSprite); } + + foreach (var highlightedSprite in _highlightedSprites) + { + if (_nextHighlightedSprites.Contains(highlightedSprite)) + continue; + + _sprite.RemovePostShader(highlightedSprite, ContentPostShaderIds.DragDropOutline); + highlightedSprite.RenderOrder = 0; + } + + _highlightedSprites.Clear(); + foreach (var highlightedSprite in _nextHighlightedSprites) + { + _highlightedSprites.Add(highlightedSprite); + } + } + + private void SetDragDropPostShader(Entity sprite, ShaderInstance shader) + { + if (_sprite.TryGetPostShader(sprite, ContentPostShaderIds.DragDropOutline, out var entry) && + entry.Shader == shader) + { + return; + } + + _sprite.SetPostShader(sprite, new SpriteComponent.PostShaderArgs(ContentPostShaderIds.DragDropOutline, shader) + { + After = ContentPostShaderIds.AfterBaseEffects, + }); } private void RemoveHighlights() { foreach (var highlightedSprite in _highlightedSprites) { - if (highlightedSprite.PostShader != _dropTargetInRangeShader && highlightedSprite.PostShader != _dropTargetOutOfRangeShader) - continue; - - highlightedSprite.PostShader = null; + _sprite.RemovePostShader(highlightedSprite, ContentPostShaderIds.DragDropOutline); highlightedSprite.RenderOrder = 0; } _highlightedSprites.Clear(); + _nextHighlightedSprites.Clear(); } /// diff --git a/Content.Client/Movement/Systems/FloorOcclusionSystem.cs b/Content.Client/Movement/Systems/FloorOcclusionSystem.cs index ce0705e347..b7a7784c94 100644 --- a/Content.Client/Movement/Systems/FloorOcclusionSystem.cs +++ b/Content.Client/Movement/Systems/FloorOcclusionSystem.cs @@ -1,3 +1,4 @@ +using Content.Client.Graphics; using Content.Shared.Movement.Components; using Content.Shared.Movement.Systems; using Robust.Client.GameObjects; @@ -11,6 +12,7 @@ public sealed partial class FloorOcclusionSystem : SharedFloorOcclusionSystem private static readonly ProtoId HorizontalCut = "HorizontalCut"; [Dependency] private EntityQuery _spriteQuery = default!; + [Dependency] private SpriteSystem _sprite = default!; public override void Initialize() { @@ -48,16 +50,16 @@ public sealed partial class FloorOcclusionSystem : SharedFloorOcclusionSystem var shader = ProtoMan.Index(HorizontalCut).Instance(); - if (sprite.Comp.PostShader is not null && sprite.Comp.PostShader != shader) - return; - if (enabled) { - sprite.Comp.PostShader = shader; + _sprite.SetPostShader(sprite, new SpriteComponent.PostShaderArgs(ContentPostShaderIds.FloorOcclusion, shader) + { + Before = ContentPostShaderIds.BeforeOutlines, + }); } else { - sprite.Comp.PostShader = null; + _sprite.RemovePostShader(sprite, ContentPostShaderIds.FloorOcclusion); } } } diff --git a/Content.Client/Outline/InteractionOutlineSystem.cs b/Content.Client/Outline/InteractionOutlineSystem.cs index 2772725da5..516e24a458 100644 --- a/Content.Client/Outline/InteractionOutlineSystem.cs +++ b/Content.Client/Outline/InteractionOutlineSystem.cs @@ -1,5 +1,7 @@ +using System.Collections.Generic; using Content.Client.ContextMenu.UI; using Content.Client.Gameplay; +using Content.Client.Graphics; using Content.Client.Interactable.Components; using Content.Client.Viewport; using Content.Shared.CCVar; @@ -18,6 +20,11 @@ namespace Content.Client.Outline; public sealed partial class InteractionOutlineSystem : EntitySystem { + private static readonly ProtoId ShaderInRange = "SelectionOutlineInrange"; + private static readonly ProtoId ShaderOutOfRange = "SelectionOutline"; + private const float DefaultWidth = 1; + private readonly Dictionary<(bool InRange, int RenderScale), ShaderInstance> _shaderCache = new(); + [Dependency] private IConfigurationManager _configManager = default!; [Dependency] private IEyeManager _eyeManager = default!; [Dependency] private IInputManager _inputManager = default!; @@ -25,9 +32,8 @@ public sealed partial class InteractionOutlineSystem : EntitySystem [Dependency] private IStateManager _stateManager = default!; [Dependency] private IUserInterfaceManager _uiManager = default!; [Dependency] private SharedInteractionSystem _interactionSystem = default!; - - [Dependency] private EntityQuery _outlineQuery = default!; - [Dependency] private EntityQuery _spriteQuery = default!; + [Dependency] private IPrototypeManager _prototypeManager = default!; + [Dependency] private SpriteSystem _sprite = default!; /// /// Whether to currently draw the outline. The outline may be temporarily disabled by other systems @@ -39,42 +45,34 @@ public sealed partial class InteractionOutlineSystem : EntitySystem /// private bool _cvarEnabled = true; - private const float OutlineWidth = 1; - - private static readonly ProtoId ShaderInRange = "SelectionOutlineInrange"; - private static readonly ProtoId ShaderOutOfRange = "SelectionOutline"; - - private ShaderInstance? _shaderInRange; - private ShaderInstance? _shaderOutOfRange; - private EntityUid? _lastHoveredEntity; + public override void Shutdown() + { + foreach (var shader in _shaderCache.Values) + { + shader.Dispose(); + } + + _shaderCache.Clear(); + base.Shutdown(); + } + public override void Initialize() { base.Initialize(); - _shaderInRange = ProtoMan.Index(ShaderInRange).InstanceUnique(); - _shaderOutOfRange = ProtoMan.Index(ShaderOutOfRange).InstanceUnique(); - Subs.CVar(_configManager, CCVars.OutlineEnabled, SetCvarEnabled); + SubscribeLocalEvent(OnShutdown); UpdatesAfter.Add(typeof(SharedEyeSystem)); } - private void CleanupLastHoveredShader() + private void OnShutdown(Entity ent, ref ComponentShutdown args) { - var uid = _lastHoveredEntity; - _lastHoveredEntity = null; + RemoveOutline(ent); - if (!_outlineQuery.HasComp(uid)) - return; - - if (!_spriteQuery.TryComp(uid, out var sprite)) - return; - - if (sprite.PostShader != _shaderInRange && sprite.PostShader != _shaderOutOfRange) - return; - - sprite.PostShader = null; + if (_lastHoveredEntity == ent.Owner) + _lastHoveredEntity = null; } public void SetCvarEnabled(bool cvarEnabled) @@ -86,7 +84,11 @@ public sealed partial class InteractionOutlineSystem : EntitySystem if (_cvarEnabled) return; - CleanupLastHoveredShader(); + if (_lastHoveredEntity == null || Deleted(_lastHoveredEntity)) + return; + + if (TryComp(_lastHoveredEntity, out InteractionOutlineComponent? outline)) + RemoveOutline((_lastHoveredEntity.Value, outline)); } public void SetEnabled(bool enabled) @@ -101,7 +103,11 @@ public sealed partial class InteractionOutlineSystem : EntitySystem if (enabled) return; - CleanupLastHoveredShader(); + if (_lastHoveredEntity == null || Deleted(_lastHoveredEntity)) + return; + + if (TryComp(_lastHoveredEntity, out InteractionOutlineComponent? outline)) + RemoveOutline((_lastHoveredEntity.Value, outline)); } public override void FrameUpdate(float frameTime) @@ -156,24 +162,106 @@ public sealed partial class InteractionOutlineSystem : EntitySystem renderScale = _eyeManager.MainViewport.GetRenderScale(); } - if (_lastHoveredEntity != entityToClick) - { - CleanupLastHoveredShader(); - - _lastHoveredEntity = entityToClick; - } - - if (!_outlineQuery.HasComp(entityToClick)) - return; - - if (!_spriteQuery.TryComp(entityToClick, out var sprite)) - return; - var inRange = false; - if (localSession.AttachedEntity != null) + if (localSession.AttachedEntity != null && !Deleted(entityToClick)) inRange = _interactionSystem.InRangeUnobstructed(localSession.AttachedEntity.Value, entityToClick.Value); - var shader = sprite.PostShader = inRange ? _shaderInRange : _shaderOutOfRange; - shader?.SetParameter("outline_width", OutlineWidth * renderScale); + InteractionOutlineComponent? outline; + + if (entityToClick == _lastHoveredEntity) + { + if (entityToClick != null && TryComp(entityToClick, out outline)) + { + UpdateOutline((entityToClick.Value, outline), inRange, renderScale); + } + + return; + } + + if (_lastHoveredEntity != null && !Deleted(_lastHoveredEntity) && + TryComp(_lastHoveredEntity, out outline)) + { + RemoveOutline((_lastHoveredEntity.Value, outline)); + } + + _lastHoveredEntity = entityToClick; + + if (_lastHoveredEntity != null && TryComp(_lastHoveredEntity, out outline)) + { + AddOutline((_lastHoveredEntity.Value, outline), inRange, renderScale); + } + } + + private void AddOutline(Entity ent, bool inInteractionRange, int renderScale) + { + ent.Comp.LastRenderScale = renderScale; + ent.Comp.InRange = inInteractionRange; + ent.Comp.Active = true; + + if (!TryComp(ent.Owner, out SpriteComponent? sprite)) + return; + + SetOutlinePostShader((ent.Owner, sprite), inInteractionRange, renderScale); + } + + private void RemoveOutline(Entity ent) + { + if (TryComp(ent.Owner, out SpriteComponent? sprite)) + { + _sprite.RemovePostShader((ent.Owner, sprite), ContentPostShaderIds.InteractionOutline); + sprite.RenderOrder = 0; + } + + ent.Comp.Active = false; + } + + private void UpdateOutline(Entity ent, bool inInteractionRange, int renderScale) + { + if (!TryComp(ent.Owner, out SpriteComponent? sprite) + || !_sprite.HasPostShader((ent.Owner, sprite), ContentPostShaderIds.InteractionOutline) + || inInteractionRange == ent.Comp.InRange && ent.Comp.LastRenderScale == renderScale) + { + return; + } + + ent.Comp.InRange = inInteractionRange; + ent.Comp.LastRenderScale = renderScale; + + SetOutlinePostShader((ent.Owner, sprite), ent.Comp.InRange, ent.Comp.LastRenderScale); + } + + private void SetOutlinePostShader(Entity sprite, bool inRange, int renderScale) + { + var shader = GetShader(inRange, renderScale); + if (_sprite.TryGetPostShader(sprite, ContentPostShaderIds.InteractionOutline, out var entry) && + entry.Shader == shader) + { + return; + } + + _sprite.SetPostShader(sprite, new SpriteComponent.PostShaderArgs(ContentPostShaderIds.InteractionOutline, shader) + { + After = ContentPostShaderIds.AfterBaseEffects, + }); + } + + private ShaderInstance GetShader(bool inRange, int renderScale) + { + var key = (inRange, renderScale); + if (_shaderCache.TryGetValue(key, out var shader)) + return shader; + + shader = MakeNewShader(inRange, renderScale); + _shaderCache.Add(key, shader); + return shader; + } + + private ShaderInstance MakeNewShader(bool inRange, int renderScale) + { + var shaderName = inRange ? ShaderInRange : ShaderOutOfRange; + + var instance = _prototypeManager.Index(shaderName).InstanceUnique(); + instance.SetParameter("outline_width", DefaultWidth * renderScale); + return instance; } } diff --git a/Content.Client/Outline/TargetOutlineSystem.cs b/Content.Client/Outline/TargetOutlineSystem.cs index 350c3e032b..806a3d5452 100644 --- a/Content.Client/Outline/TargetOutlineSystem.cs +++ b/Content.Client/Outline/TargetOutlineSystem.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Client.Graphics; using Content.Shared.Interaction; using Content.Shared.Whitelist; using Robust.Client.GameObjects; @@ -27,6 +28,7 @@ public sealed partial class TargetOutlineSystem : EntitySystem [Dependency] private EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private SharedTransformSystem _transformSystem = default!; [Dependency] private EntityQuery _spriteQuery = default!; + [Dependency] private SpriteSystem _sprite = default!; private bool _enabled = false; @@ -154,9 +156,9 @@ public sealed partial class TargetOutlineSystem : EntitySystem if (!valid) { // was this previously valid? - if (_highlightedSprites.Remove(sprite) && (sprite.PostShader == _shaderTargetValid || sprite.PostShader == _shaderTargetInvalid)) + if (_highlightedSprites.Remove(sprite)) { - sprite.PostShader = null; + _sprite.RemovePostShader(sprite, ContentPostShaderIds.TargetOutline); sprite.RenderOrder = 0; } @@ -173,13 +175,11 @@ public sealed partial class TargetOutlineSystem : EntitySystem valid = (origin - target).LengthSquared() <= Range; } - if (sprite.PostShader != null && - sprite.PostShader != _shaderTargetValid && - sprite.PostShader != _shaderTargetInvalid) - return; - // highlight depending on whether its in or out of range - sprite.PostShader = valid ? _shaderTargetValid : _shaderTargetInvalid; + _sprite.SetPostShader(sprite, new SpriteComponent.PostShaderArgs(ContentPostShaderIds.TargetOutline, valid ? _shaderTargetValid! : _shaderTargetInvalid!) + { + After = ContentPostShaderIds.AfterBaseEffects, + }); sprite.RenderOrder = EntityManager.CurrentTick.Value; _highlightedSprites.Add(sprite); } @@ -189,10 +189,7 @@ public sealed partial class TargetOutlineSystem : EntitySystem { foreach (var sprite in _highlightedSprites) { - if (sprite.PostShader != _shaderTargetValid && sprite.PostShader != _shaderTargetInvalid) - continue; - - sprite.PostShader = null; + _sprite.RemovePostShader(sprite, ContentPostShaderIds.TargetOutline); sprite.RenderOrder = 0; } diff --git a/Content.Client/Stealth/StealthSystem.cs b/Content.Client/Stealth/StealthSystem.cs index a33b11f0af..99516036f5 100644 --- a/Content.Client/Stealth/StealthSystem.cs +++ b/Content.Client/Stealth/StealthSystem.cs @@ -1,4 +1,5 @@ using Content.Client.Interactable.Components; +using Content.Client.Graphics; using Content.Shared.Stealth; using Content.Shared.Stealth.Components; using Robust.Client.GameObjects; @@ -42,9 +43,19 @@ public sealed partial class StealthSystem : SharedStealthSystem return; _sprite.SetColor((uid, sprite), Color.White); - sprite.PostShader = enabled ? _shader : null; - sprite.GetScreenTexture = enabled; - sprite.RaiseShaderEvent = enabled; + if (enabled) + { + _sprite.SetPostShader((uid, sprite), new SpriteComponent.PostShaderArgs(ContentPostShaderIds.Stealth, _shader) + { + GetScreenTexture = true, + RaiseShaderEvent = true, + Before = ContentPostShaderIds.BeforeOutlines, + }); + } + else + { + _sprite.RemovePostShader((uid, sprite), ContentPostShaderIds.Stealth); + } if (!enabled) { @@ -53,11 +64,8 @@ public sealed partial class StealthSystem : SharedStealthSystem return; } - if (TryComp(uid, out InteractionOutlineComponent? outline)) - { - RemCompDeferred(uid, outline); + if (HasComp(uid)) component.HadOutline = true; - } } private void OnStartup(EntityUid uid, StealthComponent component, ComponentStartup args) diff --git a/Resources/Textures/Shaders/hcut.swsl b/Resources/Textures/Shaders/hcut.swsl index 4f8bfd9573..0b1bd19f23 100644 --- a/Resources/Textures/Shaders/hcut.swsl +++ b/Resources/Textures/Shaders/hcut.swsl @@ -9,5 +9,5 @@ void fragment() highp vec4 tex = zTexture(UV); highp float modifier = (UV.y > c ^^ !below) ? 1.0 : alphaModifier; - COLOR = vec4(tex.x, tex.y, tex.z, tex.w * modifier); + COLOR = vec4(tex.rgb * modifier, tex.a * modifier); } diff --git a/Resources/Textures/Shaders/outline.swsl b/Resources/Textures/Shaders/outline.swsl index df031d7d6e..ddf5b0ee3d 100644 --- a/Resources/Textures/Shaders/outline.swsl +++ b/Resources/Textures/Shaders/outline.swsl @@ -70,11 +70,14 @@ void fragment() { maxa = max(a, maxa); mina = min(a, mina); - a = texture2D(TEXTURE, UV + vec2(outline_width, outline_width)*ps).a; - maxa = max(a, maxa); - mina = min(a, mina); + a = texture2D(TEXTURE, UV + vec2(outline_width, outline_width)*ps).a; + maxa = max(a, maxa); + mina = min(a, mina); lowp float sampledLight = outline_fullbright ? 1.0 : clamp( (pow( zGrayscale_BT601(lightSample.rgb) * light_whitepoint, light_gamma) / light_whitepoint ) * light_boost, 0.0, 1.0); - COLOR = mix(col, outline_color * vec4(vec3(1.0), sampledLight), maxa - col.a); + highp float outlineAmount = max(maxa - col.a, 0.0); + highp float outlineAlpha = outline_color.a * sampledLight * outlineAmount; + highp vec4 outline = vec4(outline_color.rgb * outlineAlpha, outlineAlpha); + COLOR = mix(outline, col, step(0.01, col.a)); lightSample = vec3(1.0); }