From 4033d96327421ef5571c423b941510a0efd844b0 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sat, 27 Apr 2024 08:03:35 +0200 Subject: [PATCH] Engine changes for displacement maps. (#5023) * Add load parameter support to RSIs. Currently only supports turning sRGB off. RSIs with custom load parameters are not thrown into the meta-atlas. As part of this, TextureLoadParameters and TextureSampleParameters has been made to support equality. * Add UV2 channel to vertices. This is a bad hack to make displacement maps work in Robust. The UV2 channel goes from 0 -> 1 across the draw and can therefore be used by displacement maps to map a separate displacement map layer on top of the regular meta-atlas RSI texture. This creates float inaccuracy issues but they weren't bad enough to completely void the feature. I'm thinking I learn from this experience and completely re-do how UVs work with the renderer rewrite, so that hopefully won't happen anymore. This required dumping the optimized PadVerticesV2 because the changed struct size made it impractical. RIP. I don't like this approach at all but the renderer is slated for a rewrite anyways, and all shaders will need to be rewritten regardless. * Add CopyToShaderParameters for sprite layers. This effectively allows copying the parameters of a sprite layer into another layer's shader parameters. The use case is to copy texture coordinates for displacement maps, as the exact map used changes depending on orientation. It also enables animations to be used though I didn't use that personally. --- RELEASE-NOTES.md | 4 +- .../Components/Renderable/SpriteComponent.cs | 85 ++++++++++++++++--- .../Graphics/Clyde/Clyde.Constants.cs | 3 +- Robust.Client/Graphics/Clyde/Clyde.Layout.cs | 17 +++- .../Graphics/Clyde/Clyde.RenderHandle.cs | 13 ++- .../Graphics/Clyde/Clyde.Rendering.cs | 8 +- .../Graphics/Clyde/Clyde.Textures.cs | 2 +- .../Graphics/Clyde/Shaders/base-default.frag | 1 + .../Graphics/Clyde/Shaders/base-default.vert | 5 +- .../Graphics/Clyde/Shaders/base-raw.frag | 1 + .../Graphics/Clyde/Shaders/base-raw.vert | 5 +- .../Graphics/Drawing/DrawingHandleBase.cs | 41 ++------- .../ResourceCache.Preload.cs | 34 ++++++-- .../ResourceTypes/RSIResource.cs | 16 ++-- .../Components/Renderable/SpriteLayerData.cs | 41 +++++++++ .../Graphics/TextureLoadParameters.cs | 30 ++++++- .../Graphics/TextureSampleParameters.cs | 29 ++++++- Robust.Shared/Resources/RsiLoading.cs | 31 ++++--- 18 files changed, 278 insertions(+), 88 deletions(-) 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 @@ internal sealed partial class Clyde { ("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 static unsafe void SetupVAOLayout() { 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 SetProjView(in Matrix3 proj, in Matrix3 view) { 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 DrawSetProjViewTransform(in Matrix3 proj, in Matrix3 view) // 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 SetTransform(in Vector2 position, in Angle rotation) 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]