Skip to content
This repository has been archived by the owner on Apr 28, 2023. It is now read-only.

Commit

Permalink
Adds non-blocking BufferReadback to have symmetry for GPU upload and …
Browse files Browse the repository at this point in the history
…download. Also using it in Pipet. #237
  • Loading branch information
tebjan committed Jul 1, 2021
1 parent d886f6b commit 2fda6ba
Show file tree
Hide file tree
Showing 8 changed files with 1,343 additions and 446 deletions.
2 changes: 0 additions & 2 deletions packages/VL.Stride.Runtime/VL.Stride.Engine.vl
Original file line number Diff line number Diff line change
Expand Up @@ -22940,9 +22940,7 @@
<Fragment Id="VRb8ukE1nlbPeRZWewguZc" Patch="JC5zNcazfe5PemN2vFTgvH" Enabled="true" />
<Fragment Id="IzWNXnYO95QN8WwpoYgyJi" Patch="VnB7dRwuoWoN5IyXhYChtZ" Enabled="true" />
<Fragment Id="OBVUa3UNeN2O90hR2mG9Ni" Patch="TBD6Xc1bmYhN5YxHX2qcT2" Enabled="true" />
<Fragment Id="FYM8YNcGvnxPF49cxJhzpn" Patch="JtEKw9nfAekMKAwZWEzXlb" />
</ProcessDefinition>
<Patch Id="JtEKw9nfAekMKAwZWEzXlb" Name="CreateDefault (Hidden)" />
</Patch>
</Node>
<!--
Expand Down
177 changes: 118 additions & 59 deletions packages/VL.Stride.Runtime/VL.Stride.Graphics.vl

Large diffs are not rendered by default.

735 changes: 384 additions & 351 deletions packages/VL.Stride.Runtime/VL.Stride.Rendering.Temp.vl

Large diffs are not rendered by default.

255 changes: 248 additions & 7 deletions packages/VL.Stride.Runtime/VL.Stride.Rendering.vl

Large diffs are not rendered by default.

Large diffs are not rendered by default.

142 changes: 142 additions & 0 deletions packages/VL.Stride.Runtime/src/Graphics/BufferExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Reflection;
using Stride.Core;
using System.Diagnostics;
using MapMode = Stride.Graphics.MapMode;

namespace VL.Stride.Graphics
{
Expand Down Expand Up @@ -304,5 +305,146 @@ public static unsafe Buffer SetDataFromStream(this Buffer buffer, CommandList co
}
return buffer;
}

/// <summary>
/// Calculates the expected element count of a buffer using a specified type.
/// </summary>
/// <typeparam name="TData">The type of the T pixel data.</typeparam>
/// <returns>The expected width</returns>
/// <exception cref="System.ArgumentException">If the size is invalid</exception>
public static int CalculateElementCount<TData>(this Buffer input) where TData : struct
{
var dataStrideInBytes = Utilities.SizeOf<TData>();

return input.SizeInBytes / dataStrideInBytes;
}

/// <summary>
/// Copies the content of this buffer to an array of data.
/// </summary>
/// <typeparam name="TData">The type of the T data.</typeparam>
/// <param name="thisBuffer"></param>
/// <param name="commandList">The command list.</param>
/// <param name="toData">The destination array to receive a copy of the buffer datas.</param>
/// <param name="doNotWait">if set to <c>true</c> this method will return immediately if the resource is still being used by the GPU for writing. Default is false</param>
/// <param name="offsetInBytes"></param>
/// <param name="lengthInBytes"></param>
/// <returns><c>true</c> if data was correctly retrieved, <c>false</c> if <see cref="doNotWait"/> flag was true and the resource is still being used by the GPU for writing.</returns>
/// <remarks>
/// This method is only working when called from the main thread that is accessing the main <see cref="GraphicsDevice"/>.
/// This method creates internally a stagging resource if this buffer is not already a stagging resouce, copies to it and map it to memory. Use method with explicit staging resource
/// for optimal performances.</remarks>
public static bool GetData<TData>(this Buffer thisBuffer, CommandList commandList, TData[] toData, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) where TData : struct
{
// Get data from this resource
if (thisBuffer.Usage == GraphicsResourceUsage.Staging)
{
// Directly if this is a staging resource
return thisBuffer.GetData(commandList, thisBuffer, toData, doNotWait, offsetInBytes, lengthInBytes);
}
else
{
// Unefficient way to use the Copy method using dynamic staging texture
using (var throughStaging = thisBuffer.ToStaging())
return thisBuffer.GetData(commandList, throughStaging, toData, doNotWait, offsetInBytes, lengthInBytes);
}
}

/// <summary>
/// Copies the content of this buffer from GPU memory to a CPU memory using a specific staging resource.
/// </summary>
/// <param name="thisBuffer"></param>
/// <param name="commandList"></param>
/// <param name="staginBuffer">The staging buffer used to transfer the buffer.</param>
/// <param name="toData">To data pointer.</param>
/// <param name="doNotWait"></param>
/// <param name="offsetInBytes"></param>
/// <param name="lengthInBytes"></param>
/// <exception cref="System.ArgumentException">When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length</exception>
/// <remarks>
/// This method is only working when called from the main thread that is accessing the main <see cref="GraphicsDevice"/>.
/// </remarks>
public static bool GetData<TData>(this Buffer thisBuffer, CommandList commandList, Buffer staginBuffer, TData[] toData, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0) where TData : struct
{
using (var pinner = new GCPinner(toData))
return thisBuffer.GetData(commandList, staginBuffer, new DataPointer(pinner.Pointer, toData.Length * Utilities.SizeOf<TData>()), doNotWait, offsetInBytes, lengthInBytes);
}

/// <summary>
/// Copies the content of this buffer to an array of data.
/// </summary>
/// <typeparam name="TData">The type of the T data.</typeparam>
/// <param name="thisBuffer"></param>
/// <param name="commandList">The command list.</param>
/// <param name="toData">The destination array to receive a copy of the buffer datas.</param>
/// <param name="doNotWait">if set to <c>true</c> this method will return immediately if the resource is still being used by the GPU for writing. Default is false</param>
/// <param name="offsetInBytes"></param>
/// <param name="lengthInBytes"></param>
/// <returns><c>true</c> if data was correctly retrieved, <c>false</c> if <see cref="doNotWait"/> flag was true and the resource is still being used by the GPU for writing.</returns>
/// <remarks>
/// This method is only working when called from the main thread that is accessing the main <see cref="GraphicsDevice"/>.
/// This method creates internally a stagging resource if this buffer is not already a stagging resouce, copies to it and map it to memory. Use method with explicit staging resource
/// for optimal performances.</remarks>
public static bool GetData(this Buffer thisBuffer, CommandList commandList, DataPointer toData, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0)
{
// Get data from this resource
if (thisBuffer.Usage == GraphicsResourceUsage.Staging)
{
// Directly if this is a staging resource
return thisBuffer.GetData(commandList, thisBuffer, toData, doNotWait, offsetInBytes, lengthInBytes);
}
else
{
// Unefficient way to use the Copy method using dynamic staging texture
using (var throughStaging = thisBuffer.ToStaging())
return thisBuffer.GetData(commandList, throughStaging, toData, doNotWait, offsetInBytes, lengthInBytes);
}
}

/// <summary>
/// Copies the content of this buffer from GPU memory to a CPU memory using a specific staging resource.
/// </summary>
/// <param name="thisBuffer"></param>
/// <param name="commandList"></param>
/// <param name="stagingBuffer">The staging buffer used to transfer the buffer.</param>
/// <param name="toData">To data pointer.</param>
/// <param name="doNotWait"></param>
/// <param name="offsetInBytes"></param>
/// <param name="lengthInBytes"></param>
/// <exception cref="System.ArgumentException">When strides is different from optimal strides, and TData is not the same size as the pixel format, or Width * Height != toData.Length</exception>
/// <remarks>
/// This method is only working when called from the main thread that is accessing the main <see cref="GraphicsDevice"/>.
/// </remarks>
public static bool GetData(this Buffer thisBuffer, CommandList commandList, Buffer stagingBuffer, DataPointer toData, bool doNotWait = false, int offsetInBytes = 0, int lengthInBytes = 0)
{
// Check size validity of data to copy to
if (toData.Pointer == IntPtr.Zero || toData.Size != thisBuffer.SizeInBytes)
return false;

// Copy the texture to a staging resource
if (!ReferenceEquals(thisBuffer, stagingBuffer))
commandList.Copy(thisBuffer, stagingBuffer);

var mappedResource = commandList.MapSubresource(stagingBuffer, 0, MapMode.Read, doNotWait, offsetInBytes, lengthInBytes);

try
{
if (mappedResource.DataBox.DataPointer != IntPtr.Zero)
{
Utilities.CopyMemory(toData.Pointer, mappedResource.DataBox.DataPointer, toData.Size);
}
else
{
return false;
}
}
finally
{
// Make sure that we unmap the resource in case of an exception
commandList.UnmapSubresource(mappedResource);
}

return true;
}
}
}
38 changes: 28 additions & 10 deletions packages/VL.Stride.Runtime/src/Graphics/ResourceDataHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,17 +144,26 @@ public PinnedGraphicsData Pin()
}
}

public class VLImagePinner : IDisposable
public struct VLImagePinner : IDisposable
{
IImageData imageData;
MemoryHandle imageDataHandle;
IntPtr pointer;

public unsafe VLImagePinner(IImage image)
{
imageData = image.GetData();
imageDataHandle = imageData.Bytes.Pin();
pointer = (IntPtr)imageDataHandle.Pointer;
if (image != null)
{
imageData = image.GetData();
imageDataHandle = imageData.Bytes.Pin();
pointer = (IntPtr)imageDataHandle.Pointer;
}
else
{
imageData = null;
imageDataHandle = new MemoryHandle();
pointer = IntPtr.Zero;
}
}

public IntPtr Pointer
Expand All @@ -175,17 +184,20 @@ public int ScanSize
public void Dispose()
{
imageDataHandle.Dispose();
imageData.Dispose();
imageData?.Dispose();
}
}

public class GCPinner : IDisposable
public struct GCPinner : IDisposable
{
GCHandle pinnedObject;

public GCPinner(object obj)
{
pinnedObject = GCHandle.Alloc(obj, GCHandleType.Pinned);
if (obj != null)
pinnedObject = GCHandle.Alloc(obj, GCHandleType.Pinned);
else
pinnedObject = new GCHandle();
}

public IntPtr Pointer
Expand All @@ -206,7 +218,6 @@ public static void PinSpread<T>(Spread<T> input, out IntPtr pointer, out int siz
pointer = IntPtr.Zero;
sizeInBytes = 0;
byteStride = 0;
pinner = null;

var count = input.Count;
if (count > 0)
Expand All @@ -216,15 +227,17 @@ public static void PinSpread<T>(Spread<T> input, out IntPtr pointer, out int siz

pinner = new GCPinner(input);
pointer = pinner.Pointer;
return;
}

pinner = new GCPinner(null);
}

public static void PinArray<T>(T[] input, out IntPtr pointer, out int sizeInBytes, out int byteStride, out GCPinner pinner) where T : struct
{
pointer = IntPtr.Zero;
sizeInBytes = 0;
byteStride = 0;
pinner = null;

var count = input.Length;
if (count > 0)
Expand All @@ -235,7 +248,10 @@ public static void PinArray<T>(T[] input, out IntPtr pointer, out int sizeInByte

pinner = new GCPinner(input);
pointer = pinner.Pointer;
return;
}

pinner = new GCPinner(null);
}

public static void PinImage(IImage input, out IntPtr pointer, out int sizeInBytes, out int bytePerRow, out int bytesPerPixel, out VLImagePinner pinner)
Expand All @@ -244,7 +260,6 @@ public static void PinImage(IImage input, out IntPtr pointer, out int sizeInByte
sizeInBytes = 0;
bytePerRow = 0;
bytesPerPixel = 0;
pinner = null;

if (input != null)
{
Expand All @@ -253,7 +268,10 @@ public static void PinImage(IImage input, out IntPtr pointer, out int sizeInByte
bytePerRow = pinner.ScanSize;
bytesPerPixel = input.Info.PixelSize;
pointer = pinner.Pointer;
return;
}

pinner = new VLImagePinner(null);
}
}
}
Loading

0 comments on commit 2fda6ba

Please sign in to comment.