Files
UnrealEngine/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalRenderTarget.cpp
2025-05-18 13:04:45 +08:00

503 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
MetalRenderTarget.cpp: Metal render target implementation.
=============================================================================*/
#include "MetalRHIPrivate.h"
#include "ScreenRendering.h"
#include "MetalCommandBuffer.h"
#include "MetalCommandQueue.h"
#include "MetalDynamicRHI.h"
#include "MetalProfiler.h"
#include "ResolveShader.h"
#include "PipelineStateCache.h"
#include "Math/PackedVector.h"
#include "RHISurfaceDataConversion.h"
static FResolveRect GetDefaultRect(const FResolveRect& Rect, uint32 DefaultWidth, uint32 DefaultHeight)
{
if (Rect.X1 >= 0 && Rect.X2 >= 0 && Rect.Y1 >= 0 && Rect.Y2 >= 0)
{
return Rect;
}
else
{
return FResolveRect(0, 0, DefaultWidth, DefaultHeight);
}
}
int32 GMetalUseTexGetBytes = 1;
static FAutoConsoleVariableRef CVarMetalUseTexGetBytes(
TEXT("rhi.Metal.UseTexGetBytes"),
GMetalUseTexGetBytes,
TEXT("If true prefer using -[MTLTexture getBytes:...] to retreive texture data, creating a temporary shared/managed texture to copy from private texture storage when required, rather than using a temporary MTLBuffer. This works around data alignment bugs on some GPU vendor's drivers and may be more appropriate on iOS. (Default: True)"),
ECVF_RenderThreadSafe
);
/** Helper for accessing R10G10B10A2 colors. */
struct FMetalR10G10B10A2
{
uint32 R : 10;
uint32 G : 10;
uint32 B : 10;
uint32 A : 2;
};
/** Helper for accessing R16G16 colors. */
struct FMetalRG16
{
uint16 R;
uint16 G;
};
/** Helper for accessing R16G16B16A16 colors. */
struct FMetalRGBA16
{
uint16 R;
uint16 G;
uint16 B;
uint16 A;
};
void FMetalDynamicRHI::RHIReadSurfaceData(FRHITexture* TextureRHI, FIntRect InRect, TArray<FLinearColor>& OutData, FReadSurfaceDataFlags InFlags)
{
// Use our current surface read implementation and convert to linear - should refactor to make optimal
TArray<FColor> OutDataUnConverted;
RHIReadSurfaceData(TextureRHI, InRect, OutDataUnConverted, InFlags);
OutData.SetNumUninitialized(OutDataUnConverted.Num());
for (uint32 i = 0; i < OutDataUnConverted.Num(); ++i)
{
OutData[i] = OutDataUnConverted[i].ReinterpretAsLinear();
}
}
static void ConvertSurfaceDataToFColor(EPixelFormat Format, uint32 Width, uint32 Height, uint8 *In, uint32 SrcPitch, FColor* Out, FReadSurfaceDataFlags InFlags)
{
bool bLinearToGamma = InFlags.GetLinearToGamma();
if (Format == PF_G16 || Format == PF_R16_UINT || Format == PF_R16_SINT)
{
ConvertRawR16DataToFColor(Width, Height, In, SrcPitch, Out);
}
else if (Format == PF_R8G8B8A8)
{
ConvertRawR8G8B8A8DataToFColor(Width, Height, In, SrcPitch, Out);
}
else if (Format == PF_B8G8R8A8)
{
ConvertRawB8G8R8A8DataToFColor(Width, Height, In, SrcPitch, Out);
}
else if (Format == PF_A2B10G10R10)
{
ConvertRawR10G10B10A2DataToFColor(Width, Height, In, SrcPitch, Out);
}
else if (Format == PF_FloatRGBA || Format == PF_PLATFORM_HDR_0)
{
ConvertRawR16G16B16A16FDataToFColor(Width, Height, In, SrcPitch, Out, bLinearToGamma);
}
else if (Format == PF_FloatR11G11B10)
{
ConvertRawR11G11B10DataToFColor(Width, Height, In, SrcPitch, Out, bLinearToGamma);
}
else if (Format == PF_A32B32G32R32F)
{
ConvertRawR32G32B32A32DataToFColor(Width, Height, In, SrcPitch, Out, bLinearToGamma);
}
else if (Format == PF_A16B16G16R16)
{
ConvertRawR16G16B16A16DataToFColor(Width, Height, In, SrcPitch, Out, bLinearToGamma);
}
else if (Format == PF_G16R16)
{
ConvertRawR16G16DataToFColor(Width, Height, In, SrcPitch, Out);
}
else if (Format == PF_DepthStencil)
{
ConvertRawD32S8DataToFColor(Width, Height, In, SrcPitch, Out, InFlags);
}
else
{
// not supported yet
NOT_SUPPORTED("RHIReadSurfaceData Format");
}
}
void FMetalDynamicRHI::RHIReadSurfaceData(FRHITexture* TextureRHI, FIntRect Rect, TArray<FColor>& OutData, FReadSurfaceDataFlags InFlags)
{
MTL_SCOPED_AUTORELEASE_POOL;
// allocate output space
const uint32 SizeX = Rect.Width();
const uint32 SizeY = Rect.Height();
OutData.SetNumUninitialized(SizeX * SizeY);
if (!ensure(TextureRHI))
{
FMemory::Memzero(OutData.GetData(), sizeof(FColor) * OutData.Num());
return;
}
FMetalSurface* Surface = GetMetalSurfaceFromRHITexture(TextureRHI);
FColor* OutDataPtr = OutData.GetData();
MTL::Region Region(Rect.Min.X, Rect.Min.Y, SizeX, SizeY);
MTLTexturePtr Texture = Surface->Texture;
if(!Texture && EnumHasAnyFlags(Surface->GetDesc().Flags, TexCreate_Presentable))
{
Texture = Surface->GetCurrentTexture();
}
if(!Texture)
{
UE_LOG(LogRHI, Error, TEXT("Trying to read from an uninitialised texture."));
return;
}
if (GMetalUseTexGetBytes && Surface->GetDesc().Format != PF_DepthStencil && Surface->GetDesc().Format != PF_ShadowDepth)
{
SCOPE_CYCLE_COUNTER(STAT_MetalTexturePageOffTime);
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
MTLTexturePtr TempTexture;
RHICmdList.EnqueueLambda([this, &Texture, &TempTexture, SizeX, SizeY, &Region, Surface, InFlags](FRHICommandListImmediate& RHICmdList)
{
FMetalRHICommandContext& Context = FMetalRHICommandContext::Get(RHICmdList);
if (Texture->storageMode() == MTL::StorageModePrivate)
{
#if PLATFORM_MAC
MTL::StorageMode StorageMode = MTL::StorageModeManaged;
#else
#if WITH_IOS_SIMULATOR
MTL::StorageMode StorageMode = MTL::StorageModePrivate;
#else
MTL::StorageMode StorageMode = MTL::StorageModeShared;
#endif
#endif
MTL::PixelFormat MetalFormat = (MTL::PixelFormat)GPixelFormats[Surface->GetDesc().Format].PlatformFormat;
MTL::TextureDescriptor* Desc = MTL::TextureDescriptor::alloc()->init();
check(Desc);
Desc->setTextureType(Texture->textureType());
Desc->setPixelFormat(Texture->pixelFormat());
Desc->setWidth(SizeX);
Desc->setHeight(SizeY);
Desc->setDepth(1);
Desc->setMipmapLevelCount(1); // Only consider a single subresource and not the whole texture (like in the other RHIs)
Desc->setSampleCount(Texture->sampleCount());
Desc->setArrayLength(Texture->arrayLength());
MTL::ResourceOptions GeneralResourceOption = (MTL::ResourceOptions)FMetalCommandQueue::GetCompatibleResourceOptions(MTL::ResourceOptions(((NS::UInteger)Texture->cpuCacheMode() << MTL::ResourceCpuCacheModeShift) | ((NS::UInteger)StorageMode << MTL::ResourceStorageModeShift) | MTL::ResourceHazardTrackingModeUntracked));
Desc->setResourceOptions(GeneralResourceOption);
Desc->setCpuCacheMode(Texture->cpuCacheMode());
Desc->setStorageMode(StorageMode);
Desc->setUsage(Texture->usage());
TempTexture = NS::TransferPtr(Device->GetDevice()->newTexture(Desc));
Desc->release();
Context.CopyFromTextureToTexture(Texture.get(), 0, InFlags.GetMip(), MTL::Origin(Region.origin), MTL::Size(Region.size), TempTexture.get(), 0, 0, MTL::Origin(0, 0, 0));
Texture = TempTexture;
Region = MTL::Region(0, 0, SizeX, SizeY);
}
#if PLATFORM_MAC
if(Texture->storageMode() == MTL::StorageModeManaged)
{
// Synchronise the texture with the CPU
Context.SynchronizeTexture(Texture.get(), 0, InFlags.GetMip());
}
#endif
});
//kick the current command buffer.
RHICmdList.SubmitAndBlockUntilGPUIdle();
const uint32 Stride = GPixelFormats[Surface->GetDesc().Format].BlockBytes * SizeX;
const uint32 BytesPerImage = Stride * SizeY;
TArray<uint8> Data;
Data.AddUninitialized(BytesPerImage);
Texture->getBytes(Data.GetData(), Stride, BytesPerImage, Region, 0, 0);
ConvertSurfaceDataToFColor(Surface->GetDesc().Format, SizeX, SizeY, (uint8*)Data.GetData(), Stride, OutDataPtr, InFlags);
if (TempTexture)
{
FMetalDynamicRHI::Get().DeferredDelete(TempTexture);
}
}
else
{
uint32 BytesPerPixel = (Surface->GetDesc().Format != PF_DepthStencil || !InFlags.GetOutputStencil()) ? GPixelFormats[Surface->GetDesc().Format].BlockBytes : 1;
const uint32 Stride = BytesPerPixel * SizeX;
const uint32 Alignment = PLATFORM_MAC ? 1u : 64u; // Mac permits natural row alignment (tightly-packed) but iOS does not.
const uint32 AlignedStride = ((Stride - 1) & ~(Alignment - 1)) + Alignment;
const uint32 BytesPerImage = AlignedStride * SizeY;
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
FMetalBufferPtr Buffer = Device->CreatePooledBuffer(FMetalPooledBufferArgs(Device, BytesPerImage, BUF_Dynamic, MTL::StorageModeShared));
RHICmdList.EnqueueLambda([this, &Buffer, &Texture, &Region, Surface, AlignedStride, BytesPerImage, InFlags](FRHICommandListImmediate& RHICmdList)
{
// Synchronise the texture with the CPU
SCOPE_CYCLE_COUNTER(STAT_MetalTexturePageOffTime);
FMetalRHICommandContext& Context = FMetalRHICommandContext::Get(RHICmdList);
if (Surface->GetDesc().Format != PF_DepthStencil)
{
Context.CopyFromTextureToBuffer(Texture.get(), 0, InFlags.GetMip(), Region.origin, Region.size, Buffer, 0, AlignedStride, BytesPerImage, MTL::BlitOptionNone);
}
else
{
if (!InFlags.GetOutputStencil())
{
Context.CopyFromTextureToBuffer(Texture.get(), 0, InFlags.GetMip(), Region.origin, Region.size, Buffer, 0, AlignedStride, BytesPerImage, MTL::BlitOptionDepthFromDepthStencil);
}
else
{
Context.CopyFromTextureToBuffer(Texture.get(), 0, InFlags.GetMip(), Region.origin, Region.size, Buffer, 0, AlignedStride, BytesPerImage, MTL::BlitOptionStencilFromDepthStencil);
}
}
});
//kick the current command buffer.
RHICmdList.SubmitAndBlockUntilGPUIdle();
ConvertSurfaceDataToFColor(Surface->GetDesc().Format, SizeX, SizeY, (uint8*)Buffer->Contents(), AlignedStride, OutDataPtr, InFlags);
FMetalDynamicRHI::Get().DeferredDelete(Buffer);
}
}
#if PLATFORM_IOS
void FMetalDynamicRHI::RHIReadSurfaceDataDirect(FRHITexture* TextureRHI, FIntRect Rect, TArray<FColor>& OutData)
{
if (!ensure(TextureRHI)) return;
FMetalSurface* Surface = GetMetalSurfaceFromRHITexture(TextureRHI);
// allocate output space
const uint32 SizeX = Rect.Width();
const uint32 SizeY = Rect.Height();
OutData.SetNumUninitialized(SizeX * SizeY);
MTLTexturePtr Texture = Surface->Texture;
if(!Texture && EnumHasAnyFlags(Surface->GetDesc().Flags, TexCreate_Presentable))
{
Texture = Surface->GetCurrentTexture();
}
if(!Texture)
{
OutData.Empty();
UE_LOG(LogRHI, Error, TEXT("Trying to read from an uninitialised texture."));
return;
}
const uint32 Stride = GPixelFormats[Surface->GetDesc().Format].BlockBytes * SizeX;
const uint32 BytesPerImage = Stride * SizeY;
MTL::Region Region(Rect.Min.X, Rect.Min.Y, SizeX, SizeY);
Texture->getBytes(OutData.GetData(), Stride, BytesPerImage, Region, 0, 0);
}
#endif // PLATFORM_IOS
void FMetalDynamicRHI::RHIMapStagingSurface(FRHITexture* TextureRHI, FRHIGPUFence* FenceRHI, void*& OutData, int32& OutWidth, int32& OutHeight, uint32 GPUIndex)
{
MTL_SCOPED_AUTORELEASE_POOL;
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RHICmdList.SubmitAndBlockUntilGPUIdle();
if (FenceRHI && !FenceRHI->Poll())
{
ResourceCast(FenceRHI)->Wait(RHICmdList, FRHIGPUMask::FromIndex(GPUIndex));
}
FMetalSurface* Surface = GetMetalSurfaceFromRHITexture(TextureRHI);
FRHILockTextureResult Result = Surface->Lock(FRHILockTextureArgs::Lock2D(Surface, 0, RLM_ReadOnly, false), false);
OutWidth = Surface->GetSizeX();
OutHeight = Surface->GetSizeY();
OutData = Result.Data;
}
void FMetalDynamicRHI::RHIUnmapStagingSurface(FRHITexture* TextureRHI, uint32 GPUIndex)
{
MTL_SCOPED_AUTORELEASE_POOL;
FMetalSurface* Surface = GetMetalSurfaceFromRHITexture(TextureRHI);
Surface->Unlock(FRHILockTextureArgs::Lock2D(Surface, 0, RLM_ReadOnly, false));
}
void FMetalDynamicRHI::RHIReadSurfaceFloatData(FRHITexture* TextureRHI, FIntRect Rect, TArray<FFloat16Color>& OutData, ECubeFace CubeFace,int32 ArrayIndex,int32 MipIndex)
{
MTL_SCOPED_AUTORELEASE_POOL;
FMetalSurface* Surface = GetMetalSurfaceFromRHITexture(TextureRHI);
MTLTexturePtr Texture = Surface->Texture;
if(!Texture && EnumHasAnyFlags(Surface->GetDesc().Flags, TexCreate_Presentable))
{
Texture = Surface->GetCurrentTexture();
}
if(!Texture)
{
UE_LOG(LogRHI, Error, TEXT("Trying to read from an uninitialised texture."));
return;
}
// verify the input image format (but don't crash)
if (Surface->GetDesc().Format != PF_FloatRGBA)
{
UE_LOG(LogRHI, Log, TEXT("Trying to read non-FloatRGBA surface."));
}
if (TextureRHI->GetTextureCube())
{
// adjust index to account for cubemaps as texture arrays
ArrayIndex *= CubeFace_MAX;
ArrayIndex += GetMetalCubeFace(CubeFace);
}
// allocate output space
const uint32 SizeX = Rect.Width();
const uint32 SizeY = Rect.Height();
OutData.SetNumUninitialized(SizeX * SizeY);
MTL::Region Region = MTL::Region(Rect.Min.X, Rect.Min.Y, SizeX, SizeY);
// function wants details about the destination, not the source
const uint32 Stride = GPixelFormats[Surface->GetDesc().Format].BlockBytes * SizeX;
const uint32 Alignment = PLATFORM_MAC ? 1u : 64u; // Mac permits natural row alignment (tightly-packed) but iOS does not.
const uint32 AlignedStride = ((Stride - 1) & ~(Alignment - 1)) + Alignment;
const uint32 BytesPerImage = AlignedStride * SizeY;
int32 FloatBGRADataSize = BytesPerImage;
FMetalBufferPtr Buffer = Device->CreatePooledBuffer(FMetalPooledBufferArgs(Device, FloatBGRADataSize, BUF_Dynamic, MTL::StorageModeShared));
{
// Synchronise the texture with the CPU
SCOPE_CYCLE_COUNTER(STAT_MetalTexturePageOffTime);
{
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
// Enqueue an RHI thread command to fully flush the GPU and write back caches
RHICmdList.EnqueueLambda([&Texture, ArrayIndex, MipIndex, Region, &Buffer, AlignedStride, BytesPerImage](FRHICommandListImmediate& RHICmdList)
{
FMetalRHICommandContext& Context = FMetalRHICommandContext::Get(RHICmdList);
Context.CopyFromTextureToBuffer(Texture.get(), ArrayIndex, MipIndex, Region.origin, Region.size, Buffer, 0, AlignedStride, BytesPerImage, MTL::BlitOptionNone);
});
RHICmdList.SubmitAndBlockUntilGPUIdle();
}
}
uint8* DataPtr = (uint8*)Buffer->Contents();
FFloat16Color* OutDataPtr = OutData.GetData();
if (Alignment > 1u)
{
for (uint32 Row = 0; Row < SizeY; Row++)
{
FFloat16Color* FloatBGRAData = (FFloat16Color*)DataPtr;
FMemory::Memcpy(OutDataPtr, FloatBGRAData, Stride);
DataPtr += AlignedStride;
OutDataPtr += SizeX;
}
}
else
{
FFloat16Color* FloatBGRAData = (FFloat16Color*)DataPtr;
FMemory::Memcpy(OutDataPtr, FloatBGRAData, FloatBGRADataSize);
}
FMetalDynamicRHI::Get().DeferredDelete(Buffer);
}
void FMetalDynamicRHI::RHIRead3DSurfaceFloatData(FRHITexture* TextureRHI,FIntRect InRect,FIntPoint ZMinMax,TArray<FFloat16Color>& OutData)
{
MTL_SCOPED_AUTORELEASE_POOL;
FMetalSurface* Surface = GetMetalSurfaceFromRHITexture(TextureRHI);
MTL::Texture* Texture = Surface->Texture.get();
if(!Texture)
{
UE_LOG(LogRHI, Error, TEXT("Trying to read from an uninitialised texture."));
return;
}
// verify the input image format (but don't crash)
if (Surface->GetDesc().Format != PF_FloatRGBA)
{
UE_LOG(LogRHI, Log, TEXT("Trying to read non-FloatRGBA surface."));
}
// allocate output space
const uint32 SizeX = InRect.Width();
const uint32 SizeY = InRect.Height();
const uint32 SizeZ = ZMinMax.Y - ZMinMax.X;
OutData.SetNumUninitialized(SizeX * SizeY * SizeZ);
MTL::Region Region = MTL::Region(InRect.Min.X, InRect.Min.Y, ZMinMax.X, SizeX, SizeY, SizeZ);
// function wants details about the destination, not the source
const uint32 Stride = GPixelFormats[Surface->GetDesc().Format].BlockBytes * SizeX;
const uint32 Alignment = PLATFORM_MAC ? 1u : 64u; // Mac permits natural row alignment (tightly-packed) but iOS does not.
const uint32 AlignedStride = ((Stride - 1) & ~(Alignment - 1)) + Alignment;
const uint32 BytesPerImage = AlignedStride * SizeY;
int32 FloatBGRADataSize = BytesPerImage * SizeZ;
FMetalBufferPtr Buffer = Device->CreatePooledBuffer(FMetalPooledBufferArgs(Device, FloatBGRADataSize, BUF_Dynamic, MTL::StorageModeShared));
{
// Synchronise the texture with the CPU
SCOPE_CYCLE_COUNTER(STAT_MetalTexturePageOffTime);
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RHICmdList.EnqueueLambda([this, &Buffer, &Texture, &Region, Surface, AlignedStride, BytesPerImage](FRHICommandListImmediate& RHICmdList)
{
FMetalRHICommandContext& Context = FMetalRHICommandContext::Get(RHICmdList);
Context.CopyFromTextureToBuffer(Texture, 0, 0, Region.origin, Region.size, Buffer, 0, AlignedStride, BytesPerImage, MTL::BlitOptionNone);
});
//kick the current command buffer.
RHICmdList.SubmitAndBlockUntilGPUIdle();
}
uint8* DataPtr = (uint8*)Buffer->Contents();
FFloat16Color* OutDataPtr = OutData.GetData();
if (Alignment > 1u)
{
for (uint32 Image = 0; Image < SizeZ; Image++)
{
for (uint32 Row = 0; Row < SizeY; Row++)
{
FFloat16Color* FloatBGRAData = (FFloat16Color*)DataPtr;
FMemory::Memcpy(OutDataPtr, FloatBGRAData, Stride);
DataPtr += AlignedStride;
OutDataPtr += SizeX;
}
}
}
else
{
FFloat16Color* FloatBGRAData = (FFloat16Color*)DataPtr;
FMemory::Memcpy(OutDataPtr, FloatBGRAData, FloatBGRADataSize);
}
FMetalDynamicRHI::Get().DeferredDelete(Buffer);
}