2253 lines
77 KiB
C++
2253 lines
77 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
OpenGLTexture.cpp: OpenGL texture RHI implementation.
|
|
=============================================================================*/
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Containers/ResourceArray.h"
|
|
#include "Stats/Stats.h"
|
|
#include "RHI.h"
|
|
#include "RenderUtils.h"
|
|
#include "OpenGLDrv.h"
|
|
#include "OpenGLDrvPrivate.h"
|
|
#include "HAL/LowLevelMemStats.h"
|
|
#include "HAL/LowLevelMemTracker.h"
|
|
#include "ProfilingDebugging/AssetMetadataTrace.h"
|
|
#include "Engine/Texture.h"
|
|
#include "RHICoreStats.h"
|
|
#include "RHITextureUtils.h"
|
|
#include "RHICoreTexture.h"
|
|
#include "RHICoreTextureInitializer.h"
|
|
|
|
#if PLATFORM_ANDROID
|
|
#include "ThirdParty/Android/detex/AndroidETC.h"
|
|
#endif //PLATFORM_ANDROID
|
|
|
|
static TAutoConsoleVariable<int32> CVarDeferTextureCreation(
|
|
TEXT("r.OpenGL.DeferTextureCreation"),
|
|
0,
|
|
TEXT("0: OpenGL textures are sent to the driver to be created immediately. (default)\n")
|
|
TEXT("1: Where possible OpenGL textures are stored in system memory and created only when required for rendering.\n")
|
|
TEXT(" This can avoid memory overhead seen in some GL drivers."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static TAutoConsoleVariable<int32> CVarDeferTextureCreationExcludeMask(
|
|
TEXT("r.OpenGL.DeferTextureCreationExcludeFlags"),
|
|
static_cast<int32>(~(TexCreate_ShaderResource | TexCreate_SRGB | TexCreate_Streamable | TexCreate_OfflineProcessed)),
|
|
TEXT("Deferred texture creation exclusion mask, any texture requested with flags in this mask will be excluded from deferred creation."),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static int32 GOGLDeferTextureCreationKeepLowerMipCount = -1;
|
|
static FAutoConsoleVariableRef CVarDeferTextureCreationKeepLowerMipCount(
|
|
TEXT("r.OpenGL.DeferTextureCreationKeepLowerMipCount"),
|
|
GOGLDeferTextureCreationKeepLowerMipCount,
|
|
TEXT("Maximum number of texture mips to retain in CPU memory after a deferred texture has been sent to the driver for GPU memory creation.\n")
|
|
TEXT("-1: to match the number of mips kept resident by the texture streamer (default).\n")
|
|
TEXT(" 0: to disable texture eviction and discard CPU mips after sending them to the driver.\n")
|
|
TEXT(" 16: keep all mips around.\n"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static int32 GOGLTextureEvictFramesToLive = 500;
|
|
static FAutoConsoleVariableRef CVarTextureEvictionFrameCount(
|
|
TEXT("r.OpenGL.TextureEvictionFrameCount"),
|
|
GOGLTextureEvictFramesToLive,
|
|
TEXT("The number of frames since a texture was last referenced before it will considered for eviction.\n")
|
|
TEXT("Textures can only be evicted after creation if all their mips are resident, ie its mip count <= r.OpenGL.DeferTextureCreationKeepLowerMipCount."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
int32 GOGLTexturesToEvictPerFrame = 10;
|
|
static FAutoConsoleVariableRef CVarTexturesToEvictPerFrame(
|
|
TEXT("r.OpenGL.TextureEvictsPerFrame"),
|
|
GOGLTexturesToEvictPerFrame,
|
|
TEXT("The maximum number of evictable textures to evict per frame, limited to avoid potential driver CPU spikes.\n")
|
|
TEXT("Textures can only be evicted after creation if all their mips are resident, ie its mip count <= r.OpenGL.DeferTextureCreationKeepLowerMipCount."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GOGLTextureEvictLogging = 0;
|
|
static FAutoConsoleVariableRef CVarTextureEvictionLogging(
|
|
TEXT("r.OpenGL.TextureEvictionLogging"),
|
|
GOGLTextureEvictLogging,
|
|
TEXT("Enables debug logging for texture eviction."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
static int32 GOGLTextureMinLRUCapacity = 0;
|
|
static FAutoConsoleVariableRef CVarTextureEvictionMinLRUCapacity(
|
|
TEXT("r.OpenGL.TextureEvictionMinLRUCapacity"),
|
|
GOGLTextureMinLRUCapacity,
|
|
TEXT("Keep a minimum number of textures resident in GL When using the texture LRU\n")
|
|
TEXT("This can reduce LRU restore times when resuming from static scenes.\n")
|
|
TEXT("0: (default)")
|
|
,
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
|
|
struct FScopedPackAlignment
|
|
{
|
|
FScopedPackAlignment()
|
|
{
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
}
|
|
~FScopedPackAlignment()
|
|
{
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
}
|
|
};
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Texture allocator support.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
/** Caching it here, to avoid getting it every time we create a texture. 0 is no multisampling. */
|
|
GLint GMaxOpenGLColorSamples = 0;
|
|
GLint GMaxOpenGLDepthSamples = 0;
|
|
GLint GMaxOpenGLIntegerSamples = 0;
|
|
|
|
// in bytes, never change after RHI, needed to scale game features
|
|
int64 GOpenGLDedicatedVideoMemory = 0;
|
|
// In bytes. Never changed after RHI init. Our estimate of the amount of memory that we can use for graphics resources in total.
|
|
int64 GOpenGLTotalGraphicsMemory = 0;
|
|
|
|
void FOpenGLTexture::UpdateTextureStats(FOpenGLTexture* Texture, bool bAllocating)
|
|
{
|
|
const FRHITextureDesc& Desc = Texture->GetDesc();
|
|
|
|
const uint64 TextureSize = Texture->MemorySize;
|
|
|
|
const bool bOnlyStreamableTexturesInTexturePool = false;
|
|
UE::RHICore::UpdateGlobalTextureStats(Desc, TextureSize, bOnlyStreamableTexturesInTexturePool, bAllocating);
|
|
|
|
const int64 TextureSizeDelta = bAllocating ? int64(TextureSize) : -int64(TextureSize);
|
|
|
|
#if ENABLE_LOW_LEVEL_MEM_TRACKER
|
|
const ELLMTag TextureTag = EnumHasAnyFlags(Desc.Flags, ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::ResolveTargetable | ETextureCreateFlags::DepthStencilTargetable)
|
|
? ELLMTag::RenderTargets
|
|
: ELLMTag::Textures;
|
|
|
|
LLM_SCOPED_PAUSE_TRACKING_WITH_ENUM_AND_AMOUNT(ELLMTag::GraphicsPlatform, TextureSizeDelta, ELLMTracker::Platform, ELLMAllocType::None);
|
|
LLM_SCOPED_PAUSE_TRACKING_WITH_ENUM_AND_AMOUNT(TextureTag , TextureSizeDelta, ELLMTracker::Default , ELLMAllocType::None);
|
|
#endif // ENABLE_LOW_LEVEL_MEM_TRACKER
|
|
}
|
|
|
|
FDynamicRHI::FRHICalcTextureSizeResult FOpenGLDynamicRHI::RHICalcTexturePlatformSize(FRHITextureDesc const& Desc, uint32 FirstMipIndex)
|
|
{
|
|
FDynamicRHI::FRHICalcTextureSizeResult Result;
|
|
Result.Size = Desc.CalcMemorySizeEstimate(FirstMipIndex);
|
|
Result.Align = 1;
|
|
return Result;
|
|
}
|
|
|
|
/**
|
|
* Retrieves texture memory stats. Unsupported with this allocator.
|
|
*
|
|
* @return false, indicating that out variables were left unchanged.
|
|
*/
|
|
void FOpenGLDynamicRHI::RHIGetTextureMemoryStats(FTextureMemoryStats& OutStats)
|
|
{
|
|
UE::RHICore::FillBaselineTextureMemoryStats(OutStats);
|
|
|
|
OutStats.DedicatedVideoMemory = GOpenGLDedicatedVideoMemory;
|
|
OutStats.TotalGraphicsMemory = GOpenGLTotalGraphicsMemory ? GOpenGLTotalGraphicsMemory : -1;
|
|
|
|
OutStats.LargestContiguousAllocation = OutStats.StreamingMemorySize;
|
|
}
|
|
|
|
|
|
/**
|
|
* Fills a texture with to visualize the texture pool memory.
|
|
*
|
|
* @param TextureData Start address
|
|
* @param SizeX Number of pixels along X
|
|
* @param SizeY Number of pixels along Y
|
|
* @param Pitch Number of bytes between each row
|
|
* @param PixelSize Number of bytes each pixel represents
|
|
*
|
|
* @return true if successful, false otherwise
|
|
*/
|
|
bool FOpenGLDynamicRHI::RHIGetTextureMemoryVisualizeData( FColor* /*TextureData*/, int32 /*SizeX*/, int32 /*SizeY*/, int32 /*Pitch*/, int32 /*PixelSize*/ )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FOpenGLTextureDesc::FOpenGLTextureDesc(FRHITextureDesc const& InDesc)
|
|
: bCubemap (InDesc.IsTextureCube())
|
|
, bArrayTexture (InDesc.IsTextureArray())
|
|
, bStreamable (EnumHasAnyFlags(InDesc.Flags, TexCreate_Streamable))
|
|
, bDepthStencil (EnumHasAnyFlags(InDesc.Flags, TexCreate_DepthStencilTargetable))
|
|
, bCanCreateAsEvicted(false)
|
|
, bIsPowerOfTwo (false)
|
|
, bMultisampleRenderbuffer(EnumHasAnyFlags(InDesc.Flags, TexCreate_Memoryless) && InDesc.NumSamples > 1)
|
|
{
|
|
checkf(!bCubemap || InDesc.NumSamples == 1, TEXT("Texture cubes cannot be multisampled."));
|
|
checkf(FOpenGL::SupportsTexture3D() || (!InDesc.IsTexture3D() && !InDesc.IsTextureArray()), TEXT("Texture3D / Texture2DArray support requires FOpenGL::SupportsTexture3D()."));
|
|
checkf(!bMultisampleRenderbuffer || EnumHasAnyFlags(InDesc.Flags, TexCreate_RenderTargetable | TexCreate_DepthStencilTargetable), TEXT("Only render targets can be memoryless"));
|
|
|
|
// Special case for multiview MSAA depth target. It has to be a non-MSAA texture with multisample rendering
|
|
const bool bMultiviewMSAADepthTarget = (bDepthStencil && InDesc.NumSamples > 1 && InDesc.Dimension == ETextureDimension::Texture2DArray);
|
|
if (bMultiviewMSAADepthTarget || FOpenGL::GetMaxMSAASamplesTileMem() == 1)
|
|
{
|
|
bMultisampleRenderbuffer = false;
|
|
}
|
|
|
|
// Select an appropriate texture target
|
|
if (bMultisampleRenderbuffer)
|
|
{
|
|
// Special case for multisample memoryless render targets
|
|
Target = GL_RENDERBUFFER;
|
|
}
|
|
else
|
|
{
|
|
if (bMultiviewMSAADepthTarget)
|
|
{
|
|
Target = GL_TEXTURE_2D_ARRAY;
|
|
}
|
|
else if (EnumHasAnyFlags(InDesc.Flags, TexCreate_External))
|
|
{
|
|
check(InDesc.IsTexture2D());
|
|
check(!InDesc.IsTextureArray());
|
|
|
|
Target = FOpenGL::SupportsImageExternal()
|
|
? GL_TEXTURE_EXTERNAL_OES
|
|
// Fall back to a regular 2d texture if we don't have support.
|
|
// Texture samplers in the shader will also fall back to a regular sampler2D.
|
|
: GL_TEXTURE_2D;
|
|
}
|
|
else if (EnumHasAnyFlags(InDesc.Flags, TexCreate_Presentable))
|
|
{
|
|
check(InDesc.Dimension == ETextureDimension::Texture2D);
|
|
Target = GL_RENDERBUFFER;
|
|
}
|
|
else
|
|
{
|
|
switch (InDesc.Dimension)
|
|
{
|
|
default: checkNoEntry();
|
|
case ETextureDimension::Texture2D: Target = (InDesc.NumSamples > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; break;
|
|
case ETextureDimension::Texture2DArray: Target = (InDesc.NumSamples > 1) ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY : GL_TEXTURE_2D_ARRAY; break;
|
|
case ETextureDimension::TextureCubeArray: Target = GL_TEXTURE_CUBE_MAP_ARRAY; break;
|
|
case ETextureDimension::TextureCube: Target = GL_TEXTURE_CUBE_MAP; break;
|
|
case ETextureDimension::Texture3D: Target = GL_TEXTURE_3D; break;
|
|
}
|
|
}
|
|
}
|
|
check(Target != GL_NONE);
|
|
|
|
// can run on RT.
|
|
bCanCreateAsEvicted =
|
|
CanDeferTextureCreation()
|
|
&& InDesc.Flags != TexCreate_None // ignore TexCreate_None
|
|
&& !EnumHasAnyFlags((ETextureCreateFlags)CVarDeferTextureCreationExcludeMask.GetValueOnAnyThread(), InDesc.Flags) // Anything outside of these flags cannot be evicted.
|
|
&& Target == GL_TEXTURE_2D
|
|
&& InDesc.IsTexture2D(); // 2d only.
|
|
|
|
if (GOGLTextureEvictLogging)
|
|
{
|
|
UE_CLOG(!bCanCreateAsEvicted, LogRHI, Warning, TEXT("CanDeferTextureCreation:%d, Flags:%llx Mask:%x, Target:%x"),
|
|
bCanCreateAsEvicted, int(InDesc.Flags), CVarDeferTextureCreationExcludeMask.GetValueOnAnyThread(), Target);
|
|
}
|
|
|
|
bIsPowerOfTwo =
|
|
FMath::IsPowerOfTwo(InDesc.Extent.X)
|
|
&& FMath::IsPowerOfTwo(InDesc.Extent.Y)
|
|
&& FMath::IsPowerOfTwo(InDesc.Depth);
|
|
|
|
MemorySize = InDesc.CalcMemorySizeEstimate();
|
|
|
|
// Determine the attachment point for the texture.
|
|
if (EnumHasAnyFlags(InDesc.Flags, TexCreate_RenderTargetable | TexCreate_CPUReadback))
|
|
{
|
|
Attachment = GL_COLOR_ATTACHMENT0;
|
|
}
|
|
else if (EnumHasAnyFlags(InDesc.Flags, TexCreate_DepthStencilTargetable))
|
|
{
|
|
Attachment = (InDesc.Format == PF_DepthStencil) ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
|
|
}
|
|
else if (EnumHasAnyFlags(InDesc.Flags, TexCreate_ResolveTargetable))
|
|
{
|
|
Attachment = (InDesc.Format == PF_DepthStencil)
|
|
? GL_DEPTH_STENCIL_ATTACHMENT
|
|
: ((InDesc.Format == PF_ShadowDepth || InDesc.Format == PF_D24)
|
|
? GL_DEPTH_ATTACHMENT
|
|
: GL_COLOR_ATTACHMENT0);
|
|
}
|
|
else
|
|
{
|
|
Attachment = GL_NONE;
|
|
}
|
|
|
|
switch (Attachment)
|
|
{
|
|
case GL_COLOR_ATTACHMENT0:
|
|
check(GMaxOpenGLColorSamples >= (GLint)InDesc.NumSamples);
|
|
break;
|
|
case GL_DEPTH_ATTACHMENT:
|
|
case GL_DEPTH_STENCIL_ATTACHMENT:
|
|
check(GMaxOpenGLDepthSamples >= (GLint)InDesc.NumSamples);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Constructor for RHICreateAliasedTexture
|
|
FOpenGLTexture::FOpenGLTexture(FOpenGLTexture& Other, const FString& Name, EAliasConstructorParam)
|
|
: FRHITexture(FRHITextureCreateDesc(Other.GetDesc(), ERHIAccess::SRVMask, *Name))
|
|
, Target (Other.Target)
|
|
, Attachment (Other.Attachment)
|
|
, MemorySize (0)
|
|
, bIsPowerOfTwo (Other.bIsPowerOfTwo)
|
|
, bCanCreateAsEvicted(false)
|
|
, bStreamable (Other.bStreamable)
|
|
, bCubemap (Other.bCubemap)
|
|
, bArrayTexture (Other.bArrayTexture)
|
|
, bDepthStencil (Other.bDepthStencil)
|
|
, bAlias (true)
|
|
, bMultisampleRenderbuffer(Other.bMultisampleRenderbuffer)
|
|
{
|
|
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
|
|
RHICmdList.EnqueueLambda([&](FRHICommandListImmediate&)
|
|
{
|
|
AliasResources(Other);
|
|
});
|
|
}
|
|
|
|
void FOpenGLTexture::AliasResources(FOpenGLTexture& Texture)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
check(bAlias && !Texture.bAlias);
|
|
|
|
// restore the source texture, do not allow the texture to become evicted, the aliasing texture cannot re-create the resource.
|
|
if (Texture.IsEvicted())
|
|
{
|
|
Texture.RestoreEvictedGLResource(false);
|
|
}
|
|
|
|
Resource = Texture.Resource;
|
|
}
|
|
|
|
// Constructor for external resources (RHICreateTexture2DFromResource etc).
|
|
FOpenGLTexture::FOpenGLTexture(FOpenGLTextureCreateDesc const& CreateDesc, GLuint InResource)
|
|
: FRHITexture (CreateDesc)
|
|
, Resource (InResource)
|
|
, Target (CreateDesc.Target)
|
|
, Attachment (CreateDesc.Attachment)
|
|
, MemorySize (CreateDesc.MemorySize)
|
|
, bIsPowerOfTwo (CreateDesc.bIsPowerOfTwo)
|
|
, bCanCreateAsEvicted(false)
|
|
, bStreamable (CreateDesc.bStreamable)
|
|
, bCubemap (CreateDesc.bCubemap)
|
|
, bArrayTexture (CreateDesc.bArrayTexture)
|
|
, bDepthStencil (CreateDesc.bDepthStencil)
|
|
, bAlias (true)
|
|
, bMultisampleRenderbuffer(CreateDesc.bMultisampleRenderbuffer)
|
|
{
|
|
}
|
|
|
|
#if PLATFORM_ANDROID
|
|
// Constructor for external hardware buffer.
|
|
FOpenGLTexture::FOpenGLTexture(FRHICommandListBase& RHICmdList, FOpenGLTextureCreateDesc const& CreateDesc, const AHardwareBuffer_Desc& HardwareBufferDesc, AHardwareBuffer* HardwareBuffer)
|
|
: FRHITexture(CreateDesc)
|
|
, Target(CreateDesc.Target)
|
|
, Attachment(CreateDesc.Attachment)
|
|
, MemorySize(CreateDesc.MemorySize)
|
|
, bIsPowerOfTwo(CreateDesc.bIsPowerOfTwo)
|
|
, bCanCreateAsEvicted(CreateDesc.bCanCreateAsEvicted)
|
|
, bStreamable(CreateDesc.bStreamable)
|
|
, bCubemap(CreateDesc.bCubemap)
|
|
, bArrayTexture(CreateDesc.bArrayTexture)
|
|
, bDepthStencil(CreateDesc.bDepthStencil)
|
|
, bAlias(false)
|
|
, bMultisampleRenderbuffer(CreateDesc.bMultisampleRenderbuffer)
|
|
, HardwareBufferImage(nullptr)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_OpenGLCreateTextureTime);
|
|
|
|
RHICmdList.EnqueueLambda([this, HardwareBuffer](FRHICommandListBase& CmdList)
|
|
{
|
|
check(IsInRHIThread());
|
|
|
|
VERIFY_GL_SCOPE();
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FRHICommandUpdateDecoderExternaTexture_Execute);
|
|
|
|
ensure(eglGetNativeClientBufferANDROID_p != nullptr);
|
|
ensure(eglCreateImageKHR_p != nullptr);
|
|
ensure(eglDestroyImageKHR_p != nullptr);
|
|
ensure(glEGLImageTargetTexture2DOES_p != nullptr);
|
|
|
|
// Ext. EGL_ANDROID_get_native_client_buffer
|
|
EGLClientBuffer NativeClientBuffer = eglGetNativeClientBufferANDROID_p(HardwareBuffer);
|
|
if (NativeClientBuffer == nullptr)
|
|
{
|
|
UE_LOG(LogRHI, Warning, TEXT("eglGetNativeClientBufferANDROID - Could not get native client buffer!"));
|
|
return;
|
|
}
|
|
|
|
// Ext. EGL_ANDROID_image_native_buffer
|
|
EGLImageKHR EglImage = eglCreateImageKHR_p(eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, NativeClientBuffer, nullptr);
|
|
if (EglImage == nullptr)
|
|
{
|
|
UE_LOG(LogRHI, Warning, TEXT("eglCreateImageKHR - Could not create EGLimage from native client buffer! B=0x%x E=0x%x"), NativeClientBuffer, eglGetError());
|
|
return;
|
|
}
|
|
|
|
this->HardwareBufferImage = EglImage;
|
|
|
|
glEGLImageTargetTexture2DOES_p(GL_TEXTURE_EXTERNAL_OES, this->HardwareBufferImage);
|
|
check(eglGetError() == EGL_SUCCESS);
|
|
check(glGetError() == 0);
|
|
|
|
// Bind the RHI texture's GL texture to the external image we got data about now...
|
|
glBindTexture(GL_TEXTURE_EXTERNAL_OES, *reinterpret_cast<int32*>(this->GetNativeResource()));
|
|
check(glGetError() == 0);
|
|
});
|
|
|
|
UpdateTextureStats(this, true);
|
|
}
|
|
#endif
|
|
|
|
// Standard constructor.
|
|
FOpenGLTexture::FOpenGLTexture(const FOpenGLTextureCreateDesc& CreateDesc)
|
|
: FRHITexture (CreateDesc)
|
|
, Target (CreateDesc.Target)
|
|
, Attachment (CreateDesc.Attachment)
|
|
, MemorySize (CreateDesc.MemorySize)
|
|
, bIsPowerOfTwo (CreateDesc.bIsPowerOfTwo)
|
|
, bCanCreateAsEvicted(CreateDesc.bCanCreateAsEvicted)
|
|
, bStreamable (CreateDesc.bStreamable)
|
|
, bCubemap (CreateDesc.bCubemap)
|
|
, bArrayTexture (CreateDesc.bArrayTexture)
|
|
, bDepthStencil (CreateDesc.bDepthStencil)
|
|
, bAlias (false)
|
|
, bMultisampleRenderbuffer(CreateDesc.bMultisampleRenderbuffer)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_OpenGLCreateTextureTime);
|
|
|
|
if (bCanCreateAsEvicted)
|
|
{
|
|
EvictionParamsPtr = MakeUnique<FTextureEvictionParams>(CreateDesc.NumMips);
|
|
}
|
|
|
|
UpdateTextureStats(this, true);
|
|
|
|
PixelBuffers.AddZeroed(CreateDesc.NumMips * (bCubemap ? 6 : 1) * GetEffectiveSizeZ());
|
|
}
|
|
|
|
FOpenGLTexture::~FOpenGLTexture()
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
|
|
FTextureEvictionLRU::Get().Remove(this);
|
|
|
|
if (!bCanCreateAsEvicted)
|
|
{
|
|
ReleaseOpenGLFramebuffers(this);
|
|
}
|
|
|
|
DeleteGLResource();
|
|
UpdateTextureStats(this, false);
|
|
}
|
|
|
|
void FOpenGLTexture::Initialize(FRHICommandListBase& RHICmdList)
|
|
{
|
|
RHICmdList.EnqueueLambda([this](FRHICommandListBase&)
|
|
{
|
|
FOpenGLDynamicRHI::Get().InitializeGLTexture(this, nullptr, 0);
|
|
});
|
|
}
|
|
|
|
void FOpenGLTexture::DeleteGLResource()
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_OpenGLDeleteGLTextureTime);
|
|
|
|
#if PLATFORM_ANDROID
|
|
if (HardwareBufferImage != nullptr)
|
|
{
|
|
eglDestroyImageKHR_p(eglGetCurrentDisplay(), HardwareBufferImage);
|
|
}
|
|
#endif //PLATFORM_ANDROID
|
|
|
|
if (Resource != 0)
|
|
{
|
|
switch (Target)
|
|
{
|
|
case GL_TEXTURE_2D:
|
|
case GL_TEXTURE_2D_MULTISAMPLE:
|
|
case GL_TEXTURE_3D:
|
|
case GL_TEXTURE_CUBE_MAP:
|
|
case GL_TEXTURE_2D_ARRAY:
|
|
case GL_TEXTURE_CUBE_MAP_ARRAY:
|
|
case GL_TEXTURE_EXTERNAL_OES:
|
|
FOpenGLDynamicRHI::Get().InvalidateTextureResourceInCache(Resource);
|
|
if (!bAlias)
|
|
{
|
|
FOpenGL::DeleteTextures(1, &Resource);
|
|
}
|
|
break;
|
|
|
|
case GL_RENDERBUFFER:
|
|
if (!bAlias)
|
|
{
|
|
glDeleteRenderbuffers(1, &Resource);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
checkNoEntry();
|
|
break;
|
|
}
|
|
}
|
|
|
|
Resource = GL_NONE;
|
|
}
|
|
|
|
static inline bool IsAstcLdrRGBAFormat(GLenum Format)
|
|
{
|
|
return Format >= GL_COMPRESSED_RGBA_ASTC_4x4_KHR && Format <= GL_COMPRESSED_RGBA_ASTC_12x12_KHR;
|
|
}
|
|
|
|
uint32 GTotalTexStorageSkipped = 0;
|
|
uint32 GTotalCompressedTexStorageSkipped = 0;
|
|
|
|
void FOpenGLDynamicRHI::InitializeGLTexture(FOpenGLTexture* Texture, const void* BulkDataPtr, uint64 BulkDataSize)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
|
|
if (EnumHasAnyFlags(Texture->GetDesc().Flags, TexCreate_Presentable))
|
|
return;
|
|
|
|
// Allocate the GL resource ID
|
|
GLuint TextureID;
|
|
if (Texture->bMultisampleRenderbuffer)
|
|
{
|
|
check(Texture->Target == GL_RENDERBUFFER);
|
|
glGenRenderbuffers(1, &TextureID);
|
|
}
|
|
else
|
|
{
|
|
check(Texture->Target != GL_RENDERBUFFER);
|
|
glGenTextures(1, &TextureID);
|
|
}
|
|
Texture->SetResource(TextureID);
|
|
|
|
if (!Texture->IsEvicted())
|
|
{
|
|
InitializeGLTextureInternal(Texture, BulkDataPtr, BulkDataSize);
|
|
}
|
|
else
|
|
{
|
|
// creating this as 'evicted'.
|
|
GTotalTexStorageSkipped++;
|
|
|
|
EPixelFormat PixelFormat = Texture->GetFormat();
|
|
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[PixelFormat];
|
|
bool bIsCompressed = GLFormat.bCompressed;
|
|
GTotalCompressedTexStorageSkipped += bIsCompressed ? 1 : 0;
|
|
|
|
if (BulkDataPtr)
|
|
{
|
|
check(!GLFormat.bCompressed);
|
|
const uint32 BlockSizeX = GPixelFormats[PixelFormat].BlockSizeX;
|
|
const uint32 BlockSizeY = GPixelFormats[PixelFormat].BlockSizeY;
|
|
|
|
uint8* Data = (uint8*)BulkDataPtr;
|
|
uint32 MipOffset = 0;
|
|
|
|
const FRHITextureDesc& Desc = Texture->GetDesc();
|
|
|
|
// copy bulk data to evicted mip store:
|
|
for (uint32 MipIndex = 0; MipIndex < Desc.NumMips; MipIndex++)
|
|
{
|
|
uint32 NumBlocksX = AlignArbitrary(FMath::Max<uint32>(1, (Desc.Extent.X >> MipIndex)), BlockSizeX) / BlockSizeX;
|
|
uint32 NumBlocksY = AlignArbitrary(FMath::Max<uint32>(1, (Desc.Extent.Y >> MipIndex)), BlockSizeY) / BlockSizeY;
|
|
uint32 NumLayers = FMath::Max<uint32>(1, Desc.ArraySize);
|
|
uint32 MipDataSize = NumBlocksX * NumBlocksY * NumLayers * GPixelFormats[PixelFormat].BlockBytes;
|
|
|
|
Texture->EvictionParamsPtr->SetMipData(MipIndex, &Data[MipOffset], MipDataSize);
|
|
MipOffset += MipDataSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FOpenGLDynamicRHI::InitializeGLTextureInternal(FOpenGLTexture* Texture, void const* BulkDataPtr, uint64 BulkDataSize)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
|
|
GLuint const TextureID = Texture->GetRawResourceName();
|
|
|
|
const FRHITextureDesc& Desc = Texture->GetDesc();
|
|
const GLenum Target = Texture->Target;
|
|
|
|
const bool bSRGB = EnumHasAnyFlags(Desc.Flags, TexCreate_SRGB);
|
|
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[Desc.Format];
|
|
if (GLFormat.InternalFormat[bSRGB] == GL_NONE)
|
|
{
|
|
UE_LOG(LogRHI, Fatal,TEXT("Texture format '%s' not supported (sRGB=%d)."), GPixelFormats[Desc.Format].Name, bSRGB);
|
|
}
|
|
|
|
const bool bMultiviewMSAADepthTarget = (Texture->bDepthStencil && Desc.NumSamples > 1 && Desc.Dimension == ETextureDimension::Texture2DArray);
|
|
|
|
// Make sure PBO is disabled
|
|
CachedBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
bool bAllocatedStorage = false;
|
|
if (Texture->bMultisampleRenderbuffer)
|
|
{
|
|
check(Texture->IsMultisampled());
|
|
check(Target == GL_RENDERBUFFER);
|
|
// Multisample Renderbuffers will be allocated on first use. See ConditionallyAllocateRenderbufferStorage
|
|
}
|
|
else
|
|
{
|
|
// Use a texture stage that's not likely to be used for draws, to avoid waiting
|
|
CachedSetupTextureStage(FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Target, TextureID, 0, Desc.NumMips);
|
|
|
|
if((GLFormat.bBGRA && !EnumHasAnyFlags(Desc.Flags, TexCreate_RenderTargetable))
|
|
#if !PLATFORM_ANDROID
|
|
|| (GLFormat.InternalFormat[0] == GL_RGB5_A1)
|
|
#endif
|
|
)
|
|
{
|
|
glTexParameteri(Target, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
|
glTexParameteri(Target, GL_TEXTURE_SWIZZLE_B, GL_RED);
|
|
}
|
|
|
|
if (!Texture->IsMultisampled())
|
|
{
|
|
if (Target == GL_TEXTURE_EXTERNAL_OES || !FMath::IsPowerOfTwo(Desc.Extent.X) || !FMath::IsPowerOfTwo(Desc.Extent.Y))
|
|
{
|
|
glTexParameteri(Target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(Target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
if (FOpenGL::SupportsTexture3D())
|
|
{
|
|
glTexParameteri(Target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
glTexParameteri(Target, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
glTexParameteri(Target, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
if (FOpenGL::SupportsTexture3D())
|
|
{
|
|
glTexParameteri(Target, GL_TEXTURE_WRAP_R, GL_REPEAT);
|
|
}
|
|
}
|
|
|
|
glTexParameteri(Target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(Target, GL_TEXTURE_MIN_FILTER, Desc.NumMips > 1 ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST);
|
|
|
|
if (FOpenGL::SupportsTextureFilterAnisotropic())
|
|
{
|
|
glTexParameteri(Target, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1);
|
|
}
|
|
}
|
|
|
|
glTexParameteri(Target, GL_TEXTURE_BASE_LEVEL, 0);
|
|
|
|
TextureMipLimits.Add(TextureID, TPair<GLenum, GLenum>(0, Desc.NumMips - 1));
|
|
|
|
if (FOpenGL::SupportsASTCDecodeMode())
|
|
{
|
|
if (IsAstcLdrRGBAFormat(GLFormat.InternalFormat[bSRGB]))
|
|
{
|
|
glTexParameteri(Target, TEXTURE_ASTC_DECODE_PRECISION_EXT, GL_RGBA8);
|
|
}
|
|
}
|
|
|
|
if (Target != GL_TEXTURE_EXTERNAL_OES)
|
|
{
|
|
using TEnumerationCallback = TFunctionRef<bool(GLenum Target, FUintVector3 Extent, uint32 MipIndex, uint32 ArraySlice, const void* MipSliceData, uint64 MipSliceSize)>;
|
|
|
|
auto EnumerateSubresources = [&](void const* Data, TEnumerationCallback&& Callback)
|
|
{
|
|
FScopedPackAlignment PackAlignment;
|
|
|
|
const FPixelFormatInfo& PixelFormat = GPixelFormats[Desc.Format];
|
|
|
|
for (uint32 ArraySlice = 0; ArraySlice < Desc.ArraySize; ++ArraySlice)
|
|
{
|
|
for (uint32 FaceIndex = 0; FaceIndex < (Desc.IsTextureCube() ? 6u : 1u); ++FaceIndex)
|
|
{
|
|
for (uint32 MipIndex = 0; MipIndex < Desc.NumMips; ++MipIndex)
|
|
{
|
|
uint64 SubresourceStride{};
|
|
uint64 SubresourceSize{};
|
|
uint64 ResourceOffset = UE::RHITextureUtils::CalculateSubresourceOffset(Desc, FaceIndex, ArraySlice, MipIndex, SubresourceStride, SubresourceSize);
|
|
const uint64 MipSize = SubresourceSize;
|
|
|
|
const FUintVector3 BlockCounts = UE::RHITextureUtils::CalculateMipBlockCounts(Desc, MipIndex);
|
|
const FUintVector3 MipExtents = FUintVector3(BlockCounts.X * PixelFormat.BlockSizeX, BlockCounts.Y * PixelFormat.BlockSizeY, BlockCounts.Z * PixelFormat.BlockSizeZ);
|
|
|
|
const GLenum CurrentTarget = Target == GL_TEXTURE_CUBE_MAP
|
|
? GL_TEXTURE_CUBE_MAP_POSITIVE_X + FaceIndex
|
|
: Target;
|
|
|
|
if (!Callback(CurrentTarget, MipExtents, MipIndex, ArraySlice, reinterpret_cast<const uint8*>(Data) + ResourceOffset, MipSize))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Create the texture resource
|
|
switch (Target)
|
|
{
|
|
default: checkNoEntry();
|
|
case GL_RENDERBUFFER:
|
|
case GL_TEXTURE_2D:
|
|
case GL_TEXTURE_CUBE_MAP:
|
|
{
|
|
// Try to create the texture using immutable storage
|
|
FOpenGL::TexStorage2D(Target, Desc.NumMips, GLFormat.InternalFormat[bSRGB], Desc.Extent.X, Desc.Extent.Y, GLFormat.Format, GLFormat.Type, Desc.Flags);
|
|
|
|
// Texture created with immutable storage. Now fill in the bulk data.
|
|
bAllocatedStorage = true;
|
|
|
|
if (BulkDataPtr)
|
|
{
|
|
EnumerateSubresources(BulkDataPtr, [&](GLenum CurrentTarget, FUintVector3 MipExtent, uint32 MipIndex, uint32 ArraySlice, void const* MipSliceData, uint32 MipSliceSize)
|
|
{
|
|
if (GLFormat.bCompressed)
|
|
{
|
|
glCompressedTexSubImage2D(
|
|
CurrentTarget,
|
|
MipIndex,
|
|
0, 0, // X/Y offset
|
|
MipExtent.X, MipExtent.Y,
|
|
GLFormat.Format,
|
|
MipSliceSize,
|
|
MipSliceData
|
|
);
|
|
GL_CHECK();
|
|
}
|
|
else
|
|
{
|
|
glTexSubImage2D(
|
|
CurrentTarget,
|
|
MipIndex,
|
|
0, 0, // X/Y offset
|
|
MipExtent.X, MipExtent.Y,
|
|
GLFormat.Format,
|
|
GLFormat.Type,
|
|
MipSliceData
|
|
);
|
|
GL_CHECK();
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GL_TEXTURE_2D_ARRAY:
|
|
case GL_TEXTURE_CUBE_MAP_ARRAY:
|
|
case GL_TEXTURE_3D:
|
|
{
|
|
bAllocatedStorage = true; // Always supported if 3D textures are supported.
|
|
|
|
const uint32 SizeZ =
|
|
Target == GL_TEXTURE_3D ? Desc.Depth :
|
|
Target == GL_TEXTURE_CUBE_MAP_ARRAY ? Desc.ArraySize * 6 :
|
|
Desc.ArraySize;
|
|
|
|
FOpenGL::TexStorage3D(Target, Desc.NumMips, GLFormat.InternalFormat[bSRGB], Desc.Extent.X, Desc.Extent.Y, SizeZ, GLFormat.Format, GLFormat.Type);
|
|
|
|
// Texture created with immutable storage. Now fill in the bulk data.
|
|
if (BulkDataPtr)
|
|
{
|
|
EnumerateSubresources(BulkDataPtr, [&](GLenum CurrentTarget, FUintVector3 MipExtent, uint32 MipIndex, uint32 ArraySlice, void const* MipSliceData, uint32 MipSliceSize)
|
|
{
|
|
if (GLFormat.bCompressed)
|
|
{
|
|
glCompressedTexSubImage3D(
|
|
CurrentTarget,
|
|
MipIndex,
|
|
0, 0, ArraySlice, // X/Y/Z offset
|
|
MipExtent.X, MipExtent.Y, MipExtent.Z,
|
|
GLFormat.Format,
|
|
MipSliceSize,
|
|
MipSliceData
|
|
);
|
|
GL_CHECK();
|
|
}
|
|
else
|
|
{
|
|
glTexSubImage3D(
|
|
CurrentTarget,
|
|
MipIndex,
|
|
0, 0, ArraySlice, // X/Y/Z offset
|
|
MipExtent.X, MipExtent.Y, MipExtent.Z,
|
|
GLFormat.Format,
|
|
GLFormat.Type,
|
|
MipSliceData
|
|
);
|
|
GL_CHECK();
|
|
}
|
|
|
|
return true;
|
|
});
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GL_TEXTURE_2D_MULTISAMPLE:
|
|
{
|
|
checkf(BulkDataPtr == nullptr, TEXT("Multisample textures cannot be created with initial bulk data."));
|
|
|
|
// Try to create an immutable storage texture and fallback if it fails
|
|
const int32 NumSamples = Texture->GetDesc().NumSamples;
|
|
const bool FixedSampleLocations = true;
|
|
FOpenGL::TexStorage2DMultisample(Target, NumSamples, GLFormat.InternalFormat[bSRGB], Desc.Extent.X, Desc.Extent.Y, FixedSampleLocations);
|
|
bAllocatedStorage = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// @todo: If integer pixel format
|
|
//check(GMaxOpenGLIntegerSamples>=NumSamples);
|
|
Texture->SetAllocatedStorage(bAllocatedStorage);
|
|
|
|
// No need to restore texture stage; leave it like this,
|
|
// and the next draw will take care of cleaning it up; or
|
|
// next operation that needs the stage will switch something else in on it.
|
|
}
|
|
|
|
|
|
void FOpenGLDynamicRHI::ResolveTexture(FOpenGLTexture* Texture, uint32 MipIndex,uint32 ArrayIndex)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
check(!Texture->GetTexture2D() || Texture->GetNumSamples() == 1);
|
|
|
|
// Calculate the dimensions of the mip-map.
|
|
EPixelFormat PixelFormat = Texture->GetFormat();
|
|
const uint32 BlockSizeX = GPixelFormats[PixelFormat].BlockSizeX;
|
|
const uint32 BlockSizeY = GPixelFormats[PixelFormat].BlockSizeY;
|
|
const uint32 BlockBytes = GPixelFormats[PixelFormat].BlockBytes;
|
|
const uint32 MipSizeX = FMath::Max(Texture->GetSizeX() >> MipIndex, BlockSizeX);
|
|
const uint32 MipSizeY = FMath::Max(Texture->GetSizeY() >> MipIndex, BlockSizeY);
|
|
uint32 NumBlocksX = (MipSizeX + BlockSizeX - 1) / BlockSizeX;
|
|
uint32 NumBlocksY = (MipSizeY + BlockSizeY - 1) / BlockSizeY;
|
|
const uint32 MipBytes = NumBlocksX * NumBlocksY * BlockBytes;
|
|
|
|
const int32 BufferIndex = MipIndex * (Texture->bCubemap ? 6 : 1) * Texture->GetEffectiveSizeZ() + ArrayIndex;
|
|
|
|
// Standard path with a PBO mirroring ever slice of a texture to allow multiple simulataneous maps
|
|
if (!IsValidRef(Texture->PixelBuffers[BufferIndex]))
|
|
{
|
|
const FRHIBufferCreateDesc CreateDesc =
|
|
FRHIBufferCreateDesc::Create(TEXT("PixelBuffer"), MipBytes, 0, EBufferUsageFlags::Dynamic);
|
|
|
|
Texture->PixelBuffers[BufferIndex] = new FOpenGLPixelBuffer(nullptr, GL_PIXEL_UNPACK_BUFFER, CreateDesc, nullptr);
|
|
}
|
|
|
|
TRefCountPtr<FOpenGLPixelBuffer> PixelBuffer = Texture->PixelBuffers[BufferIndex];
|
|
check(PixelBuffer->GetSize() == MipBytes);
|
|
check(!PixelBuffer->IsLocked());
|
|
|
|
// Transfer data from texture to pixel buffer.
|
|
// This may be further optimized by caching information if surface content was changed since last lock.
|
|
|
|
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[PixelFormat];
|
|
const bool bSRGB = EnumHasAnyFlags(Texture->GetFlags(), TexCreate_SRGB);
|
|
|
|
// Use a texture stage that's not likely to be used for draws, to avoid waiting
|
|
CachedSetupTextureStage(FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Texture->Target, Texture->GetResource(), -1, Texture->GetNumMips());
|
|
|
|
glBindBuffer( GL_PIXEL_PACK_BUFFER, PixelBuffer->Resource );
|
|
|
|
{
|
|
if (Texture->GetDesc().IsTextureArray() || Texture->GetDesc().IsTexture3D())
|
|
{
|
|
// apparently it's not possible to retrieve compressed image from GL_TEXTURE_2D_ARRAY in OpenGL for compressed images
|
|
// and for uncompressed ones it's not possible to specify the image index
|
|
check(0);
|
|
}
|
|
else
|
|
{
|
|
if (GLFormat.bCompressed)
|
|
{
|
|
FOpenGL::GetCompressedTexImage(
|
|
Texture->bCubemap
|
|
? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex
|
|
: Texture->Target,
|
|
MipIndex,
|
|
0); // offset into PBO
|
|
}
|
|
else
|
|
{
|
|
// Get framebuffer for texture
|
|
GLuint SourceFramebuffer = GetOpenGLFramebuffer(1, &Texture, (Texture->bCubemap ? &ArrayIndex : nullptr), &MipIndex, nullptr);
|
|
// Bind the framebuffer
|
|
glBindFramebuffer(UGL_READ_FRAMEBUFFER, SourceFramebuffer);
|
|
FOpenGL::ReadBuffer(GL_COLOR_ATTACHMENT0);
|
|
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
|
glReadPixels(0, 0, MipSizeX, MipSizeY, GLFormat.Format, GLFormat.Type, 0);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
|
|
|
ContextState.Framebuffer = (GLuint)-1;
|
|
}
|
|
}
|
|
}
|
|
|
|
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
|
|
|
// No need to restore texture stage; leave it like this,
|
|
// and the next draw will take care of cleaning it up; or
|
|
// next operation that needs the stage will switch something else in on it.
|
|
}
|
|
|
|
uint32 FOpenGLTexture::GetLockSize(uint32 InMipIndex, EResourceLockMode LockMode, uint32& DestStride)
|
|
{
|
|
check(LockMode != EResourceLockMode::RLM_WriteOnly_NoOverwrite);
|
|
uint64 LargerStride{};
|
|
const uint64 MipSize = UE::RHITextureUtils::CalculateTextureMipSize(GetDesc(), InMipIndex, LargerStride);
|
|
|
|
DestStride = static_cast<uint32>(LargerStride);
|
|
return MipSize;
|
|
}
|
|
|
|
void FOpenGLTexture::FillGLTextureImage(const FOpenGLTextureFormat& GLFormat, bool bSRGB, uint32 MipIndex, uint32 ArrayIndex, const FUintVector3& MipExtents, const void* BufferOrPBOOffset, size_t ImageSize)
|
|
{
|
|
FScopedPackAlignment PackAlignment;
|
|
|
|
if (GetDesc().IsTextureArray() || GetDesc().IsTexture3D())
|
|
{
|
|
const GLsizei Depth = 1;
|
|
|
|
if (GLFormat.bCompressed)
|
|
{
|
|
FOpenGL::CompressedTexSubImage3D(
|
|
Target,
|
|
MipIndex,
|
|
0, 0, ArrayIndex,
|
|
MipExtents.X, MipExtents.Y, Depth,
|
|
GLFormat.InternalFormat[bSRGB],
|
|
ImageSize,
|
|
BufferOrPBOOffset
|
|
);
|
|
GL_CHECK();
|
|
}
|
|
else
|
|
{
|
|
check(FOpenGL::SupportsTexture3D());
|
|
FOpenGL::TexSubImage3D(
|
|
Target,
|
|
MipIndex,
|
|
0, 0, ArrayIndex,
|
|
MipExtents.X, MipExtents.Y, Depth,
|
|
GLFormat.Format,
|
|
GLFormat.Type,
|
|
BufferOrPBOOffset
|
|
);
|
|
GL_CHECK();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const GLenum ActualTarget = bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Target;
|
|
|
|
if (GLFormat.bCompressed)
|
|
{
|
|
if (GetAllocatedStorageForMip(MipIndex, ArrayIndex))
|
|
{
|
|
glCompressedTexSubImage2D(
|
|
ActualTarget,
|
|
MipIndex,
|
|
0, 0,
|
|
MipExtents.X, MipExtents.Y,
|
|
GLFormat.InternalFormat[bSRGB],
|
|
ImageSize,
|
|
BufferOrPBOOffset
|
|
);
|
|
GL_CHECK();
|
|
}
|
|
else
|
|
{
|
|
glCompressedTexImage2D(
|
|
ActualTarget,
|
|
MipIndex,
|
|
GLFormat.InternalFormat[bSRGB],
|
|
MipExtents.X, MipExtents.Y,
|
|
0,
|
|
ImageSize,
|
|
BufferOrPBOOffset
|
|
);
|
|
GL_CHECK();
|
|
|
|
SetAllocatedStorageForMip(MipIndex, ArrayIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// All construction paths should have called TexStorage2D or TexImage2D. So we will always call TexSubImage2D.
|
|
check(GetAllocatedStorageForMip(MipIndex, ArrayIndex) == true);
|
|
|
|
glTexSubImage2D(
|
|
ActualTarget,
|
|
MipIndex,
|
|
0, 0,
|
|
MipExtents.X, MipExtents.Y,
|
|
GLFormat.Format,
|
|
GLFormat.Type,
|
|
BufferOrPBOOffset
|
|
);
|
|
GL_CHECK();
|
|
}
|
|
}
|
|
}
|
|
|
|
void* FOpenGLTexture::Lock(uint32 InMipIndex,uint32 ArrayIndex,EResourceLockMode LockMode,uint32& DestStride)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
check(!GetTexture2D() || GetNumSamples() == 1);
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_OpenGLLockTextureTime);
|
|
|
|
const uint32 MipBytes = GetLockSize(InMipIndex, LockMode, DestStride);
|
|
|
|
check(!IsEvicted() || ArrayIndex==0);
|
|
void* result = NULL;
|
|
|
|
const int32 BufferIndex = InMipIndex * (bCubemap ? 6 : 1) * this->GetEffectiveSizeZ() + ArrayIndex;
|
|
EPixelFormat PixelFormat = this->GetFormat();
|
|
|
|
// Should we use client-storage to improve update time on platforms that require it
|
|
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[PixelFormat];
|
|
if (IsEvicted())
|
|
{
|
|
check(ArrayIndex == 0);
|
|
EvictionParamsPtr->SetMipData(InMipIndex, 0, MipBytes);
|
|
return EvictionParamsPtr->MipImageData[InMipIndex].GetData();
|
|
}
|
|
else
|
|
{
|
|
if(LockMode != RLM_ReadOnly)
|
|
{
|
|
// if we modify an active but evictable texture then just make it non-evictable, this is to avoid keeping the cpu backup copies in sync.
|
|
RemoveEvictionData();
|
|
}
|
|
|
|
// Standard path with a PBO mirroring ever slice of a texture to allow multiple simulataneous maps
|
|
bool bBufferExists = true;
|
|
if (!IsValidRef(PixelBuffers[BufferIndex]))
|
|
{
|
|
bBufferExists = false;
|
|
const FRHIBufferCreateDesc CreateDesc =
|
|
FRHIBufferCreateDesc::Create(TEXT("PixelBuffer"), MipBytes, 0, EBufferUsageFlags::Dynamic);
|
|
PixelBuffers[BufferIndex] = new FOpenGLPixelBuffer(nullptr, GL_PIXEL_UNPACK_BUFFER, CreateDesc, nullptr);
|
|
}
|
|
|
|
TRefCountPtr<FOpenGLPixelBuffer> PixelBuffer = PixelBuffers[BufferIndex];
|
|
check(PixelBuffer->GetSize() == MipBytes);
|
|
check(!PixelBuffer->IsLocked());
|
|
|
|
// If the buffer already exists & the flags are such that the texture cannot be rendered to & is CPU accessible then we can skip the internal resolve for read locks. This makes HZB occlusion faster.
|
|
const bool bCPUTexResolved = bBufferExists && EnumHasAnyFlags(this->GetFlags(), TexCreate_CPUReadback) && !EnumHasAnyFlags(this->GetFlags(), TexCreate_RenderTargetable|TexCreate_DepthStencilTargetable);
|
|
|
|
if (LockMode != RLM_WriteOnly && !bCPUTexResolved)
|
|
{
|
|
FOpenGLDynamicRHI::Get().ResolveTexture(this, InMipIndex, ArrayIndex);
|
|
}
|
|
|
|
result = PixelBuffer->Lock(0, PixelBuffer->GetSize(), LockMode == RLM_ReadOnly, LockMode != RLM_ReadOnly);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Copied from OpenGLDebugFrameDump.
|
|
inline uint32 HalfFloatToFloatInteger(uint16 HalfFloat)
|
|
{
|
|
uint32 Sign = (HalfFloat >> 15) & 0x00000001;
|
|
uint32 Exponent = (HalfFloat >> 10) & 0x0000001f;
|
|
uint32 Mantiss = HalfFloat & 0x000003ff;
|
|
|
|
if (Exponent == 0)
|
|
{
|
|
if (Mantiss == 0) // Plus or minus zero
|
|
{
|
|
return Sign << 31;
|
|
}
|
|
else // Denormalized number -- renormalize it
|
|
{
|
|
while ((Mantiss & 0x00000400) == 0)
|
|
{
|
|
Mantiss <<= 1;
|
|
Exponent -= 1;
|
|
}
|
|
|
|
Exponent += 1;
|
|
Mantiss &= ~0x00000400;
|
|
}
|
|
}
|
|
else if (Exponent == 31)
|
|
{
|
|
if (Mantiss == 0) // Inf
|
|
return (Sign << 31) | 0x7f800000;
|
|
else // NaN
|
|
return (Sign << 31) | 0x7f800000 | (Mantiss << 13);
|
|
}
|
|
|
|
Exponent = Exponent + (127 - 15);
|
|
Mantiss = Mantiss << 13;
|
|
|
|
return (Sign << 31) | (Exponent << 23) | Mantiss;
|
|
}
|
|
|
|
inline float HalfFloatToFloat(uint16 HalfFloat)
|
|
{
|
|
union
|
|
{
|
|
float F;
|
|
uint32 I;
|
|
} Convert;
|
|
|
|
Convert.I = HalfFloatToFloatInteger(HalfFloat);
|
|
return Convert.F;
|
|
}
|
|
|
|
void FOpenGLTexture::Unlock(uint32 MipIndex,uint32 ArrayIndex)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
SCOPE_CYCLE_COUNTER(STAT_OpenGLUnlockTextureTime);
|
|
|
|
if (IsEvicted())
|
|
{
|
|
// evicted textures didn't actually perform a lock, so we can bail out early
|
|
check(ArrayIndex == 0);
|
|
// check the space was allocated
|
|
ensure(MipIndex < (uint32)EvictionParamsPtr->MipImageData.Num() && EvictionParamsPtr->MipImageData[MipIndex].Num());
|
|
return;
|
|
}
|
|
|
|
const int32 BufferIndex = MipIndex * (bCubemap ? 6 : 1) * this->GetEffectiveSizeZ() + ArrayIndex;
|
|
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[this->GetFormat()];
|
|
const bool bSRGB = EnumHasAnyFlags(this->GetFlags(), TexCreate_SRGB);
|
|
|
|
check(BufferIndex < PixelBuffers.Num());
|
|
TRefCountPtr<FOpenGLPixelBuffer> PixelBuffer = PixelBuffers[BufferIndex];
|
|
|
|
check(IsValidRef(PixelBuffer));
|
|
|
|
#if PLATFORM_ANDROID
|
|
// check for FloatRGBA to RGBA8 conversion needed
|
|
if (this->GetFormat() == PF_FloatRGBA && GLFormat.Type == GL_UNSIGNED_BYTE)
|
|
{
|
|
UE_LOG(LogRHI, Warning, TEXT("Converting texture from PF_FloatRGBA to RGBA8! Only supported for limited cases of 0.0 to 1.0 values (clamped)"));
|
|
|
|
// Code path for non-PBO: and always uncompressed!
|
|
// Volume/array textures are currently only supported if PixelBufferObjects are also supported.
|
|
check(this->GetSizeZ() == 0);
|
|
|
|
// Use a texture stage that's not likely to be used for draws, to avoid waiting
|
|
FOpenGLDynamicRHI::Get().CachedSetupTextureStage(FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Target, GetResource(), -1, this->GetNumMips());
|
|
|
|
FOpenGLDynamicRHI::Get().CachedBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
// get the source data and size
|
|
uint16* floatData = (uint16*)PixelBuffer->GetLockedBuffer();
|
|
int32 texWidth = FMath::Max<uint32>(1, (this->GetSizeX() >> MipIndex));
|
|
int32 texHeight = FMath::Max<uint32>(1, (this->GetSizeY() >> MipIndex));
|
|
|
|
// always RGBA8 so 4 bytes / pixel
|
|
int nValues = texWidth * texHeight * 4;
|
|
uint8* rgbaData = (uint8*)FMemory::Malloc(nValues);
|
|
|
|
// convert to GL_BYTE (saturate)
|
|
uint8* outPtr = rgbaData;
|
|
while (nValues--)
|
|
{
|
|
int32 pixelValue = (int32)(HalfFloatToFloat(*floatData++) * 255.0f);
|
|
*outPtr++ = (uint8)(pixelValue < 0 ? 0 : (pixelValue < 256 ? pixelValue : 255));
|
|
}
|
|
|
|
// All construction paths should have called TexStorage2D or TexImage2D. So we will
|
|
// always call TexSubImage2D.
|
|
check(GetAllocatedStorageForMip(MipIndex, ArrayIndex) == true);
|
|
glTexSubImage2D(
|
|
bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Target,
|
|
MipIndex,
|
|
0,
|
|
0,
|
|
texWidth,
|
|
texHeight,
|
|
GLFormat.Format,
|
|
GLFormat.Type,
|
|
rgbaData);
|
|
|
|
// free temporary conversion buffer
|
|
FMemory::Free(rgbaData);
|
|
|
|
// Unlock "PixelBuffer" and free the temp memory after the texture upload.
|
|
PixelBuffer->Unlock();
|
|
|
|
// No need to restore texture stage; leave it like this,
|
|
// and the next draw will take care of cleaning it up; or
|
|
// next operation that needs the stage will switch something else in on it.
|
|
|
|
FOpenGLDynamicRHI::Get().CachedBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
// Code path for PBO per slice
|
|
check(IsValidRef(PixelBuffers[BufferIndex]));
|
|
|
|
PixelBuffer->Unlock();
|
|
|
|
// Modify permission?
|
|
if (!PixelBuffer->IsLockReadOnly())
|
|
{
|
|
// Use a texture stage that's not likely to be used for draws, to avoid waiting
|
|
FOpenGLDynamicRHI::Get().CachedSetupTextureStage(FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Target, GetResource(), -1, this->GetNumMips());
|
|
|
|
const FUintVector3 MipExtents = UE::RHITextureUtils::CalculateMipExtents(GetDesc(), MipIndex);
|
|
|
|
FillGLTextureImage(GLFormat, bSRGB, MipIndex, ArrayIndex, MipExtents, nullptr, PixelBuffer->GetSize());
|
|
}
|
|
|
|
//need to free PBO if we aren't keeping shadow copies
|
|
PixelBuffers[BufferIndex] = NULL;
|
|
|
|
// No need to restore texture stage; leave it like this,
|
|
// and the next draw will take care of cleaning it up; or
|
|
// next operation that needs the stage will switch something else in on it.
|
|
|
|
FOpenGLDynamicRHI::Get().CachedBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
}
|
|
|
|
|
|
uint32 GTotalEvictedMipMemStored = 0;
|
|
uint32 GTotalEvictedMipMemDuplicated = 0;
|
|
uint32 GTotalMipStoredCount = 0;
|
|
uint32 GTotalMipRestores = 0;
|
|
|
|
extern uint32 GTotalEvictedMipMemStored;
|
|
extern uint32 GTotalTexStorageSkipped;
|
|
|
|
float GMaxRestoreTime = 0.0f;
|
|
float GAvgRestoreTime = 0.0f;
|
|
uint32 GAvgRestoreCount = 0;
|
|
|
|
void FOpenGLTexture::RestoreEvictedGLResource(bool bAttemptToRetainMips)
|
|
{
|
|
// double StartTime = FPlatformTime::Seconds();
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_OpenGLRestoreEvictedTextureTime);
|
|
|
|
check(!EvictionParamsPtr->bHasRestored);
|
|
EvictionParamsPtr->bHasRestored = true;
|
|
|
|
const FClearValueBinding ClearBinding = this->GetClearBinding();
|
|
FOpenGLDynamicRHI::Get().InitializeGLTextureInternal(this, nullptr, 0);
|
|
|
|
EPixelFormat PixelFormat = this->GetFormat();
|
|
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[PixelFormat];
|
|
const bool bSRGB = EnumHasAnyFlags(this->GetFlags(), TexCreate_SRGB);
|
|
checkf(EvictionParamsPtr->MipImageData.Num() == this->GetNumMips(), TEXT("EvictionParamsPtr->MipImageData.Num() =%d, this->GetNumMips() = %d"), EvictionParamsPtr->MipImageData.Num(), this->GetNumMips());
|
|
|
|
FOpenGLDynamicRHI::Get().CachedBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
for (int32 MipIndex = EvictionParamsPtr->MipImageData.Num() - 1; MipIndex >= 0; MipIndex--)
|
|
{
|
|
TConstArrayView<uint8> MipMem = EvictionParamsPtr->MipImageData[MipIndex];
|
|
if (MipMem.Num())
|
|
{
|
|
const FUintVector3 MipExtents = UE::RHITextureUtils::CalculateMipExtents(GetDesc(), MipIndex);
|
|
|
|
FillGLTextureImage(GLFormat, bSRGB, MipIndex, 0, MipExtents, MipMem.GetData(), MipMem.Num());
|
|
}
|
|
}
|
|
|
|
// Use the resident streaming mips if our cvar is -1.
|
|
uint32 DeferTextureCreationKeepLowerMipCount = (uint32)(GOGLDeferTextureCreationKeepLowerMipCount >= 0 ? GOGLDeferTextureCreationKeepLowerMipCount : UTexture::GetStaticMinTextureResidentMipCount());
|
|
|
|
uint32 RetainMips = bAttemptToRetainMips && EnumHasAnyFlags(this->GetFlags(), TexCreate_Streamable) && this->GetNumMips() > 1 && !bAlias
|
|
? DeferTextureCreationKeepLowerMipCount
|
|
: 0;
|
|
|
|
// keep the mips for streamable textures
|
|
EvictionParamsPtr->ReleaseMipData(RetainMips);
|
|
|
|
if (CanBeEvicted())
|
|
{
|
|
if (FTextureEvictionLRU::Get().Add(this) == false)
|
|
{
|
|
// could not store this in the LRU. Deleting all backup mips, as this texture will never be evicted.
|
|
EvictionParamsPtr->ReleaseMipData(0);
|
|
}
|
|
}
|
|
|
|
#if GLDEBUG_LABELS_ENABLED
|
|
if (FAnsiString& TextureDebugName = EvictionParamsPtr->GetDebugLabelName(); TextureDebugName.Len())
|
|
{
|
|
SetOpenGLResourceName(this, *TextureDebugName);
|
|
if (RetainMips == 0)
|
|
{
|
|
TextureDebugName.Empty();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
GTotalEvictedMipMemDuplicated += EvictionParamsPtr->GetTotalAllocated();
|
|
// float ThisTime = (float)(FPlatformTime::Seconds() - StartTime);
|
|
// GAvgRestoreCount++;
|
|
// GMaxRestoreTime = FMath::Max(GMaxRestoreTime, ThisTime);
|
|
// GAvgRestoreTime += ThisTime;
|
|
}
|
|
|
|
void FOpenGLTexture::TryEvictGLResource()
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
if (bCanCreateAsEvicted && EvictionParamsPtr->bHasRestored)
|
|
{
|
|
if (CanBeEvicted())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_OpenGLTryEvictGLResource);
|
|
DeleteGLResource();
|
|
|
|
// create a new texture id.
|
|
EvictionParamsPtr->bHasRestored = false;
|
|
const FClearValueBinding ClearBinding = this->GetClearBinding();
|
|
// recreate the GL tex resource name (but not allocate the memory)
|
|
FOpenGLDynamicRHI::Get().InitializeGLTexture(this, nullptr, 0);
|
|
GTotalEvictedMipMemDuplicated -= EvictionParamsPtr->GetTotalAllocated();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FOpenGLTextureDesc::CanDeferTextureCreation()
|
|
{
|
|
bool bCanDeferTextureCreation = CVarDeferTextureCreation.GetValueOnAnyThread() != 0;
|
|
#if PLATFORM_ANDROID
|
|
static bool bDeferTextureCreationConfigRulesChecked = false;
|
|
static TOptional<bool> bConfigRulesCanDeferTextureCreation;
|
|
if (!bDeferTextureCreationConfigRulesChecked)
|
|
{
|
|
const FString* ConfigRulesDeferOpenGLTextureCreationStr = FAndroidMisc::GetConfigRulesVariable(TEXT("DeferOpenGLTextureCreation"));
|
|
if (ConfigRulesDeferOpenGLTextureCreationStr)
|
|
{
|
|
bConfigRulesCanDeferTextureCreation = ConfigRulesDeferOpenGLTextureCreationStr->Equals("true", ESearchCase::IgnoreCase);
|
|
UE_LOG(LogRHI, Log, TEXT("OpenGL deferred texture creation, set by config rules: %d"), (int)bConfigRulesCanDeferTextureCreation.GetValue());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogRHI, Log, TEXT("OpenGL deferred texture creation, no config rule set: %d"), (int)bCanDeferTextureCreation);
|
|
}
|
|
bDeferTextureCreationConfigRulesChecked = true;
|
|
}
|
|
|
|
if (bConfigRulesCanDeferTextureCreation.IsSet())
|
|
{
|
|
bCanDeferTextureCreation = bConfigRulesCanDeferTextureCreation.GetValue();
|
|
}
|
|
#endif
|
|
return bCanDeferTextureCreation;
|
|
}
|
|
|
|
bool FOpenGLTexture::CanBeEvicted()
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
checkf(!bCanCreateAsEvicted || EvictionParamsPtr.IsValid(), TEXT("%p, bCanCreateAsEvicted %d, EvictionParamsPtr.IsValid() %d"), this, bCanCreateAsEvicted, EvictionParamsPtr.IsValid());
|
|
|
|
// if we're aliased check that there's no eviction data.
|
|
check(!bCanCreateAsEvicted || !bAlias || (EvictionParamsPtr->MipImageData.Num() == 0 && EvictionParamsPtr->MipImageData.Num() != this->GetNumMips()));
|
|
|
|
// cant evict if we're aliased, or there are mips are not backed by stored data.
|
|
bool bRet = bCanCreateAsEvicted && EvictionParamsPtr->MipImageData.Num() == this->GetNumMips() && EvictionParamsPtr->AreAllMipsPresent();
|
|
|
|
return bRet;
|
|
}
|
|
|
|
void FOpenGLTexture::RemoveEvictionData()
|
|
{
|
|
if (EvictionParamsPtr.IsValid() && CanBeEvicted())
|
|
{
|
|
// Make sure the GL resource is created before removing the MIP data.
|
|
TryRestoreGLResource();
|
|
|
|
EvictionParamsPtr->ReleaseMipData(0);
|
|
FTextureEvictionLRU::Get().Remove(this);
|
|
}
|
|
}
|
|
|
|
void FOpenGLTexture::CloneViaCopyImage(FOpenGLTexture* Src, uint32 InNumMips, int32 SrcOffset, int32 DstOffset)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
|
|
check(Src->bCanCreateAsEvicted == bCanCreateAsEvicted);
|
|
if (bCanCreateAsEvicted)
|
|
{
|
|
// Copy all mips that are present.
|
|
if (!(!Src->IsEvicted() || Src->EvictionParamsPtr->AreAllMipsPresent()))
|
|
{
|
|
UE_LOG(LogRHI, Warning, TEXT("IsEvicted %d, MipsPresent %d, InNumMips %d, SrcOffset %d, DstOffset %d"), Src->IsEvicted(), Src->EvictionParamsPtr->AreAllMipsPresent(), InNumMips, SrcOffset, DstOffset);
|
|
int MessageCount = 0;
|
|
for (const auto& MipData : Src->EvictionParamsPtr->MipImageData)
|
|
{
|
|
UE_LOG(LogRHI, Warning, TEXT("SrcMipData[%d].Num() == %d"), MessageCount++, MipData.Num());
|
|
}
|
|
}
|
|
check(!Src->IsEvicted() || Src->EvictionParamsPtr->AreAllMipsPresent() );
|
|
EvictionParamsPtr->CloneMipData(*Src->EvictionParamsPtr, InNumMips, SrcOffset, DstOffset);
|
|
|
|
// the dest texture can remain evicted if: the src was also evicted or has all of the resident mips available or the dest texture has all mips already evicted.
|
|
if(IsEvicted() && (Src->IsEvicted() || Src->EvictionParamsPtr->AreAllMipsPresent() || EvictionParamsPtr->AreAllMipsPresent()))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (uint32 ArrayIndex = 0; ArrayIndex < this->GetEffectiveSizeZ(); ArrayIndex++)
|
|
{
|
|
// use the Copy Image functionality to copy mip level by mip level
|
|
for(uint32 MipIndex = 0;MipIndex < InNumMips;++MipIndex)
|
|
{
|
|
// Calculate the dimensions of the mip-map.
|
|
const uint32 DstMipIndex = MipIndex + DstOffset;
|
|
const uint32 SrcMipIndex = MipIndex + SrcOffset;
|
|
const uint32 MipSizeX = FMath::Max(this->GetSizeX() >> DstMipIndex,uint32(1));
|
|
const uint32 MipSizeY = FMath::Max(this->GetSizeY() >> DstMipIndex,uint32(1));
|
|
|
|
if(FOpenGL::AmdWorkaround() && ((MipSizeX < 4) || (MipSizeY < 4))) break;
|
|
|
|
// copy the texture data
|
|
FOpenGL::CopyImageSubData(Src->GetResource(), Src->Target, SrcMipIndex, 0, 0, ArrayIndex,
|
|
GetResource(), Target, DstMipIndex, 0, 0, ArrayIndex, MipSizeX, MipSizeY, 1);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
2D texture support.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
static UE::RHICore::FDefaultLayoutTextureInitializer CreateWritableTextureInitializer(FRHICommandListBase& RHICmdList, FOpenGLTexture* Texture, const FRHITextureCreateDesc& CreateDesc)
|
|
{
|
|
const uint64 WritableMemorySize = UE::RHITextureUtils::CalculateTextureSize(CreateDesc);
|
|
void* WritableMemory = FMemory::Malloc(WritableMemorySize, 16);
|
|
|
|
// TODO: when not evicted, we can use PixelBuffer memory for the Initializer. This may get too complicated to justify.
|
|
|
|
return UE::RHICore::FDefaultLayoutTextureInitializer(RHICmdList, Texture, WritableMemory, WritableMemorySize,
|
|
[Texture = TRefCountPtr<FOpenGLTexture>(Texture), WritableMemory = UE::RHICore::FInitializerScopedMemory(WritableMemory), WritableMemorySize](FRHICommandListBase& RHICmdList) mutable
|
|
{
|
|
RHICmdList.EnqueueLambda(
|
|
[Texture, WritableMemory = MoveTemp(WritableMemory), WritableMemorySize](FRHICommandListBase& RHICmdList)
|
|
{
|
|
FOpenGLDynamicRHI::Get().InitializeGLTexture(Texture, nullptr, 0);
|
|
|
|
const FRHITextureDesc& TextureDesc = Texture->GetDesc();
|
|
const uint32 FaceCount = TextureDesc.IsTextureCube() ? 6 : 1;
|
|
|
|
for (uint32 FaceIndex = 0; FaceIndex < FaceCount; FaceIndex++)
|
|
{
|
|
for (uint32 ArrayIndex = 0; ArrayIndex < TextureDesc.ArraySize; ArrayIndex++)
|
|
{
|
|
for (uint32 MipIndex = 0; MipIndex < TextureDesc.NumMips; MipIndex++)
|
|
{
|
|
uint64 SubresourceStride = 0;
|
|
uint64 SubresourceSize = 0;
|
|
const uint64 Offset = UE::RHITextureUtils::CalculateSubresourceOffset(TextureDesc, FaceIndex, ArrayIndex, MipIndex, SubresourceStride, SubresourceSize);
|
|
|
|
check((Offset + SubresourceSize) <= WritableMemorySize);
|
|
|
|
const uint32 GLArrayIndex = FaceIndex + ArrayIndex * FaceCount;
|
|
|
|
uint32 DestStride = 0;
|
|
void* DestinationMemory = Texture->Lock(MipIndex, GLArrayIndex, RLM_WriteOnly, DestStride);
|
|
|
|
FMemory::Memcpy(DestinationMemory, reinterpret_cast<const uint8*>(WritableMemory.Pointer) + Offset, SubresourceSize);
|
|
Texture->Unlock(MipIndex, GLArrayIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
return TRefCountPtr<FRHITexture>(MoveTemp(Texture));
|
|
}
|
|
);
|
|
}
|
|
|
|
FRHITextureInitializer FOpenGLDynamicRHI::RHICreateTextureInitializer(FRHICommandListBase& RHICmdList, const FRHITextureCreateDesc& CreateDesc)
|
|
{
|
|
LLM_SCOPE_DYNAMIC_STAT_OBJECTPATH_FNAME(CreateDesc.OwnerName, ELLMTagSet::Assets);
|
|
LLM_SCOPE_DYNAMIC_STAT_OBJECTPATH_FNAME(CreateDesc.GetTraceClassName(), ELLMTagSet::AssetClasses);
|
|
UE_TRACE_METADATA_SCOPE_ASSET_FNAME(CreateDesc.DebugName, CreateDesc.GetTraceClassName(), CreateDesc.OwnerName);
|
|
|
|
FOpenGLTexture* Texture = new FOpenGLTexture(CreateDesc);
|
|
|
|
if (CreateDesc.InitAction == ERHITextureInitAction::Default)
|
|
{
|
|
return UE::RHICore::FBaseTextureInitializerImplementation(RHICmdList, Texture,
|
|
[Texture = TRefCountPtr<FOpenGLTexture>(Texture)](FRHICommandListBase& RHICmdList) mutable
|
|
{
|
|
Texture->Initialize(RHICmdList);
|
|
return TRefCountPtr<FRHITexture>(MoveTemp(Texture));
|
|
}
|
|
);
|
|
}
|
|
|
|
if (CreateDesc.InitAction == ERHITextureInitAction::BulkData)
|
|
{
|
|
check(CreateDesc.BulkData);
|
|
|
|
UE::RHICore::FDefaultLayoutTextureInitializer Initializer = CreateWritableTextureInitializer(RHICmdList, Texture, CreateDesc);
|
|
check(CreateDesc.BulkData->GetResourceBulkDataSize() <= Initializer.GetWritableSize());
|
|
|
|
FMemory::Memcpy(Initializer.GetWritableData(), CreateDesc.BulkData->GetResourceBulkData(), CreateDesc.BulkData->GetResourceBulkDataSize());
|
|
|
|
CreateDesc.BulkData->Discard();
|
|
|
|
return MoveTemp(Initializer);
|
|
}
|
|
|
|
if (CreateDesc.InitAction == ERHITextureInitAction::Initializer)
|
|
{
|
|
return CreateWritableTextureInitializer(RHICmdList, Texture, CreateDesc);
|
|
}
|
|
|
|
return UE::RHICore::HandleUnknownTextureInitializerInitAction(RHICmdList, CreateDesc);
|
|
}
|
|
|
|
|
|
FTextureRHIRef FOpenGLDynamicRHI::RHIAsyncCreateTexture2D(uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags Flags, ERHIAccess InResourceState, void** InitialMipData, uint32 NumInitialMips, const TCHAR* DebugName, FGraphEventRef& OutCompletionEvent)
|
|
{
|
|
check(0);
|
|
return FTextureRHIRef();
|
|
}
|
|
|
|
/** Generates mip maps for the surface. */
|
|
void FOpenGLDynamicRHI::RHIGenerateMips(FRHITexture* TextureRHI)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
#if (RHI_NEW_GPU_PROFILER == 0)
|
|
RegisterGPUWork(0);
|
|
#endif
|
|
|
|
FOpenGLTexture* Texture = ResourceCast(TextureRHI);
|
|
// Setup the texture on a disused unit
|
|
// need to figure out how to setup mips properly in no views case
|
|
CachedSetupTextureStage(FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Texture->Target, Texture->GetResource(), -1, Texture->GetNumMips());
|
|
FOpenGL::GenerateMipmap(Texture->Target);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Computes the size in memory required by a given texture.
|
|
*
|
|
* @param TextureRHI - Texture we want to know the size of
|
|
* @return - Size in Bytes
|
|
*/
|
|
uint32 FOpenGLDynamicRHI::RHIComputeMemorySize(FRHITexture* TextureRHI)
|
|
{
|
|
if (!TextureRHI)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
FOpenGLTexture* Texture = ResourceCast(TextureRHI);
|
|
return Texture->MemorySize;
|
|
}
|
|
|
|
FTextureRHIRef FOpenGLDynamicRHI::AsyncReallocateTexture2D_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITexture* Texture2DRHI, int32 NewMipCount, int32 NewSizeX, int32 NewSizeY, FThreadSafeCounter* RequestStatus)
|
|
{
|
|
return this->RHIAsyncReallocateTexture2D(Texture2DRHI, NewMipCount, NewSizeX, NewSizeY, RequestStatus);
|
|
}
|
|
|
|
FTextureRHIRef FOpenGLDynamicRHI::RHIAsyncReallocateTexture2D(FRHITexture* Texture2DRHI, int32 NewMipCount, int32 NewSizeX, int32 NewSizeY, FThreadSafeCounter* RequestStatus)
|
|
{
|
|
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
|
|
|
|
FOpenGLTexture* OldTexture = ResourceCast(Texture2DRHI);
|
|
|
|
FRHITextureDesc Desc = OldTexture->GetDesc();
|
|
int32 SourceMipCount = Desc.NumMips;
|
|
|
|
Desc.Extent = FIntPoint(NewSizeX, NewSizeY);
|
|
Desc.NumMips = NewMipCount;
|
|
|
|
FRHITextureCreateDesc CreateDesc(
|
|
Desc,
|
|
RHIGetDefaultResourceState(Desc.Flags, false),
|
|
TEXT("RHIAsyncReallocateTexture2D")
|
|
);
|
|
CreateDesc.SetOwnerName(OldTexture->GetOwnerName());
|
|
|
|
LLM_SCOPE_DYNAMIC_STAT_OBJECTPATH_FNAME(CreateDesc.OwnerName, ELLMTagSet::Assets);
|
|
LLM_SCOPE_DYNAMIC_STAT_OBJECTPATH_FNAME(CreateDesc.GetTraceClassName(), ELLMTagSet::AssetClasses);
|
|
UE_TRACE_METADATA_SCOPE_ASSET_FNAME(CreateDesc.DebugName, CreateDesc.GetTraceClassName(), CreateDesc.OwnerName);
|
|
|
|
FOpenGLTexture* NewTexture = new FOpenGLTexture(CreateDesc);
|
|
|
|
RHICmdList.EnqueueLambda([OldTexture, NewTexture, SourceMipCount, NewMipCount, RequestStatus](FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
|
|
NewTexture->Initialize(RHICmdList);
|
|
|
|
// Use the GPU to asynchronously copy the old mip-maps into the new texture.
|
|
const uint32 NumSharedMips = FMath::Min(SourceMipCount, NewMipCount);
|
|
const uint32 SourceMipOffset = SourceMipCount - NumSharedMips;
|
|
const uint32 DestMipOffset = NewMipCount - NumSharedMips;
|
|
|
|
NewTexture->CloneViaCopyImage(OldTexture, NumSharedMips, SourceMipOffset, DestMipOffset);
|
|
|
|
RequestStatus->Decrement();
|
|
});
|
|
|
|
return NewTexture;
|
|
}
|
|
|
|
void FOpenGLDynamicRHI::RHIUpdateTexture2D(FRHICommandListBase& RHICmdList, FRHITexture* TextureRHI, uint32 MipIndex, const FUpdateTextureRegion2D& UpdateRegion, uint32 SourcePitch, const uint8* SourceData)
|
|
{
|
|
const FPixelFormatInfo& FormatInfo = GPixelFormats[TextureRHI->GetFormat()];
|
|
|
|
check(UpdateRegion.Width % FormatInfo.BlockSizeX == 0);
|
|
check(UpdateRegion.Height % FormatInfo.BlockSizeY == 0);
|
|
check(UpdateRegion.DestX % FormatInfo.BlockSizeX == 0);
|
|
check(UpdateRegion.DestY % FormatInfo.BlockSizeY == 0);
|
|
check(UpdateRegion.SrcX % FormatInfo.BlockSizeX == 0);
|
|
check(UpdateRegion.SrcY % FormatInfo.BlockSizeY == 0);
|
|
|
|
const uint32 SrcXInBlocks = FMath::DivideAndRoundUp<uint32>(UpdateRegion.SrcX, FormatInfo.BlockSizeX);
|
|
const uint32 SrcYInBlocks = FMath::DivideAndRoundUp<uint32>(UpdateRegion.SrcY, FormatInfo.BlockSizeY);
|
|
const uint32 WidthInBlocks = FMath::DivideAndRoundUp<uint32>(UpdateRegion.Width, FormatInfo.BlockSizeX);
|
|
const uint32 HeightInBlocks = FMath::DivideAndRoundUp<uint32>(UpdateRegion.Height, FormatInfo.BlockSizeY);
|
|
|
|
const void* UpdateMemory = SourceData + FormatInfo.BlockBytes * SrcXInBlocks + SourcePitch * SrcYInBlocks;
|
|
uint32 UpdatePitch = SourcePitch;
|
|
|
|
const bool bNeedStagingMemory = RHICmdList.IsTopOfPipe();
|
|
if (bNeedStagingMemory)
|
|
{
|
|
const size_t SourceDataSizeInBlocks = static_cast<size_t>(WidthInBlocks) * static_cast<size_t>(HeightInBlocks);
|
|
const size_t SourceDataSize = SourceDataSizeInBlocks * FormatInfo.BlockBytes;
|
|
|
|
uint8* const StagingMemory = (uint8*)FMemory::Malloc(SourceDataSize);
|
|
const size_t StagingPitch = static_cast<size_t>(WidthInBlocks) * FormatInfo.BlockBytes;
|
|
|
|
const uint8* CopySrc = (const uint8*)UpdateMemory;
|
|
uint8* CopyDst = (uint8*)StagingMemory;
|
|
for (uint32 BlockRow = 0; BlockRow < HeightInBlocks; BlockRow++)
|
|
{
|
|
FMemory::Memcpy(CopyDst, CopySrc, WidthInBlocks * FormatInfo.BlockBytes);
|
|
CopySrc += SourcePitch;
|
|
CopyDst += StagingPitch;
|
|
}
|
|
|
|
UpdateMemory = StagingMemory;
|
|
UpdatePitch = StagingPitch;
|
|
}
|
|
|
|
RHICmdList.EnqueueLambda([this, TextureRHI, MipIndex, UpdateRegion, UpdatePitch, UpdateMemory, bNeedStagingMemory](FRHICommandListBase&)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
|
|
FOpenGLTexture* Texture = ResourceCast(TextureRHI);
|
|
const EPixelFormat PixelFormat = TextureRHI->GetFormat();
|
|
|
|
const FPixelFormatInfo& FormatInfo = GPixelFormats[PixelFormat];
|
|
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[PixelFormat];
|
|
|
|
// Use a texture stage that's not likely to be used for draws, to avoid waiting
|
|
CachedSetupTextureStage(FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Texture->Target, Texture->GetResource(), 0, Texture->GetNumMips());
|
|
CachedBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, UpdatePitch / FormatInfo.BlockBytes);
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
|
|
if (GLFormat.bCompressed)
|
|
{
|
|
glCompressedTexSubImage2D(
|
|
Texture->Target,
|
|
MipIndex,
|
|
UpdateRegion.DestX, UpdateRegion.DestY,
|
|
UpdateRegion.Width, UpdateRegion.Height,
|
|
GLFormat.Format,
|
|
UpdatePitch * FMath::DivideAndRoundUp<uint32>(UpdateRegion.Height, FormatInfo.BlockSizeY),
|
|
UpdateMemory);
|
|
GL_CHECK();
|
|
}
|
|
else
|
|
{
|
|
glTexSubImage2D(
|
|
Texture->Target,
|
|
MipIndex,
|
|
UpdateRegion.DestX, UpdateRegion.DestY,
|
|
UpdateRegion.Width, UpdateRegion.Height,
|
|
GLFormat.Format,
|
|
GLFormat.Type,
|
|
UpdateMemory);
|
|
GL_CHECK();
|
|
}
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
|
|
// No need to restore texture stage; leave it like this,
|
|
// and the next draw will take care of cleaning it up; or
|
|
// next operation that needs the stage will switch something else in on it.
|
|
|
|
// free source data if we're on RHIT
|
|
if (bNeedStagingMemory)
|
|
{
|
|
FMemory::Free(const_cast<void*>(UpdateMemory));
|
|
}
|
|
});
|
|
}
|
|
|
|
void FOpenGLDynamicRHI::RHIUpdateTexture3D(FRHICommandListBase& RHICmdList, FRHITexture* TextureRHI, uint32 MipIndex, const FUpdateTextureRegion3D& UpdateRegion, uint32 SourceRowPitch, uint32 SourceDepthPitch, const uint8* SourceData)
|
|
{
|
|
uint8* RHITSourceData = nullptr;
|
|
if (RHICmdList.IsTopOfPipe())
|
|
{
|
|
uint32 DataSize = SourceDepthPitch * UpdateRegion.Depth;
|
|
RHITSourceData = (uint8*)FMemory::Malloc(DataSize, 16);
|
|
FMemory::Memcpy(RHITSourceData, SourceData, DataSize);
|
|
SourceData = RHITSourceData;
|
|
}
|
|
|
|
RHICmdList.EnqueueLambda([this, TextureRHI, MipIndex, UpdateRegion, SourceRowPitch, SourceDepthPitch, SourceData, RHITSourceData](FRHICommandListBase&)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
check(FOpenGL::SupportsTexture3D());
|
|
FOpenGLTexture* Texture = ResourceCast(TextureRHI);
|
|
|
|
// Use a texture stage that's not likely to be used for draws, to avoid waiting
|
|
CachedSetupTextureStage(FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Texture->Target, Texture->GetResource(), 0, Texture->GetNumMips());
|
|
CachedBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
|
|
|
EPixelFormat PixelFormat = Texture->GetFormat();
|
|
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[PixelFormat];
|
|
const FPixelFormatInfo& FormatInfo = GPixelFormats[PixelFormat];
|
|
const uint32 FormatBPP = FormatInfo.BlockBytes;
|
|
|
|
check(FOpenGL::SupportsTexture3D());
|
|
// TO DO - add appropriate offsets to source data when necessary
|
|
check(UpdateRegion.SrcX == 0);
|
|
check(UpdateRegion.SrcY == 0);
|
|
check(UpdateRegion.SrcZ == 0);
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
|
|
|
const bool bSRGB = EnumHasAnyFlags(Texture->GetFlags(), TexCreate_SRGB);
|
|
|
|
if (GLFormat.bCompressed)
|
|
{
|
|
FOpenGL::CompressedTexSubImage3D(
|
|
Texture->Target,
|
|
MipIndex,
|
|
UpdateRegion.DestX, UpdateRegion.DestY, UpdateRegion.DestZ,
|
|
UpdateRegion.Width, UpdateRegion.Height, UpdateRegion.Depth,
|
|
GLFormat.InternalFormat[bSRGB],
|
|
SourceDepthPitch * UpdateRegion.Depth,
|
|
SourceData);
|
|
}
|
|
else
|
|
{
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, UpdateRegion.Width / FormatInfo.BlockSizeX);
|
|
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, UpdateRegion.Height / FormatInfo.BlockSizeY);
|
|
|
|
FOpenGL::TexSubImage3D(Texture->Target, MipIndex, UpdateRegion.DestX, UpdateRegion.DestY, UpdateRegion.DestZ, UpdateRegion.Width, UpdateRegion.Height, UpdateRegion.Depth, GLFormat.Format, GLFormat.Type, SourceData);
|
|
}
|
|
|
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
|
|
|
// free source data if we're on RHIT
|
|
if (RHITSourceData)
|
|
{
|
|
FMemory::Free(RHITSourceData);
|
|
}
|
|
|
|
// No need to restore texture stage; leave it like this,
|
|
// and the next draw will take care of cleaning it up; or
|
|
// next operation that needs the stage will switch something else in on it.
|
|
});
|
|
}
|
|
|
|
void FOpenGLDynamicRHI::InvalidateTextureResourceInCache(GLuint Resource)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
auto InvalidateContextTextureResources = [&Resource](TArray<FTextureStage>& Textures)
|
|
{
|
|
for (FTextureStage& TextureStage : Textures)
|
|
{
|
|
if (TextureStage.Resource == Resource)
|
|
{
|
|
TextureStage.Target = GL_NONE;
|
|
TextureStage.Resource = 0;
|
|
}
|
|
}
|
|
};
|
|
InvalidateContextTextureResources(ContextState.Textures);
|
|
InvalidateContextTextureResources(PendingState.Textures);
|
|
|
|
TextureMipLimits.Remove(Resource);
|
|
|
|
if (PendingState.DepthStencil && PendingState.DepthStencil->GetResource() == Resource)
|
|
{
|
|
PendingState.DepthStencil = nullptr;
|
|
}
|
|
}
|
|
|
|
void FOpenGLDynamicRHI::InvalidateUAVResourceInCache(GLuint Resource)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
int32 NumUAVs = ContextState.UAVs.Num();
|
|
|
|
for (int32 UAVIndex = 0; UAVIndex < NumUAVs; ++UAVIndex)
|
|
{
|
|
if (ContextState.UAVs[UAVIndex].Resource == Resource)
|
|
{
|
|
ContextState.UAVs[UAVIndex].Format = GL_NONE;
|
|
ContextState.UAVs[UAVIndex].Resource = 0;
|
|
}
|
|
|
|
if (PendingState.UAVs[UAVIndex].Resource == Resource)
|
|
{
|
|
PendingState.UAVs[UAVIndex].Format = GL_NONE;
|
|
PendingState.UAVs[UAVIndex].Resource = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------------
|
|
Cubemap texture support.
|
|
-----------------------------------------------------------------------------*/
|
|
|
|
void FOpenGLDynamicRHI::RHIBindDebugLabelName(FRHICommandListBase& RHICmdList, FRHITexture* TextureRHI, const TCHAR* Name)
|
|
{
|
|
TextureRHI->SetName(Name);
|
|
|
|
#if GLDEBUG_LABELS_ENABLED
|
|
RHICmdList.EnqueueLambda(
|
|
[TextureRHI, Name = FAnsiString(Name)] (FRHICommandListBase& RHICmdList)
|
|
{
|
|
SetOpenGLResourceName(ResourceCast(TextureRHI), *Name);
|
|
}
|
|
);
|
|
#endif
|
|
}
|
|
|
|
void FOpenGLDynamicRHI::RHICopyTexture(FRHITexture* SourceTextureRHI, FRHITexture* DestTextureRHI, const FRHICopyTextureInfo& CopyInfo)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
FOpenGLTexture* SourceTexture = ResourceCast(SourceTextureRHI);
|
|
FOpenGLTexture* DestTexture = ResourceCast(DestTextureRHI);
|
|
|
|
//since the texture will be modified we cannot use the eviction data.
|
|
DestTexture->RemoveEvictionData();
|
|
|
|
GLsizei Width, Height, Depth;
|
|
|
|
if (CopyInfo.Size == FIntVector::ZeroValue)
|
|
{
|
|
const FRHITextureDesc& SourceDesc = SourceTextureRHI->GetDesc();
|
|
|
|
// Copy whole texture when zero vector is specified for region size.
|
|
FIntVector SrcTexSize = SourceDesc.GetSize();
|
|
Width = FMath::Max(1, SrcTexSize.X >> CopyInfo.SourceMipIndex);
|
|
Height = FMath::Max(1, SrcTexSize.Y >> CopyInfo.SourceMipIndex);
|
|
switch (SourceTexture->Target)
|
|
{
|
|
case GL_TEXTURE_3D: Depth = FMath::Max(1, SrcTexSize.Z >> CopyInfo.SourceMipIndex); break;
|
|
case GL_TEXTURE_CUBE_MAP: Depth = 6; break;
|
|
default: Depth = 1; break;
|
|
}
|
|
ensure(CopyInfo.SourcePosition == FIntVector::ZeroValue);
|
|
}
|
|
else
|
|
{
|
|
Width = CopyInfo.Size.X;
|
|
Height = CopyInfo.Size.Y;
|
|
switch (SourceTexture->Target)
|
|
{
|
|
case GL_TEXTURE_3D: Depth = CopyInfo.Size.Z; break;
|
|
case GL_TEXTURE_CUBE_MAP: Depth = CopyInfo.NumSlices; break;
|
|
default: Depth = 1; break;
|
|
}
|
|
}
|
|
|
|
GLint SrcMip = CopyInfo.SourceMipIndex;
|
|
GLint DestMip = CopyInfo.DestMipIndex;
|
|
|
|
for (uint32 MipIndex = 0; MipIndex < CopyInfo.NumMips; ++MipIndex)
|
|
{
|
|
// X, Y, Z
|
|
FIntVector Src, Dst;
|
|
|
|
auto SetOffsets = [MipIndex, &CopyInfo, &Depth] (const GLenum Target, const FIntVector& Position, const uint32& SliceIndex, FIntVector& OutOffsets)
|
|
{
|
|
switch (Target)
|
|
{
|
|
case GL_TEXTURE_3D:
|
|
case GL_TEXTURE_CUBE_MAP:
|
|
// For cube maps, the Z offsets select the starting faces.
|
|
OutOffsets.Z = Position.Z >> MipIndex;
|
|
break;
|
|
case GL_TEXTURE_1D_ARRAY:
|
|
case GL_TEXTURE_2D_ARRAY:
|
|
// For texture arrays, the Z offsets and depth actually refer to the range of slices to copy.
|
|
OutOffsets.Z = SliceIndex;
|
|
Depth = CopyInfo.NumSlices;
|
|
break;
|
|
default:
|
|
OutOffsets.Z = 0;
|
|
break;
|
|
}
|
|
|
|
OutOffsets.X = Position.X >> MipIndex;
|
|
OutOffsets.Y = Position.Y >> MipIndex;
|
|
};
|
|
|
|
SetOffsets(SourceTexture->Target, CopyInfo.SourcePosition, CopyInfo.SourceSliceIndex, Src);
|
|
SetOffsets(DestTexture->Target, CopyInfo.DestPosition, CopyInfo.DestSliceIndex, Dst);
|
|
|
|
FOpenGL::CopyImageSubData(SourceTexture->GetResource(), SourceTexture->Target, SrcMip, Src.X, Src.Y, Src.Z,
|
|
DestTexture->GetResource(), DestTexture->Target, DestMip, Dst.X, Dst.Y, Dst.Z,
|
|
Width, Height, Depth);
|
|
|
|
++SrcMip;
|
|
++DestMip;
|
|
|
|
Width = FMath::Max(1, Width >> 1);
|
|
Height = FMath::Max(1, Height >> 1);
|
|
if(DestTexture->Target == GL_TEXTURE_3D)
|
|
{
|
|
Depth = FMath::Max(1, Depth >> 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FTextureRHIRef FOpenGLDynamicRHI::RHICreateTexture2DFromResource(EPixelFormat Format, uint32 SizeX, uint32 SizeY, uint32 NumMips, uint32 NumSamples, uint32 NumSamplesTileMem, const FClearValueBinding& ClearValueBinding, GLuint Resource, ETextureCreateFlags TexCreateFlags)
|
|
{
|
|
const FRHITextureCreateDesc Desc =
|
|
FRHITextureCreateDesc::Create2D(TEXT("RHICreateTexture2DFromResource"), SizeX, SizeY, Format)
|
|
.SetClearValue(ClearValueBinding)
|
|
.SetFlags(TexCreateFlags)
|
|
.SetNumMips(NumMips)
|
|
.SetNumSamples(NumSamples)
|
|
.DetermineInititialState();
|
|
|
|
return new FOpenGLTexture(Desc, Resource);
|
|
}
|
|
|
|
#if PLATFORM_ANDROID
|
|
FTextureRHIRef FOpenGLDynamicRHI::RHICreateTexture2DFromAndroidHardwareBuffer(FRHICommandListBase& RHICmdList, AHardwareBuffer* HardwareBuffer)
|
|
{
|
|
check(HardwareBuffer);
|
|
|
|
AHardwareBuffer_Desc HardwareBufferDesc;
|
|
AHardwareBuffer_describe(HardwareBuffer, &HardwareBufferDesc);
|
|
check((HardwareBufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) != 0);
|
|
|
|
// OpenGL driver will auto convert the color space for YUV (tested 420 and P010).
|
|
// But we should test with different video formats to identify what is not supported.
|
|
// UE_LOG(LogRHI, Log, TEXT("[Media] hardware buffer format: 0x%x"), HardwareBufferDesc.format & 0xff);
|
|
|
|
// Temp for avoiding a checking failure of PF_Unknown in FRHITextureDesc::Validate()
|
|
EPixelFormat PixelFormat = PF_R8G8B8A8;
|
|
|
|
const FRHITextureCreateDesc Desc =
|
|
FRHITextureCreateDesc::Create2D(TEXT("OpenGLTexture2DFromAndroidHardwareBuffer"), HardwareBufferDesc.width, HardwareBufferDesc.height, PixelFormat)
|
|
.SetFlags(ETextureCreateFlags::External)
|
|
.DetermineInititialState();
|
|
|
|
return new FOpenGLTexture(RHICmdList, Desc, HardwareBufferDesc, HardwareBuffer);
|
|
}
|
|
#endif //PLATFORM_ANDROID
|
|
|
|
FTextureRHIRef FOpenGLDynamicRHI::RHICreateTexture2DArrayFromResource(EPixelFormat Format, uint32 SizeX, uint32 SizeY, uint32 ArraySize, uint32 NumMips, uint32 NumSamples, uint32 NumSamplesTileMem, const FClearValueBinding& ClearValueBinding, GLuint Resource, ETextureCreateFlags TexCreateFlags)
|
|
{
|
|
const FRHITextureCreateDesc Desc =
|
|
FRHITextureCreateDesc::Create2DArray(TEXT("RHICreateTexture2DArrayFromResource"), SizeX, SizeY, ArraySize, Format)
|
|
.SetClearValue(ClearValueBinding)
|
|
.SetFlags(TexCreateFlags)
|
|
.SetNumMips(NumMips)
|
|
.SetNumSamples(NumSamples)
|
|
.DetermineInititialState();
|
|
|
|
return new FOpenGLTexture(Desc, Resource);
|
|
}
|
|
|
|
FTextureRHIRef FOpenGLDynamicRHI::RHICreateTextureCubeFromResource(EPixelFormat Format, uint32 Size, bool bArray, uint32 ArraySize, uint32 NumMips, uint32 NumSamples, uint32 NumSamplesTileMem, const FClearValueBinding& ClearValueBinding, GLuint Resource, ETextureCreateFlags TexCreateFlags)
|
|
{
|
|
const FRHITextureCreateDesc Desc =
|
|
FRHITextureCreateDesc::Create(TEXT("RHICreateTextureCubeFromResource"), bArray ? ETextureDimension::TextureCube : ETextureDimension::TextureCubeArray)
|
|
.SetExtent(Size)
|
|
.SetArraySize(bArray ? ArraySize : 1)
|
|
.SetFormat(Format)
|
|
.SetClearValue(ClearValueBinding)
|
|
.SetFlags(TexCreateFlags)
|
|
.SetNumMips(NumMips)
|
|
.SetNumSamples(NumSamples)
|
|
.DetermineInititialState();
|
|
|
|
return new FOpenGLTexture(Desc, Resource);
|
|
}
|
|
|
|
void FOpenGLDynamicRHI::RHIAliasTextureResources(FTextureRHIRef& DestRHITexture, FTextureRHIRef& SrcRHITexture)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
FOpenGLTexture* DestTexture = ResourceCast(DestRHITexture);
|
|
FOpenGLTexture* SrcTexture = ResourceCast(SrcRHITexture);
|
|
|
|
if (DestTexture && SrcTexture)
|
|
{
|
|
DestTexture->AliasResources(*SrcTexture);
|
|
}
|
|
}
|
|
|
|
FTextureRHIRef FOpenGLDynamicRHI::RHICreateAliasedTexture(FTextureRHIRef& SourceTexture)
|
|
{
|
|
const FString Name = SourceTexture->GetName().ToString() + TEXT("Alias");
|
|
return new FOpenGLTexture(*ResourceCast(SourceTexture), *Name, FOpenGLTexture::AliasResource);
|
|
}
|
|
|
|
FRHILockTextureResult FOpenGLDynamicRHI::RHILockTexture(FRHICommandListImmediate& RHICmdList, const FRHILockTextureArgs& Arguments)
|
|
{
|
|
check(IsInRenderingThread());
|
|
check(Arguments.LockMode != EResourceLockMode::RLM_WriteOnly_NoOverwrite);
|
|
|
|
FOpenGLTexture* Texture = ResourceCast(Arguments.Texture);
|
|
const uint32 ArrayIndex = UE::RHICore::GetLockArrayIndex(Texture->GetDesc(), Arguments);
|
|
|
|
FRHILockTextureResult Result;
|
|
|
|
if (Arguments.LockMode == RLM_ReadOnly)
|
|
{
|
|
RHICmdList.EnqueueLambda([this, &Result, Texture, MipIndex = Arguments.MipIndex, ArrayIndex](FRHICommandListImmediate&)
|
|
{
|
|
Result.Data = Texture->Lock(MipIndex, ArrayIndex, RLM_ReadOnly, Result.Stride);
|
|
});
|
|
|
|
RHITHREAD_GLTRACE_BLOCKING;
|
|
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
|
|
Result.ByteCount = Texture->GetLockSize(Arguments.MipIndex, RLM_ReadOnly, Result.Stride);
|
|
}
|
|
else
|
|
{
|
|
Result.ByteCount = Texture->GetLockSize(Arguments.MipIndex, RLM_WriteOnly, Result.Stride);
|
|
Result.Data = FMemory::Malloc(Result.ByteCount, 16);
|
|
}
|
|
|
|
check(Result.Data);
|
|
GLLockTracker.Lock(Texture, Result.Data, ArrayIndex, Arguments.MipIndex, Result.Stride, Result.ByteCount, Arguments.LockMode);
|
|
return Result;
|
|
}
|
|
|
|
void FOpenGLDynamicRHI::RHIUnlockTexture(FRHICommandListImmediate& RHICmdList, const FRHILockTextureArgs& Arguments)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
FOpenGLTexture* Texture = ResourceCast(Arguments.Texture);
|
|
const uint32 ArrayIndex = UE::RHICore::GetLockArrayIndex(Texture->GetDesc(), Arguments);
|
|
|
|
FTextureLockTracker::FLockParams Params = GLLockTracker.Unlock(Texture, ArrayIndex, Arguments.MipIndex);
|
|
if (Params.LockMode == RLM_ReadOnly)
|
|
{
|
|
RHICmdList.EnqueueLambda([this, Texture, MipIndex = Arguments.MipIndex, ArrayIndex](FRHICommandListImmediate&)
|
|
{
|
|
Texture->Unlock(MipIndex, ArrayIndex);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
RHICmdList.EnqueueLambda([this, Params, Texture, MipIndex = Arguments.MipIndex, ArrayIndex](FRHICommandListImmediate&)
|
|
{
|
|
uint32 DestStride = 0;
|
|
void* DestinationMemory = Texture->Lock(MipIndex, ArrayIndex, RLM_WriteOnly, DestStride);
|
|
check(DestStride == Params.Stride);
|
|
|
|
FMemory::Memcpy(DestinationMemory, Params.Buffer, Params.BufferSize);
|
|
FMemory::Free(Params.Buffer);
|
|
|
|
Texture->Unlock(MipIndex, ArrayIndex);
|
|
});
|
|
}
|
|
}
|
|
|
|
void LogTextureEvictionDebugInfo()
|
|
{
|
|
static int counter = 0;
|
|
if (GOGLTextureEvictLogging && ++counter == 100)
|
|
{
|
|
UE_LOG(LogRHI, Warning, TEXT("txdbg: Texture mipmem %d. GTotalTexStorageSkipped %d, GTotalCompressedTexStorageSkipped %d, Total noncompressed = %d"), GTotalEvictedMipMemStored, GTotalTexStorageSkipped, GTotalCompressedTexStorageSkipped, GTotalTexStorageSkipped - GTotalCompressedTexStorageSkipped);
|
|
UE_LOG(LogRHI, Warning, TEXT("txdbg: Texture GTotalEvictedMipMemDuplicated %d"), GTotalEvictedMipMemDuplicated);
|
|
UE_LOG(LogRHI, Warning, TEXT("txdbg: Texture GTotalMipRestores %d, GTotalMipStoredCount %d"), GTotalMipRestores, GTotalMipStoredCount);
|
|
UE_LOG(LogRHI, Warning, TEXT("txdbg: Texture GAvgRestoreTime %f (%d), GMaxRestoreTime %f, TotalRestoreTime %f"), GAvgRestoreCount ? (float)(GAvgRestoreTime / GAvgRestoreCount) : 0.f, GAvgRestoreCount, (float)GMaxRestoreTime, (float)GAvgRestoreTime);
|
|
UE_LOG(LogRHI, Warning, TEXT("txdbg: Texture LRU %d"), FTextureEvictionLRU::Get().Num());
|
|
|
|
GAvgRestoreCount = 0;
|
|
GMaxRestoreTime = 0;
|
|
GAvgRestoreTime = 0;
|
|
|
|
counter = 0;
|
|
}
|
|
}
|
|
|
|
void FTextureEvictionLRU::TickEviction()
|
|
{
|
|
#if (UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT)
|
|
LogTextureEvictionDebugInfo();
|
|
#endif
|
|
|
|
FScopeLock Lock(&TextureLRULock);
|
|
FOpenGLTextureLRUContainer& TextureLRU = GetLRUContainer();
|
|
|
|
for (int32 EvictCount = 0;
|
|
TextureLRU.Num() > FMath::Max(0, GOGLTextureMinLRUCapacity) && (TextureLRU.GetLeastRecent()->EvictionParamsPtr->FrameLastRendered + GOGLTextureEvictFramesToLive) < GFrameNumberRenderThread && EvictCount < GOGLTexturesToEvictPerFrame
|
|
;EvictCount++)
|
|
{
|
|
FOpenGLTexture* RemovedFromLRU = TextureLRU.RemoveLeastRecent();
|
|
RemovedFromLRU->EvictionParamsPtr->LRUNode = FSetElementId();
|
|
RemovedFromLRU->TryEvictGLResource();
|
|
}
|
|
}
|
|
|
|
void FTextureEvictionLRU::Remove(FOpenGLTexture* TextureBase)
|
|
{
|
|
if( TextureBase->EvictionParamsPtr.IsValid() )
|
|
{
|
|
FScopeLock Lock(&TextureLRULock);
|
|
|
|
check(!TextureBase->EvictionParamsPtr->LRUNode.IsValidId() || GetLRUContainer().Contains(TextureBase));
|
|
check(TextureBase->EvictionParamsPtr->LRUNode.IsValidId() || !GetLRUContainer().Contains(TextureBase));
|
|
if( TextureBase->EvictionParamsPtr->LRUNode.IsValidId())
|
|
{
|
|
GetLRUContainer().Remove(TextureBase);
|
|
TextureBase->EvictionParamsPtr->LRUNode = FSetElementId();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FTextureEvictionLRU::Add(FOpenGLTexture* TextureBase)
|
|
{
|
|
FScopeLock Lock(&TextureLRULock);
|
|
check(TextureBase->EvictionParamsPtr);
|
|
check(!TextureBase->EvictionParamsPtr->LRUNode.IsValidId())
|
|
FOpenGLTextureLRUContainer& TextureLRU = GetLRUContainer();
|
|
check(!TextureLRU.Contains(TextureBase));
|
|
|
|
if(ensure(TextureLRU.Num() != TextureLRU.Max()))
|
|
{
|
|
TextureBase->EvictionParamsPtr->LRUNode = TextureLRU.Add(TextureBase, TextureBase);
|
|
TextureBase->EvictionParamsPtr->FrameLastRendered = GFrameNumberRenderThread;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FTextureEvictionLRU::Touch(FOpenGLTexture* TextureBase)
|
|
{
|
|
FScopeLock Lock(&TextureLRULock);
|
|
check(TextureBase->EvictionParamsPtr);
|
|
check(TextureBase->EvictionParamsPtr->LRUNode.IsValidId())
|
|
check(GetLRUContainer().Contains(TextureBase));
|
|
GetLRUContainer().MarkAsRecent(TextureBase->EvictionParamsPtr->LRUNode);
|
|
TextureBase->EvictionParamsPtr->FrameLastRendered = GFrameNumberRenderThread;
|
|
}
|
|
|
|
FOpenGLTexture* FTextureEvictionLRU::GetLeastRecent()
|
|
{
|
|
return GetLRUContainer().GetLeastRecent();
|
|
}
|
|
|
|
|
|
FTextureEvictionParams::FTextureEvictionParams(uint32 NumMips) : bHasRestored(0), FrameLastRendered(0)
|
|
{
|
|
MipImageData.Reserve(NumMips);
|
|
MipImageData.SetNum(NumMips);
|
|
}
|
|
|
|
FTextureEvictionParams::~FTextureEvictionParams()
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
|
|
if (bHasRestored)
|
|
{
|
|
GTotalEvictedMipMemDuplicated -= GetTotalAllocated();
|
|
}
|
|
|
|
for (int i = MipImageData.Num() - 1; i >= 0; i--)
|
|
{
|
|
GTotalEvictedMipMemStored -= MipImageData[i].Num();
|
|
}
|
|
GTotalMipStoredCount -= MipImageData.Num();
|
|
}
|
|
|
|
void FTextureEvictionParams::SetMipData(uint32 MipIndex, const void* Data, uint32 Bytes)
|
|
{
|
|
checkf(Bytes, TEXT("FTextureEvictionParams::SetMipData: MipIndex %d, Data %p, Bytes %d)"), MipIndex, Data, Bytes);
|
|
ensure(MipIndex < (uint32)MipImageData.Num());
|
|
|
|
VERIFY_GL_SCOPE();
|
|
if (MipImageData[MipIndex].Num() == 0)
|
|
{
|
|
GTotalMipStoredCount++;
|
|
}
|
|
MipImageData[MipIndex].Reserve(Bytes);
|
|
MipImageData[MipIndex].SetNumUninitialized(Bytes);
|
|
if (Data)
|
|
{
|
|
FMemory::Memcpy(MipImageData[MipIndex].GetData(), Data, Bytes);
|
|
}
|
|
GTotalEvictedMipMemStored += Bytes;
|
|
}
|
|
|
|
void FTextureEvictionParams::CloneMipData(const FTextureEvictionParams& Src, uint32 InNumMips, int32 SrcOffset, int DstOffset)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
|
|
int32 MaxMip = FMath::Min((int32)InNumMips, (int32)Src.MipImageData.Num() - SrcOffset);
|
|
for (int32 MipIndex = 0; MipIndex < MaxMip; ++MipIndex)
|
|
{
|
|
if (MipImageData[MipIndex + DstOffset].Num())
|
|
{
|
|
checkNoEntry();
|
|
}
|
|
else
|
|
{
|
|
GTotalMipStoredCount++;
|
|
}
|
|
MipImageData[MipIndex + DstOffset] = Src.MipImageData[MipIndex + SrcOffset];
|
|
GTotalEvictedMipMemStored += MipImageData[MipIndex + DstOffset].Num();
|
|
}
|
|
}
|
|
|
|
void FTextureEvictionParams::ReleaseMipData(uint32 RetainMips)
|
|
{
|
|
VERIFY_GL_SCOPE();
|
|
|
|
for (int i = MipImageData.Num() - 1 - RetainMips; i >= 0; i--)
|
|
{
|
|
GTotalEvictedMipMemStored -= MipImageData[i].Num();
|
|
GTotalMipStoredCount -= MipImageData[i].Num() ? 1 : 0;
|
|
MipImageData[i].Empty();
|
|
}
|
|
|
|
// if we're retaining mips then keep entire MipImageData array to ensure there's no MipIndex confusion.
|
|
if (RetainMips == 0)
|
|
{
|
|
MipImageData.Empty();
|
|
}
|
|
}
|