Add GPUTexture::DownloadData to C#

#2973 #1446
This commit is contained in:
Wojtek Figat
2025-04-07 23:32:37 +02:00
parent 6fc5e1f423
commit 15da2d59e5
7 changed files with 109 additions and 42 deletions

View File

@@ -697,7 +697,7 @@ bool GPUTexture::DownloadData(TextureData& result)
PROFILE_CPU();
// Use faster path for staging resources
if (IsStaging())
if (IsStaging()) // TODO: what about chips with unified memory? if rendering is not active then we can access GPU memory from CPU directly (eg. mobile, integrated GPUs and some consoles)
{
const auto arraySize = ArraySize();
const auto mipLevels = MipLevels();
@@ -736,7 +736,11 @@ bool GPUTexture::DownloadData(TextureData& result)
return false;
}
#if GPU_ENABLE_RESOURCE_NAMING
const auto name = ToString();
#else
const StringView name = TEXT("Texture");
#endif
// Ensure not running on main thread - we support DownloadData from textures only on a worker threads (Thread Pool Workers or Content Loaders)
if (IsInMainThread())

View File

@@ -517,11 +517,11 @@ public:
GPUTask* UploadMipMapAsync(const BytesContainer& data, int32 mipIndex, int32 rowPitch, int32 slicePitch, bool copyData = false);
/// <summary>
/// Stops current thread execution to gather texture data from the GPU.
/// Downloads the texture data to be accessible from CPU. For frequent access, use staging textures, otherwise current thread will be stalled to wait for the GPU frame to copy data into staging buffer.
/// </summary>
/// <param name="result">The result data.</param>
/// <param name="result">The destination texture data container.</param>
/// <returns>True if cannot download data, otherwise false.</returns>
bool DownloadData(TextureData& result);
API_FUNCTION() bool DownloadData(TextureData& result);
/// <summary>
/// Creates GPU async task that will gather texture data from the GPU.

View File

@@ -202,6 +202,43 @@ void TextureMipData::Copy(void* data, uint32 dataRowPitch, uint32 dataDepthPitch
}
}
int32 TextureData::GetArraySize() const
{
return Items.Count();
}
int32 TextureData::GetMipLevels() const
{
return Items.HasItems() ? Items[0].Mips.Count() : 0;
}
void TextureData::Clear()
{
Items.Resize(0, false);
}
bool TextureData::GetPixels(Array<Color32>& pixels, int32 mipIndex, int32 arrayIndex)
{
if (Items.IsValidIndex(arrayIndex) && Items.Get()[arrayIndex].Mips.IsValidIndex(mipIndex))
{
const int32 mipWidth = Math::Max(1, Width >> mipIndex);
const int32 mipHeight = Math::Max(1, Height >> mipIndex);
return Items.Get()[arrayIndex].Mips.Get()[mipIndex].GetPixels(pixels, mipWidth, mipHeight, Format);
}
return true;
}
bool TextureData::GetPixels(Array<Color>& pixels, int32 mipIndex, int32 arrayIndex)
{
if (Items.IsValidIndex(arrayIndex) && Items.Get()[arrayIndex].Mips.IsValidIndex(mipIndex))
{
const int32 mipWidth = Math::Max(1, Width >> mipIndex);
const int32 mipHeight = Math::Max(1, Height >> mipIndex);
return Items.Get()[arrayIndex].Mips.Get()[mipIndex].GetPixels(pixels, mipWidth, mipHeight, Format);
}
return true;
}
REGISTER_BINARY_ASSET_ABSTRACT(TextureBase, "FlaxEngine.TextureBase");
TextureBase::TextureBase(const SpawnParams& params, const AssetInfo* info)

View File

@@ -5,6 +5,20 @@ using System.Runtime.InteropServices;
namespace FlaxEngine
{
partial class GPUTexture
{
/// <summary>
/// Downloads the texture data to be accessible from CPU. For frequent access, use staging textures, otherwise current thread will be stalled to wait for the GPU frame to copy data into staging buffer.
/// </summary>
/// <returns>Downloaded texture data container, or nul if failed.</returns>
[Unmanaged]
public TextureData DownloadData()
{
var result = new TextureData();
return DownloadData(result) ? null : result;
}
}
partial class TextureBase
{
/// <summary>

View File

@@ -44,8 +44,9 @@ public:
/// <summary>
/// Texture data container (used to keep data downloaded from the GPU).
/// </summary>
class FLAXENGINE_API TextureData
API_CLASS() class FLAXENGINE_API TextureData : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(TextureData, ScriptingObject);
public:
/// <summary>
/// Single entry of the texture array. Contains collection of mip maps.
@@ -60,39 +61,24 @@ public:
public:
/// <summary>
/// Init
/// Top level texture surface width (in pixels).
/// </summary>
TextureData()
{
}
API_FIELD(ReadOnly) int32 Width = 0;
/// <summary>
/// Destructor
/// Top level texture surface height (in pixels).
/// </summary>
~TextureData()
{
}
public:
/// <summary>
/// Top level texture width (in pixels).
/// </summary>
int32 Width = 0;
API_FIELD(ReadOnly) int32 Height = 0;
/// <summary>
/// Top level texture height (in pixels).
/// Top level texture surface depth (in pixels).
/// </summary>
int32 Height = 0;
/// <summary>
/// Top level texture depth (in pixels).
/// </summary>
int32 Depth = 0;
API_FIELD(ReadOnly) int32 Depth = 0;
/// <summary>
/// The texture data format.
/// </summary>
PixelFormat Format = PixelFormat::Unknown;
API_FIELD(ReadOnly) PixelFormat Format = PixelFormat::Unknown;
/// <summary>
/// The items collection (depth slices or array slices).
@@ -123,26 +109,38 @@ public:
}
/// <summary>
/// Gets amount of textures in the array
/// Gets amount of texture slices in the array.
/// </summary>
int32 GetArraySize() const
{
return Items.Count();
}
API_PROPERTY() int32 GetArraySize() const;
/// <summary>
/// Gets amount of mip maps in the textures
/// Gets amount of mip maps in the texture.
/// </summary>
int32 GetMipLevels() const
{
return Items.HasItems() ? Items[0].Mips.Count() : 0;
}
API_PROPERTY() int32 GetMipLevels() const;
/// <summary>
/// Clear data
/// Clear allocated memory.
/// </summary>
void Clear()
{
Items.Resize(0, false);
}
API_FUNCTION() void Clear();
public:
/// <summary>
/// Gets the texture pixels as Color32 array.
/// </summary>
/// <remarks>Supported only for 'basic' texture formats (uncompressed, single plane).</remarks>
/// <param name="pixels">The result texture pixels array.</param>
/// <param name="mipIndex">The mip index (zero-based).</param>
/// <param name="arrayIndex">The array or depth slice index (zero-based).</param>
/// <returns>True if failed, otherwise false.</returns>
API_FUNCTION() bool GetPixels(API_PARAM(Out) Array<Color32>& pixels, int32 mipIndex = 0, int32 arrayIndex = 0);
/// <summary>
/// Gets the texture pixels as Color array.
/// </summary>
/// <remarks>Supported only for 'basic' texture formats (uncompressed, single plane).</remarks>
/// <param name="pixels">The result texture pixels array.</param>
/// <param name="mipIndex">The mip index (zero-based).</param>
/// <param name="arrayIndex">The array or depth slice index (zero-based).</param>
/// <returns>True if failed, otherwise false.</returns>
API_FUNCTION() bool GetPixels(API_PARAM(Out) Array<Color>& pixels, int32 mipIndex = 0, int32 arrayIndex = 0);
};

View File

@@ -92,3 +92,11 @@ extern "C" FLAXENGINE_API void mono_add_internal_call(const char* name, const vo
#define INTERNAL_CALL_CHECK_EXP_RETURN(expression, defaultValue)
#endif
template<typename T>
T& InternalGetReference(T* obj)
{
if (!obj)
DebugLog::ThrowNullReference();
return *obj;
}

View File

@@ -875,6 +875,12 @@ namespace Flax.Build.Bindings
type = "MObject*";
return "(" + typeInfo.Type + "*)ScriptingObject::ToNative({0})";
}
else if (apiType.IsScriptingObject && typeInfo.IsRef && !typeInfo.IsPtr)
{
// Scripting Object is passed as pointer from C# but C++ uses reference
type = typeInfo.ToString(false) + '*';
return $"InternalGetReference({{0}})";
}
// Nested type (namespace prefix is required)
if (!(apiType.Parent is FileInfo))