From 52289f5a1274ea9fe8b0950e5e00fbb77f215274 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 6 Aug 2025 22:35:41 +0200 Subject: [PATCH 01/17] feat: BufferHelper API --- .../Stride.Graphics/ConcreteSemantics.cs | 73 ++++++ .../Stride.Graphics/IndexBufferHelper.cs | 143 +++++++++++ .../engine/Stride.Graphics/MeshExtension.cs | 134 ++++++++++ .../Stride.Graphics/Semantic/IConversion.cs | 9 + .../Stride.Graphics/Semantic/ISemantic.cs | 122 +++++++++ .../Stride.Graphics/VertexBufferHelper.cs | 235 ++++++++++++++++++ 6 files changed, 716 insertions(+) create mode 100644 sources/engine/Stride.Graphics/ConcreteSemantics.cs create mode 100644 sources/engine/Stride.Graphics/IndexBufferHelper.cs create mode 100644 sources/engine/Stride.Graphics/MeshExtension.cs create mode 100644 sources/engine/Stride.Graphics/Semantic/IConversion.cs create mode 100644 sources/engine/Stride.Graphics/Semantic/ISemantic.cs create mode 100644 sources/engine/Stride.Graphics/VertexBufferHelper.cs diff --git a/sources/engine/Stride.Graphics/ConcreteSemantics.cs b/sources/engine/Stride.Graphics/ConcreteSemantics.cs new file mode 100644 index 0000000000..bbfb9c0790 --- /dev/null +++ b/sources/engine/Stride.Graphics/ConcreteSemantics.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +using Stride.Graphics.Semantic; + +namespace Stride.Graphics; + +public struct PositionSemantic : IFloat3Semantic +{ + public static string Name => VertexElementUsage.Position; +} + +public struct NormalSemantic : IFloat3Semantic +{ + public static string Name => VertexElementUsage.Normal; +} + +public struct ColorSemantic : IFloat4Semantic +{ + public static string Name => VertexElementUsage.Color; +} + +public struct TangentSemantic : IFloat4Semantic +{ + public static string Name => VertexElementUsage.Tangent; +} + +public struct BiTangentSemantic : IFloat4Semantic +{ + public static string Name => VertexElementUsage.BiTangent; +} + +public struct TextureCoordinateSemantic : IFloat2Semantic +{ + public static string Name => VertexElementUsage.TextureCoordinate; +} + +public struct BlendIndicesSemantic : IUShort4Semantic +{ + public static string Name => VertexElementUsage.BlendIndices; +} + +public struct BlendWeightSemantic : IFloat4Semantic +{ + public static string Name => VertexElementUsage.BlendWeight; +} + +/// +/// A semantic extension to allow users to provide non default destination datatypes +/// +/// +/// Reading positions of a mesh into a Half3 array +/// +/// , Half3>(positions); +/// ]]> +/// +/// +/// The actual semantic used, for example +public struct Relaxed : + IFloat4Semantic, + IFloat3Semantic, + IFloat2Semantic, + IHalf4Semantic, + IHalf3Semantic, + IHalf2Semantic, + IUShort4Semantic, + IByte4Semantic + where T : ISemantic +{ + public static string Name => T.Name; +} diff --git a/sources/engine/Stride.Graphics/IndexBufferHelper.cs b/sources/engine/Stride.Graphics/IndexBufferHelper.cs new file mode 100644 index 0000000000..116730c87f --- /dev/null +++ b/sources/engine/Stride.Graphics/IndexBufferHelper.cs @@ -0,0 +1,143 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#nullable enable +using System; +using System.Runtime.InteropServices; +using Stride.Core; + +namespace Stride.Graphics; + +/// +/// Reading the indices of a mesh: +/// +/// +/// +/// +public class IndexBufferHelper +{ + /// + /// Full index buffer, does not account for the binding offset or length + /// + public readonly byte[] DataOuter; + public readonly IndexBufferBinding Binding; + + /// + /// Effective index buffer, handles the binding offset + /// + public Span DataInner => DataOuter.AsSpan(Binding.Offset, Binding.Count * (Binding.Is32Bit ? 4 : 2)); + + /// + public IndexBufferHelper(IndexBufferBinding binding, IServiceRegistry services, out int count) + { + DataOuter = MeshExtension.FetchBufferContentOrThrow(binding.Buffer, services); + Binding = binding; + count = Binding.Count; + } + + /// + /// Branch to read the buffer as a 16 or 32 bit buffer, does not allocate + /// + /// + /// + /// if (Is32Bit(out var d32, out var d16)) + /// { + /// foreach (var value in d32) + /// { + /// // Your logic for 32 bit + /// } + /// } + /// else + /// { + /// foreach (var value in d16) + /// { + /// // Your logic for 16bit + /// } + /// } + /// + /// + public bool Is32Bit(out Span data32, out Span data16) + { + if (Binding.Is32Bit) + { + data32 = MemoryMarshal.Cast(DataInner); + data16 = Span.Empty; + return true; + } + else + { + data32 = Span.Empty; + data16 = MemoryMarshal.Cast(DataInner); + return false; + } + } + + /// + /// Does not allocate if the buffer is already 32 bit, + /// otherwise allocates a new int[] and copies the data into it + /// + public Span To32Bit() + { + if (Is32Bit(out var d32, out var d16)) + { + return d32; + } + else + { + var output = new int[d16.Length]; + for (int i = 0; i < d16.Length; i++) + output[i] = d16[i]; + + return output; + } + } + + /// + /// Does not allocate if the buffer is already 16 bit, + /// otherwise allocates a new ushort[] and copies the data into it + /// + public Span To16Bit() + { + if (Is32Bit(out var d32, out var d16)) + { + var output = new ushort[d32.Length]; + for (int i = 0; i < d32.Length; i++) + output[i] = (ushort)d32[i]; + + return output; + } + else + { + return d16; + } + } + + public void CopyTo(Span dest) + { + if (Is32Bit(out var d32, out var d16)) + { + d32.CopyTo(dest); + } + else + { + for (int i = 0; i < d16.Length; i++) + dest[i] = d16[i]; + } + } + + public void CopyTo(Span dest) + { + if (Is32Bit(out var d32, out var d16)) + { + for (int i = 0; i < d32.Length; i++) + dest[i] = (ushort)d32[i]; + } + else + { + d16.CopyTo(dest); + } + } +} diff --git a/sources/engine/Stride.Graphics/MeshExtension.cs b/sources/engine/Stride.Graphics/MeshExtension.cs new file mode 100644 index 0000000000..2b7dd75803 --- /dev/null +++ b/sources/engine/Stride.Graphics/MeshExtension.cs @@ -0,0 +1,134 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +#nullable enable + +using System; +using Stride.Core; +using Stride.Core.IO; +using Stride.Core.Serialization; +using Stride.Core.Serialization.Contents; +using Stride.Graphics.Data; + +namespace Stride.Graphics; + +public static class MeshExtension +{ + /// + /// Fetch this buffer and create a helper to read from it. + /// + /// + /// This operation loads the buffer from disk, or directly from the gpu. It is very slow, avoid calling this too often if at all possible. + /// + /// The bindings for this buffer + /// The service used to retrieve the buffer from disk/GPU + /// The helper class to interact with the loaded buffer + /// The amount of vertices this buffer holds + /// + public static void AsReadable(this VertexBufferBinding binding, IServiceRegistry services, out VertexBufferHelper helper, out int count) + { + helper = new VertexBufferHelper(binding, services, out count); + } + + /// + /// Fetch this buffer and create a helper to read from it. + /// + /// + /// This operation loads the buffer from disk, or directly from the gpu. It is very slow, avoid calling this too often if at all possible. + /// + /// The bindings for this buffer + /// The service used to retrieve the buffer from disk/GPU + /// The helper class to interact with the loaded buffer + /// The amount of indices this buffer holds + /// + public static void AsReadable(this IndexBufferBinding binding, IServiceRegistry services, out IndexBufferHelper helper, out int count) + { + helper = new IndexBufferHelper(binding, services, out count); + } + + /// + /// Given a semantic and its index, returns its offset and size in the given vertex buffer. Similar to + /// + public static bool TryGetElement(this VertexBufferBinding binding, string vertexElementUsage, int semanticIndex, out VertexElementWithOffset result) + { + int offset = 0; + foreach (var element in binding.Declaration.VertexElements) + { + // Get new offset (if specified) + var currentElementOffset = element.AlignedByteOffset; + if (currentElementOffset != VertexElement.AppendAligned) + offset = currentElementOffset; + + var elementSize = element.Format.SizeInBytes(); + if (vertexElementUsage == element.SemanticName && semanticIndex == element.SemanticIndex) + { + result = new VertexElementWithOffset(element, offset, elementSize); + return true; + } + + // Compute next offset (if automatic) + offset += elementSize; + } + + result = default; + return false; + } + + /// + /// Same as but throws on failure + internal static byte[] FetchBufferContentOrThrow(Buffer buffer, IServiceRegistry services) + { + var data = TryFetchBufferContent(buffer, services); + if (data is null || data.Length == 0) + { + throw new InvalidOperationException( + $"Failed to find mesh buffers while attempting to {nameof(FetchBufferContentOrThrow)}. " + + $"Make sure that the mesh is either an asset on disk, or has its buffer data attached to the buffer through '{nameof(AttachedReference)}'\n"); + } + + return data; + } + + /// Get buffer content from GPU, shared memory or disk + internal static byte[]? TryFetchBufferContent(Buffer buffer, IServiceRegistry services) + { + var bufRef = AttachedReferenceManager.GetAttachedReference(buffer); + if (bufRef?.Data != null && ((BufferData)bufRef.Data).Content is { } output) + return output; + + // Try to load it from disk, a file provider is required, editor does not provide one + if (bufRef?.Url != null && services.GetService() is {} provider && provider.FileProvider is not null) + { + // We have to create a new one without providing services to ensure that it dumps the graphics buffer data to the attached reference below + var cleanManager = new ContentManager(provider); + var bufferCopy = cleanManager.Load(bufRef.Url); + try + { + return bufferCopy.GetSerializationData().Content; + } + finally + { + cleanManager.Unload(bufRef.Url); + } + } + + // When the mesh is created at runtime, or when the file provider is null as can be the case in editor, fetch from GPU + // will most likely break on non-dx11 APIs + if (services.GetService() is { } context) + { + output = new byte[buffer.SizeInBytes]; + if (buffer.Description.Usage == GraphicsResourceUsage.Staging) // Directly if this is a staging resource + { + buffer.GetData(context.CommandList, buffer, output); + } + else // inefficient way to use the Copy method using dynamic staging texture + { + using var throughStaging = buffer.ToStaging(); + buffer.GetData(context.CommandList, throughStaging, output); + } + + return output; + } + + return null; + } +} diff --git a/sources/engine/Stride.Graphics/Semantic/IConversion.cs b/sources/engine/Stride.Graphics/Semantic/IConversion.cs new file mode 100644 index 0000000000..daa4fb39a8 --- /dev/null +++ b/sources/engine/Stride.Graphics/Semantic/IConversion.cs @@ -0,0 +1,9 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +namespace Stride.Graphics.Semantic; + +public interface IConversion +{ + static abstract void Convert(in TSource source, out TDest dest); +} diff --git a/sources/engine/Stride.Graphics/Semantic/ISemantic.cs b/sources/engine/Stride.Graphics/Semantic/ISemantic.cs new file mode 100644 index 0000000000..aaaaaa1fd6 --- /dev/null +++ b/sources/engine/Stride.Graphics/Semantic/ISemantic.cs @@ -0,0 +1,122 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#nullable enable +namespace Stride.Graphics.Semantic; +using System; +using Core.Mathematics; + +public interface ISemantic +{ + public static abstract string Name { get; } +} + + +public interface ISemantic : + ISemantic, + // Default source types we support for conversion + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion; + +public interface IFloat2Semantic : ISemantic +{ + static void IConversion.Convert(in Vector2 source, out Vector2 dest) => dest = source; + static void IConversion.Convert(in Vector3 source, out Vector2 dest) => dest = (Vector2)source; + static void IConversion.Convert(in Vector4 source, out Vector2 dest) => dest = (Vector2)source; + static void IConversion.Convert(in Half2 source, out Vector2 dest) => dest = (Vector2)source; + static void IConversion.Convert(in Half4 source, out Vector2 dest) => dest = new(source.X, source.Y); + static void IConversion.Convert(in UShort4 source, out Vector2 dest) => dest = new(source.X, source.Y); + static void IConversion.Convert(in Byte4 source, out Vector2 dest) => dest = new(source.X, source.Y); +} + +public interface IFloat3Semantic : ISemantic +{ + static void IConversion.Convert(in Vector2 source, out Vector3 dest) => dest = (Vector3)source; + static void IConversion.Convert(in Vector3 source, out Vector3 dest) => dest = source; + static void IConversion.Convert(in Vector4 source, out Vector3 dest) => dest = (Vector3)source; + static void IConversion.Convert(in Half2 source, out Vector3 dest) => dest = new(source.X, source.Y, 0f); + static void IConversion.Convert(in Half4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); + static void IConversion.Convert(in UShort4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); + static void IConversion.Convert(in Byte4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); +} + +public interface IFloat4Semantic : ISemantic +{ + static void IConversion.Convert(in Vector2 source, out Vector4 dest) => dest = (Vector4)source; + static void IConversion.Convert(in Vector3 source, out Vector4 dest) => dest = (Vector4)source; + static void IConversion.Convert(in Vector4 source, out Vector4 dest) => dest = source; + static void IConversion.Convert(in Half2 source, out Vector4 dest) => dest = new(source.X, source.Y, 0f, 0f); + static void IConversion.Convert(in Half4 source, out Vector4 dest) => dest = (Vector4)source; + static void IConversion.Convert(in UShort4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); + static void IConversion.Convert(in Byte4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); +} + +public interface IHalf2Semantic : ISemantic +{ + static void IConversion.Convert(in Vector2 source, out Half2 dest) => dest = (Half2)source; + static void IConversion.Convert(in Vector3 source, out Half2 dest) => dest = new(source.X, source.Y); + static void IConversion.Convert(in Vector4 source, out Half2 dest) => dest = new(source.X, source.Y); + static void IConversion.Convert(in Half2 source, out Half2 dest) => dest = source; + static void IConversion.Convert(in Half4 source, out Half2 dest) => dest = new(source.X, source.Y); + static void IConversion.Convert(in UShort4 source, out Half2 dest) => dest = new(source.X, source.Y); + static void IConversion.Convert(in Byte4 source, out Half2 dest) => dest = new(source.X, source.Y); +} + +public interface IHalf3Semantic : ISemantic +{ + static void IConversion.Convert(in Vector2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); + static void IConversion.Convert(in Vector3 source, out Half3 dest) => dest = (Half3)source; + static void IConversion.Convert(in Vector4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); + static void IConversion.Convert(in Half2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); + static void IConversion.Convert(in Half4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); + static void IConversion.Convert(in UShort4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); + static void IConversion.Convert(in Byte4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); +} + +public interface IHalf4Semantic : ISemantic +{ + static void IConversion.Convert(in Vector2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); + static void IConversion.Convert(in Vector3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); + static void IConversion.Convert(in Vector4 source, out Half4 dest) => dest = (Half4)source; + static void IConversion.Convert(in Half2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); + static void IConversion.Convert(in Half4 source, out Half4 dest) => dest = source; + static void IConversion.Convert(in UShort4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); + static void IConversion.Convert(in Byte4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); +} + +public interface IUShort4Semantic : ISemantic +{ + static void IConversion.Convert(in Vector2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); + static void IConversion.Convert(in Vector3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); + static void IConversion.Convert(in Vector4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); + static void IConversion.Convert(in Half2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); + static void IConversion.Convert(in Half4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); + static void IConversion.Convert(in UShort4 source, out UShort4 dest) => dest = source; + static void IConversion.Convert(in Byte4 source, out UShort4 dest) => dest = new(source.X, source.Y, source.Z, source.W); +} + +public interface IByte4Semantic : ISemantic +{ + static void IConversion.Convert(in Vector2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); + static void IConversion.Convert(in Vector3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); + static void IConversion.Convert(in Vector4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); + static void IConversion.Convert(in Half2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); + static void IConversion.Convert(in Half4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); + static void IConversion.Convert(in UShort4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); + static void IConversion.Convert(in Byte4 source, out Byte4 dest) => dest = source; +} + +public struct UShort4(ushort x, ushort y, ushort z, ushort w) +{ + public ushort X = x, Y = y, Z = z, W = w; +} + +public struct Byte4(byte x, byte y, byte z, byte w) +{ + public byte X = x, Y = y, Z = z, W = w; +} diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs new file mode 100644 index 0000000000..cdda13c8c6 --- /dev/null +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -0,0 +1,235 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#nullable enable +using System; +using Stride.Core; +using Stride.Core.Mathematics; +using Stride.Graphics.Semantic; + +namespace Stride.Graphics; + +/// +/// Reading the vertex positions of a mesh: +/// +/// (vertexPositions); +/// ]]> +/// +/// +public class VertexBufferHelper +{ + /// + /// Full vertex buffer, does not account for the binding offset or length + /// + public readonly byte[] DataOuter; + public readonly VertexBufferBinding Binding; + + /// + /// Effective vertex buffer, accounts for the binding offset and length + /// + public Span DataInner => DataOuter.AsSpan(Binding.Offset, Binding.Count * Binding.Stride); + + /// + public VertexBufferHelper(VertexBufferBinding binding, IServiceRegistry services, out int count) + { + var data = MeshExtension.FetchBufferContentOrThrow(binding.Buffer, services); + DataOuter = data; + Binding = binding; + count = Binding.Count; + } + + /// + /// Extract individual element from each vertex contained in this vertex buffer and copies them into + /// + /// + /// The buffer which will be written to, must have exactly the same amount of items as there are vertices in the buffer + /// + /// + /// The semantic to read with that index, starts at zero.
+ /// For example, to sample the second TextureCoordinate, you would use + /// + /// (myUvs, 1); + /// ]]> + /// + /// + /// The semantic to read, for example + /// The value type to read, depends entirely on the used + /// True when this semantic exists in the vertex buffer, false otherwise + /// + /// When the data format for this semantic is too arcane - no conversion logic is implemented for that type + /// + /// + public bool Copy(Span buffer, int semanticIndex = 0) where TSemantic : ISemantic where TValue : unmanaged + { + return Read>(buffer, new CopyToDest(), semanticIndex); + } + + /// + /// Provides custom access to the vertex buffer while having access to the auto-conversion + /// + /// + /// An implementation of , implement this interface to read directly from the vertex buffer + /// while making use of the auto-conversion of the provided
+ /// Preferably as a struct to ensure it is inlined by the JIT + /// + /// + /// The semantic to read with that index, starts at zero.
+ /// For example, to sample the second TextureCoordinate, you would use + /// + /// (yourReader, 1); + /// ]]> + /// + /// + /// The semantic to read, for example + /// The value type to read, depends on the used, when your for example + /// The type of the reader you're providing + /// True when this semantic exists in the vertex buffer, false otherwise + /// + /// When the data format for this semantic is too arcane - no conversion logic is implemented for that type + /// + /// + public bool Read(Span destination, TReader reader, int semanticIndex = 0) + where TSemantic : ISemantic where TValue : unmanaged + where TReader : IReader + { + if (Binding.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) + { + switch (elementData.VertexElement.Format) + { + case PixelFormat.R32G32_Float: Inner(DataInner, destination, reader, Binding, elementData); break; + case PixelFormat.R32G32B32_Float: Inner(DataInner, destination, reader, Binding, elementData); break; + case PixelFormat.R32G32B32A32_Float: Inner(DataInner, destination, reader, Binding, elementData); break; + case PixelFormat.R16G16_Float: Inner(DataInner, destination, reader, Binding, elementData); break; + case PixelFormat.R16G16B16A16_Float: Inner(DataInner, destination, reader, Binding, elementData); break; + case PixelFormat.R16G16B16A16_UInt: Inner(DataInner, destination, reader, Binding, elementData); break; + case PixelFormat.R8G8B8A8_UInt: Inner(DataInner, destination, reader, Binding, elementData); break; + default: throw new NotImplementedException($"Unsupported format when converting vertex element ({elementData.VertexElement.Format})"); + } + + return true; + } + + return false; + + static unsafe void Inner(Span vertexBuffer, Span destination, IReader reader, VertexBufferBinding binding, VertexElementWithOffset element) + where TConversion : IConversion where TSource : unmanaged + { + var stride = binding.Declaration.VertexStride; + var offset = element.Offset; + var count = vertexBuffer.Length / element.Size; + + if (sizeof(TSource) != element.Size) + throw new ArgumentException($"{typeof(TSource)} does not match element size ({sizeof(TSource)} != {element.Size})"); + + fixed (byte* ptrSr = vertexBuffer) + { + byte* src = ptrSr + offset; + reader.Read(src, count, stride, destination); + } + } + } + + public struct CopyAsTriangleList : IReader + { + public required IndexBufferHelper IndexBufferHelper; + + public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) + where TConversion : IConversion where TSource : unmanaged + { + if (destination.Length != IndexBufferHelper.Binding.Count) + throw new ArgumentException($"{nameof(destination)} length does not match the amount of indices contained within the index buffer buffer ({destination.Length} / {IndexBufferHelper.Binding.Count})"); + + fixed (Vector3* destPtr = destination) + { + Vector3* dest = destPtr; + if (IndexBufferHelper.Is32Bit(out var indices32, out var indices16)) + { + foreach (var index in indices32) + { + TConversion.Convert(*(TSource*)(sourcePointer + index * stride), out *dest); + dest++; + } + } + else + { + foreach (var index in indices16) + { + TConversion.Convert(*(TSource*)(sourcePointer + index * stride), out *dest); + dest++; + } + } + } + } + } + + private struct CopyToDest : IReader where T : unmanaged + { + public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) + where TConversion : IConversion where TSource : unmanaged + { + if (destination.Length != elementCount) + throw new ArgumentException($"{nameof(destination)} length does not match the amount of vertices contained within this vertex buffer ({destination.Length} / {elementCount})"); + + fixed (T* ptrDest = destination) + { + byte* end = sourcePointer + elementCount * stride; + T* dest = ptrDest; + for (; sourcePointer < end; sourcePointer += stride, dest++) + TConversion.Convert(*(TSource*)sourcePointer, out *dest); + } + } + } + + /// + /// Implementing manually: + /// + /// (vertexPositions, myReader); + /// + /// struct CopyTo : IReader + /// { + /// public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) + /// where TConversion : IConversion where TSource : unmanaged + /// { + /// if (destination.Length != elementCount) + /// throw new ArgumentException($"{nameof(destination)} length does not match the amount of vertices contained within this vertex buffer ({destination.Length} / {elementCount})"); + /// + /// fixed (T* ptrDest = destination) + /// { + /// byte* end = sourcePointer + elementCount * stride; + /// T* dest = ptrDest; + /// for (; sourcePointer < end; sourcePointer += stride, dest++) + /// TConversion.Convert(*(TSource*)sourcePointer, out *dest); + /// } + /// } + /// } + /// ]]> + /// + /// + public interface IReader + { + /// + /// + /// + /// The pointer already offset by the buffer and element offset + /// The amount of vertices, this is not the same as the size of the vertex buffer, or the size in bytes taken by individual vertices + /// The size in bytes taken by individual vertices + /// The span passed into the method call + /// A helper to convert between and properly + /// + /// The source type this vertex buffer was built with, for example or , + /// use to convert it into a . + /// + /// + unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) + where TConversion : IConversion where TSource : unmanaged; + } +} From 3f0c61905b5740a9213589a0b26981f57d2cd457 Mon Sep 17 00:00:00 2001 From: Eideren Date: Thu, 7 Aug 2025 17:32:47 +0200 Subject: [PATCH 02/17] fix: Wrong offset used in IndexExtensions --- sources/engine/Stride.Rendering/Extensions/IndexExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/engine/Stride.Rendering/Extensions/IndexExtensions.cs b/sources/engine/Stride.Rendering/Extensions/IndexExtensions.cs index 7dba87f285..017c4c73f0 100644 --- a/sources/engine/Stride.Rendering/Extensions/IndexExtensions.cs +++ b/sources/engine/Stride.Rendering/Extensions/IndexExtensions.cs @@ -47,7 +47,7 @@ public static unsafe void RemoveIndexBuffer(this MeshDraw meshData) fixed (byte* newVerticesStart = &newVertices[0]) // throw for null or empty fixed (byte* indexBufferStart = &indexBuffer.Buffer.GetDataSafe()[indexBuffer.Offset]) - fixed (byte* vertexBufferStart = &vertexBuffer.Buffer.GetDataSafe()[indexBuffer.Offset]) + fixed (byte* vertexBufferStart = &vertexBuffer.Buffer.GetDataSafe()[vertexBuffer.Offset]) { for (int i = 0; i < indexBuffer.Count; ++i) { From be0e611b7b722bf079da722986a053db34a300b3 Mon Sep 17 00:00:00 2001 From: Eideren Date: Thu, 7 Aug 2025 17:33:52 +0200 Subject: [PATCH 03/17] chore: Use *BufferHelper API to handle more mesh format in physics logic --- .../Systems/ShapeCacheSystem.cs | 150 +++--------------- .../Shapes/StaticMeshColliderShape.cs | 121 +++----------- 2 files changed, 37 insertions(+), 234 deletions(-) diff --git a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs index 25101a7ac2..4de0dcc1a4 100644 --- a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs +++ b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs @@ -6,12 +6,8 @@ using Stride.BepuPhysics.Definitions; using Stride.BepuPhysics.Definitions.Colliders; using Stride.Core; -using Stride.Core.IO; using Stride.Core.Mathematics; -using Stride.Core.Serialization; -using Stride.Core.Serialization.Contents; using Stride.Graphics; -using Stride.Graphics.Data; using Stride.Graphics.GeometricPrimitives; using Stride.Rendering; using BufferPool = BepuUtilities.Memory.BufferPool; @@ -140,72 +136,6 @@ private static void ExtractHull(DecomposedHulls hullDesc, out VertexPosition3[] } } - internal static IEnumerable<(Stride.Rendering.Mesh mesh, byte[] verticesBytes, byte[] indicesBytes)> ExtractMeshes(Model model, IServiceRegistry services) - { - foreach (var meshData in model.Meshes) - { - byte[]? verticesBytes = TryFetchBufferContent(meshData.Draw.VertexBuffers[0].Buffer, services); - byte[]? indicesBytes = TryFetchBufferContent(meshData.Draw.IndexBuffer.Buffer, services); - - if(verticesBytes is null || indicesBytes is null || verticesBytes.Length == 0 || indicesBytes.Length == 0) - { - throw new InvalidOperationException( - $"Failed to find mesh buffers while attempting to {nameof(ExtractMeshes)}. " + - $"Make sure that the {nameof(model)} is either an asset on disk, or has its buffer data attached to the buffer through '{nameof(AttachedReference)}'\n"); - } - - yield return (meshData, verticesBytes, indicesBytes); - } - - // Get mesh data from GPU, shared memory or disk - static unsafe byte[]? TryFetchBufferContent(Graphics.Buffer buffer, IServiceRegistry services) - { - var bufRef = AttachedReferenceManager.GetAttachedReference(buffer); - if (bufRef?.Data != null && ((BufferData)bufRef.Data).Content is { } output) - return output; - - // Try to load it from disk, a file provider is required, editor does not provide one - if (bufRef?.Url != null && services.GetService() is {} provider && provider.FileProvider is not null) - { - // We have to create a new one without providing services to ensure that it dumps the graphics buffer data to the attached reference below - var cleanManager = new ContentManager(provider); - var bufferCopy = cleanManager.Load(bufRef.Url); - try - { - return bufferCopy.GetSerializationData().Content; - } - finally - { - cleanManager.Unload(bufRef.Url); - } - } - - // When the mesh is created at runtime, or when the file provider is null as can be the case in editor, fetch from GPU - // will most likely break on non-dx11 APIs - if (services.GetService() is { } context) - { - output = new byte[buffer.SizeInBytes]; - fixed (byte* window = output) - { - var ptr = new DataPointer(window, output.Length); - if (buffer.Description.Usage == GraphicsResourceUsage.Staging) // Directly if this is a staging resource - { - buffer.GetData(context.CommandList, buffer, ptr); - } - else // inefficient way to use the Copy method using dynamic staging texture - { - using var throughStaging = buffer.ToStaging(); - buffer.GetData(context.CommandList, throughStaging, ptr); - } - } - - return output; - } - - return null; - } - } - internal static unsafe BepuUtilities.Memory.Buffer ExtractBepuMesh(Model model, IServiceRegistry services, BufferPool pool) { int totalIndices = 0; @@ -215,42 +145,23 @@ internal static unsafe BepuUtilities.Memory.Buffer ExtractBepuMesh(Mod } pool.Take(totalIndices / 3, out var triangles); - var triangleAsV3 = triangles.As(); - int triangleV3Index = 0; - - foreach ((Rendering.Mesh mesh, byte[] verticesBytes, byte[] indicesBytes) in ExtractMeshes(model, services)) + var bepuTriangles = triangles.As(); + var spanLeft = new Span(bepuTriangles.Memory, bepuTriangles.Length); + foreach (var mesh in model.Meshes) { - var vBindings = mesh.Draw.VertexBuffers[0]; - int vStride = vBindings.Declaration.VertexStride; - var position = vBindings.Declaration.EnumerateWithOffsets().First(x => x.VertexElement.SemanticName == VertexElementUsage.Position); + mesh.Draw.IndexBuffer.AsReadable(services, out var indexHelper, out int indexCount); + mesh.Draw.VertexBuffers[0].AsReadable(services, out var vertexHelper, out int vertexCount); - if (position.VertexElement.Format is PixelFormat.R32G32B32_Float or PixelFormat.R32G32B32A32_Float == false) - throw new ArgumentException($"{model}'s vertex position must be declared as float3 or float4"); + var copyJob = new VertexBufferHelper.CopyAsTriangleList { IndexBufferHelper = indexHelper }; + vertexHelper.Read(spanLeft[..indexCount], copyJob); - fixed (byte* vBuffer = &verticesBytes[vBindings.Offset]) - fixed (byte* iBuffer = indicesBytes) - { - if (mesh.Draw.IndexBuffer.Is32Bit) - { - foreach (int i in new Span(iBuffer + mesh.Draw.IndexBuffer.Offset, mesh.Draw.IndexBuffer.Count)) - { - triangleAsV3[triangleV3Index++] = *(Vector3*)(vBuffer + vStride * i + position.Offset); // start of the buffer, move to the 'i'th vertex, and read from the position field of that vertex - } - } - else - { - foreach (ushort i in new Span(iBuffer + mesh.Draw.IndexBuffer.Offset, mesh.Draw.IndexBuffer.Count)) - { - triangleAsV3[triangleV3Index++] = *(Vector3*)(vBuffer + vStride * i + position.Offset); - } - } - } + spanLeft = spanLeft[indexCount..]; } return triangles; } - private static unsafe void ExtractMeshBuffers(Model model, IServiceRegistry services, out VertexPosition3[] vertices, out int[] indices) + private static void ExtractMeshBuffers(Model model, IServiceRegistry services, out VertexPosition3[] vertices, out int[] indices) { int totalVertices = 0, totalIndices = 0; foreach (var meshData in model.Meshes) @@ -262,42 +173,19 @@ private static unsafe void ExtractMeshBuffers(Model model, IServiceRegistry serv vertices = new VertexPosition3[totalVertices]; indices = new int[totalIndices]; - int vertexWriteHead = 0; - int indexWriteHead = 0; - foreach ((Rendering.Mesh mesh, byte[] verticesBytes, byte[] indicesBytes) in ExtractMeshes(model, services)) - { - int vertMappingStart = vertexWriteHead; - fixed (byte* bytePtr = verticesBytes) - { - var vBindings = mesh.Draw.VertexBuffers[0]; - int count = vBindings.Count; - int stride = vBindings.Declaration.VertexStride; + var verticesLeft = MemoryMarshal.Cast(vertices.AsSpan()); + var indicesLeft = indices.AsSpan(); - for (int i = 0, vHead = vBindings.Offset; i < count; i++, vHead += stride) - { - vertices[vertexWriteHead++].Position = *(Vector3*)(bytePtr + vHead); - } - } + foreach (var mesh in model.Meshes) + { + mesh.Draw.IndexBuffer.AsReadable(services, out var indexHelper, out int indexCount); + mesh.Draw.VertexBuffers[0].AsReadable(services, out var vertexHelper, out int vertexCount); - fixed (byte* bytePtr = indicesBytes) - { - var count = mesh.Draw.IndexBuffer.Count; + vertexHelper.Copy(verticesLeft[..vertexCount]); + indexHelper.CopyTo(indicesLeft[..indexCount]); - if (mesh.Draw.IndexBuffer.Is32Bit) - { - foreach (int indexBufferValue in new Span(bytePtr + mesh.Draw.IndexBuffer.Offset, count)) - { - indices[indexWriteHead++] = vertMappingStart + indexBufferValue; - } - } - else - { - foreach (ushort indexBufferValue in new Span(bytePtr + mesh.Draw.IndexBuffer.Offset, count)) - { - indices[indexWriteHead++] = vertMappingStart + indexBufferValue; - } - } - } + verticesLeft = verticesLeft[vertexCount..]; + indicesLeft = indicesLeft[indexCount..]; } } diff --git a/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs b/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs index 44a2b4ad3f..68f201a462 100644 --- a/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs +++ b/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs @@ -7,13 +7,10 @@ using System.Linq; using Stride.Core; using Stride.Core.Annotations; -using Stride.Core.IO; using Stride.Core.Mathematics; -using Stride.Core.Serialization; using Stride.Core.Serialization.Contents; using Stride.Extensions; using Stride.Graphics; -using Stride.Graphics.Data; using Stride.Graphics.GeometricPrimitives; using Stride.Rendering; @@ -101,7 +98,7 @@ public override MeshDraw CreateDebugPrimitive(GraphicsDevice device) return new GeometricPrimitive(device, meshData).ToMeshDraw(); } - static unsafe SharedMeshData BuildAndShareMeshes(Model model, IServiceRegistry services) + static SharedMeshData BuildAndShareMeshes(Model model, IServiceRegistry services) { var sharedContent = services.GetService(); @@ -118,9 +115,6 @@ static unsafe SharedMeshData BuildAndShareMeshes(Model model, IServiceRegistry s } } - var dbProvider = services.GetService(); - ContentManager rawContent = null; - Matrix[] nodeTransforms = null; if (model.Skeleton != null) { @@ -159,60 +153,30 @@ static unsafe SharedMeshData BuildAndShareMeshes(Model model, IServiceRegistry s totalIndices += meshData.Draw.IndexBuffer.Count; } - var combinedVerts = new List(totalVerts); - var combinedIndices = new List(totalIndices); - + var combinedVerts = new Vector3[totalVerts]; + var combinedIndices = new int[totalIndices]; + var verticesLeft = combinedVerts.AsSpan(); + var indicesLeft = combinedIndices.AsSpan(); + foreach (var meshData in model.Meshes) { - var vBuffer = meshData.Draw.VertexBuffers[0].Buffer; - var iBuffer = meshData.Draw.IndexBuffer.Buffer; - byte[] verticesBytes = TryFetchBufferContent(vBuffer, ref rawContent, sharedContent, dbProvider); - byte[] indicesBytes = TryFetchBufferContent(iBuffer, ref rawContent, sharedContent, dbProvider); + meshData.Draw.VertexBuffers[0].AsReadable(services, out var vertexHelper, out var vertexCount); + meshData.Draw.IndexBuffer.AsReadable(services, out var indexHelper, out var indexCount); - if((verticesBytes?.Length ?? 0) == 0 || (indicesBytes?.Length ?? 0) == 0) - { - throw new InvalidOperationException( - $"Failed to find mesh buffers while attempting to build a {nameof(StaticMeshColliderShape)}. " + - $"Make sure that the {nameof(model)} is either an asset on disk, or has its buffer data attached to the buffer through '{nameof(AttachedReference)}'\n" + - $"You can also explicitly build a {nameof(StaticMeshColliderShape)} using the second constructor instead of this one."); - } + var sliceForTheseVertices = verticesLeft[..vertexCount]; + vertexHelper.Copy(sliceForTheseVertices); + indexHelper.CopyTo(indicesLeft[..indexCount]); - int vertMappingStart = combinedVerts.Count; + verticesLeft = verticesLeft[vertexCount..]; + indicesLeft = indicesLeft[indexCount..]; - fixed (byte* bytePtr = verticesBytes) + if (nodeTransforms != null) { - var vBindings = meshData.Draw.VertexBuffers[0]; - int count = vBindings.Count; - int stride = vBindings.Declaration.VertexStride; - for (int i = 0, vHead = vBindings.Offset; i < count; i++, vHead += stride) + for (int i = 0; i < sliceForTheseVertices.Length; i++) { - var pos = *(Vector3*)(bytePtr + vHead); - if (nodeTransforms != null) - { - Matrix posMatrix = Matrix.Translation(pos); - Matrix.Multiply(ref posMatrix, ref nodeTransforms[meshData.NodeIndex], out var finalMatrix); - pos = finalMatrix.TranslationVector; - } - - combinedVerts.Add(pos); - } - } - - fixed (byte* bytePtr = indicesBytes) - { - if (meshData.Draw.IndexBuffer.Is32Bit) - { - foreach (int i in new Span(bytePtr + meshData.Draw.IndexBuffer.Offset, meshData.Draw.IndexBuffer.Count)) - { - combinedIndices.Add(vertMappingStart + i); - } - } - else - { - foreach (ushort i in new Span(bytePtr + meshData.Draw.IndexBuffer.Offset, meshData.Draw.IndexBuffer.Count)) - { - combinedIndices.Add(vertMappingStart + i); - } + Matrix posMatrix = Matrix.Translation(sliceForTheseVertices[i]); + Matrix.Multiply(ref posMatrix, ref nodeTransforms[meshData.NodeIndex], out var finalMatrix); + sliceForTheseVertices[i] = finalMatrix.TranslationVector; } } } @@ -245,55 +209,6 @@ static unsafe SharedMeshData BuildAndShareMeshes(Model model, IServiceRegistry s return sharedData; } } - - static unsafe byte[] TryFetchBufferContent(Graphics.Buffer buffer, ref ContentManager rawContent, ContentManager sharedContent, IDatabaseFileProviderService dbProvider) - { - byte[] output; - var bufRef = AttachedReferenceManager.GetAttachedReference(buffer); - if (bufRef.Data != null && (output = ((BufferData)bufRef.Data).Content) != null) - return output; - - // Editor-specific workaround, we can't load assets when the file provider is null, - // will most likely break on non-dx11 APIs - if (dbProvider != null && dbProvider.FileProvider == null) - { - var flags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance; - var commandList = (CommandList)typeof(GraphicsDevice) - .GetField("InternalMainCommandList", flags) - .GetValue(buffer.GraphicsDevice); - - output = new byte[buffer.SizeInBytes]; - if (buffer.Description.Usage == GraphicsResourceUsage.Staging) - { - // Directly if this is a staging resource - buffer.GetData(commandList, buffer, output); - } - else - { - // inefficient way to use the Copy method using dynamic staging texture - using var throughStaging = buffer.ToStaging(); - buffer.GetData(commandList, throughStaging, output); - } - - return output; - } - - if (sharedContent.TryGetAssetUrl(buffer, out var url)) - { - rawContent ??= new ContentManager(dbProvider); - var data = rawContent.Load(url); - try - { - return data.GetSerializationData().Content; - } - finally - { - rawContent.Unload(url); - } - } - - return null; - } private record SharedMeshData : IDisposable { From fe3a81886a2c8643a2ce8f872da4a4fa261e61ab Mon Sep 17 00:00:00 2001 From: Eideren Date: Thu, 7 Aug 2025 18:27:58 +0200 Subject: [PATCH 04/17] fix: Wrong count used, performance regression --- .../Stride.Graphics/VertexBufferHelper.cs | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index cdda13c8c6..5f30b24a2a 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -86,28 +86,28 @@ public bool Copy(Span buffer, int semanticIndex = 0) /// /// /// The semantic to read, for example - /// The value type to read, depends on the used, when your for example + /// The value type to read, depends on the used, when your for example /// The type of the reader you're providing /// True when this semantic exists in the vertex buffer, false otherwise /// /// When the data format for this semantic is too arcane - no conversion logic is implemented for that type /// /// - public bool Read(Span destination, TReader reader, int semanticIndex = 0) - where TSemantic : ISemantic where TValue : unmanaged - where TReader : IReader + public bool Read(Span destination, TReader reader, int semanticIndex = 0) + where TSemantic : ISemantic where TDest : unmanaged + where TReader : IReader { if (Binding.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) { switch (elementData.VertexElement.Format) { - case PixelFormat.R32G32_Float: Inner(DataInner, destination, reader, Binding, elementData); break; - case PixelFormat.R32G32B32_Float: Inner(DataInner, destination, reader, Binding, elementData); break; - case PixelFormat.R32G32B32A32_Float: Inner(DataInner, destination, reader, Binding, elementData); break; - case PixelFormat.R16G16_Float: Inner(DataInner, destination, reader, Binding, elementData); break; - case PixelFormat.R16G16B16A16_Float: Inner(DataInner, destination, reader, Binding, elementData); break; - case PixelFormat.R16G16B16A16_UInt: Inner(DataInner, destination, reader, Binding, elementData); break; - case PixelFormat.R8G8B8A8_UInt: Inner(DataInner, destination, reader, Binding, elementData); break; + case PixelFormat.R32G32_Float: Inner(destination, reader, elementData); break; + case PixelFormat.R32G32B32_Float: Inner(destination, reader, elementData); break; + case PixelFormat.R32G32B32A32_Float: Inner(destination, reader, elementData); break; + case PixelFormat.R16G16_Float: Inner(destination, reader, elementData); break; + case PixelFormat.R16G16B16A16_Float: Inner(destination, reader, elementData); break; + case PixelFormat.R16G16B16A16_UInt: Inner(destination, reader, elementData); break; + case PixelFormat.R8G8B8A8_UInt: Inner(destination, reader, elementData); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({elementData.VertexElement.Format})"); } @@ -115,22 +115,24 @@ public bool Read(Span destination, TReader r } return false; + } - static unsafe void Inner(Span vertexBuffer, Span destination, IReader reader, VertexBufferBinding binding, VertexElementWithOffset element) - where TConversion : IConversion where TSource : unmanaged - { - var stride = binding.Declaration.VertexStride; - var offset = element.Offset; - var count = vertexBuffer.Length / element.Size; + private unsafe void Inner(Span destination, TReader reader, VertexElementWithOffset element) + where TConversion : IConversion + where TSource : unmanaged + where TReader : IReader + { + if (sizeof(TSource) != element.Size) + throw new ArgumentException($"{typeof(TSource)} does not match element size ({sizeof(TSource)} != {element.Size})"); - if (sizeof(TSource) != element.Size) - throw new ArgumentException($"{typeof(TSource)} does not match element size ({sizeof(TSource)} != {element.Size})"); + var stride = Binding.Declaration.VertexStride; + var offset = element.Offset; + var count = Binding.Count; - fixed (byte* ptrSr = vertexBuffer) - { - byte* src = ptrSr + offset; - reader.Read(src, count, stride, destination); - } + fixed (byte* ptrSr = DataInner) + { + byte* firstElement = ptrSr + offset; + reader.Read(firstElement, count, stride, destination); } } From 4461d4752f96018a37f442030cb06ed1275c1563 Mon Sep 17 00:00:00 2001 From: Eideren Date: Thu, 7 Aug 2025 18:30:27 +0200 Subject: [PATCH 05/17] chore: Improve docs --- .../engine/Stride.Graphics/VertexBufferHelper.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index 5f30b24a2a..f9c5548d41 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -71,6 +71,10 @@ public bool Copy(Span buffer, int semanticIndex = 0) /// /// Provides custom access to the vertex buffer while having access to the auto-conversion /// + /// + /// The destination span your method receives, + /// you may pass if you do not need one. + /// /// /// An implementation of , implement this interface to read directly from the vertex buffer /// while making use of the auto-conversion of the provided
@@ -172,7 +176,8 @@ public unsafe void Read(byte* sourcePointer, int elementCo private struct CopyToDest : IReader where T : unmanaged { public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) - where TConversion : IConversion where TSource : unmanaged + where TConversion : IConversion + where TSource : unmanaged { if (destination.Length != elementCount) throw new ArgumentException($"{nameof(destination)} length does not match the amount of vertices contained within this vertex buffer ({destination.Length} / {elementCount})"); @@ -218,12 +223,9 @@ public unsafe void Read(byte* sourcePointer, int elementCo /// public interface IReader { - /// - /// - /// - /// The pointer already offset by the buffer and element offset - /// The amount of vertices, this is not the same as the size of the vertex buffer, or the size in bytes taken by individual vertices - /// The size in bytes taken by individual vertices + /// Points to the first element in the vertex buffer, read it as a TSource* to retrieve its value + /// The amount of vertices. This is not equivalent to the size of the vertex buffer, or the size in bytes taken by individual vertices + /// The size in bytes taken by individual vertices, add it to to point to the next element /// The span passed into the method call /// A helper to convert between and properly /// From 154d5be7d8f540b99ea33afaad64dd152aa4970d Mon Sep 17 00:00:00 2001 From: Eideren Date: Fri, 29 Aug 2025 19:17:40 +0200 Subject: [PATCH 06/17] Added Write() to manipulate the buffer directly and Copy(Span) to transpose a buffer into a different definition --- .../engine/Stride.Graphics/MeshExtension.cs | 8 +- .../Stride.Graphics/Semantic/ISemantic.cs | 234 +++++++++------ .../Stride.Graphics/VertexBufferHelper.cs | 273 +++++++++++++++++- .../Extensions/VertexExtensions.cs | 2 + 4 files changed, 416 insertions(+), 101 deletions(-) diff --git a/sources/engine/Stride.Graphics/MeshExtension.cs b/sources/engine/Stride.Graphics/MeshExtension.cs index 2b7dd75803..3f1f7a5d44 100644 --- a/sources/engine/Stride.Graphics/MeshExtension.cs +++ b/sources/engine/Stride.Graphics/MeshExtension.cs @@ -20,7 +20,7 @@ public static class MeshExtension /// This operation loads the buffer from disk, or directly from the gpu. It is very slow, avoid calling this too often if at all possible. /// /// The bindings for this buffer - /// The service used to retrieve the buffer from disk/GPU + /// The service used to retrieve the buffer from disk/GPU if it wasn't found through other means /// The helper class to interact with the loaded buffer /// The amount of vertices this buffer holds /// @@ -36,7 +36,7 @@ public static void AsReadable(this VertexBufferBinding binding, IServiceRegistry /// This operation loads the buffer from disk, or directly from the gpu. It is very slow, avoid calling this too often if at all possible. /// /// The bindings for this buffer - /// The service used to retrieve the buffer from disk/GPU + /// The service used to retrieve the buffer from disk/GPU if it wasn't found through other means /// The helper class to interact with the loaded buffer /// The amount of indices this buffer holds /// @@ -48,10 +48,10 @@ public static void AsReadable(this IndexBufferBinding binding, IServiceRegistry /// /// Given a semantic and its index, returns its offset and size in the given vertex buffer. Similar to /// - public static bool TryGetElement(this VertexBufferBinding binding, string vertexElementUsage, int semanticIndex, out VertexElementWithOffset result) + public static bool TryGetElement(this VertexDeclaration declaration, string vertexElementUsage, int semanticIndex, out VertexElementWithOffset result) { int offset = 0; - foreach (var element in binding.Declaration.VertexElements) + foreach (var element in declaration.VertexElements) { // Get new offset (if specified) var currentElementOffset = element.AlignedByteOffset; diff --git a/sources/engine/Stride.Graphics/Semantic/ISemantic.cs b/sources/engine/Stride.Graphics/Semantic/ISemantic.cs index aaaaaa1fd6..6ae13aef4e 100644 --- a/sources/engine/Stride.Graphics/Semantic/ISemantic.cs +++ b/sources/engine/Stride.Graphics/Semantic/ISemantic.cs @@ -11,7 +11,6 @@ public interface ISemantic public static abstract string Name { get; } } - public interface ISemantic : ISemantic, // Default source types we support for conversion @@ -21,95 +20,166 @@ public interface ISemantic : IConversion, IConversion, IConversion, - IConversion; + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion; -public interface IFloat2Semantic : ISemantic -{ - static void IConversion.Convert(in Vector2 source, out Vector2 dest) => dest = source; - static void IConversion.Convert(in Vector3 source, out Vector2 dest) => dest = (Vector2)source; - static void IConversion.Convert(in Vector4 source, out Vector2 dest) => dest = (Vector2)source; - static void IConversion.Convert(in Half2 source, out Vector2 dest) => dest = (Vector2)source; - static void IConversion.Convert(in Half4 source, out Vector2 dest) => dest = new(source.X, source.Y); - static void IConversion.Convert(in UShort4 source, out Vector2 dest) => dest = new(source.X, source.Y); - static void IConversion.Convert(in Byte4 source, out Vector2 dest) => dest = new(source.X, source.Y); -} +public interface V2V2 : IConversion { static void IConversion.Convert(in Vector2 source, out Vector2 dest) => dest = source; } +public interface V3V2 : IConversion { static void IConversion.Convert(in Vector3 source, out Vector2 dest) => dest = (Vector2)source; } +public interface V4V2 : IConversion { static void IConversion.Convert(in Vector4 source, out Vector2 dest) => dest = (Vector2)source; } +public interface H2V2 : IConversion { static void IConversion.Convert(in Half2 source, out Vector2 dest) => dest = (Vector2)source; } +public interface H3V2 : IConversion { static void IConversion.Convert(in Half3 source, out Vector2 dest) => dest = new(source.X, source.Y); } +public interface H4V2 : IConversion { static void IConversion.Convert(in Half4 source, out Vector2 dest) => dest = new(source.X, source.Y); } +public interface U4V2 : IConversion { static void IConversion.Convert(in UShort4 source, out Vector2 dest) => dest = new(source.X, source.Y); } +public interface B4V2 : IConversion { static void IConversion.Convert(in Byte4 source, out Vector2 dest) => dest = new(source.X, source.Y); } -public interface IFloat3Semantic : ISemantic -{ - static void IConversion.Convert(in Vector2 source, out Vector3 dest) => dest = (Vector3)source; - static void IConversion.Convert(in Vector3 source, out Vector3 dest) => dest = source; - static void IConversion.Convert(in Vector4 source, out Vector3 dest) => dest = (Vector3)source; - static void IConversion.Convert(in Half2 source, out Vector3 dest) => dest = new(source.X, source.Y, 0f); - static void IConversion.Convert(in Half4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); - static void IConversion.Convert(in UShort4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); - static void IConversion.Convert(in Byte4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); -} +public interface V2V3 : IConversion { static void IConversion.Convert(in Vector2 source, out Vector3 dest) => dest = (Vector3)source; } +public interface V3V3 : IConversion { static void IConversion.Convert(in Vector3 source, out Vector3 dest) => dest = source; } +public interface V4V3 : IConversion { static void IConversion.Convert(in Vector4 source, out Vector3 dest) => dest = (Vector3)source; } +public interface H2V3 : IConversion { static void IConversion.Convert(in Half2 source, out Vector3 dest) => dest = new(source.X, source.Y, 0f); } +public interface H3V3 : IConversion { static void IConversion.Convert(in Half3 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface H4V3 : IConversion { static void IConversion.Convert(in Half4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface U4V3 : IConversion { static void IConversion.Convert(in UShort4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface B4V3 : IConversion { static void IConversion.Convert(in Byte4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface IFloat4Semantic : ISemantic -{ - static void IConversion.Convert(in Vector2 source, out Vector4 dest) => dest = (Vector4)source; - static void IConversion.Convert(in Vector3 source, out Vector4 dest) => dest = (Vector4)source; - static void IConversion.Convert(in Vector4 source, out Vector4 dest) => dest = source; - static void IConversion.Convert(in Half2 source, out Vector4 dest) => dest = new(source.X, source.Y, 0f, 0f); - static void IConversion.Convert(in Half4 source, out Vector4 dest) => dest = (Vector4)source; - static void IConversion.Convert(in UShort4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); - static void IConversion.Convert(in Byte4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); -} +public interface V2V4 : IConversion { static void IConversion.Convert(in Vector2 source, out Vector4 dest) => dest = (Vector4)source; } +public interface V3V4 : IConversion { static void IConversion.Convert(in Vector3 source, out Vector4 dest) => dest = (Vector4)source; } +public interface V4V4 : IConversion { static void IConversion.Convert(in Vector4 source, out Vector4 dest) => dest = source; } +public interface H2V4 : IConversion { static void IConversion.Convert(in Half2 source, out Vector4 dest) => dest = new(source.X, source.Y, 0f, 0f); } +public interface H3V4 : IConversion { static void IConversion.Convert(in Half3 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } +public interface H4V4 : IConversion { static void IConversion.Convert(in Half4 source, out Vector4 dest) => dest = (Vector4)source; } +public interface U4V4 : IConversion { static void IConversion.Convert(in UShort4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface B4V4 : IConversion { static void IConversion.Convert(in Byte4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } -public interface IHalf2Semantic : ISemantic -{ - static void IConversion.Convert(in Vector2 source, out Half2 dest) => dest = (Half2)source; - static void IConversion.Convert(in Vector3 source, out Half2 dest) => dest = new(source.X, source.Y); - static void IConversion.Convert(in Vector4 source, out Half2 dest) => dest = new(source.X, source.Y); - static void IConversion.Convert(in Half2 source, out Half2 dest) => dest = source; - static void IConversion.Convert(in Half4 source, out Half2 dest) => dest = new(source.X, source.Y); - static void IConversion.Convert(in UShort4 source, out Half2 dest) => dest = new(source.X, source.Y); - static void IConversion.Convert(in Byte4 source, out Half2 dest) => dest = new(source.X, source.Y); -} +public interface V2H2 : IConversion { static void IConversion.Convert(in Vector2 source, out Half2 dest) => dest = (Half2)source; } +public interface V3H2 : IConversion { static void IConversion.Convert(in Vector3 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface V4H2 : IConversion { static void IConversion.Convert(in Vector4 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface H2H2 : IConversion { static void IConversion.Convert(in Half2 source, out Half2 dest) => dest = source; } +public interface H3H2 : IConversion { static void IConversion.Convert(in Half3 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface H4H2 : IConversion { static void IConversion.Convert(in Half4 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface U4H2 : IConversion { static void IConversion.Convert(in UShort4 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface B4H2 : IConversion { static void IConversion.Convert(in Byte4 source, out Half2 dest) => dest = new(source.X, source.Y); } -public interface IHalf3Semantic : ISemantic -{ - static void IConversion.Convert(in Vector2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); - static void IConversion.Convert(in Vector3 source, out Half3 dest) => dest = (Half3)source; - static void IConversion.Convert(in Vector4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); - static void IConversion.Convert(in Half2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); - static void IConversion.Convert(in Half4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); - static void IConversion.Convert(in UShort4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); - static void IConversion.Convert(in Byte4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); -} +public interface V2H3 : IConversion { static void IConversion.Convert(in Vector2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); } +public interface V3H3 : IConversion { static void IConversion.Convert(in Vector3 source, out Half3 dest) => dest = (Half3)source; } +public interface V4H3 : IConversion { static void IConversion.Convert(in Vector4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface H2H3 : IConversion { static void IConversion.Convert(in Half2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); } +public interface H3H3 : IConversion { static void IConversion.Convert(in Half3 source, out Half3 dest) => dest = source; } +public interface H4H3 : IConversion { static void IConversion.Convert(in Half4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface U4H3 : IConversion { static void IConversion.Convert(in UShort4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface B4H3 : IConversion { static void IConversion.Convert(in Byte4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface IHalf4Semantic : ISemantic -{ - static void IConversion.Convert(in Vector2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); - static void IConversion.Convert(in Vector3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); - static void IConversion.Convert(in Vector4 source, out Half4 dest) => dest = (Half4)source; - static void IConversion.Convert(in Half2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); - static void IConversion.Convert(in Half4 source, out Half4 dest) => dest = source; - static void IConversion.Convert(in UShort4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); - static void IConversion.Convert(in Byte4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); -} +public interface V2H4 : IConversion { static void IConversion.Convert(in Vector2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); } +public interface V3H4 : IConversion { static void IConversion.Convert(in Vector3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } +public interface V4H4 : IConversion { static void IConversion.Convert(in Vector4 source, out Half4 dest) => dest = (Half4)source; } +public interface H2H4 : IConversion { static void IConversion.Convert(in Half2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); } +public interface H3H4 : IConversion { static void IConversion.Convert(in Half3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } +public interface H4H4 : IConversion { static void IConversion.Convert(in Half4 source, out Half4 dest) => dest = source; } +public interface U4H4 : IConversion { static void IConversion.Convert(in UShort4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface B4H4 : IConversion { static void IConversion.Convert(in Byte4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } -public interface IUShort4Semantic : ISemantic -{ - static void IConversion.Convert(in Vector2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); - static void IConversion.Convert(in Vector3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); - static void IConversion.Convert(in Vector4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); - static void IConversion.Convert(in Half2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); - static void IConversion.Convert(in Half4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); - static void IConversion.Convert(in UShort4 source, out UShort4 dest) => dest = source; - static void IConversion.Convert(in Byte4 source, out UShort4 dest) => dest = new(source.X, source.Y, source.Z, source.W); -} +public interface V2U4 : IConversion { static void IConversion.Convert(in Vector2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); } +public interface V3U4 : IConversion { static void IConversion.Convert(in Vector3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); } +public interface V4U4 : IConversion { static void IConversion.Convert(in Vector4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); } +public interface H2U4 : IConversion { static void IConversion.Convert(in Half2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); } +public interface H3U4 : IConversion { static void IConversion.Convert(in Half3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); } +public interface H4U4 : IConversion { static void IConversion.Convert(in Half4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); } +public interface U4U4 : IConversion { static void IConversion.Convert(in UShort4 source, out UShort4 dest) => dest = source; } +public interface B4U4 : IConversion { static void IConversion.Convert(in Byte4 source, out UShort4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } -public interface IByte4Semantic : ISemantic -{ - static void IConversion.Convert(in Vector2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); - static void IConversion.Convert(in Vector3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); - static void IConversion.Convert(in Vector4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); - static void IConversion.Convert(in Half2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); - static void IConversion.Convert(in Half4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); - static void IConversion.Convert(in UShort4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); - static void IConversion.Convert(in Byte4 source, out Byte4 dest) => dest = source; -} +public interface V2B4 : IConversion { static void IConversion.Convert(in Vector2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); } +public interface V3B4 : IConversion { static void IConversion.Convert(in Vector3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); } +public interface V4B4 : IConversion { static void IConversion.Convert(in Vector4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } +public interface H2B4 : IConversion { static void IConversion.Convert(in Half2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); } +public interface H3B4 : IConversion { static void IConversion.Convert(in Half3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); } +public interface H4B4 : IConversion { static void IConversion.Convert(in Half4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } +public interface U4B4 : IConversion { static void IConversion.Convert(in UShort4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } +public interface B4B4 : IConversion { static void IConversion.Convert(in Byte4 source, out Byte4 dest) => dest = source; } + +public interface IFloat2Semantic : ISemantic, + V2V2, + V2V3, V3V2, + V2V4, V4V2, + V2B4, B4V2, + V2U4, U4V2, + V2H2, H2V2, + V2H3, + V2H4, H4V2; + +public interface IFloat3Semantic : ISemantic, + V3V2, V2V3, + V3V3, + V3V4, V4V3, + V3B4, B4V3, + V3U4, U4V3, + V3H2, H2V3, + V3H3, + V3H4, H4V3; + +public interface IFloat4Semantic : ISemantic, + V4V2, V2V4, + V4V3, V3V4, + V4V4, + V4B4, B4V4, + V4U4, U4V4, + V4H2, H2V4, + V4H3, + V4H4, H4V4; + +public interface IHalf2Semantic : ISemantic, + H2V2, V2H2, + H2V3, V3H2, + H2V4, V4H2, + H2B4, B4H2, + H2U4, U4H2, + H2H2, + H2H3, + H2H4, H4H2; + +public interface IHalf3Semantic : ISemantic, + H3V2, V2H3, + H3V3, V3H3, + H3V4, V4H3, + H3B4, B4H3, + H3U4, U4H3, + H3H2, H2H3, + H3H3, + H3H4; + +public interface IHalf4Semantic : ISemantic, + H4V2, V2H4, + H4V3, V3H4, + H4V4, V4H4, + H4B4, B4H4, + H4U4, U4H4, + H4H2, H2H4, + H4H3, + H4H4; + +public interface IUShort4Semantic : ISemantic, + U4V2, V2U4, + U4V3, V3U4, + U4V4, V4U4, + U4B4, B4U4, + U4U4, + U4H2, H2U4, + U4H3, + U4H4, H4U4; + +public interface IByte4Semantic : ISemantic, + B4V2, V2B4, + B4V3, V3B4, + B4V4, V4B4, + B4B4, + B4U4, U4B4, + B4H2, H2B4, + B4H3, + B4H4, H4B4; public struct UShort4(ushort x, ushort y, ushort z, ushort w) { diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index f9c5548d41..6c79be6b2d 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -3,6 +3,7 @@ #nullable enable using System; +using System.Runtime.InteropServices; using Stride.Core; using Stride.Core.Mathematics; using Stride.Graphics.Semantic; @@ -19,10 +20,11 @@ namespace Stride.Graphics; /// ]]> /// /// -public class VertexBufferHelper +public readonly struct VertexBufferHelper { /// - /// Full vertex buffer, does not account for the binding offset or length + /// Full vertex buffer, the start and end of this buffer may contain data that does not map to this , + /// use if you want to only read the data that is mapped to this binding. /// public readonly byte[] DataOuter; public readonly VertexBufferBinding Binding; @@ -33,10 +35,24 @@ public class VertexBufferHelper public Span DataInner => DataOuter.AsSpan(Binding.Offset, Binding.Count * Binding.Stride); /// - public VertexBufferHelper(VertexBufferBinding binding, IServiceRegistry services, out int count) + public VertexBufferHelper(VertexBufferBinding binding, IServiceRegistry services, out int count) + : this(binding, MeshExtension.FetchBufferContentOrThrow(binding.Buffer, services), out count) { - var data = MeshExtension.FetchBufferContentOrThrow(binding.Buffer, services); - DataOuter = data; + } + + /// + /// Create the helper from existing data instead of trying to fetch the buffer automatically + /// + /// + /// does not match the binding definition provided, + /// must be the entire vertex buffer + /// + public VertexBufferHelper(VertexBufferBinding binding, byte[] dataOuter, out int count) + { + if (dataOuter.Length < binding.Offset + binding.Count * binding.Stride) + throw new ArgumentException($"{nameof(dataOuter)} does not fit the bindings provided. Make sure that the span provided contains the entirety of the vertex buffer"); + + DataOuter = dataOuter; Binding = binding; count = Binding.Count; } @@ -69,7 +85,101 @@ public bool Copy(Span buffer, int semanticIndex = 0) } /// - /// Provides custom access to the vertex buffer while having access to the auto-conversion + /// Copies this vertex buffer's data to the vertex-buffer-like span provided. + /// Any semantic data present in that is not present in this buffer is left untouched. + /// + /// + /// The buffer which will be written to, must be of the same length as the amount of vertices in this buffer + /// + /// True if every semantic element of was written to from this buffers' data, false if at least one semantic was missing + /// + /// When the data format for this semantic is too arcane - no conversion logic is implemented for that type + /// + /// + /// Copying a mesh's vertex positions, colors and UVs: + /// + /// + /// + /// + public unsafe bool Copy(Span destination) where TDest : unmanaged, IVertex + { + bool missing = false; + var parameters = new InterleavedParameters(DataInner, MemoryMarshal.Cast(destination), Binding.Stride, sizeof(TDest), Binding.Count); + foreach (var destDef in default(TDest).GetLayout().EnumerateWithOffsets()) + { + if (Binding.Declaration.TryGetElement(destDef.VertexElement.SemanticName, destDef.VertexElement.SemanticIndex, out var srcDef)) + { + var srcOffset = srcDef.Offset; + var destOffset = destDef.Offset; + + var srcFormat = srcDef.VertexElement.Format; + switch (destDef.VertexElement.Format) + { + // The particular semantic used doesn't matter too much here, we're just abusing the relaxed definition to fit any TDest + case PixelFormat.R32G32_Float: SelectSrcType, Vector2>(parameters, srcOffset, destOffset, srcFormat); break; + case PixelFormat.R32G32B32_Float: SelectSrcType, Vector3>(parameters, srcOffset, destOffset, srcFormat); break; + case PixelFormat.R32G32B32A32_Float: SelectSrcType, Vector4>(parameters, srcOffset, destOffset, srcFormat); break; + case PixelFormat.R16G16_Float: SelectSrcType, Half2>(parameters, srcOffset, destOffset, srcFormat); break; + case PixelFormat.R16G16B16A16_Float: SelectSrcType, Half4>(parameters, srcOffset, destOffset, srcFormat); break; + case PixelFormat.R16G16B16A16_UInt: SelectSrcType, UShort4>(parameters, srcOffset, destOffset, srcFormat); break; + case PixelFormat.R8G8B8A8_UInt: SelectSrcType, Byte4>(parameters, srcOffset, destOffset, srcFormat); break; + default: throw new NotImplementedException($"Unsupported format when converting vertex element ({srcDef.VertexElement.Format})"); + } + } + else + { + missing = true; + } + } + + return missing; + + static void SelectSrcType(InterleavedParameters param, int srcElemOffset, int destElemOffset, PixelFormat format) + where TSemantic : ISemantic + where TOutput : unmanaged + { + switch (format) + { + case PixelFormat.R32G32_Float: InterleavedCopy(param, srcElemOffset, destElemOffset); break; + case PixelFormat.R32G32B32_Float: InterleavedCopy(param, srcElemOffset, destElemOffset); break; + case PixelFormat.R32G32B32A32_Float: InterleavedCopy(param, srcElemOffset, destElemOffset); break; + case PixelFormat.R16G16_Float: InterleavedCopy(param, srcElemOffset, destElemOffset); break; + case PixelFormat.R16G16B16A16_Float: InterleavedCopy(param, srcElemOffset, destElemOffset); break; + case PixelFormat.R16G16B16A16_UInt: InterleavedCopy(param, srcElemOffset, destElemOffset); break; + case PixelFormat.R8G8B8A8_UInt: InterleavedCopy(param, srcElemOffset, destElemOffset); break; + default: throw new NotImplementedException($"Unsupported format when converting vertex element ({format})"); + } + } + + static void InterleavedCopy(InterleavedParameters param, int srcElemOffset, int destElemOffset) + where TConversion : IConversion + where TSourceSemVal : unmanaged + where TDestSemVal : unmanaged + { + fixed (byte* srcStart = param.Source) + fixed (byte* destStart = param.Destination) + { + for (byte* + src = srcStart + srcElemOffset, + dest = destStart + destElemOffset, + endSrc = src + param.VertexCount * param.DestStride; + + src < endSrc; + + src += param.SourceStride, dest += param.DestStride) + { + TConversion.Convert(*(TSourceSemVal*)src, out *(TDestSemVal*)dest); + } + } + } + } + + /// + /// Lower level access to read into the vertex buffer /// /// /// The destination span your method receives, @@ -101,17 +211,17 @@ public bool Read(Span destination, TReader rea where TSemantic : ISemantic where TDest : unmanaged where TReader : IReader { - if (Binding.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) + if (Binding.Declaration.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) { switch (elementData.VertexElement.Format) { - case PixelFormat.R32G32_Float: Inner(destination, reader, elementData); break; - case PixelFormat.R32G32B32_Float: Inner(destination, reader, elementData); break; - case PixelFormat.R32G32B32A32_Float: Inner(destination, reader, elementData); break; - case PixelFormat.R16G16_Float: Inner(destination, reader, elementData); break; - case PixelFormat.R16G16B16A16_Float: Inner(destination, reader, elementData); break; - case PixelFormat.R16G16B16A16_UInt: Inner(destination, reader, elementData); break; - case PixelFormat.R8G8B8A8_UInt: Inner(destination, reader, elementData); break; + case PixelFormat.R32G32_Float: InnerRead(destination, reader, elementData); break; + case PixelFormat.R32G32B32_Float: InnerRead(destination, reader, elementData); break; + case PixelFormat.R32G32B32A32_Float: InnerRead(destination, reader, elementData); break; + case PixelFormat.R16G16_Float: InnerRead(destination, reader, elementData); break; + case PixelFormat.R16G16B16A16_Float: InnerRead(destination, reader, elementData); break; + case PixelFormat.R16G16B16A16_UInt: InnerRead(destination, reader, elementData); break; + case PixelFormat.R8G8B8A8_UInt: InnerRead(destination, reader, elementData); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({elementData.VertexElement.Format})"); } @@ -121,7 +231,7 @@ public bool Read(Span destination, TReader rea return false; } - private unsafe void Inner(Span destination, TReader reader, VertexElementWithOffset element) + private unsafe void InnerRead(Span destination, TReader reader, VertexElementWithOffset element) where TConversion : IConversion where TSource : unmanaged where TReader : IReader @@ -140,6 +250,77 @@ private unsafe void Inner(Span dest } } + /// + /// Lower level access to write directly to the vertex buffer + /// + /// + /// An implementation of , implement this interface to write directly into the vertex buffer + /// while making use of the auto-conversion of the provided
+ /// Preferably as a struct to ensure it is inlined by the JIT + /// + /// + /// The semantic to read with that index, starts at zero.
+ /// For example, to sample the second TextureCoordinate, you would use + /// + /// (yourWriter, 1); + /// ]]> + /// + /// + /// The semantic to read, for example + /// The concrete type this writer will work with + /// + /// A struct implementing which will be called in turn to write + /// into this buffer when this method is called. + /// + /// True when this semantic exists in the vertex buffer, false otherwise + /// + /// When the data format for this semantic is too arcane - no conversion logic is implemented for that type + /// + /// + public bool Write(TWriter writer, int semanticIndex = 0) + where TSemantic : ISemantic where TDest : unmanaged + where TWriter : IWriter + { + if (Binding.Declaration.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) + { + switch (elementData.VertexElement.Format) + { + case PixelFormat.R32G32_Float: InnerWrite(writer, elementData); break; + case PixelFormat.R32G32B32_Float: InnerWrite(writer, elementData); break; + case PixelFormat.R32G32B32A32_Float: InnerWrite(writer, elementData); break; + case PixelFormat.R16G16_Float: InnerWrite(writer, elementData); break; + case PixelFormat.R16G16B16A16_Float: InnerWrite(writer, elementData); break; + case PixelFormat.R16G16B16A16_UInt: InnerWrite(writer, elementData); break; + case PixelFormat.R8G8B8A8_UInt: InnerWrite(writer, elementData); break; + default: throw new NotImplementedException($"Unsupported format when converting vertex element ({elementData.VertexElement.Format})"); + } + + return true; + } + + return false; + } + + private unsafe void InnerWrite(TWriter reader, VertexElementWithOffset element) + where TConversion : IConversion, IConversion + where TSource : unmanaged + where TWriter : IWriter + { + if (sizeof(TSource) != element.Size) + throw new ArgumentException($"{typeof(TSource)} does not match element size ({sizeof(TSource)} != {element.Size})"); + + var stride = Binding.Declaration.VertexStride; + var offset = element.Offset; + var count = Binding.Count; + + fixed (byte* ptrSr = DataInner) + { + byte* firstElement = ptrSr + offset; + reader.Write(firstElement, count, stride); + } + } + public struct CopyAsTriangleList : IReader { public required IndexBufferHelper IndexBufferHelper; @@ -192,6 +373,27 @@ public unsafe void Read(byte* sourcePointer, int elementCo } } + private readonly ref struct InterleavedParameters + { + public readonly Span Source, Destination; + public readonly int SourceStride, DestStride; + public readonly int VertexCount; + + public InterleavedParameters(Span source, Span destination, int sourceStride, int destStride, int vertexCount) + { + if (destination.Length / DestStride != vertexCount) + throw new ArgumentException($"The length and stride of {nameof(destination)} does not match the vertices required ({destination.Length / DestStride} / {vertexCount})"); + if (source.Length / SourceStride != vertexCount) + throw new ArgumentException($"The length and stride of {nameof(source)} does not match the vertices required ({source.Length / SourceStride} / {vertexCount})"); + + Source = source; + Destination = destination; + SourceStride = sourceStride; + DestStride = destStride; + VertexCount = vertexCount; + } + } + /// /// Implementing manually: /// @@ -236,4 +438,45 @@ public interface IReader unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) where TConversion : IConversion where TSource : unmanaged; } + + /// + /// Writing directly to mesh color: + /// + /// (new MultColor(){ Color = Color.Gray }); + /// + /// private struct MultColor : VertexBufferHelper.IWriter + /// { + /// public Color Color; + /// + /// public unsafe void Write(byte* sourcePointer, int elementCount, int stride) + /// where TConversion : IConversion, IConversion + /// where TSource : unmanaged + /// { + /// for (byte* end = sourcePointer + elementCount * stride; sourcePointer < end; sourcePointer += stride) + /// { + /// TConversion.Convert(*(TSource*)sourcePointer, out var val); + /// val *= (Vector4)Color; + /// TConversion.Convert(val, out *(TSource*)sourcePointer); + /// } + /// } + /// } + /// ]]> + /// + /// + public interface IWriter + { + /// Points to the first element in the vertex buffer, read it as a TSource* to retrieve its value + /// The amount of vertices. This is not equivalent to the size of the vertex buffer, or the size in bytes taken by individual vertices + /// The size in bytes taken by individual vertices, add it to to point to the next element + /// A helper to convert between and properly + /// + /// The source type this vertex buffer was built with, for example or , + /// use to convert it into a . + /// + /// + unsafe void Write(byte* sourcePointer, int elementCount, int stride) + where TConversion : IConversion, IConversion where TSource : unmanaged; + } } diff --git a/sources/engine/Stride.Rendering/Extensions/VertexExtensions.cs b/sources/engine/Stride.Rendering/Extensions/VertexExtensions.cs index fbee5cdc02..643e16706e 100644 --- a/sources/engine/Stride.Rendering/Extensions/VertexExtensions.cs +++ b/sources/engine/Stride.Rendering/Extensions/VertexExtensions.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Stride.Core; +using Stride.Graphics; using Stride.Graphics.Data; using Stride.Rendering; @@ -17,6 +18,7 @@ public static class VertexExtensions /// /// The mesh data. /// The declaration to extract (e.g. "POSITION0"...etc.) + [Obsolete($"Use {nameof(VertexBufferHelper)} instead")] public static unsafe T[] GetVertexBufferData(this MeshDraw meshData, params string[] vertexElementToExtract) where T : unmanaged { var declaration = meshData.VertexBuffers[0].Declaration; From 3c7528ab694e37fb08d9ac63a03464bb596a8f90 Mon Sep 17 00:00:00 2001 From: Eideren Date: Fri, 29 Aug 2025 19:20:56 +0200 Subject: [PATCH 07/17] ComputeBounds using vertexbuffer helper to support non float3 positions --- .../Extensions/BoundingExtensions.cs | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs b/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs index ce4cb0d9ce..bd30c68410 100644 --- a/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs +++ b/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs @@ -6,69 +6,73 @@ using Stride.Core.Mathematics; using Stride.Graphics; using Stride.Graphics.Data; +using Stride.Graphics.Semantic; namespace Stride.Extensions { public static class BoundingExtensions { - public static unsafe BoundingBox ComputeBounds(this VertexBufferBinding vertexBufferBinding, ref Matrix matrix, out BoundingSphere boundingSphere) + public static BoundingBox ComputeBounds(this VertexBufferBinding vertexBufferBinding, ref Matrix matrix, out BoundingSphere boundingSphere) { - var positionOffset = vertexBufferBinding.Declaration - .EnumerateWithOffsets() - .First(x => x.VertexElement.SemanticAsText == "POSITION") - .Offset; + var helper = new VertexBufferHelper(vertexBufferBinding, vertexBufferBinding.Buffer.GetSerializationData().Content, out _); - var boundingBox = BoundingBox.Empty; - boundingSphere = new BoundingSphere(); + var computeBoundsStruct = new ComputeBoundsStruct + { + Box = BoundingBox.Empty, + Sphere = new BoundingSphere(), + Matrix = matrix + }; + helper.Read(default, computeBoundsStruct); + + boundingSphere = computeBoundsStruct.Sphere; + return computeBoundsStruct.Box; + } + + struct ComputeBoundsStruct : VertexBufferHelper.IReader + { + public required BoundingBox Box; + public required BoundingSphere Sphere; + public required Matrix Matrix; - var vertexStride = vertexBufferBinding.Declaration.VertexStride; - fixed (byte* bufferStart = &vertexBufferBinding.Buffer.GetSerializationData().Content[vertexBufferBinding.Offset]) + public unsafe void Read(byte* startPointer, int elementCount, int stride, Span destination) where TConversion : IConversion where TSource : unmanaged { // Calculates bounding box and bounding sphere center - byte* buffer = bufferStart + positionOffset; - for (int i = 0; i < vertexBufferBinding.Count; ++i) + for (byte* sourcePtr = startPointer, end = startPointer + elementCount * stride; sourcePtr < end; sourcePtr += stride) { - var position = (Vector3*)buffer; + TConversion.Convert(*(TSource*)sourcePtr, out var position); Vector3 transformedPosition; - Vector3.TransformCoordinate(ref *position, ref matrix, out transformedPosition); + Vector3.TransformCoordinate(ref position, ref Matrix, out transformedPosition); // Prepass calculate the center of the sphere - Vector3.Add(ref transformedPosition, ref boundingSphere.Center, out boundingSphere.Center); - - BoundingBox.Merge(ref boundingBox, ref transformedPosition, out boundingBox); + Vector3.Add(ref transformedPosition, ref Sphere.Center, out Sphere.Center); - buffer += vertexStride; + BoundingBox.Merge(ref Box, ref transformedPosition, out Box); } //This is the center of our sphere. - boundingSphere.Center /= (float)vertexBufferBinding.Count; + Sphere.Center /= elementCount; // Calculates bounding sphere center - buffer = bufferStart + positionOffset; - for (int i = 0; i < vertexBufferBinding.Count; ++i) + for (byte* sourcePtr = startPointer, end = startPointer + elementCount * stride; sourcePtr < end; sourcePtr += stride) { - var position = (Vector3*)buffer; + TConversion.Convert(*(TSource*)sourcePtr, out var position); Vector3 transformedPosition; - Vector3.TransformCoordinate(ref *position, ref matrix, out transformedPosition); + Vector3.TransformCoordinate(ref position, ref Matrix, out transformedPosition); - //We are doing a relative distance comparasin to find the maximum distance + //We are doing a relative distance comparison to find the maximum distance //from the center of our sphere. float distance; - Vector3.DistanceSquared(ref boundingSphere.Center, ref transformedPosition, out distance); + Vector3.DistanceSquared(ref Sphere.Center, ref transformedPosition, out distance); - if (distance > boundingSphere.Radius) - boundingSphere.Radius = distance; - - buffer += vertexStride; + if (distance > Sphere.Radius) + Sphere.Radius = distance; } //Find the real distance from the DistanceSquared. - boundingSphere.Radius = MathF.Sqrt(boundingSphere.Radius); + Sphere.Radius = MathF.Sqrt(Sphere.Radius); } - - return boundingBox; } } } From 47aaecb5cb6b3aea9757b71e170e5e969feedf2e Mon Sep 17 00:00:00 2001 From: Eideren Date: Fri, 29 Aug 2025 19:21:29 +0200 Subject: [PATCH 08/17] TransformBuffer using vertexbuffer helper to support non float3/float4 data --- .../Extensions/TransformExtensions.cs | 132 +++++++++--------- 1 file changed, 63 insertions(+), 69 deletions(-) diff --git a/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs b/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs index 39e9e21c7d..4195ead8e8 100644 --- a/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs +++ b/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs @@ -1,9 +1,11 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System; using System.Linq; using Stride.Core.Mathematics; using Stride.Graphics; using Stride.Graphics.Data; +using Stride.Graphics.Semantic; namespace Stride.Extensions { @@ -16,89 +18,81 @@ public static class TransformExtensions /// The vertex container to transform /// The source/destination data array to transform /// The matrix to use for the transform - public static unsafe void TransformBuffer(this VertexBufferBinding vertexBufferBinding, byte[] bufferData, ref Matrix matrix) + public static void TransformBuffer(this VertexBufferBinding vertexBufferBinding, byte[] bufferData, ref Matrix matrix) { + var helper = new VertexBufferHelper(vertexBufferBinding, bufferData, out _); + // List of items that need to be transformed by the matrix - var vertexElementsToTransform1 = vertexBufferBinding.Declaration.EnumerateWithOffsets() - .Where(x => x.VertexElement.SemanticName == VertexElementUsage.Position && - (x.VertexElement.Format == PixelFormat.R32G32B32A32_Float || - x.VertexElement.Format == PixelFormat.R32G32B32_Float)).ToArray(); + helper.Write(new Transform { Matrix = matrix }); - // List of items that need to be transformed by the inverse transpose matrix - var vertexElementsToTransform2 = vertexBufferBinding.Declaration.EnumerateWithOffsets() - .Where(x => ((x.VertexElement.SemanticName == VertexElementUsage.Normal || - x.VertexElement.SemanticName == VertexElementUsage.Tangent || - x.VertexElement.SemanticName == VertexElementUsage.BiTangent)) && - (x.VertexElement.Format == PixelFormat.R32G32B32_Float || - x.VertexElement.Format == PixelFormat.R32G32B32A32_Float)).ToArray(); - - // List the items that have handedness encoded in the W component - var vertexElementsWithHandedness = vertexElementsToTransform2 - .Where(x => x.VertexElement.SemanticName == VertexElementUsage.Tangent && - x.VertexElement.Format == PixelFormat.R32G32B32A32_Float).ToArray(); - - // If needed, compute matrix inverse transpose + // compute matrix inverse transpose Matrix inverseTransposeMatrix; - if (vertexElementsToTransform2.Length > 0) - { - Matrix.Invert(ref matrix, out inverseTransposeMatrix); - Matrix.Transpose(ref inverseTransposeMatrix, out inverseTransposeMatrix); - } - else - { - inverseTransposeMatrix = Matrix.Identity; - } + Matrix.Invert(ref matrix, out inverseTransposeMatrix); + Matrix.Transpose(ref inverseTransposeMatrix, out inverseTransposeMatrix); - // Check if handedness is inverted - bool flipHandedness = false; - if (vertexElementsWithHandedness.Length > 0) - { - flipHandedness = Vector3.Dot(Vector3.Cross(matrix.Right, matrix.Forward), matrix.Up) < 0.0f; - } + // List of items that need to be transformed by the inverse transpose matrix + var inverseTransposeTransform = new InverseTranspose { InverseTransposeMatrix = inverseTransposeMatrix }; + helper.Write, Vector4, InverseTranspose>(inverseTransposeTransform); + helper.Write(inverseTransposeTransform); + helper.Write(inverseTransposeTransform); - // Transform buffer data - var vertexStride = vertexBufferBinding.Declaration.VertexStride; - var vertexCount = vertexBufferBinding.Count; - fixed (byte* bufferPointerStart = &bufferData[vertexBufferBinding.Offset]) + if (Vector3.Dot(Vector3.Cross(matrix.Right, matrix.Forward), matrix.Up) < 0.0f) + helper.Write(new FlipHandedness()); + } + + private struct Transform : VertexBufferHelper.IWriter + { + public required Matrix Matrix; + + public unsafe void Write(byte* sourcePointer, int elementCount, int stride) + where TConversion : IConversion, IConversion where TSource : unmanaged { - var bufferPointer = bufferPointerStart; - - for (int i = 0; i < vertexCount; ++i) + for (byte* end = sourcePointer + elementCount * stride; sourcePointer < end; sourcePointer += stride) { - // Transform positions - foreach (var vertexElement in vertexElementsToTransform1) + if (typeof(TSource) == typeof(Vector4)) { - var elementPointer = bufferPointer + vertexElement.Offset; - if (vertexElement.VertexElement.Format == PixelFormat.R32G32B32A32_Float) - { - Vector4.Transform(ref *(Vector4*)elementPointer, ref matrix, out *(Vector4*)elementPointer); - } - else - { - Vector3.TransformCoordinate(ref *(Vector3*)elementPointer, ref matrix, out *(Vector3*)elementPointer); - } + Vector4.Transform(ref *(Vector4*)sourcePointer, ref Matrix, out *(Vector4*)sourcePointer); } - - // Transform normals - foreach (var vertexElement in vertexElementsToTransform2) + else { - var elementPointer = (Vector3*)(bufferPointer + vertexElement.Offset); - Vector3.TransformNormal(ref *elementPointer, ref inverseTransposeMatrix, out *elementPointer); - elementPointer->Normalize(); + TConversion.Convert(*(TSource*)sourcePointer, out var val); + Vector3.TransformCoordinate(ref val, ref Matrix, out val); + TConversion.Convert(val, out *(TSource*)sourcePointer); } + } + } + } - // Correct handedness - if (flipHandedness) - { - foreach (var vertexElement in vertexElementsWithHandedness) - { - var elementPointer = bufferPointer + vertexElement.Offset; - var handednessPointer = (float*)elementPointer + 3; - *handednessPointer = -*handednessPointer; - } - } + private struct InverseTranspose : VertexBufferHelper.IWriter + { + public required Matrix InverseTransposeMatrix; + + public unsafe void Write(byte* sourcePointer, int elementCount, int stride) + where TConversion : IConversion, IConversion where TSource : unmanaged + { + for (byte* end = sourcePointer + elementCount * stride; sourcePointer < end; sourcePointer += stride) + { + TConversion.Convert(*(TSource*)sourcePointer, out var val); - bufferPointer += vertexStride; + var v3Pointer = (Vector3*)&val; + Vector3.TransformNormal(ref *v3Pointer, ref InverseTransposeMatrix, out *v3Pointer); + v3Pointer->Normalize(); + + TConversion.Convert(val, out *(TSource*)sourcePointer); + } + } + } + + private struct FlipHandedness : VertexBufferHelper.IWriter + { + public unsafe void Write(byte* sourcePointer, int elementCount, int stride) + where TConversion : IConversion, IConversion where TSource : unmanaged + { + for (byte* end = sourcePointer + elementCount * stride; sourcePointer < end; sourcePointer += stride) + { + TConversion.Convert(*(TSource*)sourcePointer, out var val); + val.W = -val.W; + TConversion.Convert(val, out *(TSource*)sourcePointer); } } } From da176b39e4d4dc5264ff6db84e09ef4c1c0d1159 Mon Sep 17 00:00:00 2001 From: Eideren Date: Tue, 2 Sep 2025 16:12:14 +0200 Subject: [PATCH 09/17] Workaround for IConversion unification issue --- .../Stride.Graphics/Semantic/ISemantic.cs | 18 +------ .../Stride.Graphics/VertexBufferHelper.cs | 47 +++++++++++++++++-- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/sources/engine/Stride.Graphics/Semantic/ISemantic.cs b/sources/engine/Stride.Graphics/Semantic/ISemantic.cs index 6ae13aef4e..53ad240db8 100644 --- a/sources/engine/Stride.Graphics/Semantic/ISemantic.cs +++ b/sources/engine/Stride.Graphics/Semantic/ISemantic.cs @@ -11,23 +11,7 @@ public interface ISemantic public static abstract string Name { get; } } -public interface ISemantic : - ISemantic, - // Default source types we support for conversion - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion; +public interface ISemantic : ISemantic; public interface V2V2 : IConversion { static void IConversion.Convert(in Vector2 source, out Vector2 dest) => dest = source; } public interface V3V2 : IConversion { static void IConversion.Convert(in Vector3 source, out Vector2 dest) => dest = (Vector2)source; } diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index 6c79be6b2d..c814487f67 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -79,7 +79,15 @@ public VertexBufferHelper(VertexBufferBinding binding, byte[] dataOuter, out int /// When the data format for this semantic is too arcane - no conversion logic is implemented for that type /// /// - public bool Copy(Span buffer, int semanticIndex = 0) where TSemantic : ISemantic where TValue : unmanaged + public bool Copy(Span buffer, int semanticIndex = 0) where TSemantic : + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + ISemantic where TValue : unmanaged { return Read>(buffer, new CopyToDest(), semanticIndex); } @@ -139,7 +147,15 @@ public unsafe bool Copy(Span destination) where TDest : unmanaged, return missing; static void SelectSrcType(InterleavedParameters param, int srcElemOffset, int destElemOffset, PixelFormat format) - where TSemantic : ISemantic + where TSemantic : + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + ISemantic where TOutput : unmanaged { switch (format) @@ -208,7 +224,15 @@ static void InterleavedCopy(Interleaved /// /// public bool Read(Span destination, TReader reader, int semanticIndex = 0) - where TSemantic : ISemantic where TDest : unmanaged + where TSemantic : + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + ISemantic where TDest : unmanaged where TReader : IReader { if (Binding.Declaration.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) @@ -279,7 +303,22 @@ private unsafe void InnerRead(Span /// /// public bool Write(TWriter writer, int semanticIndex = 0) - where TSemantic : ISemantic where TDest : unmanaged + where TSemantic : + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + IConversion, + ISemantic where TDest : unmanaged where TWriter : IWriter { if (Binding.Declaration.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) From 7a50fd6768bb127392adb56148b2c84d5f253e15 Mon Sep 17 00:00:00 2001 From: Eideren Date: Tue, 2 Sep 2025 16:38:21 +0200 Subject: [PATCH 10/17] Unconstrain the generic type from the semantic were applicable --- sources/engine/Stride.Graphics/VertexBufferHelper.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index c814487f67..a9c65e1eb0 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -87,7 +87,8 @@ public bool Copy(Span buffer, int semanticIndex = 0) IConversion, IConversion, IConversion, - ISemantic where TValue : unmanaged + ISemantic + where TValue : unmanaged { return Read>(buffer, new CopyToDest(), semanticIndex); } @@ -155,7 +156,7 @@ static void SelectSrcType(InterleavedParameters param, int s IConversion, IConversion, IConversion, - ISemantic + ISemantic where TOutput : unmanaged { switch (format) @@ -232,7 +233,8 @@ public bool Read(Span destination, TReader rea IConversion, IConversion, IConversion, - ISemantic where TDest : unmanaged + ISemantic + where TDest : unmanaged where TReader : IReader { if (Binding.Declaration.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) @@ -318,7 +320,8 @@ public bool Write(TWriter writer, int semanticIndex = IConversion, IConversion, IConversion, - ISemantic where TDest : unmanaged + ISemantic + where TDest : unmanaged where TWriter : IWriter { if (Binding.Declaration.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) From df9fc4214fce6c74f824024f6a05387ad0472775 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 3 Sep 2025 10:13:45 +0200 Subject: [PATCH 11/17] Add comments to the writing example --- sources/engine/Stride.Graphics/VertexBufferHelper.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index a9c65e1eb0..1b98e99c47 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -486,7 +486,10 @@ unsafe void Read(byte* sourcePointer, int elementCount, in /// /// (new MultColor(){ Color = Color.Gray }); + /// // Upload changes to the GPU + /// Model.Meshes[0].Draw.VertexBuffers[0].Buffer.Recreate(helper.DataOuter); /// /// private struct MultColor : VertexBufferHelper.IWriter /// { From 59ad3e2bd1a6431a43d028a3d5d22ec38415a2a4 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 3 Sep 2025 10:25:29 +0200 Subject: [PATCH 12/17] Semantic -> Semantics --- .../Stride.BepuPhysics/Systems/ShapeCacheSystem.cs | 1 + .../Stride.Graphics/{ => Semantics}/ConcreteSemantics.cs | 4 +--- .../{Semantic/IConversion.cs => Semantics/IConverter.cs} | 2 +- .../Stride.Graphics/{Semantic => Semantics}/ISemantic.cs | 2 +- sources/engine/Stride.Graphics/VertexBufferHelper.cs | 2 +- .../engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs | 1 + .../engine/Stride.Rendering/Extensions/BoundingExtensions.cs | 2 +- .../engine/Stride.Rendering/Extensions/TransformExtensions.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) rename sources/engine/Stride.Graphics/{ => Semantics}/ConcreteSemantics.cs (96%) rename sources/engine/Stride.Graphics/{Semantic/IConversion.cs => Semantics/IConverter.cs} (89%) rename sources/engine/Stride.Graphics/{Semantic => Semantics}/ISemantic.cs (99%) diff --git a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs index 4de0dcc1a4..64832cef95 100644 --- a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs +++ b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs @@ -9,6 +9,7 @@ using Stride.Core.Mathematics; using Stride.Graphics; using Stride.Graphics.GeometricPrimitives; +using Stride.Graphics.Semantics; using Stride.Rendering; using BufferPool = BepuUtilities.Memory.BufferPool; using Mesh = BepuPhysics.Collidables.Mesh; diff --git a/sources/engine/Stride.Graphics/ConcreteSemantics.cs b/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs similarity index 96% rename from sources/engine/Stride.Graphics/ConcreteSemantics.cs rename to sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs index bbfb9c0790..f9e05e5606 100644 --- a/sources/engine/Stride.Graphics/ConcreteSemantics.cs +++ b/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs @@ -1,9 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Graphics.Semantic; - -namespace Stride.Graphics; +namespace Stride.Graphics.Semantics; public struct PositionSemantic : IFloat3Semantic { diff --git a/sources/engine/Stride.Graphics/Semantic/IConversion.cs b/sources/engine/Stride.Graphics/Semantics/IConverter.cs similarity index 89% rename from sources/engine/Stride.Graphics/Semantic/IConversion.cs rename to sources/engine/Stride.Graphics/Semantics/IConverter.cs index daa4fb39a8..0b95a60c38 100644 --- a/sources/engine/Stride.Graphics/Semantic/IConversion.cs +++ b/sources/engine/Stride.Graphics/Semantics/IConverter.cs @@ -1,7 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Graphics.Semantic; +namespace Stride.Graphics.Semantics; public interface IConversion { diff --git a/sources/engine/Stride.Graphics/Semantic/ISemantic.cs b/sources/engine/Stride.Graphics/Semantics/ISemantic.cs similarity index 99% rename from sources/engine/Stride.Graphics/Semantic/ISemantic.cs rename to sources/engine/Stride.Graphics/Semantics/ISemantic.cs index 53ad240db8..fb1092f419 100644 --- a/sources/engine/Stride.Graphics/Semantic/ISemantic.cs +++ b/sources/engine/Stride.Graphics/Semantics/ISemantic.cs @@ -2,7 +2,7 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #nullable enable -namespace Stride.Graphics.Semantic; +namespace Stride.Graphics.Semantics; using System; using Core.Mathematics; diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index 1b98e99c47..f92f76c865 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -6,7 +6,7 @@ using System.Runtime.InteropServices; using Stride.Core; using Stride.Core.Mathematics; -using Stride.Graphics.Semantic; +using Stride.Graphics.Semantics; namespace Stride.Graphics; diff --git a/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs b/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs index 68f201a462..de8da31a45 100644 --- a/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs +++ b/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs @@ -12,6 +12,7 @@ using Stride.Extensions; using Stride.Graphics; using Stride.Graphics.GeometricPrimitives; +using Stride.Graphics.Semantics; using Stride.Rendering; namespace Stride.Physics diff --git a/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs b/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs index bd30c68410..c6d6a0ced9 100644 --- a/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs +++ b/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs @@ -6,7 +6,7 @@ using Stride.Core.Mathematics; using Stride.Graphics; using Stride.Graphics.Data; -using Stride.Graphics.Semantic; +using Stride.Graphics.Semantics; namespace Stride.Extensions { diff --git a/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs b/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs index 4195ead8e8..8a51d756fc 100644 --- a/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs +++ b/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs @@ -5,7 +5,7 @@ using Stride.Core.Mathematics; using Stride.Graphics; using Stride.Graphics.Data; -using Stride.Graphics.Semantic; +using Stride.Graphics.Semantics; namespace Stride.Extensions { From e1d1e77564e12d0e65295d123d0a91f5d5cdede9 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 3 Sep 2025 11:18:14 +0200 Subject: [PATCH 13/17] Rename IConversion to IConverter --- .../Stride.Graphics/Semantics/IConverter.cs | 2 +- .../Stride.Graphics/Semantics/ISemantic.cs | 142 +++++++++--------- .../Stride.Graphics/VertexBufferHelper.cs | 136 ++++++++--------- .../Extensions/BoundingExtensions.cs | 6 +- .../Extensions/TransformExtensions.cs | 24 +-- 5 files changed, 155 insertions(+), 155 deletions(-) diff --git a/sources/engine/Stride.Graphics/Semantics/IConverter.cs b/sources/engine/Stride.Graphics/Semantics/IConverter.cs index 0b95a60c38..3bc7a7cf76 100644 --- a/sources/engine/Stride.Graphics/Semantics/IConverter.cs +++ b/sources/engine/Stride.Graphics/Semantics/IConverter.cs @@ -3,7 +3,7 @@ namespace Stride.Graphics.Semantics; -public interface IConversion +public interface IConverter { static abstract void Convert(in TSource source, out TDest dest); } diff --git a/sources/engine/Stride.Graphics/Semantics/ISemantic.cs b/sources/engine/Stride.Graphics/Semantics/ISemantic.cs index fb1092f419..bc82644851 100644 --- a/sources/engine/Stride.Graphics/Semantics/ISemantic.cs +++ b/sources/engine/Stride.Graphics/Semantics/ISemantic.cs @@ -13,77 +13,77 @@ public interface ISemantic public interface ISemantic : ISemantic; -public interface V2V2 : IConversion { static void IConversion.Convert(in Vector2 source, out Vector2 dest) => dest = source; } -public interface V3V2 : IConversion { static void IConversion.Convert(in Vector3 source, out Vector2 dest) => dest = (Vector2)source; } -public interface V4V2 : IConversion { static void IConversion.Convert(in Vector4 source, out Vector2 dest) => dest = (Vector2)source; } -public interface H2V2 : IConversion { static void IConversion.Convert(in Half2 source, out Vector2 dest) => dest = (Vector2)source; } -public interface H3V2 : IConversion { static void IConversion.Convert(in Half3 source, out Vector2 dest) => dest = new(source.X, source.Y); } -public interface H4V2 : IConversion { static void IConversion.Convert(in Half4 source, out Vector2 dest) => dest = new(source.X, source.Y); } -public interface U4V2 : IConversion { static void IConversion.Convert(in UShort4 source, out Vector2 dest) => dest = new(source.X, source.Y); } -public interface B4V2 : IConversion { static void IConversion.Convert(in Byte4 source, out Vector2 dest) => dest = new(source.X, source.Y); } - -public interface V2V3 : IConversion { static void IConversion.Convert(in Vector2 source, out Vector3 dest) => dest = (Vector3)source; } -public interface V3V3 : IConversion { static void IConversion.Convert(in Vector3 source, out Vector3 dest) => dest = source; } -public interface V4V3 : IConversion { static void IConversion.Convert(in Vector4 source, out Vector3 dest) => dest = (Vector3)source; } -public interface H2V3 : IConversion { static void IConversion.Convert(in Half2 source, out Vector3 dest) => dest = new(source.X, source.Y, 0f); } -public interface H3V3 : IConversion { static void IConversion.Convert(in Half3 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface H4V3 : IConversion { static void IConversion.Convert(in Half4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface U4V3 : IConversion { static void IConversion.Convert(in UShort4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface B4V3 : IConversion { static void IConversion.Convert(in Byte4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } - -public interface V2V4 : IConversion { static void IConversion.Convert(in Vector2 source, out Vector4 dest) => dest = (Vector4)source; } -public interface V3V4 : IConversion { static void IConversion.Convert(in Vector3 source, out Vector4 dest) => dest = (Vector4)source; } -public interface V4V4 : IConversion { static void IConversion.Convert(in Vector4 source, out Vector4 dest) => dest = source; } -public interface H2V4 : IConversion { static void IConversion.Convert(in Half2 source, out Vector4 dest) => dest = new(source.X, source.Y, 0f, 0f); } -public interface H3V4 : IConversion { static void IConversion.Convert(in Half3 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } -public interface H4V4 : IConversion { static void IConversion.Convert(in Half4 source, out Vector4 dest) => dest = (Vector4)source; } -public interface U4V4 : IConversion { static void IConversion.Convert(in UShort4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } -public interface B4V4 : IConversion { static void IConversion.Convert(in Byte4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } - -public interface V2H2 : IConversion { static void IConversion.Convert(in Vector2 source, out Half2 dest) => dest = (Half2)source; } -public interface V3H2 : IConversion { static void IConversion.Convert(in Vector3 source, out Half2 dest) => dest = new(source.X, source.Y); } -public interface V4H2 : IConversion { static void IConversion.Convert(in Vector4 source, out Half2 dest) => dest = new(source.X, source.Y); } -public interface H2H2 : IConversion { static void IConversion.Convert(in Half2 source, out Half2 dest) => dest = source; } -public interface H3H2 : IConversion { static void IConversion.Convert(in Half3 source, out Half2 dest) => dest = new(source.X, source.Y); } -public interface H4H2 : IConversion { static void IConversion.Convert(in Half4 source, out Half2 dest) => dest = new(source.X, source.Y); } -public interface U4H2 : IConversion { static void IConversion.Convert(in UShort4 source, out Half2 dest) => dest = new(source.X, source.Y); } -public interface B4H2 : IConversion { static void IConversion.Convert(in Byte4 source, out Half2 dest) => dest = new(source.X, source.Y); } - -public interface V2H3 : IConversion { static void IConversion.Convert(in Vector2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); } -public interface V3H3 : IConversion { static void IConversion.Convert(in Vector3 source, out Half3 dest) => dest = (Half3)source; } -public interface V4H3 : IConversion { static void IConversion.Convert(in Vector4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface H2H3 : IConversion { static void IConversion.Convert(in Half2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); } -public interface H3H3 : IConversion { static void IConversion.Convert(in Half3 source, out Half3 dest) => dest = source; } -public interface H4H3 : IConversion { static void IConversion.Convert(in Half4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface U4H3 : IConversion { static void IConversion.Convert(in UShort4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface B4H3 : IConversion { static void IConversion.Convert(in Byte4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } - -public interface V2H4 : IConversion { static void IConversion.Convert(in Vector2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); } -public interface V3H4 : IConversion { static void IConversion.Convert(in Vector3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } -public interface V4H4 : IConversion { static void IConversion.Convert(in Vector4 source, out Half4 dest) => dest = (Half4)source; } -public interface H2H4 : IConversion { static void IConversion.Convert(in Half2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); } -public interface H3H4 : IConversion { static void IConversion.Convert(in Half3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } -public interface H4H4 : IConversion { static void IConversion.Convert(in Half4 source, out Half4 dest) => dest = source; } -public interface U4H4 : IConversion { static void IConversion.Convert(in UShort4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } -public interface B4H4 : IConversion { static void IConversion.Convert(in Byte4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } - -public interface V2U4 : IConversion { static void IConversion.Convert(in Vector2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); } -public interface V3U4 : IConversion { static void IConversion.Convert(in Vector3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); } -public interface V4U4 : IConversion { static void IConversion.Convert(in Vector4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); } -public interface H2U4 : IConversion { static void IConversion.Convert(in Half2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); } -public interface H3U4 : IConversion { static void IConversion.Convert(in Half3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); } -public interface H4U4 : IConversion { static void IConversion.Convert(in Half4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); } -public interface U4U4 : IConversion { static void IConversion.Convert(in UShort4 source, out UShort4 dest) => dest = source; } -public interface B4U4 : IConversion { static void IConversion.Convert(in Byte4 source, out UShort4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } - -public interface V2B4 : IConversion { static void IConversion.Convert(in Vector2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); } -public interface V3B4 : IConversion { static void IConversion.Convert(in Vector3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); } -public interface V4B4 : IConversion { static void IConversion.Convert(in Vector4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } -public interface H2B4 : IConversion { static void IConversion.Convert(in Half2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); } -public interface H3B4 : IConversion { static void IConversion.Convert(in Half3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); } -public interface H4B4 : IConversion { static void IConversion.Convert(in Half4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } -public interface U4B4 : IConversion { static void IConversion.Convert(in UShort4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } -public interface B4B4 : IConversion { static void IConversion.Convert(in Byte4 source, out Byte4 dest) => dest = source; } +public interface V2V2 : IConverter { static void IConverter.Convert(in Vector2 source, out Vector2 dest) => dest = source; } +public interface V3V2 : IConverter { static void IConverter.Convert(in Vector3 source, out Vector2 dest) => dest = (Vector2)source; } +public interface V4V2 : IConverter { static void IConverter.Convert(in Vector4 source, out Vector2 dest) => dest = (Vector2)source; } +public interface H2V2 : IConverter { static void IConverter.Convert(in Half2 source, out Vector2 dest) => dest = (Vector2)source; } +public interface H3V2 : IConverter { static void IConverter.Convert(in Half3 source, out Vector2 dest) => dest = new(source.X, source.Y); } +public interface H4V2 : IConverter { static void IConverter.Convert(in Half4 source, out Vector2 dest) => dest = new(source.X, source.Y); } +public interface U4V2 : IConverter { static void IConverter.Convert(in UShort4 source, out Vector2 dest) => dest = new(source.X, source.Y); } +public interface B4V2 : IConverter { static void IConverter.Convert(in Byte4 source, out Vector2 dest) => dest = new(source.X, source.Y); } + +public interface V2V3 : IConverter { static void IConverter.Convert(in Vector2 source, out Vector3 dest) => dest = (Vector3)source; } +public interface V3V3 : IConverter { static void IConverter.Convert(in Vector3 source, out Vector3 dest) => dest = source; } +public interface V4V3 : IConverter { static void IConverter.Convert(in Vector4 source, out Vector3 dest) => dest = (Vector3)source; } +public interface H2V3 : IConverter { static void IConverter.Convert(in Half2 source, out Vector3 dest) => dest = new(source.X, source.Y, 0f); } +public interface H3V3 : IConverter { static void IConverter.Convert(in Half3 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface H4V3 : IConverter { static void IConverter.Convert(in Half4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface U4V3 : IConverter { static void IConverter.Convert(in UShort4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface B4V3 : IConverter { static void IConverter.Convert(in Byte4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } + +public interface V2V4 : IConverter { static void IConverter.Convert(in Vector2 source, out Vector4 dest) => dest = (Vector4)source; } +public interface V3V4 : IConverter { static void IConverter.Convert(in Vector3 source, out Vector4 dest) => dest = (Vector4)source; } +public interface V4V4 : IConverter { static void IConverter.Convert(in Vector4 source, out Vector4 dest) => dest = source; } +public interface H2V4 : IConverter { static void IConverter.Convert(in Half2 source, out Vector4 dest) => dest = new(source.X, source.Y, 0f, 0f); } +public interface H3V4 : IConverter { static void IConverter.Convert(in Half3 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } +public interface H4V4 : IConverter { static void IConverter.Convert(in Half4 source, out Vector4 dest) => dest = (Vector4)source; } +public interface U4V4 : IConverter { static void IConverter.Convert(in UShort4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface B4V4 : IConverter { static void IConverter.Convert(in Byte4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } + +public interface V2H2 : IConverter { static void IConverter.Convert(in Vector2 source, out Half2 dest) => dest = (Half2)source; } +public interface V3H2 : IConverter { static void IConverter.Convert(in Vector3 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface V4H2 : IConverter { static void IConverter.Convert(in Vector4 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface H2H2 : IConverter { static void IConverter.Convert(in Half2 source, out Half2 dest) => dest = source; } +public interface H3H2 : IConverter { static void IConverter.Convert(in Half3 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface H4H2 : IConverter { static void IConverter.Convert(in Half4 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface U4H2 : IConverter { static void IConverter.Convert(in UShort4 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface B4H2 : IConverter { static void IConverter.Convert(in Byte4 source, out Half2 dest) => dest = new(source.X, source.Y); } + +public interface V2H3 : IConverter { static void IConverter.Convert(in Vector2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); } +public interface V3H3 : IConverter { static void IConverter.Convert(in Vector3 source, out Half3 dest) => dest = (Half3)source; } +public interface V4H3 : IConverter { static void IConverter.Convert(in Vector4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface H2H3 : IConverter { static void IConverter.Convert(in Half2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); } +public interface H3H3 : IConverter { static void IConverter.Convert(in Half3 source, out Half3 dest) => dest = source; } +public interface H4H3 : IConverter { static void IConverter.Convert(in Half4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface U4H3 : IConverter { static void IConverter.Convert(in UShort4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface B4H3 : IConverter { static void IConverter.Convert(in Byte4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } + +public interface V2H4 : IConverter { static void IConverter.Convert(in Vector2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); } +public interface V3H4 : IConverter { static void IConverter.Convert(in Vector3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } +public interface V4H4 : IConverter { static void IConverter.Convert(in Vector4 source, out Half4 dest) => dest = (Half4)source; } +public interface H2H4 : IConverter { static void IConverter.Convert(in Half2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); } +public interface H3H4 : IConverter { static void IConverter.Convert(in Half3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } +public interface H4H4 : IConverter { static void IConverter.Convert(in Half4 source, out Half4 dest) => dest = source; } +public interface U4H4 : IConverter { static void IConverter.Convert(in UShort4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface B4H4 : IConverter { static void IConverter.Convert(in Byte4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } + +public interface V2U4 : IConverter { static void IConverter.Convert(in Vector2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); } +public interface V3U4 : IConverter { static void IConverter.Convert(in Vector3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); } +public interface V4U4 : IConverter { static void IConverter.Convert(in Vector4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); } +public interface H2U4 : IConverter { static void IConverter.Convert(in Half2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); } +public interface H3U4 : IConverter { static void IConverter.Convert(in Half3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); } +public interface H4U4 : IConverter { static void IConverter.Convert(in Half4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); } +public interface U4U4 : IConverter { static void IConverter.Convert(in UShort4 source, out UShort4 dest) => dest = source; } +public interface B4U4 : IConverter { static void IConverter.Convert(in Byte4 source, out UShort4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } + +public interface V2B4 : IConverter { static void IConverter.Convert(in Vector2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); } +public interface V3B4 : IConverter { static void IConverter.Convert(in Vector3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); } +public interface V4B4 : IConverter { static void IConverter.Convert(in Vector4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } +public interface H2B4 : IConverter { static void IConverter.Convert(in Half2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); } +public interface H3B4 : IConverter { static void IConverter.Convert(in Half3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); } +public interface H4B4 : IConverter { static void IConverter.Convert(in Half4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } +public interface U4B4 : IConverter { static void IConverter.Convert(in UShort4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } +public interface B4B4 : IConverter { static void IConverter.Convert(in Byte4 source, out Byte4 dest) => dest = source; } public interface IFloat2Semantic : ISemantic, V2V2, diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index f92f76c865..7381c28d61 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -80,13 +80,13 @@ public VertexBufferHelper(VertexBufferBinding binding, byte[] dataOuter, out int /// /// public bool Copy(Span buffer, int semanticIndex = 0) where TSemantic : - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, ISemantic where TValue : unmanaged { @@ -149,13 +149,13 @@ public unsafe bool Copy(Span destination) where TDest : unmanaged, static void SelectSrcType(InterleavedParameters param, int srcElemOffset, int destElemOffset, PixelFormat format) where TSemantic : - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, ISemantic where TOutput : unmanaged { @@ -172,8 +172,8 @@ static void SelectSrcType(InterleavedParameters param, int s } } - static void InterleavedCopy(InterleavedParameters param, int srcElemOffset, int destElemOffset) - where TConversion : IConversion + static void InterleavedCopy(InterleavedParameters param, int srcElemOffset, int destElemOffset) + where TConverter : IConverter where TSourceSemVal : unmanaged where TDestSemVal : unmanaged { @@ -189,7 +189,7 @@ static void InterleavedCopy(Interleaved src += param.SourceStride, dest += param.DestStride) { - TConversion.Convert(*(TSourceSemVal*)src, out *(TDestSemVal*)dest); + TConverter.Convert(*(TSourceSemVal*)src, out *(TDestSemVal*)dest); } } } @@ -199,7 +199,7 @@ static void InterleavedCopy(Interleaved /// Lower level access to read into the vertex buffer /// /// - /// The destination span your method receives, + /// The destination span your method receives, /// you may pass if you do not need one. /// /// @@ -226,13 +226,13 @@ static void InterleavedCopy(Interleaved /// public bool Read(Span destination, TReader reader, int semanticIndex = 0) where TSemantic : - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, ISemantic where TDest : unmanaged where TReader : IReader @@ -257,8 +257,8 @@ public bool Read(Span destination, TReader rea return false; } - private unsafe void InnerRead(Span destination, TReader reader, VertexElementWithOffset element) - where TConversion : IConversion + private unsafe void InnerRead(Span destination, TReader reader, VertexElementWithOffset element) + where TConverter : IConverter where TSource : unmanaged where TReader : IReader { @@ -272,7 +272,7 @@ private unsafe void InnerRead(Span fixed (byte* ptrSr = DataInner) { byte* firstElement = ptrSr + offset; - reader.Read(firstElement, count, stride, destination); + reader.Read(firstElement, count, stride, destination); } } @@ -306,20 +306,20 @@ private unsafe void InnerRead(Span /// public bool Write(TWriter writer, int semanticIndex = 0) where TSemantic : - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, - IConversion, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, ISemantic where TDest : unmanaged where TWriter : IWriter @@ -344,8 +344,8 @@ public bool Write(TWriter writer, int semanticIndex = return false; } - private unsafe void InnerWrite(TWriter reader, VertexElementWithOffset element) - where TConversion : IConversion, IConversion + private unsafe void InnerWrite(TWriter reader, VertexElementWithOffset element) + where TConverter : IConverter, IConverter where TSource : unmanaged where TWriter : IWriter { @@ -359,7 +359,7 @@ private unsafe void InnerWrite(TWriter rea fixed (byte* ptrSr = DataInner) { byte* firstElement = ptrSr + offset; - reader.Write(firstElement, count, stride); + reader.Write(firstElement, count, stride); } } @@ -367,8 +367,8 @@ public struct CopyAsTriangleList : IReader { public required IndexBufferHelper IndexBufferHelper; - public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) - where TConversion : IConversion where TSource : unmanaged + public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) + where TConverter : IConverter where TSource : unmanaged { if (destination.Length != IndexBufferHelper.Binding.Count) throw new ArgumentException($"{nameof(destination)} length does not match the amount of indices contained within the index buffer buffer ({destination.Length} / {IndexBufferHelper.Binding.Count})"); @@ -380,7 +380,7 @@ public unsafe void Read(byte* sourcePointer, int elementCo { foreach (var index in indices32) { - TConversion.Convert(*(TSource*)(sourcePointer + index * stride), out *dest); + TConverter.Convert(*(TSource*)(sourcePointer + index * stride), out *dest); dest++; } } @@ -388,7 +388,7 @@ public unsafe void Read(byte* sourcePointer, int elementCo { foreach (var index in indices16) { - TConversion.Convert(*(TSource*)(sourcePointer + index * stride), out *dest); + TConverter.Convert(*(TSource*)(sourcePointer + index * stride), out *dest); dest++; } } @@ -398,8 +398,8 @@ public unsafe void Read(byte* sourcePointer, int elementCo private struct CopyToDest : IReader where T : unmanaged { - public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) - where TConversion : IConversion + public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) + where TConverter : IConverter where TSource : unmanaged { if (destination.Length != elementCount) @@ -410,7 +410,7 @@ public unsafe void Read(byte* sourcePointer, int elementCo byte* end = sourcePointer + elementCount * stride; T* dest = ptrDest; for (; sourcePointer < end; sourcePointer += stride, dest++) - TConversion.Convert(*(TSource*)sourcePointer, out *dest); + TConverter.Convert(*(TSource*)sourcePointer, out *dest); } } } @@ -447,8 +447,8 @@ public InterleavedParameters(Span source, Span destination, int sour /// /// struct CopyTo : IReader /// { - /// public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) - /// where TConversion : IConversion where TSource : unmanaged + /// public unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) + /// where TConverter : IConverter where TSource : unmanaged /// { /// if (destination.Length != elementCount) /// throw new ArgumentException($"{nameof(destination)} length does not match the amount of vertices contained within this vertex buffer ({destination.Length} / {elementCount})"); @@ -458,7 +458,7 @@ public InterleavedParameters(Span source, Span destination, int sour /// byte* end = sourcePointer + elementCount * stride; /// T* dest = ptrDest; /// for (; sourcePointer < end; sourcePointer += stride, dest++) - /// TConversion.Convert(*(TSource*)sourcePointer, out *dest); + /// TConverter.Convert(*(TSource*)sourcePointer, out *dest); /// } /// } /// } @@ -470,15 +470,15 @@ public interface IReader /// Points to the first element in the vertex buffer, read it as a TSource* to retrieve its value /// The amount of vertices. This is not equivalent to the size of the vertex buffer, or the size in bytes taken by individual vertices /// The size in bytes taken by individual vertices, add it to to point to the next element - /// The span passed into the method call - /// A helper to convert between and properly + /// The span passed into the method call + /// A helper to convert between and properly /// /// The source type this vertex buffer was built with, for example or , - /// use to convert it into a . + /// use to convert it into a . /// /// - unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) - where TConversion : IConversion where TSource : unmanaged; + unsafe void Read(byte* sourcePointer, int elementCount, int stride, Span destination) + where TConverter : IConverter where TSource : unmanaged; } /// @@ -495,15 +495,15 @@ unsafe void Read(byte* sourcePointer, int elementCount, in /// { /// public Color Color; /// - /// public unsafe void Write(byte* sourcePointer, int elementCount, int stride) - /// where TConversion : IConversion, IConversion + /// public unsafe void Write(byte* sourcePointer, int elementCount, int stride) + /// where TConverter : IConverter, IConverter /// where TSource : unmanaged /// { /// for (byte* end = sourcePointer + elementCount * stride; sourcePointer < end; sourcePointer += stride) /// { - /// TConversion.Convert(*(TSource*)sourcePointer, out var val); + /// TConverter.Convert(*(TSource*)sourcePointer, out var val); /// val *= (Vector4)Color; - /// TConversion.Convert(val, out *(TSource*)sourcePointer); + /// TConverter.Convert(val, out *(TSource*)sourcePointer); /// } /// } /// } @@ -515,13 +515,13 @@ public interface IWriter /// Points to the first element in the vertex buffer, read it as a TSource* to retrieve its value /// The amount of vertices. This is not equivalent to the size of the vertex buffer, or the size in bytes taken by individual vertices /// The size in bytes taken by individual vertices, add it to to point to the next element - /// A helper to convert between and properly + /// A helper to convert between and properly /// /// The source type this vertex buffer was built with, for example or , - /// use to convert it into a . + /// use to convert it into a . /// /// - unsafe void Write(byte* sourcePointer, int elementCount, int stride) - where TConversion : IConversion, IConversion where TSource : unmanaged; + unsafe void Write(byte* sourcePointer, int elementCount, int stride) + where TConverter : IConverter, IConverter where TSource : unmanaged; } } diff --git a/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs b/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs index c6d6a0ced9..951739a582 100644 --- a/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs +++ b/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs @@ -34,12 +34,12 @@ struct ComputeBoundsStruct : VertexBufferHelper.IReader public required BoundingSphere Sphere; public required Matrix Matrix; - public unsafe void Read(byte* startPointer, int elementCount, int stride, Span destination) where TConversion : IConversion where TSource : unmanaged + public unsafe void Read(byte* startPointer, int elementCount, int stride, Span destination) where TConverter : IConverter where TSource : unmanaged { // Calculates bounding box and bounding sphere center for (byte* sourcePtr = startPointer, end = startPointer + elementCount * stride; sourcePtr < end; sourcePtr += stride) { - TConversion.Convert(*(TSource*)sourcePtr, out var position); + TConverter.Convert(*(TSource*)sourcePtr, out var position); Vector3 transformedPosition; Vector3.TransformCoordinate(ref position, ref Matrix, out transformedPosition); @@ -56,7 +56,7 @@ public unsafe void Read(byte* startPointer, int elementCou // Calculates bounding sphere center for (byte* sourcePtr = startPointer, end = startPointer + elementCount * stride; sourcePtr < end; sourcePtr += stride) { - TConversion.Convert(*(TSource*)sourcePtr, out var position); + TConverter.Convert(*(TSource*)sourcePtr, out var position); Vector3 transformedPosition; Vector3.TransformCoordinate(ref position, ref Matrix, out transformedPosition); diff --git a/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs b/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs index 8a51d756fc..3e94739e6d 100644 --- a/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs +++ b/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs @@ -44,8 +44,8 @@ private struct Transform : VertexBufferHelper.IWriter { public required Matrix Matrix; - public unsafe void Write(byte* sourcePointer, int elementCount, int stride) - where TConversion : IConversion, IConversion where TSource : unmanaged + public unsafe void Write(byte* sourcePointer, int elementCount, int stride) + where TConverter : IConverter, IConverter where TSource : unmanaged { for (byte* end = sourcePointer + elementCount * stride; sourcePointer < end; sourcePointer += stride) { @@ -55,9 +55,9 @@ public unsafe void Write(byte* sourcePointer, int elementC } else { - TConversion.Convert(*(TSource*)sourcePointer, out var val); + TConverter.Convert(*(TSource*)sourcePointer, out var val); Vector3.TransformCoordinate(ref val, ref Matrix, out val); - TConversion.Convert(val, out *(TSource*)sourcePointer); + TConverter.Convert(val, out *(TSource*)sourcePointer); } } } @@ -67,32 +67,32 @@ private struct InverseTranspose : VertexBufferHelper.IWriter { public required Matrix InverseTransposeMatrix; - public unsafe void Write(byte* sourcePointer, int elementCount, int stride) - where TConversion : IConversion, IConversion where TSource : unmanaged + public unsafe void Write(byte* sourcePointer, int elementCount, int stride) + where TConverter : IConverter, IConverter where TSource : unmanaged { for (byte* end = sourcePointer + elementCount * stride; sourcePointer < end; sourcePointer += stride) { - TConversion.Convert(*(TSource*)sourcePointer, out var val); + TConverter.Convert(*(TSource*)sourcePointer, out var val); var v3Pointer = (Vector3*)&val; Vector3.TransformNormal(ref *v3Pointer, ref InverseTransposeMatrix, out *v3Pointer); v3Pointer->Normalize(); - TConversion.Convert(val, out *(TSource*)sourcePointer); + TConverter.Convert(val, out *(TSource*)sourcePointer); } } } private struct FlipHandedness : VertexBufferHelper.IWriter { - public unsafe void Write(byte* sourcePointer, int elementCount, int stride) - where TConversion : IConversion, IConversion where TSource : unmanaged + public unsafe void Write(byte* sourcePointer, int elementCount, int stride) + where TConverter : IConverter, IConverter where TSource : unmanaged { for (byte* end = sourcePointer + elementCount * stride; sourcePointer < end; sourcePointer += stride) { - TConversion.Convert(*(TSource*)sourcePointer, out var val); + TConverter.Convert(*(TSource*)sourcePointer, out var val); val.W = -val.W; - TConversion.Convert(val, out *(TSource*)sourcePointer); + TConverter.Convert(val, out *(TSource*)sourcePointer); } } } From 04ca9487689e4594960191757b01af3b75fea814 Mon Sep 17 00:00:00 2001 From: Eideren Date: Wed, 3 Sep 2025 15:35:09 +0200 Subject: [PATCH 14/17] Fix InterleavedParameters --- sources/engine/Stride.Graphics/VertexBufferHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index 7381c28d61..b7da56885f 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -183,7 +183,7 @@ static void InterleavedCopy(InterleavedP for (byte* src = srcStart + srcElemOffset, dest = destStart + destElemOffset, - endSrc = src + param.VertexCount * param.DestStride; + endSrc = src + param.VertexCount * param.SourceStride; src < endSrc; @@ -423,9 +423,9 @@ private readonly ref struct InterleavedParameters public InterleavedParameters(Span source, Span destination, int sourceStride, int destStride, int vertexCount) { - if (destination.Length / DestStride != vertexCount) + if (destination.Length / destStride != vertexCount) throw new ArgumentException($"The length and stride of {nameof(destination)} does not match the vertices required ({destination.Length / DestStride} / {vertexCount})"); - if (source.Length / SourceStride != vertexCount) + if (source.Length / sourceStride != vertexCount) throw new ArgumentException($"The length and stride of {nameof(source)} does not match the vertices required ({source.Length / SourceStride} / {vertexCount})"); Source = source; From 05a26780fbf5a5d818709f5c76fdd553eaf73ebc Mon Sep 17 00:00:00 2001 From: Eideren Date: Mon, 8 Sep 2025 18:40:17 +0200 Subject: [PATCH 15/17] introduce UNormByte4 and move Byte4, UShort4 to new Interop namespace --- .../engine/Stride.Graphics/Interop/Byte4.cs | 13 +++ .../Stride.Graphics/Interop/UNormByte4.cs | 13 +++ .../engine/Stride.Graphics/Interop/UShort4.cs | 13 +++ .../Semantics/ConcreteSemantics.cs | 3 +- .../Stride.Graphics/Semantics/ISemantic.cs | 81 +++++++++++++------ .../Stride.Graphics/VertexBufferHelper.cs | 12 ++- 6 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 sources/engine/Stride.Graphics/Interop/Byte4.cs create mode 100644 sources/engine/Stride.Graphics/Interop/UNormByte4.cs create mode 100644 sources/engine/Stride.Graphics/Interop/UShort4.cs diff --git a/sources/engine/Stride.Graphics/Interop/Byte4.cs b/sources/engine/Stride.Graphics/Interop/Byte4.cs new file mode 100644 index 0000000000..c7cfe5ad35 --- /dev/null +++ b/sources/engine/Stride.Graphics/Interop/Byte4.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#nullable enable +using System.Runtime.InteropServices; + +namespace Stride.Graphics.Interop; + +[StructLayout(LayoutKind.Sequential, Size = 4)] +public struct Byte4(byte x, byte y, byte z, byte w) +{ + public byte X = x, Y = y, Z = z, W = w; +} diff --git a/sources/engine/Stride.Graphics/Interop/UNormByte4.cs b/sources/engine/Stride.Graphics/Interop/UNormByte4.cs new file mode 100644 index 0000000000..8534d085ef --- /dev/null +++ b/sources/engine/Stride.Graphics/Interop/UNormByte4.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#nullable enable +using System.Runtime.InteropServices; + +namespace Stride.Graphics.Interop; + +[StructLayout(LayoutKind.Sequential, Size = 4)] +public struct UNormByte4(byte x, byte y, byte z, byte w) +{ + public byte X = x, Y = y, Z = z, W = w; +} diff --git a/sources/engine/Stride.Graphics/Interop/UShort4.cs b/sources/engine/Stride.Graphics/Interop/UShort4.cs new file mode 100644 index 0000000000..c4efc06356 --- /dev/null +++ b/sources/engine/Stride.Graphics/Interop/UShort4.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) +// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + +#nullable enable +using System.Runtime.InteropServices; + +namespace Stride.Graphics.Interop; + +[StructLayout(LayoutKind.Sequential, Size = 8)] +public struct UShort4(ushort x, ushort y, ushort z, ushort w) +{ + public ushort X = x, Y = y, Z = z, W = w; +} diff --git a/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs b/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs index f9e05e5606..f20329f1c2 100644 --- a/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs +++ b/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs @@ -64,7 +64,8 @@ public struct Relaxed : IHalf3Semantic, IHalf2Semantic, IUShort4Semantic, - IByte4Semantic + IByte4Semantic, + IUNorm4Semantic where T : ISemantic { public static string Name => T.Name; diff --git a/sources/engine/Stride.Graphics/Semantics/ISemantic.cs b/sources/engine/Stride.Graphics/Semantics/ISemantic.cs index bc82644851..7cf36cc726 100644 --- a/sources/engine/Stride.Graphics/Semantics/ISemantic.cs +++ b/sources/engine/Stride.Graphics/Semantics/ISemantic.cs @@ -2,6 +2,8 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #nullable enable +using Stride.Graphics.Interop; + namespace Stride.Graphics.Semantics; using System; using Core.Mathematics; @@ -13,6 +15,8 @@ public interface ISemantic public interface ISemantic : ISemantic; +// Implementation complexity for new types could be reduced, but right now the JIT has some issues generating optimal ASM, see #2858 + public interface V2V2 : IConverter { static void IConverter.Convert(in Vector2 source, out Vector2 dest) => dest = source; } public interface V3V2 : IConverter { static void IConverter.Convert(in Vector3 source, out Vector2 dest) => dest = (Vector2)source; } public interface V4V2 : IConverter { static void IConverter.Convert(in Vector4 source, out Vector2 dest) => dest = (Vector2)source; } @@ -21,6 +25,7 @@ public interface H2V2 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Vector2 dest) => dest = new(source.X, source.Y); } public interface U4V2 : IConverter { static void IConverter.Convert(in UShort4 source, out Vector2 dest) => dest = new(source.X, source.Y); } public interface B4V2 : IConverter { static void IConverter.Convert(in Byte4 source, out Vector2 dest) => dest = new(source.X, source.Y); } +public interface UNB4V2 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Vector2 dest) => dest = new(source.X / 255f, source.Y / 255f); } public interface V2V3 : IConverter { static void IConverter.Convert(in Vector2 source, out Vector3 dest) => dest = (Vector3)source; } public interface V3V3 : IConverter { static void IConverter.Convert(in Vector3 source, out Vector3 dest) => dest = source; } @@ -30,6 +35,7 @@ public interface V4V3 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } public interface U4V3 : IConverter { static void IConverter.Convert(in UShort4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } public interface B4V3 : IConverter { static void IConverter.Convert(in Byte4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface UNB4V3 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Vector3 dest) => dest = new(source.X / 255f, source.Y / 255f, source.Z / 255f); } public interface V2V4 : IConverter { static void IConverter.Convert(in Vector2 source, out Vector4 dest) => dest = (Vector4)source; } public interface V3V4 : IConverter { static void IConverter.Convert(in Vector3 source, out Vector4 dest) => dest = (Vector4)source; } @@ -39,6 +45,7 @@ public interface V4V4 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Vector4 dest) => dest = (Vector4)source; } public interface U4V4 : IConverter { static void IConverter.Convert(in UShort4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } public interface B4V4 : IConverter { static void IConverter.Convert(in Byte4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface UNB4V4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Vector4 dest) => dest = new(source.X / 255f, source.Y / 255f, source.Z / 255f, source.W / 255f); } public interface V2H2 : IConverter { static void IConverter.Convert(in Vector2 source, out Half2 dest) => dest = (Half2)source; } public interface V3H2 : IConverter { static void IConverter.Convert(in Vector3 source, out Half2 dest) => dest = new(source.X, source.Y); } @@ -48,6 +55,7 @@ public interface H2H2 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Half2 dest) => dest = new(source.X, source.Y); } public interface U4H2 : IConverter { static void IConverter.Convert(in UShort4 source, out Half2 dest) => dest = new(source.X, source.Y); } public interface B4H2 : IConverter { static void IConverter.Convert(in Byte4 source, out Half2 dest) => dest = new(source.X, source.Y); } +public interface UNB4H2 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Half2 dest) => dest = new(source.X / 255f, source.Y / 255f); } public interface V2H3 : IConverter { static void IConverter.Convert(in Vector2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); } public interface V3H3 : IConverter { static void IConverter.Convert(in Vector3 source, out Half3 dest) => dest = (Half3)source; } @@ -57,6 +65,7 @@ public interface H3H3 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } public interface U4H3 : IConverter { static void IConverter.Convert(in UShort4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } public interface B4H3 : IConverter { static void IConverter.Convert(in Byte4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } +public interface UNB4H3 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Half3 dest) => dest = new(source.X / 255f, source.Y / 255f, source.Z / 255f); } public interface V2H4 : IConverter { static void IConverter.Convert(in Vector2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); } public interface V3H4 : IConverter { static void IConverter.Convert(in Vector3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } @@ -66,6 +75,7 @@ public interface V4H4 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Half4 dest) => dest = source; } public interface U4H4 : IConverter { static void IConverter.Convert(in UShort4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } public interface B4H4 : IConverter { static void IConverter.Convert(in Byte4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface UNB4H4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Half4 dest) => dest = new(source.X / 255f, source.Y / 255f, source.Z / 255f, source.W / 255f); } public interface V2U4 : IConverter { static void IConverter.Convert(in Vector2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); } public interface V3U4 : IConverter { static void IConverter.Convert(in Vector3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); } @@ -75,6 +85,7 @@ public interface H4H4 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); } public interface U4U4 : IConverter { static void IConverter.Convert(in UShort4 source, out UShort4 dest) => dest = source; } public interface B4U4 : IConverter { static void IConverter.Convert(in Byte4 source, out UShort4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface UNB4U4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out UShort4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } public interface V2B4 : IConverter { static void IConverter.Convert(in Vector2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); } public interface V3B4 : IConverter { static void IConverter.Convert(in Vector3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); } @@ -84,6 +95,17 @@ public interface U4U4 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } public interface U4B4 : IConverter { static void IConverter.Convert(in UShort4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } public interface B4B4 : IConverter { static void IConverter.Convert(in Byte4 source, out Byte4 dest) => dest = source; } +public interface UNB4B4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Byte4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } + +public interface V2UNB4 : IConverter { static void IConverter.Convert(in Vector2 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), 0, 0); } +public interface V3UNB4 : IConverter { static void IConverter.Convert(in Vector3 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), 0); } +public interface V4UNB4 : IConverter { static void IConverter.Convert(in Vector4 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), (byte)(source.W * 255)); } +public interface H2UNB4 : IConverter { static void IConverter.Convert(in Half2 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), 0, 0); } +public interface H3UNB4 : IConverter { static void IConverter.Convert(in Half3 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), 0); } +public interface H4UNB4 : IConverter { static void IConverter.Convert(in Half4 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), (byte)(source.W * 255)); } +public interface U4UNB4 : IConverter { static void IConverter.Convert(in UShort4 source, out UNormByte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } +public interface B4UNB4 : IConverter { static void IConverter.Convert(in Byte4 source, out UNormByte4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface UNB4UNB4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out UNormByte4 dest) => dest = source; } public interface IFloat2Semantic : ISemantic, V2V2, @@ -92,8 +114,9 @@ public interface IFloat2Semantic : ISemantic, V2B4, B4V2, V2U4, U4V2, V2H2, H2V2, - V2H3, - V2H4, H4V2; + V2H3, H3V2, + V2H4, H4V2, + V2UNB4, UNB4V2; public interface IFloat3Semantic : ISemantic, V3V2, V2V3, @@ -102,8 +125,9 @@ public interface IFloat3Semantic : ISemantic, V3B4, B4V3, V3U4, U4V3, V3H2, H2V3, - V3H3, - V3H4, H4V3; + V3H3, H3V3, + V3H4, H4V3, + V3UNB4, UNB4V3; public interface IFloat4Semantic : ISemantic, V4V2, V2V4, @@ -112,8 +136,9 @@ public interface IFloat4Semantic : ISemantic, V4B4, B4V4, V4U4, U4V4, V4H2, H2V4, - V4H3, - V4H4, H4V4; + V4H3, H3V4, + V4H4, H4V4, + V4UNB4, UNB4V4; public interface IHalf2Semantic : ISemantic, H2V2, V2H2, @@ -122,8 +147,9 @@ public interface IHalf2Semantic : ISemantic, H2B4, B4H2, H2U4, U4H2, H2H2, - H2H3, - H2H4, H4H2; + H2H3, H3H2, + H2H4, H4H2, + H2UNB4, UNB4H2; public interface IHalf3Semantic : ISemantic, H3V2, V2H3, @@ -133,7 +159,8 @@ public interface IHalf3Semantic : ISemantic, H3U4, U4H3, H3H2, H2H3, H3H3, - H3H4; + H3H4, H4H3, + H3UNB4, UNB4H3; public interface IHalf4Semantic : ISemantic, H4V2, V2H4, @@ -142,8 +169,9 @@ public interface IHalf4Semantic : ISemantic, H4B4, B4H4, H4U4, U4H4, H4H2, H2H4, - H4H3, - H4H4; + H4H3, H3H4, + H4H4, + H4UNB4, UNB4H4; public interface IUShort4Semantic : ISemantic, U4V2, V2U4, @@ -152,8 +180,9 @@ public interface IUShort4Semantic : ISemantic, U4B4, B4U4, U4U4, U4H2, H2U4, - U4H3, - U4H4, H4U4; + U4H3, H3U4, + U4H4, H4U4, + U4UNB4, UNB4U4; public interface IByte4Semantic : ISemantic, B4V2, V2B4, @@ -162,15 +191,17 @@ public interface IByte4Semantic : ISemantic, B4B4, B4U4, U4B4, B4H2, H2B4, - B4H3, - B4H4, H4B4; - -public struct UShort4(ushort x, ushort y, ushort z, ushort w) -{ - public ushort X = x, Y = y, Z = z, W = w; -} - -public struct Byte4(byte x, byte y, byte z, byte w) -{ - public byte X = x, Y = y, Z = z, W = w; -} + B4H3, H3B4, + B4H4, H4B4, + B4UNB4, UNB4B4; + +public interface IUNorm4Semantic : ISemantic, + UNB4V2, V2UNB4, + UNB4V3, V3UNB4, + UNB4V4, V4UNB4, + UNB4B4, B4UNB4, + UNB4U4, U4UNB4, + UNB4H2, H2UNB4, + UNB4H3, H3UNB4, + UNB4H4, H4UNB4, + UNB4UNB4; diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index b7da56885f..4302a5112c 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using Stride.Core; using Stride.Core.Mathematics; +using Stride.Graphics.Interop; using Stride.Graphics.Semantics; namespace Stride.Graphics; @@ -87,6 +88,7 @@ public bool Copy(Span buffer, int semanticIndex = 0) IConverter, IConverter, IConverter, + IConverter, ISemantic where TValue : unmanaged { @@ -136,6 +138,7 @@ public unsafe bool Copy(Span destination) where TDest : unmanaged, case PixelFormat.R16G16B16A16_Float: SelectSrcType, Half4>(parameters, srcOffset, destOffset, srcFormat); break; case PixelFormat.R16G16B16A16_UInt: SelectSrcType, UShort4>(parameters, srcOffset, destOffset, srcFormat); break; case PixelFormat.R8G8B8A8_UInt: SelectSrcType, Byte4>(parameters, srcOffset, destOffset, srcFormat); break; + case PixelFormat.R8G8B8A8_UNorm: SelectSrcType, UNormByte4>(parameters, srcOffset, destOffset, srcFormat); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({srcDef.VertexElement.Format})"); } } @@ -156,6 +159,7 @@ static void SelectSrcType(InterleavedParameters param, int s IConverter, IConverter, IConverter, + IConverter, ISemantic where TOutput : unmanaged { @@ -168,6 +172,7 @@ static void SelectSrcType(InterleavedParameters param, int s case PixelFormat.R16G16B16A16_Float: InterleavedCopy(param, srcElemOffset, destElemOffset); break; case PixelFormat.R16G16B16A16_UInt: InterleavedCopy(param, srcElemOffset, destElemOffset); break; case PixelFormat.R8G8B8A8_UInt: InterleavedCopy(param, srcElemOffset, destElemOffset); break; + case PixelFormat.R8G8B8A8_UNorm: InterleavedCopy(param, srcElemOffset, destElemOffset); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({format})"); } } @@ -233,6 +238,7 @@ public bool Read(Span destination, TReader rea IConverter, IConverter, IConverter, + IConverter, ISemantic where TDest : unmanaged where TReader : IReader @@ -248,6 +254,7 @@ public bool Read(Span destination, TReader rea case PixelFormat.R16G16B16A16_Float: InnerRead(destination, reader, elementData); break; case PixelFormat.R16G16B16A16_UInt: InnerRead(destination, reader, elementData); break; case PixelFormat.R8G8B8A8_UInt: InnerRead(destination, reader, elementData); break; + case PixelFormat.R8G8B8A8_UNorm: InnerRead(destination, reader, elementData); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({elementData.VertexElement.Format})"); } @@ -312,7 +319,8 @@ public bool Write(TWriter writer, int semanticIndex = IConverter, IConverter, IConverter, - IConverter, + IConverter, + IConverter, IConverter, IConverter, IConverter, @@ -320,6 +328,7 @@ public bool Write(TWriter writer, int semanticIndex = IConverter, IConverter, IConverter, + IConverter, ISemantic where TDest : unmanaged where TWriter : IWriter @@ -335,6 +344,7 @@ public bool Write(TWriter writer, int semanticIndex = case PixelFormat.R16G16B16A16_Float: InnerWrite(writer, elementData); break; case PixelFormat.R16G16B16A16_UInt: InnerWrite(writer, elementData); break; case PixelFormat.R8G8B8A8_UInt: InnerWrite(writer, elementData); break; + case PixelFormat.R8G8B8A8_UNorm: InnerWrite(writer, elementData); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({elementData.VertexElement.Format})"); } From b7da5858ed46f7477ad50bab28f3a98f40bdc449 Mon Sep 17 00:00:00 2001 From: Eideren Date: Mon, 22 Sep 2025 20:20:27 +0200 Subject: [PATCH 16/17] Fix mapping for Color --- .../Stride.Graphics/Interop/UNormByte4.cs | 13 ---- .../Semantics/ConcreteSemantics.cs | 2 +- .../Stride.Graphics/Semantics/ISemantic.cs | 78 +++++++++---------- .../Stride.Graphics/VertexBufferHelper.cs | 18 ++--- 4 files changed, 49 insertions(+), 62 deletions(-) delete mode 100644 sources/engine/Stride.Graphics/Interop/UNormByte4.cs diff --git a/sources/engine/Stride.Graphics/Interop/UNormByte4.cs b/sources/engine/Stride.Graphics/Interop/UNormByte4.cs deleted file mode 100644 index 8534d085ef..0000000000 --- a/sources/engine/Stride.Graphics/Interop/UNormByte4.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) -// Distributed under the MIT license. See the LICENSE.md file in the project root for more information. - -#nullable enable -using System.Runtime.InteropServices; - -namespace Stride.Graphics.Interop; - -[StructLayout(LayoutKind.Sequential, Size = 4)] -public struct UNormByte4(byte x, byte y, byte z, byte w) -{ - public byte X = x, Y = y, Z = z, W = w; -} diff --git a/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs b/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs index f20329f1c2..a689d066e2 100644 --- a/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs +++ b/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs @@ -65,7 +65,7 @@ public struct Relaxed : IHalf2Semantic, IUShort4Semantic, IByte4Semantic, - IUNorm4Semantic + IColorSemantic where T : ISemantic { public static string Name => T.Name; diff --git a/sources/engine/Stride.Graphics/Semantics/ISemantic.cs b/sources/engine/Stride.Graphics/Semantics/ISemantic.cs index 7cf36cc726..1fda8d1876 100644 --- a/sources/engine/Stride.Graphics/Semantics/ISemantic.cs +++ b/sources/engine/Stride.Graphics/Semantics/ISemantic.cs @@ -2,11 +2,11 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. #nullable enable +using System; +using Stride.Core.Mathematics; using Stride.Graphics.Interop; namespace Stride.Graphics.Semantics; -using System; -using Core.Mathematics; public interface ISemantic { @@ -25,7 +25,7 @@ public interface H2V2 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Vector2 dest) => dest = new(source.X, source.Y); } public interface U4V2 : IConverter { static void IConverter.Convert(in UShort4 source, out Vector2 dest) => dest = new(source.X, source.Y); } public interface B4V2 : IConverter { static void IConverter.Convert(in Byte4 source, out Vector2 dest) => dest = new(source.X, source.Y); } -public interface UNB4V2 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Vector2 dest) => dest = new(source.X / 255f, source.Y / 255f); } +public interface COLORV2 : IConverter { static void IConverter.Convert(in Color source, out Vector2 dest) => dest = new(source.R / 255f, source.G / 255f); } public interface V2V3 : IConverter { static void IConverter.Convert(in Vector2 source, out Vector3 dest) => dest = (Vector3)source; } public interface V3V3 : IConverter { static void IConverter.Convert(in Vector3 source, out Vector3 dest) => dest = source; } @@ -35,7 +35,7 @@ public interface V4V3 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } public interface U4V3 : IConverter { static void IConverter.Convert(in UShort4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } public interface B4V3 : IConverter { static void IConverter.Convert(in Byte4 source, out Vector3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface UNB4V3 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Vector3 dest) => dest = new(source.X / 255f, source.Y / 255f, source.Z / 255f); } +public interface COLORV3 : IConverter { static void IConverter.Convert(in Color source, out Vector3 dest) => dest = new(source.R / 255f, source.G / 255f, source.B / 255f); } public interface V2V4 : IConverter { static void IConverter.Convert(in Vector2 source, out Vector4 dest) => dest = (Vector4)source; } public interface V3V4 : IConverter { static void IConverter.Convert(in Vector3 source, out Vector4 dest) => dest = (Vector4)source; } @@ -45,7 +45,7 @@ public interface V4V4 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Vector4 dest) => dest = (Vector4)source; } public interface U4V4 : IConverter { static void IConverter.Convert(in UShort4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } public interface B4V4 : IConverter { static void IConverter.Convert(in Byte4 source, out Vector4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } -public interface UNB4V4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Vector4 dest) => dest = new(source.X / 255f, source.Y / 255f, source.Z / 255f, source.W / 255f); } +public interface COLORV4 : IConverter { static void IConverter.Convert(in Color source, out Vector4 dest) => dest = new(source.R / 255f, source.G / 255f, source.B / 255f, source.A / 255f); } public interface V2H2 : IConverter { static void IConverter.Convert(in Vector2 source, out Half2 dest) => dest = (Half2)source; } public interface V3H2 : IConverter { static void IConverter.Convert(in Vector3 source, out Half2 dest) => dest = new(source.X, source.Y); } @@ -55,7 +55,7 @@ public interface H2H2 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Half2 dest) => dest = new(source.X, source.Y); } public interface U4H2 : IConverter { static void IConverter.Convert(in UShort4 source, out Half2 dest) => dest = new(source.X, source.Y); } public interface B4H2 : IConverter { static void IConverter.Convert(in Byte4 source, out Half2 dest) => dest = new(source.X, source.Y); } -public interface UNB4H2 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Half2 dest) => dest = new(source.X / 255f, source.Y / 255f); } +public interface COLORH2 : IConverter { static void IConverter.Convert(in Color source, out Half2 dest) => dest = new(source.R / 255f, source.G / 255f); } public interface V2H3 : IConverter { static void IConverter.Convert(in Vector2 source, out Half3 dest) => dest = new(source.X, source.Y, 0f); } public interface V3H3 : IConverter { static void IConverter.Convert(in Vector3 source, out Half3 dest) => dest = (Half3)source; } @@ -65,7 +65,7 @@ public interface H3H3 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } public interface U4H3 : IConverter { static void IConverter.Convert(in UShort4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } public interface B4H3 : IConverter { static void IConverter.Convert(in Byte4 source, out Half3 dest) => dest = new(source.X, source.Y, source.Z); } -public interface UNB4H3 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Half3 dest) => dest = new(source.X / 255f, source.Y / 255f, source.Z / 255f); } +public interface COLORH3 : IConverter { static void IConverter.Convert(in Color source, out Half3 dest) => dest = new(source.R / 255f, source.G / 255f, source.B / 255f); } public interface V2H4 : IConverter { static void IConverter.Convert(in Vector2 source, out Half4 dest) => dest = new(source.X, source.Y, 0f, 0f); } public interface V3H4 : IConverter { static void IConverter.Convert(in Vector3 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, 0f); } @@ -75,7 +75,7 @@ public interface V4H4 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Half4 dest) => dest = source; } public interface U4H4 : IConverter { static void IConverter.Convert(in UShort4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } public interface B4H4 : IConverter { static void IConverter.Convert(in Byte4 source, out Half4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } -public interface UNB4H4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Half4 dest) => dest = new(source.X / 255f, source.Y / 255f, source.Z / 255f, source.W / 255f); } +public interface COLORH4 : IConverter { static void IConverter.Convert(in Color source, out Half4 dest) => dest = new(source.R / 255f, source.G / 255f, source.B / 255f, source.A / 255f); } public interface V2U4 : IConverter { static void IConverter.Convert(in Vector2 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, 0, 0); } public interface V3U4 : IConverter { static void IConverter.Convert(in Vector3 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, 0); } @@ -85,7 +85,7 @@ public interface H4H4 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out UShort4 dest) => dest = new((ushort)source.X, (ushort)source.Y, (ushort)source.Z, (ushort)source.W); } public interface U4U4 : IConverter { static void IConverter.Convert(in UShort4 source, out UShort4 dest) => dest = source; } public interface B4U4 : IConverter { static void IConverter.Convert(in Byte4 source, out UShort4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } -public interface UNB4U4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out UShort4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface COLORU4 : IConverter { static void IConverter.Convert(in Color source, out UShort4 dest) => dest = new(source.R, source.G, source.B, source.A); } public interface V2B4 : IConverter { static void IConverter.Convert(in Vector2 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, 0, 0); } public interface V3B4 : IConverter { static void IConverter.Convert(in Vector3 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, 0); } @@ -95,17 +95,17 @@ public interface U4U4 : IConverter { static void IConverter { static void IConverter.Convert(in Half4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } public interface U4B4 : IConverter { static void IConverter.Convert(in UShort4 source, out Byte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } public interface B4B4 : IConverter { static void IConverter.Convert(in Byte4 source, out Byte4 dest) => dest = source; } -public interface UNB4B4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out Byte4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } - -public interface V2UNB4 : IConverter { static void IConverter.Convert(in Vector2 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), 0, 0); } -public interface V3UNB4 : IConverter { static void IConverter.Convert(in Vector3 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), 0); } -public interface V4UNB4 : IConverter { static void IConverter.Convert(in Vector4 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), (byte)(source.W * 255)); } -public interface H2UNB4 : IConverter { static void IConverter.Convert(in Half2 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), 0, 0); } -public interface H3UNB4 : IConverter { static void IConverter.Convert(in Half3 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), 0); } -public interface H4UNB4 : IConverter { static void IConverter.Convert(in Half4 source, out UNormByte4 dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), (byte)(source.W * 255)); } -public interface U4UNB4 : IConverter { static void IConverter.Convert(in UShort4 source, out UNormByte4 dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } -public interface B4UNB4 : IConverter { static void IConverter.Convert(in Byte4 source, out UNormByte4 dest) => dest = new(source.X, source.Y, source.Z, source.W); } -public interface UNB4UNB4 : IConverter { static void IConverter.Convert(in UNormByte4 source, out UNormByte4 dest) => dest = source; } +public interface COLORB4 : IConverter { static void IConverter.Convert(in Color source, out Byte4 dest) => dest = new(source.R, source.G, source.B, source.A); } + +public interface V2COLOR : IConverter { static void IConverter.Convert(in Vector2 source, out Color dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), 0, 0); } +public interface V3COLOR : IConverter { static void IConverter.Convert(in Vector3 source, out Color dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), 0); } +public interface V4COLOR : IConverter { static void IConverter.Convert(in Vector4 source, out Color dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), (byte)(source.W * 255)); } +public interface H2COLOR : IConverter { static void IConverter.Convert(in Half2 source, out Color dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), 0, 0); } +public interface H3COLOR : IConverter { static void IConverter.Convert(in Half3 source, out Color dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), 0); } +public interface H4COLOR : IConverter { static void IConverter.Convert(in Half4 source, out Color dest) => dest = new((byte)(source.X * 255), (byte)(source.Y * 255), (byte)(source.Z * 255), (byte)(source.W * 255)); } +public interface U4COLOR : IConverter { static void IConverter.Convert(in UShort4 source, out Color dest) => dest = new((byte)source.X, (byte)source.Y, (byte)source.Z, (byte)source.W); } +public interface B4COLOR : IConverter { static void IConverter.Convert(in Byte4 source, out Color dest) => dest = new(source.X, source.Y, source.Z, source.W); } +public interface COLORCOLOR : IConverter { static void IConverter.Convert(in Color source, out Color dest) => dest = source; } public interface IFloat2Semantic : ISemantic, V2V2, @@ -116,7 +116,7 @@ public interface IFloat2Semantic : ISemantic, V2H2, H2V2, V2H3, H3V2, V2H4, H4V2, - V2UNB4, UNB4V2; + V2COLOR, COLORV2; public interface IFloat3Semantic : ISemantic, V3V2, V2V3, @@ -127,7 +127,7 @@ public interface IFloat3Semantic : ISemantic, V3H2, H2V3, V3H3, H3V3, V3H4, H4V3, - V3UNB4, UNB4V3; + V3COLOR, COLORV3; public interface IFloat4Semantic : ISemantic, V4V2, V2V4, @@ -138,7 +138,7 @@ public interface IFloat4Semantic : ISemantic, V4H2, H2V4, V4H3, H3V4, V4H4, H4V4, - V4UNB4, UNB4V4; + V4COLOR, COLORV4; public interface IHalf2Semantic : ISemantic, H2V2, V2H2, @@ -149,7 +149,7 @@ public interface IHalf2Semantic : ISemantic, H2H2, H2H3, H3H2, H2H4, H4H2, - H2UNB4, UNB4H2; + H2COLOR, COLORH2; public interface IHalf3Semantic : ISemantic, H3V2, V2H3, @@ -160,7 +160,7 @@ public interface IHalf3Semantic : ISemantic, H3H2, H2H3, H3H3, H3H4, H4H3, - H3UNB4, UNB4H3; + H3COLOR, COLORH3; public interface IHalf4Semantic : ISemantic, H4V2, V2H4, @@ -171,7 +171,7 @@ public interface IHalf4Semantic : ISemantic, H4H2, H2H4, H4H3, H3H4, H4H4, - H4UNB4, UNB4H4; + H4COLOR, COLORH4; public interface IUShort4Semantic : ISemantic, U4V2, V2U4, @@ -182,7 +182,7 @@ public interface IUShort4Semantic : ISemantic, U4H2, H2U4, U4H3, H3U4, U4H4, H4U4, - U4UNB4, UNB4U4; + U4COLOR, COLORU4; public interface IByte4Semantic : ISemantic, B4V2, V2B4, @@ -193,15 +193,15 @@ public interface IByte4Semantic : ISemantic, B4H2, H2B4, B4H3, H3B4, B4H4, H4B4, - B4UNB4, UNB4B4; - -public interface IUNorm4Semantic : ISemantic, - UNB4V2, V2UNB4, - UNB4V3, V3UNB4, - UNB4V4, V4UNB4, - UNB4B4, B4UNB4, - UNB4U4, U4UNB4, - UNB4H2, H2UNB4, - UNB4H3, H3UNB4, - UNB4H4, H4UNB4, - UNB4UNB4; + B4COLOR, COLORB4; + +public interface IColorSemantic : ISemantic, + COLORV2, V2COLOR, + COLORV3, V3COLOR, + COLORV4, V4COLOR, + COLORB4, B4COLOR, + COLORU4, U4COLOR, + COLORH2, H2COLOR, + COLORH3, H3COLOR, + COLORH4, H4COLOR, + COLORCOLOR; diff --git a/sources/engine/Stride.Graphics/VertexBufferHelper.cs b/sources/engine/Stride.Graphics/VertexBufferHelper.cs index 4302a5112c..926877837d 100644 --- a/sources/engine/Stride.Graphics/VertexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -88,7 +88,7 @@ public bool Copy(Span buffer, int semanticIndex = 0) IConverter, IConverter, IConverter, - IConverter, + IConverter, ISemantic where TValue : unmanaged { @@ -138,7 +138,7 @@ public unsafe bool Copy(Span destination) where TDest : unmanaged, case PixelFormat.R16G16B16A16_Float: SelectSrcType, Half4>(parameters, srcOffset, destOffset, srcFormat); break; case PixelFormat.R16G16B16A16_UInt: SelectSrcType, UShort4>(parameters, srcOffset, destOffset, srcFormat); break; case PixelFormat.R8G8B8A8_UInt: SelectSrcType, Byte4>(parameters, srcOffset, destOffset, srcFormat); break; - case PixelFormat.R8G8B8A8_UNorm: SelectSrcType, UNormByte4>(parameters, srcOffset, destOffset, srcFormat); break; + case PixelFormat.R8G8B8A8_UNorm: SelectSrcType, Color>(parameters, srcOffset, destOffset, srcFormat); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({srcDef.VertexElement.Format})"); } } @@ -159,7 +159,7 @@ static void SelectSrcType(InterleavedParameters param, int s IConverter, IConverter, IConverter, - IConverter, + IConverter, ISemantic where TOutput : unmanaged { @@ -172,7 +172,7 @@ static void SelectSrcType(InterleavedParameters param, int s case PixelFormat.R16G16B16A16_Float: InterleavedCopy(param, srcElemOffset, destElemOffset); break; case PixelFormat.R16G16B16A16_UInt: InterleavedCopy(param, srcElemOffset, destElemOffset); break; case PixelFormat.R8G8B8A8_UInt: InterleavedCopy(param, srcElemOffset, destElemOffset); break; - case PixelFormat.R8G8B8A8_UNorm: InterleavedCopy(param, srcElemOffset, destElemOffset); break; + case PixelFormat.R8G8B8A8_UNorm: InterleavedCopy(param, srcElemOffset, destElemOffset); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({format})"); } } @@ -238,7 +238,7 @@ public bool Read(Span destination, TReader rea IConverter, IConverter, IConverter, - IConverter, + IConverter, ISemantic where TDest : unmanaged where TReader : IReader @@ -254,7 +254,7 @@ public bool Read(Span destination, TReader rea case PixelFormat.R16G16B16A16_Float: InnerRead(destination, reader, elementData); break; case PixelFormat.R16G16B16A16_UInt: InnerRead(destination, reader, elementData); break; case PixelFormat.R8G8B8A8_UInt: InnerRead(destination, reader, elementData); break; - case PixelFormat.R8G8B8A8_UNorm: InnerRead(destination, reader, elementData); break; + case PixelFormat.R8G8B8A8_UNorm: InnerRead(destination, reader, elementData); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({elementData.VertexElement.Format})"); } @@ -320,7 +320,7 @@ public bool Write(TWriter writer, int semanticIndex = IConverter, IConverter, IConverter, - IConverter, + IConverter, IConverter, IConverter, IConverter, @@ -328,7 +328,7 @@ public bool Write(TWriter writer, int semanticIndex = IConverter, IConverter, IConverter, - IConverter, + IConverter, ISemantic where TDest : unmanaged where TWriter : IWriter @@ -344,7 +344,7 @@ public bool Write(TWriter writer, int semanticIndex = case PixelFormat.R16G16B16A16_Float: InnerWrite(writer, elementData); break; case PixelFormat.R16G16B16A16_UInt: InnerWrite(writer, elementData); break; case PixelFormat.R8G8B8A8_UInt: InnerWrite(writer, elementData); break; - case PixelFormat.R8G8B8A8_UNorm: InnerWrite(writer, elementData); break; + case PixelFormat.R8G8B8A8_UNorm: InnerWrite(writer, elementData); break; default: throw new NotImplementedException($"Unsupported format when converting vertex element ({elementData.VertexElement.Format})"); } From cf6422686453244f610776332f869e3ee54d49c5 Mon Sep 17 00:00:00 2001 From: Eideren Date: Fri, 26 Sep 2025 15:59:16 +0200 Subject: [PATCH 17/17] Change IndexBufferHelper to readonly struct for consistency --- sources/engine/Stride.Graphics/IndexBufferHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/engine/Stride.Graphics/IndexBufferHelper.cs b/sources/engine/Stride.Graphics/IndexBufferHelper.cs index 116730c87f..1d93ef8800 100644 --- a/sources/engine/Stride.Graphics/IndexBufferHelper.cs +++ b/sources/engine/Stride.Graphics/IndexBufferHelper.cs @@ -17,7 +17,7 @@ namespace Stride.Graphics; /// ]]> /// /// -public class IndexBufferHelper +public readonly struct IndexBufferHelper { /// /// Full index buffer, does not account for the binding offset or length