diff --git a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs index 25101a7ac2..64832cef95 100644 --- a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs +++ b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs @@ -6,13 +6,10 @@ 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.Graphics.Semantics; using Stride.Rendering; using BufferPool = BepuUtilities.Memory.BufferPool; using Mesh = BepuPhysics.Collidables.Mesh; @@ -140,72 +137,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 +146,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 +174,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.Graphics/IndexBufferHelper.cs b/sources/engine/Stride.Graphics/IndexBufferHelper.cs new file mode 100644 index 0000000000..1d93ef8800 --- /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 readonly struct 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/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/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/MeshExtension.cs b/sources/engine/Stride.Graphics/MeshExtension.cs new file mode 100644 index 0000000000..3f1f7a5d44 --- /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 if it wasn't found through other means + /// 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 if it wasn't found through other means + /// 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 VertexDeclaration declaration, string vertexElementUsage, int semanticIndex, out VertexElementWithOffset result) + { + int offset = 0; + foreach (var element in 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/Semantics/ConcreteSemantics.cs b/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs new file mode 100644 index 0000000000..a689d066e2 --- /dev/null +++ b/sources/engine/Stride.Graphics/Semantics/ConcreteSemantics.cs @@ -0,0 +1,72 @@ +// 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.Semantics; + +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, + IColorSemantic + where T : ISemantic +{ + public static string Name => T.Name; +} diff --git a/sources/engine/Stride.Graphics/Semantics/IConverter.cs b/sources/engine/Stride.Graphics/Semantics/IConverter.cs new file mode 100644 index 0000000000..3bc7a7cf76 --- /dev/null +++ b/sources/engine/Stride.Graphics/Semantics/IConverter.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.Semantics; + +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 new file mode 100644 index 0000000000..1fda8d1876 --- /dev/null +++ b/sources/engine/Stride.Graphics/Semantics/ISemantic.cs @@ -0,0 +1,207 @@ +// 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.Mathematics; +using Stride.Graphics.Interop; + +namespace Stride.Graphics.Semantics; + +public interface ISemantic +{ + public static abstract string Name { get; } +} + +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; } +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 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; } +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 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; } +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 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); } +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 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; } +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 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); } +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 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); } +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 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); } +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 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, + V2V3, V3V2, + V2V4, V4V2, + V2B4, B4V2, + V2U4, U4V2, + V2H2, H2V2, + V2H3, H3V2, + V2H4, H4V2, + V2COLOR, COLORV2; + +public interface IFloat3Semantic : ISemantic, + V3V2, V2V3, + V3V3, + V3V4, V4V3, + V3B4, B4V3, + V3U4, U4V3, + V3H2, H2V3, + V3H3, H3V3, + V3H4, H4V3, + V3COLOR, COLORV3; + +public interface IFloat4Semantic : ISemantic, + V4V2, V2V4, + V4V3, V3V4, + V4V4, + V4B4, B4V4, + V4U4, U4V4, + V4H2, H2V4, + V4H3, H3V4, + V4H4, H4V4, + V4COLOR, COLORV4; + +public interface IHalf2Semantic : ISemantic, + H2V2, V2H2, + H2V3, V3H2, + H2V4, V4H2, + H2B4, B4H2, + H2U4, U4H2, + H2H2, + H2H3, H3H2, + H2H4, H4H2, + H2COLOR, COLORH2; + +public interface IHalf3Semantic : ISemantic, + H3V2, V2H3, + H3V3, V3H3, + H3V4, V4H3, + H3B4, B4H3, + H3U4, U4H3, + H3H2, H2H3, + H3H3, + H3H4, H4H3, + H3COLOR, COLORH3; + +public interface IHalf4Semantic : ISemantic, + H4V2, V2H4, + H4V3, V3H4, + H4V4, V4H4, + H4B4, B4H4, + H4U4, U4H4, + H4H2, H2H4, + H4H3, H3H4, + H4H4, + H4COLOR, COLORH4; + +public interface IUShort4Semantic : ISemantic, + U4V2, V2U4, + U4V3, V3U4, + U4V4, V4U4, + U4B4, B4U4, + U4U4, + U4H2, H2U4, + U4H3, H3U4, + U4H4, H4U4, + U4COLOR, COLORU4; + +public interface IByte4Semantic : ISemantic, + B4V2, V2B4, + B4V3, V3B4, + B4V4, V4B4, + B4B4, + B4U4, U4B4, + B4H2, H2B4, + B4H3, H3B4, + B4H4, H4B4, + 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 new file mode 100644 index 0000000000..926877837d --- /dev/null +++ b/sources/engine/Stride.Graphics/VertexBufferHelper.cs @@ -0,0 +1,537 @@ +// 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; +using Stride.Core.Mathematics; +using Stride.Graphics.Interop; +using Stride.Graphics.Semantics; + +namespace Stride.Graphics; + +/// +/// Reading the vertex positions of a mesh: +/// +/// (vertexPositions); +/// ]]> +/// +/// +public readonly struct VertexBufferHelper +{ + /// + /// 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; + + /// + /// 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) + : this(binding, MeshExtension.FetchBufferContentOrThrow(binding.Buffer, services), out count) + { + } + + /// + /// 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; + } + + /// + /// 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 : + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + ISemantic + where TValue : unmanaged + { + return Read>(buffer, new CopyToDest(), semanticIndex); + } + + /// + /// 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; + case PixelFormat.R8G8B8A8_UNorm: SelectSrcType, Color>(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 : + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + 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; + case PixelFormat.R8G8B8A8_UNorm: 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 TConverter : IConverter + 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.SourceStride; + + src < endSrc; + + src += param.SourceStride, dest += param.DestStride) + { + TConverter.Convert(*(TSourceSemVal*)src, out *(TDestSemVal*)dest); + } + } + } + } + + /// + /// Lower level access to read into the vertex buffer + /// + /// + /// 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
+ /// 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 : + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + ISemantic + where TDest : unmanaged + where TReader : IReader + { + if (Binding.Declaration.TryGetElement(TSemantic.Name, semanticIndex, out var elementData)) + { + switch (elementData.VertexElement.Format) + { + 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; + case PixelFormat.R8G8B8A8_UNorm: InnerRead(destination, reader, elementData); break; + default: throw new NotImplementedException($"Unsupported format when converting vertex element ({elementData.VertexElement.Format})"); + } + + return true; + } + + return false; + } + + private unsafe void InnerRead(Span destination, TReader reader, VertexElementWithOffset element) + where TConverter : IConverter + 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})"); + + var stride = Binding.Declaration.VertexStride; + var offset = element.Offset; + var count = Binding.Count; + + fixed (byte* ptrSr = DataInner) + { + byte* firstElement = ptrSr + offset; + reader.Read(firstElement, count, stride, destination); + } + } + + /// + /// 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 : + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + IConverter, + 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; + case PixelFormat.R8G8B8A8_UNorm: 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 TConverter : IConverter, IConverter + 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; + + 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})"); + + fixed (Vector3* destPtr = destination) + { + Vector3* dest = destPtr; + if (IndexBufferHelper.Is32Bit(out var indices32, out var indices16)) + { + foreach (var index in indices32) + { + TConverter.Convert(*(TSource*)(sourcePointer + index * stride), out *dest); + dest++; + } + } + else + { + foreach (var index in indices16) + { + TConverter.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 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})"); + + fixed (T* ptrDest = destination) + { + byte* end = sourcePointer + elementCount * stride; + T* dest = ptrDest; + for (; sourcePointer < end; sourcePointer += stride, dest++) + TConverter.Convert(*(TSource*)sourcePointer, out *dest); + } + } + } + + 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: + /// + /// (vertexPositions, myReader); + /// + /// struct CopyTo : IReader + /// { + /// 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})"); + /// + /// fixed (T* ptrDest = destination) + /// { + /// byte* end = sourcePointer + elementCount * stride; + /// T* dest = ptrDest; + /// for (; sourcePointer < end; sourcePointer += stride, dest++) + /// TConverter.Convert(*(TSource*)sourcePointer, out *dest); + /// } + /// } + /// } + /// ]]> + /// + /// + 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 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 TConverter : IConverter where TSource : unmanaged; + } + + /// + /// Writing directly to mesh color: + /// + /// (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 + /// { + /// public Color Color; + /// + /// 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) + /// { + /// TConverter.Convert(*(TSource*)sourcePointer, out var val); + /// val *= (Vector4)Color; + /// TConverter.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 TConverter : IConverter, IConverter where TSource : unmanaged; + } +} diff --git a/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs b/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs index 44a2b4ad3f..de8da31a45 100644 --- a/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs +++ b/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs @@ -7,14 +7,12 @@ 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.Graphics.Semantics; using Stride.Rendering; namespace Stride.Physics @@ -101,7 +99,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 +116,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 +154,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 +210,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 { diff --git a/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs b/sources/engine/Stride.Rendering/Extensions/BoundingExtensions.cs index ce4cb0d9ce..951739a582 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.Semantics; 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 TConverter : IConverter 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; + TConverter.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; + TConverter.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; } } } 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) { diff --git a/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs b/sources/engine/Stride.Rendering/Extensions/TransformExtensions.cs index 39e9e21c7d..3e94739e6d 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.Semantics; 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 TConverter : IConverter, IConverter 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(); + TConverter.Convert(*(TSource*)sourcePointer, out var val); + Vector3.TransformCoordinate(ref val, ref Matrix, out val); + TConverter.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 TConverter : IConverter, IConverter where TSource : unmanaged + { + for (byte* end = sourcePointer + elementCount * stride; sourcePointer < end; sourcePointer += stride) + { + TConverter.Convert(*(TSource*)sourcePointer, out var val); - bufferPointer += vertexStride; + var v3Pointer = (Vector3*)&val; + Vector3.TransformNormal(ref *v3Pointer, ref InverseTransposeMatrix, out *v3Pointer); + v3Pointer->Normalize(); + + TConverter.Convert(val, out *(TSource*)sourcePointer); + } + } + } + + private struct FlipHandedness : VertexBufferHelper.IWriter + { + 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) + { + TConverter.Convert(*(TSource*)sourcePointer, out var val); + val.W = -val.W; + TConverter.Convert(val, out *(TSource*)sourcePointer); } } } 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;