Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<IDatabaseFileProviderService>() 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<Graphics.Buffer>(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<GraphicsContext>() 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<Triangle> ExtractBepuMesh(Model model, IServiceRegistry services, BufferPool pool)
{
int totalIndices = 0;
Expand All @@ -215,42 +146,23 @@ internal static unsafe BepuUtilities.Memory.Buffer<Triangle> ExtractBepuMesh(Mod
}

pool.Take<Triangle>(totalIndices / 3, out var triangles);
var triangleAsV3 = triangles.As<Vector3>();
int triangleV3Index = 0;

foreach ((Rendering.Mesh mesh, byte[] verticesBytes, byte[] indicesBytes) in ExtractMeshes(model, services))
var bepuTriangles = triangles.As<Vector3>();
var spanLeft = new Span<Vector3>(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<PositionSemantic, Vector3, VertexBufferHelper.CopyAsTriangleList>(spanLeft[..indexCount], copyJob);

fixed (byte* vBuffer = &verticesBytes[vBindings.Offset])
fixed (byte* iBuffer = indicesBytes)
{
if (mesh.Draw.IndexBuffer.Is32Bit)
{
foreach (int i in new Span<int>(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<ushort>(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)
Expand All @@ -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<VertexPosition3, Vector3>(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<PositionSemantic, Vector3>(verticesLeft[..vertexCount]);
indexHelper.CopyTo(indicesLeft[..indexCount]);

if (mesh.Draw.IndexBuffer.Is32Bit)
{
foreach (int indexBufferValue in new Span<int>(bytePtr + mesh.Draw.IndexBuffer.Offset, count))
{
indices[indexWriteHead++] = vertMappingStart + indexBufferValue;
}
}
else
{
foreach (ushort indexBufferValue in new Span<ushort>(bytePtr + mesh.Draw.IndexBuffer.Offset, count))
{
indices[indexWriteHead++] = vertMappingStart + indexBufferValue;
}
}
}
verticesLeft = verticesLeft[vertexCount..];
indicesLeft = indicesLeft[indexCount..];
}
}

Expand Down
143 changes: 143 additions & 0 deletions sources/engine/Stride.Graphics/IndexBufferHelper.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <example>
/// Reading the indices of a mesh:
/// <code>
/// <![CDATA[
/// Model.Meshes[0].Draw.IndexBuffer.AsReadable(Services, out IndexBufferHelper helper, out int count)
/// var indices = helper.To32Bit();
/// ]]>
/// </code>
/// </example>
public readonly struct IndexBufferHelper
{
/// <summary>
/// Full index buffer, does not account for the binding offset or length
/// </summary>
public readonly byte[] DataOuter;
public readonly IndexBufferBinding Binding;

/// <summary>
/// Effective index buffer, handles the binding offset
/// </summary>
public Span<byte> DataInner => DataOuter.AsSpan(Binding.Offset, Binding.Count * (Binding.Is32Bit ? 4 : 2));

/// <inheritdoc cref="MeshExtension.AsReadable(IndexBufferBinding, IServiceRegistry, out IndexBufferHelper, out int)"/>
public IndexBufferHelper(IndexBufferBinding binding, IServiceRegistry services, out int count)
{
DataOuter = MeshExtension.FetchBufferContentOrThrow(binding.Buffer, services);
Binding = binding;
count = Binding.Count;
}

/// <summary>
/// Branch to read the buffer as a 16 or 32 bit buffer, does not allocate
/// </summary>
/// <example>
/// <code>
/// 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
/// }
/// }
/// </code>
/// </example>
public bool Is32Bit(out Span<int> data32, out Span<ushort> data16)
{
if (Binding.Is32Bit)
{
data32 = MemoryMarshal.Cast<byte, int>(DataInner);
data16 = Span<ushort>.Empty;
return true;
}
else
{
data32 = Span<int>.Empty;
data16 = MemoryMarshal.Cast<byte, ushort>(DataInner);
return false;
}
}

/// <summary>
/// Does not allocate if the buffer is already 32 bit,
/// otherwise allocates a new int[] and copies the data into it
/// </summary>
public Span<int> 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;
}
}

/// <summary>
/// Does not allocate if the buffer is already 16 bit,
/// otherwise allocates a new ushort[] and copies the data into it
/// </summary>
public Span<ushort> 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<int> 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<ushort> 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);
}
}
}
13 changes: 13 additions & 0 deletions sources/engine/Stride.Graphics/Interop/Byte4.cs
Original file line number Diff line number Diff line change
@@ -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;
}
13 changes: 13 additions & 0 deletions sources/engine/Stride.Graphics/Interop/UShort4.cs
Original file line number Diff line number Diff line change
@@ -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;
}
Loading
Loading