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

1149 lines
40 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
OpenGLRenderTarget.cpp: OpenGL render target implementation.
=============================================================================*/
#include "CoreMinimal.h"
#include "HAL/IConsoleManager.h"
#include "RHI.h"
#include "OpenGLDrv.h"
#include "OpenGLDrvPrivate.h"
#include "RHICore.h"
// gDEBugger is currently very buggy. For example, it cannot show render buffers correctly and doesn't
// know what combined depth/stencil is. This define makes OpenGL render directly to textures and disables
// stencil. It results in broken post process effects, but allows to debug the rendering in gDEBugger.
//#define GDEBUGGER_MODE
#define ALL_SLICES uint32(0xffffffff)
// GL_MAX_DRAW_BUFFERS value
GLint GMaxOpenGLDrawBuffers = 0;
/**
* Key used to map a set of unique render/depth stencil target combinations to
* a framebuffer resource
*/
class FOpenGLFramebufferKey
{
struct RenderTargetInfo
{
FOpenGLTexture* Texture;
GLuint Resource;
uint32 MipmapLevel;
uint32 ArrayIndex;
};
public:
FOpenGLFramebufferKey(
uint32 InNumRenderTargets,
FOpenGLTexture** InRenderTargets,
const uint32* InRenderTargetArrayIndices,
const uint32* InRenderTargetMipmapLevels,
FOpenGLTexture* InDepthStencilTarget,
FExclusiveDepthStencil DepthStencilAccess,
int32 InNumRenderingSamples
)
: DepthStencilTarget(InDepthStencilTarget)
, DepthStencilAccessIndex(DepthStencilAccess.GetIndex())
, NumRenderingSamples(InNumRenderingSamples)
{
uint32 RenderTargetIndex;
for( RenderTargetIndex = 0; RenderTargetIndex < InNumRenderTargets; ++RenderTargetIndex )
{
FMemory::Memzero(RenderTargets[RenderTargetIndex]); // since memcmp is used, we need to zero memory
RenderTargets[RenderTargetIndex].Texture = InRenderTargets[RenderTargetIndex];
RenderTargets[RenderTargetIndex].Resource = (InRenderTargets[RenderTargetIndex]) ? InRenderTargets[RenderTargetIndex]->GetResource() : 0;
RenderTargets[RenderTargetIndex].MipmapLevel = InRenderTargetMipmapLevels[RenderTargetIndex];
RenderTargets[RenderTargetIndex].ArrayIndex = (InRenderTargetArrayIndices == NULL || InRenderTargetArrayIndices[RenderTargetIndex] == -1) ? ALL_SLICES : InRenderTargetArrayIndices[RenderTargetIndex];
}
for( ; RenderTargetIndex < MaxSimultaneousRenderTargets; ++RenderTargetIndex )
{
FMemory::Memzero(RenderTargets[RenderTargetIndex]); // since memcmp is used, we need to zero memory
RenderTargets[RenderTargetIndex].ArrayIndex = ALL_SLICES;
}
}
/**
* Equality is based on render and depth stencil targets
* @param Other - instance to compare against
* @return true if equal
*/
friend bool operator ==(const FOpenGLFramebufferKey& A,const FOpenGLFramebufferKey& B)
{
return
!FMemory::Memcmp(A.RenderTargets, B.RenderTargets, sizeof(A.RenderTargets) ) &&
A.DepthStencilTarget == B.DepthStencilTarget &&
A.DepthStencilAccessIndex == B.DepthStencilAccessIndex &&
A.NumRenderingSamples == B.NumRenderingSamples;
}
/**
* Get the hash for this type.
* @param Key - struct to hash
* @return uint32 hash based on type
*/
friend uint32 GetTypeHash(const FOpenGLFramebufferKey &Key)
{
return FCrc::MemCrc_DEPRECATED(Key.RenderTargets, sizeof(Key.RenderTargets)) ^ GetTypeHash(Key.DepthStencilTarget) ^ GetTypeHash(Key.DepthStencilAccessIndex) ^ GetTypeHash(Key.NumRenderingSamples);
}
const FOpenGLTexture* GetRenderTarget( int32 Index ) const { return RenderTargets[Index].Texture; }
const FOpenGLTexture* GetDepthStencilTarget( void ) const { return DepthStencilTarget; }
int32 GetNumRenderingSamples( void ) const { return NumRenderingSamples; }
private:
RenderTargetInfo RenderTargets[MaxSimultaneousRenderTargets];
FOpenGLTexture* DepthStencilTarget;
uint32 DepthStencilAccessIndex; // we don't want to merge render passes that have different access to a depth buffer
int32 NumRenderingSamples; // MSAA on tile
};
static void ConditionallyAllocateRenderbufferStorage(FOpenGLTexture& RenderTarget)
{
if (RenderTarget.bMultisampleRenderbuffer &&
RenderTarget.GetAllocatedStorageForMip(0,0) == false)
{
check(RenderTarget.IsMultisampled());
check(RenderTarget.Target == GL_RENDERBUFFER);
GLuint TextureID = RenderTarget.GetRawResourceName();
const FRHITextureDesc& Desc = RenderTarget.GetDesc();
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[Desc.Format];
const bool bSRGB = EnumHasAnyFlags(Desc.Flags, TexCreate_SRGB);
glBindRenderbuffer(GL_RENDERBUFFER, TextureID);
FOpenGL::RenderbufferStorageMultisample(GL_RENDERBUFFER, Desc.NumSamples, GLFormat.InternalFormat[bSRGB], Desc.Extent.X, Desc.Extent.Y);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
RenderTarget.SetAllocatedStorage(true);
}
}
typedef TMap<FOpenGLFramebufferKey,GLuint> FOpenGLFramebufferCache;
/** Lazily initialized framebuffer cache singleton. */
static FOpenGLFramebufferCache& GetOpenGLFramebufferCache()
{
static FOpenGLFramebufferCache OpenGLFramebufferCache;
return OpenGLFramebufferCache;
}
GLuint FOpenGLDynamicRHI::GetOpenGLFramebuffer(uint32 NumSimultaneousRenderTargets, FOpenGLTexture** RenderTargets, const uint32* ArrayIndices, const uint32* MipmapLevels, FOpenGLTexture* DepthStencilTarget)
{
const int32 NumRenderingSamples = 1;
return GetOpenGLFramebuffer(NumSimultaneousRenderTargets, RenderTargets, ArrayIndices, MipmapLevels, DepthStencilTarget, FExclusiveDepthStencil(), NumRenderingSamples);
}
GLuint FOpenGLDynamicRHI::GetOpenGLFramebuffer(uint32 NumSimultaneousRenderTargets, FOpenGLTexture** RenderTargets, const uint32* ArrayIndices, const uint32* MipmapLevels, FOpenGLTexture* DepthStencilTarget, FExclusiveDepthStencil DepthStencilAccess, int32 NumRenderingSamples)
{
VERIFY_GL_SCOPE();
check( NumSimultaneousRenderTargets <= MaxSimultaneousRenderTargets );
uint32 FramebufferRet = GetOpenGLFramebufferCache().FindRef(FOpenGLFramebufferKey(NumSimultaneousRenderTargets, RenderTargets, ArrayIndices, MipmapLevels, DepthStencilTarget, DepthStencilAccess, NumRenderingSamples));
if( FramebufferRet > 0 )
{
// Found and is valid. We never store zero as a result, increasing all results by 1 to avoid range overlap.
return FramebufferRet-1;
}
const bool bRenderTargetsDefined = (RenderTargets != nullptr) && RenderTargets[0];
// Check for rendering to screen back buffer.
if (NumSimultaneousRenderTargets > 0 && bRenderTargetsDefined && RenderTargets[0]->GetResource() == GL_NONE)
{
// Use the default framebuffer (screen back/depth buffer)
return GL_NONE;
}
// Not found. Preparing new one.
GLuint Framebuffer;
glGenFramebuffers(1, &Framebuffer);
VERIFY_GL(glGenFramebuffer)
glBindFramebuffer(GL_FRAMEBUFFER, Framebuffer);
VERIFY_GL(glBindFramebuffer)
static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView"));
// Allocate mobile multi-view frame buffer if enabled and supported.
// Multi-view doesn't support read buffers, explicitly disable and only bind GL_DRAW_FRAMEBUFFER
// TODO: We can't reliably use packed depth stencil?
const bool bValidMultiViewDepthTarget = !DepthStencilTarget || DepthStencilTarget->Target == GL_TEXTURE_2D_ARRAY;
const bool bUsingArrayTextures = (bRenderTargetsDefined) ? (RenderTargets[0]->Target == GL_TEXTURE_2D_ARRAY && bValidMultiViewDepthTarget) : false;
const bool bMultiViewCVar = CVarMobileMultiView && CVarMobileMultiView->GetValueOnAnyThread() != 0;
if (bUsingArrayTextures && FOpenGL::SupportsMobileMultiView() && bMultiViewCVar)
{
FOpenGLTexture* const RenderTarget = RenderTargets[0];
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, Framebuffer);
if (NumRenderingSamples > 1)
{
FOpenGL::FramebufferTextureMultisampleMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, RenderTarget->GetResource(), 0, NumRenderingSamples, 0, 2);
VERIFY_GL(glFramebufferTextureMultisampleMultiviewOVR);
if (DepthStencilTarget)
{
FOpenGL::FramebufferTextureMultisampleMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, DepthStencilTarget->GetResource(), 0, NumRenderingSamples, 0, 2);
VERIFY_GL(glFramebufferTextureMultisampleMultiviewOVR);
}
}
else
{
FOpenGL::FramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, RenderTarget->GetResource(), 0, 0, 2);
VERIFY_GL(glFramebufferTextureMultiviewOVR);
if (DepthStencilTarget)
{
FOpenGL::FramebufferTextureMultiviewOVR(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, DepthStencilTarget->GetResource(), 0, 0, 2);
VERIFY_GL(glFramebufferTextureMultiviewOVR);
}
}
FOpenGL::CheckFrameBuffer();
FOpenGL::ReadBuffer(GL_NONE);
FOpenGL::DrawBuffer(GL_COLOR_ATTACHMENT0);
GetOpenGLFramebufferCache().Add(FOpenGLFramebufferKey(NumSimultaneousRenderTargets, RenderTargets, ArrayIndices, MipmapLevels, DepthStencilTarget, DepthStencilAccess, NumRenderingSamples), Framebuffer + 1);
return Framebuffer;
}
int32 FirstNonzeroRenderTarget = -1;
for (int32 RenderTargetIndex = NumSimultaneousRenderTargets - 1; RenderTargetIndex >= 0 && bRenderTargetsDefined; --RenderTargetIndex)
{
FOpenGLTexture* RenderTarget = RenderTargets[RenderTargetIndex];
if (!RenderTarget)
{
continue;
}
if (ArrayIndices == NULL || ArrayIndices[RenderTargetIndex] == -1)
{
// If no index was specified, bind the entire object, rather than a slice
switch (RenderTarget->Target)
{
case GL_RENDERBUFFER:
{
// lazily allocate render buffer storage in case it's multisampled
ConditionallyAllocateRenderbufferStorage(*RenderTarget);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, GL_RENDERBUFFER, RenderTarget->GetResource());
break;
}
case GL_TEXTURE_2D:
case GL_TEXTURE_EXTERNAL_OES:
case GL_TEXTURE_2D_MULTISAMPLE:
{
if (NumRenderingSamples > 1)
{
// GL_EXT_multisampled_render_to_texture
FOpenGL::FramebufferTexture2DMultisample(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, RenderTarget->Target, RenderTarget->GetResource(), MipmapLevels[RenderTargetIndex], NumRenderingSamples);
}
else
{
FOpenGL::FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, RenderTarget->Target, RenderTarget->GetResource(), MipmapLevels[RenderTargetIndex]);
}
break;
}
case GL_TEXTURE_3D:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_CUBE_MAP:
case GL_TEXTURE_CUBE_MAP_ARRAY:
FOpenGL::FramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, RenderTarget->GetResource(), MipmapLevels[RenderTargetIndex]);
break;
default:
FOpenGL::FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, GL_RENDERBUFFER, RenderTarget->GetResource());
break;
}
}
else
{
// Bind just one slice of the object
switch( RenderTarget->Target )
{
case GL_RENDERBUFFER:
{
check(ArrayIndices[RenderTargetIndex] == 0);
// lazily allocate render buffer storage in case it's multisampled
ConditionallyAllocateRenderbufferStorage(*RenderTarget);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, GL_RENDERBUFFER, RenderTarget->GetResource());
break;
}
case GL_TEXTURE_2D:
case GL_TEXTURE_EXTERNAL_OES:
case GL_TEXTURE_2D_MULTISAMPLE:
{
check(ArrayIndices[RenderTargetIndex] == 0);
if (NumRenderingSamples > 1)
{
// GL_EXT_multisampled_render_to_texture
FOpenGL::FramebufferTexture2DMultisample(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, RenderTarget->Target, RenderTarget->GetResource(), MipmapLevels[RenderTargetIndex], NumRenderingSamples);
}
else
{
FOpenGL::FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, RenderTarget->Target, RenderTarget->GetResource(), MipmapLevels[RenderTargetIndex]);
}
break;
}
case GL_TEXTURE_3D:
FOpenGL::FramebufferTexture3D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0 + RenderTargetIndex, RenderTarget->Target, RenderTarget->GetResource(), MipmapLevels[RenderTargetIndex], ArrayIndices[RenderTargetIndex]);
break;
case GL_TEXTURE_CUBE_MAP:
check( ArrayIndices[RenderTargetIndex] < 6);
FOpenGL::FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndices[RenderTargetIndex], RenderTarget->GetResource(), MipmapLevels[RenderTargetIndex]);
break;
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_CUBE_MAP_ARRAY:
FOpenGL::FramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, RenderTarget->GetResource(), MipmapLevels[RenderTargetIndex], ArrayIndices[RenderTargetIndex]);
break;
default:
check( ArrayIndices[RenderTargetIndex] == 0);
FOpenGL::FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + RenderTargetIndex, GL_RENDERBUFFER, RenderTarget->GetResource());
break;
}
}
FirstNonzeroRenderTarget = RenderTargetIndex;
}
if (DepthStencilTarget)
{
switch (DepthStencilTarget->Target)
{
case GL_TEXTURE_2D:
case GL_TEXTURE_EXTERNAL_OES:
case GL_TEXTURE_2D_MULTISAMPLE:
{
FOpenGL::FramebufferTexture2D(GL_FRAMEBUFFER, DepthStencilTarget->Attachment, DepthStencilTarget->Target, DepthStencilTarget->GetResource(), 0);
break;
}
case GL_RENDERBUFFER:
{
// lazily allocate render buffer storage in case it's multisampled
ConditionallyAllocateRenderbufferStorage(*DepthStencilTarget);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, DepthStencilTarget->GetResource());
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, DepthStencilTarget->GetResource());
VERIFY_GL(glFramebufferRenderbuffer);
break;
}
case GL_TEXTURE_3D:
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_CUBE_MAP:
case GL_TEXTURE_CUBE_MAP_ARRAY:
FOpenGL::FramebufferTexture(GL_FRAMEBUFFER, DepthStencilTarget->Attachment, DepthStencilTarget->GetResource(), 0);
break;
default:
FOpenGL::FramebufferRenderbuffer(GL_FRAMEBUFFER, DepthStencilTarget->Attachment, GL_RENDERBUFFER, DepthStencilTarget->GetResource());
break;
}
}
if (FirstNonzeroRenderTarget != -1)
{
FOpenGL::ReadBuffer(GL_COLOR_ATTACHMENT0 + FirstNonzeroRenderTarget);
FOpenGL::DrawBuffer(GL_COLOR_ATTACHMENT0 + FirstNonzeroRenderTarget);
}
else
{
FOpenGL::ReadBuffer(GL_NONE);
FOpenGL::DrawBuffer(GL_NONE);
}
// End frame can bind NULL / NULL
// An FBO with no attachments is framebuffer incomplete (INCOMPLETE_MISSING_ATTACHMENT)
// In this case just delete the FBO and map in the default
// In GL 4.x, NULL/NULL is valid and can be done =by specifying a default width/height
if ( FirstNonzeroRenderTarget == -1 && !DepthStencilTarget )
{
glDeleteFramebuffers( 1, &Framebuffer);
Framebuffer = 0;
glBindFramebuffer(GL_FRAMEBUFFER, Framebuffer);
}
FOpenGL::CheckFrameBuffer();
GetOpenGLFramebufferCache().Add(FOpenGLFramebufferKey(NumSimultaneousRenderTargets, RenderTargets, ArrayIndices, MipmapLevels, DepthStencilTarget, DepthStencilAccess, NumRenderingSamples), Framebuffer+1);
return Framebuffer;
}
void ReleaseOpenGLFramebuffers(FRHITexture* TextureRHI)
{
VERIFY_GL_SCOPE();
const FOpenGLTexture* Texture = FOpenGLDynamicRHI::ResourceCast(TextureRHI);
if (Texture)
{
for (FOpenGLFramebufferCache::TIterator It(GetOpenGLFramebufferCache()); It; ++It)
{
bool bPurgeFramebuffer = false;
FOpenGLFramebufferKey Key = It.Key();
const FOpenGLTexture* DepthStencilTarget = Key.GetDepthStencilTarget();
if( DepthStencilTarget && DepthStencilTarget->Target == Texture->Target && DepthStencilTarget->GetRawResourceName() == Texture->GetRawResourceName() )
{
bPurgeFramebuffer = true;
}
else
{
for( uint32 RenderTargetIndex = 0; RenderTargetIndex < MaxSimultaneousRenderTargets; ++RenderTargetIndex )
{
const FOpenGLTexture* RenderTarget = Key.GetRenderTarget(RenderTargetIndex);
if( RenderTarget && RenderTarget->Target == Texture->Target && RenderTarget->GetRawResourceName() == Texture->GetRawResourceName() )
{
bPurgeFramebuffer = true;
break;
}
}
}
if( bPurgeFramebuffer )
{
GLuint FramebufferToDelete = It.Value()-1;
check(FramebufferToDelete > 0);
FOpenGLDynamicRHI::Get().PurgeFramebufferFromCaches( FramebufferToDelete );
glDeleteFramebuffers( 1, &FramebufferToDelete );
It.RemoveCurrent();
}
}
}
}
void FOpenGLDynamicRHI::PurgeFramebufferFromCaches( GLuint Framebuffer )
{
VERIFY_GL_SCOPE();
if (Framebuffer == PendingState.Framebuffer)
{
PendingState.Framebuffer = 0;
FMemory::Memset(PendingState.RenderTargets, 0, sizeof(PendingState.RenderTargets));
FMemory::Memset(PendingState.RenderTargetMipmapLevels, 0, sizeof(PendingState.RenderTargetMipmapLevels));
FMemory::Memset(PendingState.RenderTargetArrayIndex, 0, sizeof(PendingState.RenderTargetArrayIndex));
PendingState.DepthStencil = 0;
PendingState.bFramebufferSetupInvalid = true;
}
if (Framebuffer == ContextState.Framebuffer)
{
ContextState.Framebuffer = -1;
}
}
void FOpenGLDynamicRHI::ReadSurfaceDataRaw(FRHITexture* TextureRHI, FIntRect Rect, TArray<uint8>& OutData, FReadSurfaceDataFlags InFlags)
{
VERIFY_GL_SCOPE();
FOpenGLTexture* Texture = ResourceCast(TextureRHI);
if( !Texture)
{
return; // just like in D3D11
}
GLuint FramebufferToDelete = 0;
GLuint RenderbufferToDelete = 0;
const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[TextureRHI->GetFormat()];
bool bFloatFormat = false;
bool bUnsupportedFormat = false;
bool bDepthFormat = false;
bool bDepthStencilFormat = false;
switch( TextureRHI->GetFormat() )
{
case PF_DepthStencil:
bDepthStencilFormat = true;
// pass-through
case PF_ShadowDepth:
case PF_D24:
bDepthFormat = true;
break;
case PF_A32B32G32R32F:
case PF_FloatRGBA:
case PF_FloatRGB:
case PF_R32_FLOAT:
case PF_G16R16F:
case PF_G16R16F_FILTER:
case PF_G32R32F:
case PF_R16F:
case PF_R16F_FILTER:
case PF_FloatR11G11B10:
bFloatFormat = true;
break;
case PF_DXT1:
case PF_DXT3:
case PF_DXT5:
case PF_UYVY:
case PF_BC5:
case PF_PVRTC2:
case PF_PVRTC4:
case PF_ATC_RGB:
case PF_ATC_RGBA_E:
case PF_ATC_RGBA_I:
bUnsupportedFormat = true;
break;
default: // the rest is assumed to be integer formats with one or more of ARG and B components in OpenGL
break;
}
if( bUnsupportedFormat )
{
#if UE_BUILD_DEBUG
check(0);
#endif
return;
}
check( !bDepthFormat || FOpenGL::SupportsDepthStencilReadSurface() );
check( !bFloatFormat || FOpenGL::SupportsFloatReadSurface() );
const GLenum Attachment = bDepthFormat ? (bDepthStencilFormat ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT) : GL_COLOR_ATTACHMENT0;
const bool bIsColorBuffer = (Texture->Attachment == GL_COLOR_ATTACHMENT0) || (Texture->Attachment == 0);
const uint32 MipmapLevel = InFlags.GetMip();
GLuint SourceFramebuffer = bIsColorBuffer ? GetOpenGLFramebuffer(1, &Texture, NULL, &MipmapLevel, NULL) : GetOpenGLFramebuffer(0, NULL, NULL, NULL, Texture);
if (TextureRHI->IsMultisampled())
{
// OpenGL doesn't allow to read pixels from multisample framebuffers, we need a single sample copy
glGenFramebuffers(1, &FramebufferToDelete);
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferToDelete);
GLuint Renderbuffer = 0;
glGenRenderbuffers(1, &RenderbufferToDelete);
glBindRenderbuffer(GL_RENDERBUFFER, RenderbufferToDelete);
glRenderbufferStorage(GL_RENDERBUFFER, GLFormat.InternalFormat[false], Texture->GetSizeX(), Texture->GetSizeY());
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, Attachment, GL_RENDERBUFFER, RenderbufferToDelete);
FOpenGL::CheckFrameBuffer();
glBindFramebuffer(UGL_READ_FRAMEBUFFER, SourceFramebuffer);
FOpenGL::BlitFramebuffer(
0, 0, Texture->GetSizeX(), Texture->GetSizeY(),
0, 0, Texture->GetSizeX(), Texture->GetSizeY(),
(bDepthFormat ? (bDepthStencilFormat ? (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT) : GL_DEPTH_BUFFER_BIT) : GL_COLOR_BUFFER_BIT),
GL_NEAREST
);
SourceFramebuffer = FramebufferToDelete;
}
uint32 SizeX = Rect.Width();
uint32 SizeY = Rect.Height();
OutData.Empty( SizeX * SizeY * sizeof(FColor) );
uint8* TargetBuffer = (uint8*)&OutData[OutData.AddUninitialized(SizeX * SizeY * sizeof(FColor))];
glBindFramebuffer(UGL_READ_FRAMEBUFFER, SourceFramebuffer);
FOpenGL::ReadBuffer( (!bDepthFormat && !bDepthStencilFormat && !SourceFramebuffer) ? GL_BACK : Attachment);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
if( bDepthFormat )
{
// Get the depth as luminosity, with non-transparent alpha.
// If depth values are between 0 and 1, keep them, otherwise rescale them linearly so they fit within 0-1 range
int32 DepthValueCount = SizeX * SizeY;
int32 FloatDepthDataSize = sizeof(float) * DepthValueCount;
float* FloatDepthData = (float*)FMemory::Malloc( FloatDepthDataSize );
glReadPixels(Rect.Min.X, Rect.Min.Y, SizeX, SizeY, GL_DEPTH_COMPONENT, GL_FLOAT, FloatDepthData );
// Determine minimal and maximal float value present in received data
float MinValue = FLT_MAX;
float MaxValue = FLT_MIN;
float* DataPtr = FloatDepthData;
for( int32 DepthValueIndex = 0; DepthValueIndex < DepthValueCount; ++DepthValueIndex, ++DataPtr )
{
if( *DataPtr < MinValue )
{
MinValue = *DataPtr;
}
if( *DataPtr > MaxValue )
{
MaxValue = *DataPtr;
}
}
// If necessary, rescale the data.
if( ( MinValue < 0.f ) || ( MaxValue > 1.f ) )
{
DataPtr = FloatDepthData;
float RescaleFactor = MaxValue - MinValue;
for( int32 DepthValueIndex = 0; DepthValueIndex < DepthValueCount; ++DepthValueIndex, ++DataPtr )
{
*DataPtr = ( *DataPtr - MinValue ) / RescaleFactor;
}
}
// Convert the data into rgba8 buffer
DataPtr = FloatDepthData;
uint8* TargetPtr = TargetBuffer;
for( int32 DepthValueIndex = 0; DepthValueIndex < DepthValueCount; ++DepthValueIndex )
{
uint8 Value = FColor::QuantizeUNormFloatTo8( *DataPtr++ );
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = 255;
}
FMemory::Free( FloatDepthData );
}
else if( bFloatFormat )
{
bool bLinearToGamma = InFlags.GetLinearToGamma();
// Determine minimal and maximal float value present in received data. Treat alpha separately.
int32 PixelComponentCount = 4 * SizeX * SizeY;
int32 FloatBGRADataSize = sizeof(float) * PixelComponentCount;
float* FloatBGRAData = (float*)FMemory::Malloc( FloatBGRADataSize );
if ( FOpenGL::SupportsBGRA8888() )
{
glReadPixels(Rect.Min.X, Rect.Min.Y, SizeX, SizeY, GL_BGRA, GL_FLOAT, FloatBGRAData );
GLenum Error = glGetError();
if (Error != GL_NO_ERROR)
{
glReadPixels(Rect.Min.X, Rect.Min.Y, SizeX, SizeY, GL_RGBA, GL_FLOAT, FloatBGRAData );
Error = glGetError();
if (Error == GL_NO_ERROR)
{
float* FloatDataPtr = FloatBGRAData;
float* FloatDataPtrEnd = FloatBGRAData + PixelComponentCount;
while (FloatDataPtr != FloatDataPtrEnd)
{
float Temp = FloatDataPtr[0];
FloatDataPtr[0] = FloatDataPtr[2];
FloatDataPtr[2] = Temp;
FloatDataPtr += 4;
}
}
}
}
else
{
glReadPixels(Rect.Min.X, Rect.Min.Y, SizeX, SizeY, GL_RGBA, GL_FLOAT, FloatBGRAData );
}
// Determine minimal and maximal float values present in received data. Treat each component separately.
float MinValue[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
float MaxValue[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
float* DataPtr = FloatBGRAData;
for( int32 PixelComponentIndex = 0; PixelComponentIndex < PixelComponentCount; ++PixelComponentIndex, ++DataPtr )
{
int32 ComponentIndex = PixelComponentIndex % 4;
MinValue[ComponentIndex] = FMath::Min<float>(*DataPtr,MinValue[ComponentIndex]);
MaxValue[ComponentIndex] = FMath::Max<float>(*DataPtr,MaxValue[ComponentIndex]);
}
// Convert the data into BGRA8 buffer
DataPtr = FloatBGRAData;
uint8* TargetPtr = TargetBuffer;
float RescaleFactor[4] = { MaxValue[0] - MinValue[0], MaxValue[1] - MinValue[1], MaxValue[2] - MinValue[2], MaxValue[3] - MinValue[3] };
for( int32 PixelIndex = 0; PixelIndex < PixelComponentCount / 4; ++PixelIndex )
{
float R = (DataPtr[2] - MinValue[2]) / RescaleFactor[2];
float G = (DataPtr[1] - MinValue[1]) / RescaleFactor[1];
float B = (DataPtr[0] - MinValue[0]) / RescaleFactor[0];
float A = (DataPtr[3] - MinValue[3]) / RescaleFactor[3];
if ( !FOpenGL::SupportsBGRA8888() )
{
Swap<float>( R,B );
}
FColor NormalizedColor = FLinearColor( R,G,B,A ).ToFColor(bLinearToGamma);
FMemory::Memcpy(TargetPtr,&NormalizedColor,sizeof(FColor));
DataPtr += 4;
TargetPtr += 4;
}
FMemory::Free( FloatBGRAData );
}
#if PLATFORM_ANDROID
else
{
// Flip texture data only for render targets, textures loaded from disk have Attachment set to 0 and don't need flipping.
const bool bFlipTextureData = Texture->Attachment != 0;
GLubyte* RGBAData = TargetBuffer;
if (bFlipTextureData)
{
// OpenGL ES is limited in what it can do with ReadPixels
const int32 PixelComponentCount = 4 * SizeX * SizeY;
const int32 RGBADataSize = sizeof(GLubyte) * PixelComponentCount;
RGBAData = (GLubyte*)FMemory::Malloc(RGBADataSize);
}
glReadPixels(Rect.Min.X, Rect.Min.Y, SizeX, SizeY, GL_RGBA, GL_UNSIGNED_BYTE, RGBAData);
if (bFlipTextureData)
{
//OpenGL ES reads the pixels "upside down" from what we're expecting (flipped vertically), so we need to transfer the data from the bottom up.
uint8* TargetPtr = TargetBuffer;
int32 Stride = SizeX * 4;
int32 FlipHeight = SizeY;
GLubyte* LinePtr = RGBAData + (SizeY - 1) * Stride;
while (FlipHeight--)
{
GLubyte* DataPtr = LinePtr;
int32 Pixels = SizeX;
while (Pixels--)
{
TargetPtr[0] = DataPtr[2];
TargetPtr[1] = DataPtr[1];
TargetPtr[2] = DataPtr[0];
TargetPtr[3] = DataPtr[3];
DataPtr += 4;
TargetPtr += 4;
}
LinePtr -= Stride;
}
FMemory::Free(RGBAData);
}
}
#else
else
{
// It's a simple int format. OpenGL converts them internally to what we want.
glReadPixels(Rect.Min.X, Rect.Min.Y, SizeX, SizeY, GL_BGRA, UGL_ABGR8, TargetBuffer );
}
#endif
glPixelStorei(GL_PACK_ALIGNMENT, 4);
if( FramebufferToDelete )
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers( 1, &FramebufferToDelete );
}
if( RenderbufferToDelete )
{
glDeleteRenderbuffers( 1, &RenderbufferToDelete );
}
ContextState.Framebuffer = (GLuint)-1;
}
void FOpenGLDynamicRHI::RHIReadSurfaceData(FRHITexture* TextureRHI,FIntRect Rect,TArray<FColor>& OutData, FReadSurfaceDataFlags InFlags)
{
const uint32 Size = Rect.Width() * Rect.Height();
OutData.SetNumUninitialized(Size);
if (!ensure(TextureRHI))
{
FMemory::Memzero(OutData.GetData(), Size * sizeof(FColor));
return;
}
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RHICmdList.EnqueueLambda([&](FRHICommandListImmediate&)
{
TArray<uint8> Temp;
ReadSurfaceDataRaw(TextureRHI, Rect, Temp, InFlags);
FMemory::Memcpy(OutData.GetData(), Temp.GetData(), Size * sizeof(FColor));
});
RHITHREAD_GLTRACE_BLOCKING;
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
}
void FOpenGLDynamicRHI::RHIReadSurfaceData(FRHITexture* TextureRHI, FIntRect Rect, TArray<FLinearColor>& OutData, FReadSurfaceDataFlags InFlags)
{
// Verify requirements, but don't crash
// Ignore texture format here, GL will convert it for us in glReadPixels
if (!ensure(FOpenGL::SupportsFloatReadSurface()) || !ensure(TextureRHI))
{
return;
}
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RHICmdList.EnqueueLambda([&](FRHICommandListImmediate&)
{
VERIFY_GL_SCOPE();
FOpenGLTexture* Texture = ResourceCast(TextureRHI);
if (!ensure(Texture))
{
return;
}
// Get framebuffer for texture
const uint32 MipmapLevel = InFlags.GetMip();
GLuint SourceFramebuffer = GetOpenGLFramebuffer(1, &Texture, NULL, &MipmapLevel, NULL);
uint32 SizeX = Rect.Width();
uint32 SizeY = Rect.Height();
// Initialize output
OutData.SetNumUninitialized(SizeX * SizeY);
// Bind the framebuffer
// @TODO: Do we need to worry about multisampling?
glBindFramebuffer(UGL_READ_FRAMEBUFFER, SourceFramebuffer);
FOpenGL::ReadBuffer(SourceFramebuffer == 0 ? GL_BACK : GL_COLOR_ATTACHMENT0);
// Read the float data from the buffer directly into the output data
// @TODO: Do we need to support BGRA?
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(Rect.Min.X, Rect.Min.Y, SizeX, SizeY, GL_RGBA, GL_FLOAT, OutData.GetData());
glPixelStorei(GL_PACK_ALIGNMENT, 4);
ContextState.Framebuffer = (GLuint)-1;
});
RHITHREAD_GLTRACE_BLOCKING;
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
}
void FOpenGLDynamicRHI::RHIMapStagingSurface_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITexture* TextureRHI, uint32 GPUIndex, FRHIGPUFence* Fence, void*& OutData, int32& OutWidth, int32& OutHeight)
{
// Fence is not important, GL driver handles synchonization
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
RHICmdList.EnqueueLambda([&](FRHICommandListImmediate&)
{
VERIFY_GL_SCOPE();
FOpenGLTexture* Texture = ResourceCast(TextureRHI->GetTexture2D());
check(Texture);
check(EnumHasAnyFlags(Texture->GetDesc().Flags, TexCreate_CPUReadback));
OutWidth = Texture->GetSizeX();
OutHeight = Texture->GetSizeY();
uint32 Stride = 0;
OutData = Texture->Lock( 0, 0, RLM_ReadOnly, Stride );
});
RHITHREAD_GLTRACE_BLOCKING;
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
}
void FOpenGLDynamicRHI::RHIUnmapStagingSurface_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITexture* TextureRHI, uint32 GPUIndex)
{
RHICmdList.EnqueueLambda([TextureRHI = TextureRHI](FRHICommandListImmediate&)
{
VERIFY_GL_SCOPE();
FOpenGLTexture* Texture = ResourceCast(TextureRHI->GetTexture2D());
check(Texture);
Texture->Unlock( 0, 0 );
});
}
void FOpenGLDynamicRHI::RHIMapStagingSurface(FRHITexture* TextureRHI, FRHIGPUFence* FenceRHI, void*& OutData, int32& OutWidth, int32& OutHeight, uint32 GPUIndex)
{
// Everything is handled in RHIMapStagingSurface_RenderThread. This function should not be called directly.
checkNoEntry();
}
void FOpenGLDynamicRHI::RHIUnmapStagingSurface(FRHITexture* TextureRHI, uint32 GPUIndex)
{
// Everything is handled in RHIUnmapStagingSurface_RenderThread. This function should not be called directly.
checkNoEntry();
}
void FOpenGLDynamicRHI::RHIReadSurfaceFloatData(FRHITexture* TextureRHI,FIntRect Rect,TArray<FFloat16Color>& OutData,ECubeFace CubeFace,int32 ArrayIndex,int32 MipIndex)
{
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RHICmdList.EnqueueLambda([&](FRHICommandListImmediate&)
{
VERIFY_GL_SCOPE();
//reading from arrays only supported on SM5 and up.
check(FOpenGL::SupportsFloatReadSurface() && (ArrayIndex == 0 || GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM5));
FOpenGLTexture* Texture = ResourceCast(TextureRHI);
check(TextureRHI->GetFormat() == PF_FloatRGBA);
const uint32 MipmapLevel = MipIndex;
// Temp FBO is introduced to prevent a ballooning of FBO objects, which can have a detrimental
// impact on object management performance in the driver, only for CubeMapArray presently
// as it is the target that really drives FBO permutations
const bool bTempFBO = Texture->Target == GL_TEXTURE_CUBE_MAP_ARRAY;
uint32 Index = uint32(CubeFace) + ( (Texture->Target == GL_TEXTURE_CUBE_MAP_ARRAY) ? 6 : 1) * ArrayIndex;
GLuint SourceFramebuffer = 0;
if (bTempFBO)
{
glGenFramebuffers( 1, &SourceFramebuffer);
glBindFramebuffer(UGL_READ_FRAMEBUFFER, SourceFramebuffer);
FOpenGL::FramebufferTextureLayer(UGL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, Texture->GetResource(), MipmapLevel, Index);
}
else
{
SourceFramebuffer = GetOpenGLFramebuffer(1, &Texture, &Index, &MipmapLevel, NULL);
}
uint32 SizeX = Rect.Width();
uint32 SizeY = Rect.Height();
OutData.SetNumUninitialized(SizeX * SizeY);
glBindFramebuffer(UGL_READ_FRAMEBUFFER, SourceFramebuffer);
FOpenGL::ReadBuffer(SourceFramebuffer == 0 ? GL_BACK : GL_COLOR_ATTACHMENT0);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
if (FOpenGL::GetReadHalfFloatPixelsEnum() == GL_FLOAT)
{
// Slow path: Some Adreno devices won't work with HALF_FLOAT ReadPixels
TArray<FLinearColor> FloatData;
// 4 float components per texel (RGBA)
FloatData.AddUninitialized(SizeX * SizeY);
FMemory::Memzero(FloatData.GetData(),SizeX * SizeY*sizeof(FLinearColor));
glReadPixels(Rect.Min.X, Rect.Min.Y, SizeX, SizeY, GL_RGBA, GL_FLOAT, FloatData.GetData());
FLinearColor* FloatDataPtr = FloatData.GetData();
for (uint32 DataIndex = 0; DataIndex < SizeX * SizeY; ++DataIndex, ++FloatDataPtr)
{
OutData[DataIndex] = FFloat16Color(*FloatDataPtr);
}
}
else
{
glReadPixels(Rect.Min.X, Rect.Min.Y, SizeX, SizeY, GL_RGBA, FOpenGL::GetReadHalfFloatPixelsEnum(), OutData.GetData());
}
glPixelStorei(GL_PACK_ALIGNMENT, 4);
if (bTempFBO)
{
glDeleteFramebuffers( 1, &SourceFramebuffer);
}
ContextState.Framebuffer = (GLuint)-1;
});
RHITHREAD_GLTRACE_BLOCKING;
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
}
void FOpenGLDynamicRHI::RHIRead3DSurfaceFloatData(FRHITexture* TextureRHI,FIntRect Rect,FIntPoint ZMinMax,TArray<FFloat16Color>& OutData)
{
FRHICommandListImmediate& RHICmdList = FRHICommandListImmediate::Get();
RHICmdList.EnqueueLambda([&](FRHICommandListImmediate&)
{
VERIFY_GL_SCOPE();
check( FOpenGL::SupportsFloatReadSurface() );
check( FOpenGL::SupportsTexture3D() );
check( TextureRHI->GetFormat() == PF_FloatRGBA );
FOpenGLTexture* Texture = ResourceCast(TextureRHI);
uint32 SizeX = Rect.Width();
uint32 SizeY = Rect.Height();
uint32 SizeZ = ZMinMax.Y - ZMinMax.X;
// Allocate the output buffer.
OutData.SetNumUninitialized(SizeX * SizeY * SizeZ);
// Set up the source as a temporary FBO
uint32 MipmapLevel = 0;
uint32 Index = 0;
GLuint SourceFramebuffer = 0;
glGenFramebuffers( 1, &SourceFramebuffer);
glBindFramebuffer(UGL_READ_FRAMEBUFFER, SourceFramebuffer);
// Set up the destination as a temporary texture
GLuint TempTexture = 0;
FOpenGL::GenTextures(1, &TempTexture);
glActiveTexture( GL_TEXTURE0 );
glBindTexture( GL_TEXTURE_3D, TempTexture );
FOpenGL::TexImage3D( GL_TEXTURE_3D, 0, GL_RGBA16F, SizeX, SizeY, SizeZ, 0, GL_RGBA, GL_HALF_FLOAT, NULL );
// Copy the pixels within the specified region, minimizing the amount of data that needs to be transferred from GPU to CPU memory
for ( uint32 Z=0; Z < SizeZ; ++Z )
{
FOpenGL::FramebufferTextureLayer(UGL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, Texture->GetResource(), MipmapLevel, ZMinMax.X + Z);
FOpenGL::ReadBuffer(SourceFramebuffer == 0 ? GL_BACK : GL_COLOR_ATTACHMENT0);
FOpenGL::CopyTexSubImage3D( GL_TEXTURE_3D, 0, 0, 0, Z, Rect.Min.X, Rect.Min.Y, SizeX, SizeY );
}
// Grab the raw data from the temp texture.
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
FOpenGL::GetTexImage( GL_TEXTURE_3D, 0, GL_RGBA, GL_HALF_FLOAT, OutData.GetData() );
glPixelStorei( GL_PACK_ALIGNMENT, 4 );
// Clean up
auto& TextureState = ContextState.Textures[0];
glBindTexture(GL_TEXTURE_3D, (TextureState.Target == GL_TEXTURE_3D) ? TextureState.Resource : 0);
glActiveTexture( GL_TEXTURE0 + ContextState.ActiveTexture );
glDeleteFramebuffers( 1, &SourceFramebuffer);
FOpenGL::DeleteTextures( 1, &TempTexture );
ContextState.Framebuffer = (GLuint)-1;
});
RHITHREAD_GLTRACE_BLOCKING;
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
}
void FOpenGLDynamicRHI::BindPendingFramebuffer()
{
VERIFY_GL_SCOPE();
check((GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM5) || !PendingState.bFramebufferSetupInvalid);
if (ContextState.Framebuffer != PendingState.Framebuffer)
{
if (PendingState.Framebuffer)
{
glBindFramebuffer(GL_FRAMEBUFFER, PendingState.Framebuffer);
FOpenGL::ReadBuffer( PendingState.FirstNonzeroRenderTarget >= 0 ? GL_COLOR_ATTACHMENT0 + PendingState.FirstNonzeroRenderTarget : GL_NONE);
GLenum DrawFramebuffers[MaxSimultaneousRenderTargets];
const GLint MaxDrawBuffers = GMaxOpenGLDrawBuffers;
for (int32 RenderTargetIndex = 0; RenderTargetIndex < MaxDrawBuffers; ++RenderTargetIndex)
{
DrawFramebuffers[RenderTargetIndex] = PendingState.RenderTargets[RenderTargetIndex] ? GL_COLOR_ATTACHMENT0 + RenderTargetIndex : GL_NONE;
}
FOpenGL::DrawBuffers(MaxDrawBuffers, DrawFramebuffers);
}
else
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
FOpenGL::ReadBuffer(GL_BACK);
FOpenGL::DrawBuffer(GL_BACK);
}
ContextState.Framebuffer = PendingState.Framebuffer;
}
}
// Replaces RenderTargets with ResoveTargets to utilize GL_EXT_multisampled_render_to_texture
static int32 SetupMultisampleRenderingInfo(FRHISetRenderTargetsInfo& RTInfo)
{
if (FOpenGL::GetMaxMSAASamplesTileMem() > 1 && RTInfo.NumColorRenderTargets > 0)
{
int32 NumRenderingSamples = RTInfo.ColorRenderTarget[0].Texture->GetDesc().NumSamples;
if (NumRenderingSamples > 1)
{
for (int32 i = 0; i < RTInfo.NumColorRenderTargets; ++i)
{
if (RTInfo.ColorResolveRenderTarget[i].Texture)
{
RTInfo.ColorRenderTarget[i].Texture = RTInfo.ColorResolveRenderTarget[i].Texture;
}
}
return NumRenderingSamples;
}
}
return 1;
}
void FOpenGLDynamicRHI::RHIBeginRenderPass(const FRHIRenderPassInfo& InInfo, const TCHAR* InName)
{
FRHISetRenderTargetsInfo RTInfo;
InInfo.ConvertToRenderTargetsInfo(RTInfo);
// Begin GL_EXT_multisampled_render_to_texture if any
PendingState.NumRenderingSamples = SetupMultisampleRenderingInfo(RTInfo);
SetRenderTargetsAndClear(RTInfo);
RenderPassInfo = InInfo;
#if PLATFORM_ANDROID
if (RenderPassInfo.SubpassHint == ESubpassHint::DeferredShadingSubpass &&
FOpenGL::SupportsPixelLocalStorage() && FOpenGL::SupportsShaderDepthStencilFetch())
{
glEnable(GL_SHADER_PIXEL_LOCAL_STORAGE_EXT);
}
if (FAndroidOpenGL::RequiresAdrenoTilingModeHint())
{
FAndroidOpenGL::EnableAdrenoTilingModeHint(FCString::Strcmp(InName, TEXT("SceneColorRendering")) == 0);
}
// Reenable non-coherent framebuffer fetch if needed
if (!ContextState.bNonCoherentFramebufferFetchEnabled)
{
ContextState.bNonCoherentFramebufferFetchEnabled = FAndroidOpenGL::ResetNonCoherentFramebufferFetch();
}
#endif
}
void FOpenGLDynamicRHI::RHIEndRenderPass()
{
// End GL_EXT_multisampled_render_to_texture
PendingState.NumRenderingSamples = 1;
// Discard transient color targets
uint32 ColorMask = 0u;
for (int32 ColorIndex = 0; ColorIndex < MaxSimultaneousRenderTargets; ++ColorIndex)
{
const FRHIRenderPassInfo::FColorEntry& Entry = RenderPassInfo.ColorRenderTargets[ColorIndex];
if (!Entry.RenderTarget)
{
break;
}
if (GetStoreAction(Entry.Action) == ERenderTargetStoreAction::ENoAction)
{
ColorMask |= (1u << ColorIndex);
}
}
// Discard transient DepthStencil
bool bDiscardDepthStencil = false;
if (RenderPassInfo.DepthStencilRenderTarget.DepthStencilTarget)
{
ERenderTargetActions DepthActions = GetDepthActions(RenderPassInfo.DepthStencilRenderTarget.Action);
bDiscardDepthStencil = GetStoreAction(DepthActions) == ERenderTargetStoreAction::ENoAction;
}
if (bDiscardDepthStencil || ColorMask != 0)
{
RHIDiscardRenderTargets(bDiscardDepthStencil, bDiscardDepthStencil, ColorMask);
}
FRHIRenderTargetView RTV(nullptr, ERenderTargetLoadAction::ENoAction);
FRHIDepthRenderTargetView DepthRTV(nullptr, ERenderTargetLoadAction::ENoAction, ERenderTargetStoreAction::ENoAction);
SetRenderTargets(1, &RTV, &DepthRTV);
#if PLATFORM_ANDROID
if (RenderPassInfo.SubpassHint == ESubpassHint::DeferredShadingSubpass &&
FOpenGL::SupportsPixelLocalStorage() && FOpenGL::SupportsShaderDepthStencilFetch())
{
glDisable(GL_SHADER_PIXEL_LOCAL_STORAGE_EXT);
}
#endif
}
void FOpenGLDynamicRHI::RHINextSubpass()
{
IRHICommandContext::RHINextSubpass();
if (RenderPassInfo.SubpassHint == ESubpassHint::DepthReadSubpass ||
RenderPassInfo.SubpassHint == ESubpassHint::DeferredShadingSubpass)
{
if (ContextState.bNonCoherentFramebufferFetchEnabled)
{
FOpenGL::FrameBufferFetchBarrier();
}
}
}
void FOpenGLDynamicRHI::RHIBeginTransitions(TArrayView<const FRHITransition*> Transitions)
{
}
void FOpenGLDynamicRHI::RHIEndTransitions(TArrayView<const FRHITransition*> Transitions)
{
}