diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index c981f4460e0..da7acdce7b1 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -39,7 +39,9 @@ END TEMPLATE--> ### New features -*None yet* +* RSIs can now specify load parameters, mimicking the ones from `.png.yml`. Currently only disabling sRGB is supported. +* Added a second UV channel to Clyde's vertex format. On regular batched sprite draws, this goes 0 -> 1 across the sprite quad. +* Added a new `CopyToShaderParameters` system for `SpriteComponent` layers. ### Bugfixes diff --git a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs index f42d3a8afa6..f6467daa075 100644 --- a/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs +++ b/Robust.Client/GameObjects/Components/Renderable/SpriteComponent.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Text; using Robust.Client.Graphics; +using Robust.Client.Graphics.Clyde; using Robust.Client.ResourceManagement; using Robust.Client.Utility; using Robust.Shared.Animations; @@ -28,6 +29,7 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth; using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer; using Direction = Robust.Shared.Maths.Direction; +using Vector4 = Robust.Shared.Maths.Vector4; namespace Robust.Client.GameObjects { @@ -770,15 +772,7 @@ public void LayerSetData(int index, PrototypeLayerData layerDatum) { foreach (var keyString in layerDatum.MapKeys) { - object key; - if (reflection.TryParseEnumReference(keyString, out var @enum)) - { - key = @enum; - } - else - { - key = keyString; - } + var key = ParseKey(keyString); if (LayerMap.TryGetValue(key, out var mappedIndex)) { @@ -804,9 +798,30 @@ public void LayerSetData(int index, PrototypeLayerData layerDatum) // If neither state: nor texture: were provided we assume that they want a blank invisible layer. layer.Visible = layerDatum.Visible ?? layer.Visible; + if (layerDatum.CopyToShaderParameters is { } copyParameters) + { + layer.CopyToShaderParameters = new CopyToShaderParameters(ParseKey(copyParameters.LayerKey)) + { + ParameterTexture = copyParameters.ParameterTexture, + ParameterUV = copyParameters.ParameterUV + }; + } + else + { + layer.CopyToShaderParameters = null; + } + RebuildBounds(); } + private object ParseKey(string keyString) + { + if (reflection.TryParseEnumReference(keyString, out var @enum)) + return @enum; + + return keyString; + } + public void LayerSetData(object layerKey, PrototypeLayerData data) { if (!LayerMapTryGet(layerKey, out var layer, true)) @@ -1635,6 +1650,9 @@ public Vector2 Offset [ViewVariables] public LayerRenderingStrategy RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy; + [ViewVariables(VVAccess.ReadWrite)] + public CopyToShaderParameters? CopyToShaderParameters; + public Layer(SpriteComponent parent) { _parent = parent; @@ -2007,8 +2025,6 @@ internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, // Set the drawing transform for this layer GetLayerDrawMatrix(dir, out var layerMatrix); - Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix); - drawingHandle.SetTransform(in transformMatrix); // The direction used to draw the sprite can differ from the one that the angle would naively suggest, // due to direction overrides or offsets. @@ -2018,7 +2034,41 @@ internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, // Get the correct directional texture from the state, and draw it! var texture = GetRenderTexture(_actualState, dir); - RenderTexture(drawingHandle, texture); + + if (CopyToShaderParameters == null) + { + // Set the drawing transform for this layer + Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix); + drawingHandle.SetTransform(in transformMatrix); + + RenderTexture(drawingHandle, texture); + } + else + { + // Multiple atrocities to god being committed right here. + var otherLayerIdx = _parent.LayerMap[CopyToShaderParameters.LayerKey!]; + var otherLayer = _parent.Layers[otherLayerIdx]; + if (otherLayer.Shader is not { } shader) + { + // No shader set apparently..? + return; + } + + if (!shader.Mutable) + otherLayer.Shader = shader = shader.Duplicate(); + + var clydeTexture = Clyde.RenderHandle.ExtractTexture(texture, null, out var csr); + var sr = Clyde.RenderHandle.WorldTextureBoundsToUV(clydeTexture, csr); + + if (CopyToShaderParameters.ParameterTexture is { } paramTexture) + shader.SetParameter(paramTexture, clydeTexture); + + if (CopyToShaderParameters.ParameterUV is { } paramUV) + { + var uv = new Vector4(sr.Left, sr.Bottom, sr.Right, sr.Top); + shader.SetParameter(paramUV, uv); + } + } } private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture) @@ -2096,6 +2146,17 @@ internal void AdvanceFrameAnimation(RSI.State state) } } + /// + /// Instantiated version of . + /// Has actually resolved to a a real key. + /// + public sealed class CopyToShaderParameters(object layerKey) + { + public object LayerKey = layerKey; + public string? ParameterTexture; + public string? ParameterUV; + } + void IAnimationProperties.SetAnimatableProperty(string name, object value) { if (!name.StartsWith("layer/")) diff --git a/Robust.Client/Graphics/Clyde/Clyde.Constants.cs b/Robust.Client/Graphics/Clyde/Clyde.Constants.cs index 94b1fe3acf6..2ae93219693 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Constants.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Constants.cs @@ -6,7 +6,8 @@ private static readonly (string, uint)[] BaseShaderAttribLocations = { ("aPos", 0), ("tCoord", 1), - ("modulate", 2) + ("tCoord2", 2), + ("modulate", 3) }; private const int UniIModUV = 0; diff --git a/Robust.Client/Graphics/Clyde/Clyde.Layout.cs b/Robust.Client/Graphics/Clyde/Clyde.Layout.cs index 07a0f145f76..7f6ab086b71 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Layout.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Layout.cs @@ -23,9 +23,12 @@ private static unsafe void SetupVAOLayout() // Texture Coords. GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 2 * sizeof(float)); GL.EnableVertexAttribArray(1); - // Colour Modulation. - GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float)); + // Texture Coords (2). + GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float)); GL.EnableVertexAttribArray(2); + // Colour Modulation. + GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 6 * sizeof(float)); + GL.EnableVertexAttribArray(3); } // NOTE: This is: @@ -37,6 +40,7 @@ private readonly struct Vertex2D { public readonly Vector2 Position; public readonly Vector2 TextureCoordinates; + public readonly Vector2 TextureCoordinates2; // Note that this color is in linear space. public readonly Color Modulate; @@ -48,6 +52,15 @@ public Vertex2D(Vector2 position, Vector2 textureCoordinates, Color modulate) Modulate = modulate; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vertex2D(Vector2 position, Vector2 textureCoordinates, Vector2 textureCoordinates2, Color modulate) + { + Position = position; + TextureCoordinates = textureCoordinates; + TextureCoordinates2 = textureCoordinates2; + Modulate = modulate; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vertex2D(float x, float y, float u, float v, float r, float g, float b, float a) : this(new Vector2(x, y), new Vector2(u, v), new Color(r, g, b, a)) diff --git a/Robust.Client/Graphics/Clyde/Clyde.RenderHandle.cs b/Robust.Client/Graphics/Clyde/Clyde.RenderHandle.cs index b3453a7574c..b51d70c0165 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.RenderHandle.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.RenderHandle.cs @@ -15,7 +15,7 @@ internal partial class Clyde { private RenderHandle _renderHandle = default!; - private sealed class RenderHandle : IRenderHandle + internal sealed class RenderHandle : IRenderHandle { private readonly Clyde _clyde; private readonly IEntityManager _entities; @@ -88,16 +88,21 @@ public void DrawTextureWorld(Texture texture, Vector2 bl, Vector2 br, Vector2 tl { var clydeTexture = ExtractTexture(texture, in subRegion, out var csr); - var (w, h) = clydeTexture.Size; - var sr = new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h); + var sr = WorldTextureBoundsToUV(clydeTexture, csr); _clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr); } + internal static Box2 WorldTextureBoundsToUV(ClydeTexture texture, UIBox2 csr) + { + var (w, h) = texture.Size; + return new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h); + } + /// /// Converts a subRegion (px) into texture coords (0-1) of a given texture (cells of the textureAtlas). /// - private static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr) + internal static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr) { if (texture is AtlasTexture atlas) { diff --git a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs index 8a58541e575..da77262b139 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Rendering.cs @@ -578,10 +578,10 @@ 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, modulate); - BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, modulate); - BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, modulate); - BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, modulate); + 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); BatchVertexIndex += 4; QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx); diff --git a/Robust.Client/Graphics/Clyde/Clyde.Textures.cs b/Robust.Client/Graphics/Clyde/Clyde.Textures.cs index 6b8180917cb..4dc90a723e0 100644 --- a/Robust.Client/Graphics/Clyde/Clyde.Textures.cs +++ b/Robust.Client/Graphics/Clyde/Clyde.Textures.cs @@ -601,7 +601,7 @@ private void FlushTextureDispose() } } - private sealed class ClydeTexture : OwnedTexture + internal sealed class ClydeTexture : OwnedTexture { private readonly Clyde _clyde; public readonly bool IsSrgb; diff --git a/Robust.Client/Graphics/Clyde/Shaders/base-default.frag b/Robust.Client/Graphics/Clyde/Shaders/base-default.frag index 15bdb5b1252..a0830f17f4b 100644 --- a/Robust.Client/Graphics/Clyde/Shaders/base-default.frag +++ b/Robust.Client/Graphics/Clyde/Shaders/base-default.frag @@ -1,4 +1,5 @@ varying highp vec2 UV; +varying highp vec2 UV2; varying highp vec2 Pos; varying highp vec4 VtxModulate; diff --git a/Robust.Client/Graphics/Clyde/Shaders/base-default.vert b/Robust.Client/Graphics/Clyde/Shaders/base-default.vert index 28d21c4f137..51ba6649c94 100644 --- a/Robust.Client/Graphics/Clyde/Shaders/base-default.vert +++ b/Robust.Client/Graphics/Clyde/Shaders/base-default.vert @@ -2,10 +2,12 @@ /*layout (location = 0)*/ attribute vec2 aPos; // Texture coordinates. /*layout (location = 1)*/ attribute vec2 tCoord; +/*layout (location = 2)*/ attribute vec2 tCoord2; // Colour modulation. -/*layout (location = 2)*/ attribute vec4 modulate; +/*layout (location = 3)*/ attribute vec4 modulate; varying vec2 UV; +varying vec2 UV2; varying vec2 Pos; varying vec4 VtxModulate; @@ -36,5 +38,6 @@ void main() gl_Position = vec4(VERTEX, 0.0, 1.0); Pos = (VERTEX + 1.0) / 2.0; UV = mix(modifyUV.xy, modifyUV.zw, tCoord); + UV2 = tCoord2; VtxModulate = zFromSrgb(modulate); } diff --git a/Robust.Client/Graphics/Clyde/Shaders/base-raw.frag b/Robust.Client/Graphics/Clyde/Shaders/base-raw.frag index e72eaeebbf1..b62afbb8d56 100644 --- a/Robust.Client/Graphics/Clyde/Shaders/base-raw.frag +++ b/Robust.Client/Graphics/Clyde/Shaders/base-raw.frag @@ -1,4 +1,5 @@ varying highp vec2 UV; +varying highp vec2 UV2; uniform sampler2D lightMap; diff --git a/Robust.Client/Graphics/Clyde/Shaders/base-raw.vert b/Robust.Client/Graphics/Clyde/Shaders/base-raw.vert index e5b8cf27fbc..11fee7a4f6c 100644 --- a/Robust.Client/Graphics/Clyde/Shaders/base-raw.vert +++ b/Robust.Client/Graphics/Clyde/Shaders/base-raw.vert @@ -2,10 +2,12 @@ /*layout (location = 0)*/ attribute vec2 aPos; // Texture coordinates. /*layout (location = 1)*/ attribute vec2 tCoord; +/*layout (location = 2)*/ attribute vec2 tCoord2; // Colour modulation. -/*layout (location = 2)*/ attribute vec4 modulate; +/*layout (location = 3)*/ attribute vec4 modulate; varying vec2 UV; +varying vec2 UV2; // Maybe we should merge these CPU side. // idk yet. @@ -40,6 +42,7 @@ void main() vec2 VERTEX = aPos; UV = tCoord; + UV2 = tCoord2; // [SHADER_CODE] diff --git a/Robust.Client/Graphics/Drawing/DrawingHandleBase.cs b/Robust.Client/Graphics/Drawing/DrawingHandleBase.cs index 525fa741f43..56259861687 100644 --- a/Robust.Client/Graphics/Drawing/DrawingHandleBase.cs +++ b/Robust.Client/Graphics/Drawing/DrawingHandleBase.cs @@ -114,43 +114,12 @@ public void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, ReadOnlySpan DrawPrimitives(primitiveTopology, White, indices, drawVertices); } - private static void PadVerticesV2(ReadOnlySpan input, Span output, Color color) + private void PadVerticesV2(ReadOnlySpan input, Span output, Color color) { - if (input.Length == 0) - return; - - if (input.Length != output.Length) - { - throw new InvalidOperationException("Invalid lengths!"); - } - - var colorLinear = Color.FromSrgb(color); - var colorVec = Unsafe.As>(ref colorLinear); - var uvVec = Vector128.Create(0, 0, 0.5f, 0.5f); - var maskVec = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0, 0).AsSingle(); - - var simdVectors = (nuint)(input.Length / 2); - ref readonly var srcBase = ref Unsafe.As(ref Unsafe.AsRef(in input[0])); - ref var dstBase = ref Unsafe.As(ref output[0]); - - for (nuint i = 0; i < simdVectors; i++) - { - var positions = Vector128.LoadUnsafe(in srcBase, i * 4); - - var posColorLower = (positions & maskVec) | uvVec; - var posColorUpper = (Vector128.Shuffle(positions, Vector128.Create(2, 3, 0, 0)) & maskVec) | uvVec; - - posColorLower.StoreUnsafe(ref dstBase, i * 16); - colorVec.StoreUnsafe(ref dstBase, i * 16 + 4); - posColorUpper.StoreUnsafe(ref dstBase, i * 16 + 8); - colorVec.StoreUnsafe(ref dstBase, i * 16 + 12); - } - - var lastPos = (int)simdVectors * 2; - if (lastPos != output.Length) + Color colorLinear = Color.FromSrgb(color); + for (var i = 0; i < output.Length; i++) { - // Odd number of vertices. Handle the last manually. - output[lastPos] = new DrawVertexUV2DColor(input[lastPos], new Vector2(0.5f, 0.5f), colorLinear); + output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear); } } @@ -268,6 +237,8 @@ public struct DrawVertexUV2DColor { public Vector2 Position; public Vector2 UV; + public Vector2 UV2; + /// /// Modulation colour for this vertex. /// Note that this color is in linear space. diff --git a/Robust.Client/ResourceManagement/ResourceCache.Preload.cs b/Robust.Client/ResourceManagement/ResourceCache.Preload.cs index f698171adbf..cc567565ef5 100644 --- a/Robust.Client/ResourceManagement/ResourceCache.Preload.cs +++ b/Robust.Client/ResourceManagement/ResourceCache.Preload.cs @@ -10,6 +10,7 @@ using Robust.Shared.Audio; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; +using Robust.Shared.Graphics; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Maths; @@ -142,6 +143,26 @@ private void PreloadRsis(ISawmill sawmill) } }); + // Do not meta-atlas RSIs with custom load parameters. + var atlasList = rsiList.Where(x => x.LoadParameters == TextureLoadParameters.Default).ToArray(); + var nonAtlasList = rsiList.Where(x => x.LoadParameters != TextureLoadParameters.Default).ToArray(); + + foreach (var data in nonAtlasList) + { + if (data.Bad) + continue; + + try + { + RSIResource.LoadTexture(Clyde, data); + } + catch (Exception e) + { + sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}"); + data.Bad = true; + } + } + // This combines individual RSI atlases into larger atlases to reduce draw batches. currently this is a VERY // lazy bundling and is not at all compact, its basically an atlas of RSI atlases. Really what this should // try to do is to have each RSI write directly to the atlas, rather than having each RSI write to its own @@ -155,7 +176,7 @@ private void PreloadRsis(ISawmill sawmill) // TODO allow RSIs to opt out (useful for very big & rare RSIs) // TODO combine with (non-rsi) texture atlas? - Array.Sort(rsiList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0)); + Array.Sort(atlasList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0)); // Each RSI sub atlas has a different size. // Even if we iterate through them once to estimate total area, I have NFI how to sanely estimate an optimal square-texture size. @@ -167,9 +188,9 @@ private void PreloadRsis(ISawmill sawmill) Vector2i offset = default; int finalized = -1; int atlasCount = 0; - for (int i = 0; i < rsiList.Length; i++) + for (int i = 0; i < atlasList.Length; i++) { - var rsi = rsiList[i]; + var rsi = atlasList[i]; if (rsi.Bad) continue; @@ -200,14 +221,14 @@ private void PreloadRsis(ISawmill sawmill) var height = offset.Y + deltaY; var croppedSheet = new Image(maxSize, height); sheet.Blit(new UIBox2i(0, 0, maxSize, height), croppedSheet, default); - FinalizeMetaAtlas(rsiList.Length - 1, croppedSheet); + FinalizeMetaAtlas(atlasList.Length - 1, croppedSheet); void FinalizeMetaAtlas(int toIndex, Image sheet) { var atlas = Clyde.LoadTextureFromImage(sheet); for (int i = finalized + 1; i <= toIndex; i++) { - var rsi = rsiList[i]; + var rsi = atlasList[i]; rsi.AtlasTexture = atlas; } @@ -255,9 +276,10 @@ void FinalizeMetaAtlas(int toIndex, Image sheet) } sawmill.Debug( - "Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountErrored} errored) in {LoadTime}", + "Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountNotAtlas} not atlassed, {CountErrored} errored) in {LoadTime}", rsiList.Length, atlasCount, + nonAtlasList.Length, errors, sw.Elapsed); diff --git a/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs b/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs index f207b583288..7f7b92525ca 100644 --- a/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs +++ b/Robust.Client/ResourceManagement/ResourceTypes/RSIResource.cs @@ -40,17 +40,21 @@ public override void Load(IDependencyCollection dependencies, ResPath path) var loadStepData = new LoadStepData {Path = path}; var manager = dependencies.Resolve(); LoadPreTexture(manager, loadStepData); - - loadStepData.AtlasTexture = dependencies.Resolve().LoadTextureFromImage( - loadStepData.AtlasSheet, - loadStepData.Path.ToString()); - + LoadTexture(dependencies.Resolve(), loadStepData); LoadPostTexture(loadStepData); LoadFinish(dependencies.Resolve(), loadStepData); loadStepData.AtlasSheet.Dispose(); } + internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData) + { + loadStepData.AtlasTexture = clyde.LoadTextureFromImage( + loadStepData.AtlasSheet, + loadStepData.Path.ToString(), + loadStepData.LoadParameters); + } + internal static void LoadPreTexture(IResourceManager manager, LoadStepData data) { var manifestPath = data.Path / "meta.json"; @@ -178,6 +182,7 @@ internal static void LoadPreTexture(IResourceManager manager, LoadStepData data) data.FrameSize = frameSize; data.DimX = dimensionX; data.CallbackOffsets = callbackOffsets; + data.LoadParameters = metadata.LoadParameters; } internal static void LoadPostTexture(LoadStepData data) @@ -380,6 +385,7 @@ internal sealed class LoadStepData public Texture AtlasTexture = default!; public Vector2i AtlasOffset; public RSI Rsi = default!; + public TextureLoadParameters LoadParameters; } internal struct StateReg diff --git a/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs b/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs index 68920261139..531a28a19c3 100644 --- a/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs +++ b/Robust.Shared/GameObjects/Components/Renderable/SpriteLayerData.cs @@ -29,9 +29,50 @@ public sealed partial class PrototypeLayerData [DataField("map")] public HashSet? MapKeys; [DataField("renderingStrategy")] public LayerRenderingStrategy? RenderingStrategy; + /// + /// If set, indicates that this sprite layer should instead be used to copy into shader parameters on another layer. + /// + /// + /// + /// If set, this sprite layer is not rendered. Instead, the "result" of rendering it (exact sprite layer and such) + /// are copied into the shader parameters of another object, + /// specified by the . + /// + /// + /// The specified layer must have a shader set. When it does, the shader's + /// + /// + /// Note that sprite layers are processed in-order, so to avoid 1-frame delays, + /// the layer doing the copying should occur BEFORE the layer being copied into. + /// + /// + [DataField] public PrototypeCopyToShaderParameters? CopyToShaderParameters; + [DataField] public bool Cycle; } +/// +/// Stores parameters for . +/// +[Serializable, NetSerializable, DataDefinition] +public sealed partial class PrototypeCopyToShaderParameters +{ + /// + /// The map key of the layer that will have its shader modified. + /// + [DataField(required: true)] public string LayerKey; + + /// + /// The name of the shader parameter that will receive the actual selected texture. + /// + [DataField] public string? ParameterTexture; + + /// + /// The name of the shader parameter that will receive UVs to select the sprite in . + /// + [DataField] public string? ParameterUV; +} + [Serializable, NetSerializable] public enum LayerRenderingStrategy { diff --git a/Robust.Shared/Graphics/TextureLoadParameters.cs b/Robust.Shared/Graphics/TextureLoadParameters.cs index 2ec1449c73a..b1764dad6c0 100644 --- a/Robust.Shared/Graphics/TextureLoadParameters.cs +++ b/Robust.Shared/Graphics/TextureLoadParameters.cs @@ -1,3 +1,4 @@ +using System; using JetBrains.Annotations; using Robust.Shared.Utility; using YamlDotNet.RepresentationModel; @@ -8,7 +9,7 @@ namespace Robust.Shared.Graphics; /// Flags for loading of textures. /// [PublicAPI] -public struct TextureLoadParameters +public struct TextureLoadParameters : IEquatable { /// /// The default sampling parameters for the texture. @@ -41,4 +42,29 @@ public static TextureLoadParameters FromYaml(YamlMappingNode yaml) SampleParameters = TextureSampleParameters.Default, Srgb = true }; -} \ No newline at end of file + + public bool Equals(TextureLoadParameters other) + { + return SampleParameters.Equals(other.SampleParameters) && Srgb == other.Srgb; + } + + public override bool Equals(object? obj) + { + return obj is TextureLoadParameters other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(SampleParameters, Srgb); + } + + public static bool operator ==(TextureLoadParameters left, TextureLoadParameters right) + { + return left.Equals(right); + } + + public static bool operator !=(TextureLoadParameters left, TextureLoadParameters right) + { + return !left.Equals(right); + } +} diff --git a/Robust.Shared/Graphics/TextureSampleParameters.cs b/Robust.Shared/Graphics/TextureSampleParameters.cs index 2e3eb3a5e0f..3ae52db8632 100644 --- a/Robust.Shared/Graphics/TextureSampleParameters.cs +++ b/Robust.Shared/Graphics/TextureSampleParameters.cs @@ -12,7 +12,7 @@ namespace Robust.Shared.Graphics; /// with different sampling parameters than the base texture. /// [PublicAPI] -public struct TextureSampleParameters +public struct TextureSampleParameters : IEquatable { // NOTE: If somebody is gonna add support for 3D/1D textures, change this doc comment. // See the note on this page for why: https://www.khronos.org/opengl/wiki/Sampler_Object#Filtering @@ -62,4 +62,29 @@ public static TextureSampleParameters FromYaml(YamlMappingNode node) Filter = false, WrapMode = TextureWrapMode.None }; -} \ No newline at end of file + + public bool Equals(TextureSampleParameters other) + { + return Filter == other.Filter && WrapMode == other.WrapMode; + } + + public override bool Equals(object? obj) + { + return obj is TextureSampleParameters other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Filter, (int) WrapMode); + } + + public static bool operator ==(TextureSampleParameters left, TextureSampleParameters right) + { + return left.Equals(right); + } + + public static bool operator !=(TextureSampleParameters left, TextureSampleParameters right) + { + return !left.Equals(right); + } +} diff --git a/Robust.Shared/Resources/RsiLoading.cs b/Robust.Shared/Resources/RsiLoading.cs index 37779d66a3c..66a45396407 100644 --- a/Robust.Shared/Resources/RsiLoading.cs +++ b/Robust.Shared/Resources/RsiLoading.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text.Json; using JetBrains.Annotations; +using Robust.Shared.Graphics; using Robust.Shared.Maths; using Robust.Shared.Utility; @@ -93,7 +94,17 @@ internal static RsiMetadata LoadRsiMetadata(Stream manifestFile) states[stateI] = new StateMetadata(stateName, dirValue, delays); } - return new RsiMetadata(size, states); + var textureParams = TextureLoadParameters.Default; + if (manifestJson.Load is { } load) + { + textureParams = new TextureLoadParameters + { + SampleParameters = TextureSampleParameters.Default, + Srgb = load.Srgb + }; + } + + return new RsiMetadata(size, states, textureParams); } public static void Warmup() @@ -103,16 +114,11 @@ public static void Warmup() JsonSerializer.Deserialize(warmupJson, SerializerOptions); } - internal sealed class RsiMetadata + internal sealed class RsiMetadata(Vector2i size, StateMetadata[] states, TextureLoadParameters loadParameters) { - public readonly Vector2i Size; - public readonly StateMetadata[] States; - - public RsiMetadata(Vector2i size, StateMetadata[] states) - { - Size = size; - States = states; - } + public readonly Vector2i Size = size; + public readonly StateMetadata[] States = states; + public readonly TextureLoadParameters LoadParameters = loadParameters; } internal sealed class StateMetadata @@ -134,10 +140,13 @@ public StateMetadata(string stateId, int dirCount, float[][] delays) // To be directly deserialized. [UsedImplicitly] - private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States); + private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States, RsiJsonLoad? Load); [UsedImplicitly] private sealed record StateJsonMetadata(string Name, int? Directions, float[][]? Delays); + + [UsedImplicitly] + private sealed record RsiJsonLoad(bool Srgb = true); } [Serializable]