// 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/LowLevelMemTracker.h" #include "Engine/Texture.h" #if PLATFORM_ANDROID #include "ThirdParty/Android/detex/AndroidETC.h" #endif //PLATFORM_ANDROID static TAutoConsoleVariable 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 CVarDeferTextureCreationExcludeMask( TEXT("r.OpenGL.DeferTextureCreationExcludeFlags"), static_cast(~(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 ); /*----------------------------------------------------------------------------- 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 bool bRenderTarget = EnumHasAnyFlags(Desc.Flags, TexCreate_RenderTargetable | TexCreate_ResolveTargetable | TexCreate_DepthStencilTargetable); const int64 TextureSize = bAllocating ? int64(Texture->MemorySize) : -int64(Texture->MemorySize); const int64 TextureSizeInKBs = bAllocating ? FMath::DivideAndRoundUp(int64(Texture->MemorySize), 1024ll) : -FMath::DivideAndRoundUp(int64(Texture->MemorySize), 1024ll); if (bRenderTarget) { switch (Desc.Dimension) { default: checkNoEntry(); case ETextureDimension::Texture2D: INC_MEMORY_STAT_BY(STAT_RenderTargetMemory2D , TextureSize); break; case ETextureDimension::Texture2DArray: INC_MEMORY_STAT_BY(STAT_RenderTargetMemory2D , TextureSize); break; case ETextureDimension::Texture3D: INC_MEMORY_STAT_BY(STAT_RenderTargetMemory3D , TextureSize); break; case ETextureDimension::TextureCube: INC_MEMORY_STAT_BY(STAT_RenderTargetMemoryCube, TextureSize); break; case ETextureDimension::TextureCubeArray: INC_MEMORY_STAT_BY(STAT_RenderTargetMemoryCube, TextureSize); break; } GCurrentRendertargetMemorySize += TextureSizeInKBs; LLM_SCOPED_PAUSE_TRACKING_WITH_ENUM_AND_AMOUNT(ELLMTag::GraphicsPlatform, TextureSize, ELLMTracker::Platform, ELLMAllocType::None); LLM_SCOPED_PAUSE_TRACKING_WITH_ENUM_AND_AMOUNT(ELLMTag::RenderTargets , TextureSize, ELLMTracker::Default , ELLMAllocType::None); } else { switch (Desc.Dimension) { default: checkNoEntry(); case ETextureDimension::Texture2D: INC_MEMORY_STAT_BY(STAT_TextureMemory2D , TextureSize); break; case ETextureDimension::Texture2DArray: INC_MEMORY_STAT_BY(STAT_TextureMemory2D , TextureSize); break; case ETextureDimension::Texture3D: INC_MEMORY_STAT_BY(STAT_TextureMemory3D , TextureSize); break; case ETextureDimension::TextureCube: INC_MEMORY_STAT_BY(STAT_TextureMemoryCube, TextureSize); break; case ETextureDimension::TextureCubeArray: INC_MEMORY_STAT_BY(STAT_TextureMemoryCube, TextureSize); break; } GCurrentTextureMemorySize += TextureSizeInKBs; LLM_SCOPED_PAUSE_TRACKING_WITH_ENUM_AND_AMOUNT(ELLMTag::GraphicsPlatform, TextureSize, ELLMTracker::Platform, ELLMAllocType::None); LLM_SCOPED_PAUSE_TRACKING_WITH_ENUM_AND_AMOUNT(ELLMTag::Textures , TextureSize, ELLMTracker::Default , ELLMAllocType::None); } } 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) { OutStats.DedicatedVideoMemory = GOpenGLDedicatedVideoMemory; OutStats.DedicatedSystemMemory = 0; OutStats.SharedSystemMemory = 0; OutStats.TotalGraphicsMemory = GOpenGLTotalGraphicsMemory ? GOpenGLTotalGraphicsMemory : -1; OutStats.AllocatedMemorySize = int64(GCurrentTextureMemorySize) * 1024; OutStats.LargestContiguousAllocation = OutStats.AllocatedMemorySize; OutStats.TexturePoolSize = GTexturePoolSize; OutStats.PendingMemoryAdjustment = 0; } /** * 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) : NumSamplesRendered (InDesc.NumSamples) , NumSamplesStored (InDesc.NumSamples) , bCubemap (InDesc.IsTextureCube()) , bArrayTexture (InDesc.IsTextureArray()) , bStreamable (EnumHasAnyFlags(InDesc.Flags, TexCreate_Streamable)) , bDepthStencil (EnumHasAnyFlags(InDesc.Flags, TexCreate_DepthStencilTargetable)) , bCanCreateAsEvicted(false) , bIsPowerOfTwo (false) , bTileMemDepthBuffer(false) { checkf(!bCubemap || NumSamplesStored == 1, TEXT("Texture cubes cannot be multisampled.")); checkf(FOpenGL::SupportsTexture3D() || (!InDesc.IsTexture3D() && !InDesc.IsTextureArray()), TEXT("Texture3D / Texture2DArray support requires FOpenGL::SupportsTexture3D().")); // Use on-chip tile memory for MSAA if available if (NumSamplesStored <= FOpenGL::GetMaxMSAASamplesTileMem()) { NumSamplesRendered = FMath::Min(NumSamplesStored, FOpenGL::GetMaxMSAASamplesTileMem()); NumSamplesStored = 1; } // Select an appropriate texture target #if PLATFORM_ANDROID if (bDepthStencil && NumSamplesRendered != NumSamplesStored) { // Special case for MSAA depth render target on tiled GPUs / mobile renderer. Target = GL_RENDERBUFFER; bTileMemDepthBuffer = true; } else #endif { 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 = (NumSamplesStored > 1) ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D; break; case ETextureDimension::Texture2DArray: Target = (NumSamplesStored > 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() && FOpenGL::SupportsCopyImage() && 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, SupportsCopyImage:%d, Flags:%llx Mask:%x, Target:%x"), bCanCreateAsEvicted, FOpenGL::SupportsCopyImage(), 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)NumSamplesRendered); break; case GL_DEPTH_ATTACHMENT: case GL_DEPTH_STENCIL_ATTACHMENT: check(GMaxOpenGLDepthSamples >= (GLint)NumSamplesRendered); 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) , NumSamplesInternal (Other.NumSamplesInternal) , bIsPowerOfTwo (Other.bIsPowerOfTwo) , bCanCreateAsEvicted(false) , bStreamable (Other.bStreamable) , bCubemap (Other.bCubemap) , bArrayTexture (Other.bArrayTexture) , bDepthStencil (Other.bDepthStencil) , bTileMemDepthBuffer(Other.bTileMemDepthBuffer) , bAlias (true) { RunOnGLRenderContextThread([&]() { 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) , NumSamplesInternal (CreateDesc.NumSamplesRendered) , bIsPowerOfTwo (CreateDesc.bIsPowerOfTwo) , bCanCreateAsEvicted(false) , bStreamable (CreateDesc.bStreamable) , bCubemap (CreateDesc.bCubemap) , bArrayTexture (CreateDesc.bArrayTexture) , bDepthStencil (CreateDesc.bDepthStencil) , bTileMemDepthBuffer(CreateDesc.bTileMemDepthBuffer) , bAlias (true) {} // Standard constructor. FOpenGLTexture::FOpenGLTexture(FOpenGLTextureCreateDesc const& CreateDesc) : FRHITexture (CreateDesc) , Target (CreateDesc.Target) , Attachment (CreateDesc.Attachment) , MemorySize (CreateDesc.MemorySize) , NumSamplesInternal (CreateDesc.NumSamplesRendered) , bIsPowerOfTwo (CreateDesc.bIsPowerOfTwo) , bCanCreateAsEvicted(CreateDesc.bCanCreateAsEvicted) , bStreamable (CreateDesc.bStreamable) , bCubemap (CreateDesc.bCubemap) , bArrayTexture (CreateDesc.bArrayTexture) , bDepthStencil (CreateDesc.bDepthStencil) , bTileMemDepthBuffer(CreateDesc.bTileMemDepthBuffer) , bAlias (false) { check(IsInRenderingThread()); FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList(); SCOPE_CYCLE_COUNTER(STAT_OpenGLCreateTextureTime); if (bCanCreateAsEvicted) { EvictionParamsPtr = MakeUnique(CreateDesc.NumMips); } void* BulkDataPtr = nullptr; uint64 BulkDataSize = 0; bool bFreeBulkData = false; if (CreateDesc.BulkData) { if (!ShouldRunGLRenderContextOpOnThisThread(RHICmdList)) { // If bulk data is provided, and texture initialization is done by the RHI thread, it needs to be copied out of the FResourceBulkDataInterface. // It is not safe to pass this pointer to the RHI thread, as the interface may have been stack allocated in the renderer. // @todo: remove this memcpy when the new resource creation API is available. BulkDataSize = CreateDesc.BulkData->GetResourceBulkDataSize(); BulkDataPtr = FMemory::Malloc(BulkDataSize); FMemory::Memcpy(BulkDataPtr, CreateDesc.BulkData->GetResourceBulkData(), BulkDataSize); bFreeBulkData = true; } else { // Otherwise, initialization will be done on this thread. // Just use the raw pointer / size as-is. BulkDataSize = CreateDesc.BulkData->GetResourceBulkDataSize(); BulkDataPtr = const_cast(CreateDesc.BulkData->GetResourceBulkData()); } } RunOnGLRenderContextThread([this, BulkDataPtr, BulkDataSize, bFreeBulkData]() { FOpenGLDynamicRHI::Get().InitializeGLTexture(this, BulkDataPtr, BulkDataSize); if (bFreeBulkData) { FMemory::Free(BulkDataPtr); } }); UpdateTextureStats(this, true); PixelBuffers.AddZeroed(CreateDesc.NumMips * (bCubemap ? 6 : 1) * GetEffectiveSizeZ()); if (CreateDesc.BulkData) { CreateDesc.BulkData->Discard(); } } FOpenGLTexture::~FOpenGLTexture() { VERIFY_GL_SCOPE(); FTextureEvictionLRU::Get().Remove(this); if (!bCanCreateAsEvicted) { ReleaseOpenGLFramebuffers(this); } DeleteGLResource(); UpdateTextureStats(this, false); } void FOpenGLTexture::DeleteGLResource() { VERIFY_GL_SCOPE(); QUICK_SCOPE_CYCLE_COUNTER(STAT_OpenGLDeleteGLTextureTime); 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; } } } 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->bTileMemDepthBuffer) { 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(1, (Desc.Extent.X >> MipIndex)), BlockSizeX) / BlockSizeX; uint32 NumBlocksY = AlignArbitrary(FMath::Max(1, (Desc.Extent.Y >> MipIndex)), BlockSizeY) / BlockSizeY; uint32 NumLayers = FMath::Max(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); } FOpenGLContextState& ContextState = GetContextStateForCurrentContext(); // Make sure PBO is disabled CachedBindPixelUnpackBuffer(ContextState, 0); bool bAllocatedStorage = false; #if PLATFORM_ANDROID if (Texture->bTileMemDepthBuffer) { glBindRenderbuffer(GL_RENDERBUFFER, TextureID); glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, Texture->GetNumSamplesRendered(), GL_DEPTH24_STENCIL8, Desc.Extent.X, Desc.Extent.Y); VERIFY_GL(glRenderbufferStorageMultisampleEXT); glBindRenderbuffer(GL_RENDERBUFFER, 0); bAllocatedStorage = true; } else #endif { // Use a texture stage that's not likely to be used for draws, to avoid waiting CachedSetupTextureStage(ContextState, 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->GetNumSamplesStored() == 1) { 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); // Do not use GL_TEXTURE_MAX_LEVEL if external texture if (Target != GL_TEXTURE_EXTERNAL_OES) { glTexParameteri(Target, GL_TEXTURE_MAX_LEVEL, Desc.NumMips - 1); } TextureMipLimits.Add(TextureID, TPair(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) { auto EnumerateSubresources = [&](void const* Data, TFunctionRef Callback) { struct FScopedPackAlignment { FScopedPackAlignment() { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); } ~FScopedPackAlignment() { glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } } PackAlignment; uint64 DataOffset = 0; for (uint32 MipIndex = 0; MipIndex < Desc.NumMips; ++MipIndex) { for (uint32 ArraySlice = 0; ArraySlice < Desc.ArraySize; ++ArraySlice) { for (uint32 FaceIndex = 0; FaceIndex < (Desc.IsTextureCube() ? 6u : 1u); ++FaceIndex) { const uint32 MipPixelSizeX = FMath::Max(1u, Desc.Extent.X >> MipIndex); const uint32 MipPixelSizeY = FMath::Max(1u, Desc.Extent.Y >> MipIndex); const uint32 MipPixelSizeZ = FMath::Max(1u, Desc.Depth >> MipIndex); const GLenum CurrentTarget = Target == GL_TEXTURE_CUBE_MAP ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + FaceIndex : Target; if (Data) { const uint32 MipBlockSizeX = FMath::DivideAndRoundUp(MipPixelSizeX, GPixelFormats[Desc.Format].BlockSizeX); const uint32 MipBlockSizeY = FMath::DivideAndRoundUp(MipPixelSizeY, GPixelFormats[Desc.Format].BlockSizeY); const uint32 MipBlockSizeZ = FMath::DivideAndRoundUp(MipPixelSizeZ, GPixelFormats[Desc.Format].BlockSizeZ); const uint32 MipNumBlocks = MipBlockSizeX * MipBlockSizeY * MipBlockSizeZ; const uint32 MipSize = MipNumBlocks * GPixelFormats[Desc.Format].BlockBytes * Desc.ArraySize; if (!Callback(CurrentTarget, MipPixelSizeX, MipPixelSizeY, MipPixelSizeZ, MipIndex, ArraySlice, Data, MipSize)) return; DataOffset += MipSize; if (DataOffset >= BulkDataSize) { // Reach the end of bulk data. Only pass nullptr to the callback for any subsequent mips / slices Data = nullptr; } } else { if (!Callback(CurrentTarget, MipPixelSizeX, MipPixelSizeY, MipPixelSizeZ, MipIndex, ArraySlice, nullptr, 0)) 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 if (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, uint32 MipSizeX, uint32 MipSizeY, uint32 MipSizeZ, uint32 MipIndex, uint32 ArraySlice, void const* MipSliceData, uint32 MipSliceSize) { // Stop when there's no more bulk data if (MipSliceData == nullptr) return false; if (GLFormat.bCompressed) { glCompressedTexSubImage2D( CurrentTarget, MipIndex, 0, 0, // X/Y offset MipSizeX, MipSizeY, GLFormat.Format, MipSliceSize, MipSliceData); } else { glTexSubImage2D( CurrentTarget, MipIndex, 0, 0, // X/Y offset MipSizeX, MipSizeY, GLFormat.Format, GLFormat.Type, MipSliceData); } return true; }); } } else if (GLFormat.bCompressed && !BulkDataPtr) { // Compressed textures created without using the TexStorage functions cannot be allocated via TexImage without bulk data. // Do nothing here. Texture memory will be allocated when the renderer locks/unlocks the mips for writing. } else { // Failed to create immutable storage. Fall back to the standard TexImage functions. // This both allocates the memory and fills in the bulk data simultaneously. EnumerateSubresources(BulkDataPtr, [&](GLenum CurrentTarget, uint32 MipSizeX, uint32 MipSizeY, uint32 MipSizeZ, uint32 MipIndex, uint32 ArraySlice, void const* MipSliceData, uint32 MipSliceSize) { if (GLFormat.bCompressed) { glCompressedTexImage2D( CurrentTarget, MipIndex, GLFormat.InternalFormat[bSRGB], MipSizeX, MipSizeY, 0, MipSliceSize, MipSliceData); } else { glTexImage2D( CurrentTarget, MipIndex, GLFormat.InternalFormat[bSRGB], MipSizeX, MipSizeY, 0, GLFormat.Format, GLFormat.Type, MipSliceData); } // Always continue iterating to allocate all mips/slices. 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, uint32 MipSizeX, uint32 MipSizeY, uint32 MipSizeZ, uint32 MipIndex, uint32 ArraySlice, void const* MipSliceData, uint32 MipSliceSize) { // Stop when there's no more bulk data if (MipSliceData == nullptr) return false; if (GLFormat.bCompressed) { glCompressedTexSubImage3D( CurrentTarget, MipIndex, 0, 0, ArraySlice, // X/Y/Z offset MipSizeX, MipSizeY, MipSizeZ, GLFormat.Format, MipSliceSize, MipSliceData); } else { glTexSubImage3D( CurrentTarget, MipIndex, 0, 0, ArraySlice, // X/Y/Z offset MipSizeX, MipSizeY, MipSizeZ, GLFormat.Format, GLFormat.Type, MipSliceData); } 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 bool FixedSampleLocations = true; if (FOpenGL::TexStorage2DMultisample(Target, Texture->GetNumSamplesStored(), GLFormat.InternalFormat[bSRGB], Desc.Extent.X, Desc.Extent.Y, FixedSampleLocations)) { bAllocatedStorage = true; } else { FOpenGL::TexImage2DMultisample(Target, Texture->GetNumSamplesStored(), GLFormat.InternalFormat[bSRGB], Desc.Extent.X, Desc.Extent.Y, FixedSampleLocations); } } 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 FOpenGLTexture::Resolve(uint32 MipIndex,uint32 ArrayIndex) { VERIFY_GL_SCOPE(); check(!GetTexture2D() || GetNumSamples() == 1); // Calculate the dimensions of the mip-map. EPixelFormat PixelFormat = this->GetFormat(); const uint32 BlockSizeX = GPixelFormats[PixelFormat].BlockSizeX; const uint32 BlockSizeY = GPixelFormats[PixelFormat].BlockSizeY; const uint32 BlockBytes = GPixelFormats[PixelFormat].BlockBytes; const uint32 MipSizeX = FMath::Max(this->GetSizeX() >> MipIndex,BlockSizeX); const uint32 MipSizeY = FMath::Max(this->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 * (bCubemap ? 6 : 1) * this->GetEffectiveSizeZ() + ArrayIndex; // Standard path with a PBO mirroring ever slice of a texture to allow multiple simulataneous maps if (!IsValidRef(PixelBuffers[BufferIndex])) { PixelBuffers[BufferIndex] = new FOpenGLPixelBuffer(GL_PIXEL_UNPACK_BUFFER, 0, MipBytes, BUF_Dynamic, nullptr); } TRefCountPtr PixelBuffer = 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(this->GetFlags(), TexCreate_SRGB); // Use a texture stage that's not likely to be used for draws, to avoid waiting FOpenGLContextState& ContextState = FOpenGLDynamicRHI::Get().GetContextStateForCurrentContext(); FOpenGLDynamicRHI::Get().CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Target, this->GetResource(), -1, this->GetNumMips()); glBindBuffer( GL_PIXEL_PACK_BUFFER, PixelBuffer->Resource ); { if (GetDesc().IsTextureArray() || 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( bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Target, MipIndex, 0); // offset into PBO } else { // Get framebuffer for texture FOpenGLTexture* Texture = this; GLuint SourceFramebuffer = FOpenGLDynamicRHI::Get().GetOpenGLFramebuffer(1, &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); FOpenGLDynamicRHI::Get().GetContextStateForCurrentContext().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, uint32 ArrayIndex, EResourceLockMode LockMode, uint32& DestStride) { // Calculate the dimensions of the mip-map. EPixelFormat PixelFormat = this->GetFormat(); const uint32 BlockSizeX = GPixelFormats[PixelFormat].BlockSizeX; const uint32 BlockSizeY = GPixelFormats[PixelFormat].BlockSizeY; const uint32 BlockBytes = GPixelFormats[PixelFormat].BlockBytes; const uint32 MipSizeX = FMath::Max(this->GetSizeX() >> InMipIndex, BlockSizeX); const uint32 MipSizeY = FMath::Max(this->GetSizeY() >> InMipIndex, BlockSizeY); uint32 NumBlocksX = (MipSizeX + BlockSizeX - 1) / BlockSizeX; uint32 NumBlocksY = (MipSizeY + BlockSizeY - 1) / BlockSizeY; const uint32 MipBytes = NumBlocksX * NumBlocksY * BlockBytes; DestStride = NumBlocksX * BlockBytes; return MipBytes; } void FOpenGLTexture::Fill2DGLTextureImage(const FOpenGLTextureFormat& GLFormat, const bool bSRGB, uint32 MipIndex, const void* BufferOrPBOOffset, uint32 ImageSize, uint32 ArrayIndex) { if (GLFormat.bCompressed) { if (GetAllocatedStorageForMip(MipIndex,ArrayIndex)) { glCompressedTexSubImage2D( bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Target, MipIndex, 0, 0, FMath::Max(1,(this->GetSizeX() >> MipIndex)), FMath::Max(1,(this->GetSizeY() >> MipIndex)), GLFormat.InternalFormat[bSRGB], ImageSize, BufferOrPBOOffset); // offset into PBO } else { glCompressedTexImage2D( bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Target, MipIndex, GLFormat.InternalFormat[bSRGB], FMath::Max(1,(this->GetSizeX() >> MipIndex)), FMath::Max(1,(this->GetSizeY() >> MipIndex)), 0, ImageSize, BufferOrPBOOffset); // offset into PBO SetAllocatedStorageForMip(MipIndex,ArrayIndex); } } else { // All construction paths should have called TexStorage2D or TexImage2D. So we will // always call TexSubImage2D. check(GetAllocatedStorageForMip(MipIndex,ArrayIndex) == true); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexSubImage2D( bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Target, MipIndex, 0, 0, FMath::Max(1,(this->GetSizeX() >> MipIndex)), FMath::Max(1,(this->GetSizeY() >> MipIndex)), GLFormat.Format, GLFormat.Type, BufferOrPBOOffset); // offset into PBO glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } } 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, ArrayIndex, 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); // check there's nothing already here? ensure(InMipIndex >= (uint32)EvictionParamsPtr->MipImageData.Num() || EvictionParamsPtr->MipImageData[InMipIndex].Num() == 0); EvictionParamsPtr->SetMipData(InMipIndex, 0, MipBytes); return EvictionParamsPtr->MipImageData[InMipIndex].GetData(); } else { // 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; PixelBuffers[BufferIndex] = new FOpenGLPixelBuffer(GL_PIXEL_UNPACK_BUFFER, 0, MipBytes, BUF_Dynamic, nullptr); } TRefCountPtr 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) { Resolve(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); TRefCountPtr 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 FOpenGLContextState& ContextState = FOpenGLDynamicRHI::Get().GetContextStateForCurrentContext(); FOpenGLDynamicRHI::Get().CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Target, GetResource(), -1, this->GetNumMips()); CachedBindPixelUnpackBuffer(GL_PIXEL_UNPACK_BUFFER, 0); // get the source data and size uint16* floatData = (uint16*)PixelBuffer->GetLockedBuffer(); int32 texWidth = FMath::Max(1, (this->GetSizeX() >> MipIndex)); int32 texHeight = FMath::Max(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. CachedBindPixelUnpackBuffer(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 FOpenGLContextState& ContextState = FOpenGLDynamicRHI::Get().GetContextStateForCurrentContext(); FOpenGLDynamicRHI::Get().CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Target, GetResource(), -1, this->GetNumMips()); if (GetDesc().IsTextureArray() || GetDesc().IsTexture3D()) { if (GLFormat.bCompressed) { FOpenGL::CompressedTexSubImage3D( Target, MipIndex, 0, 0, ArrayIndex, FMath::Max(1,(this->GetSizeX() >> MipIndex)), FMath::Max(1,(this->GetSizeY() >> MipIndex)), 1, GLFormat.InternalFormat[bSRGB], PixelBuffer->GetSize(), 0); } else { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); check( FOpenGL::SupportsTexture3D() ); FOpenGL::TexSubImage3D( Target, MipIndex, 0, 0, ArrayIndex, FMath::Max(1,(this->GetSizeX() >> MipIndex)), FMath::Max(1,(this->GetSizeY() >> MipIndex)), 1, GLFormat.Format, GLFormat.Type, 0); // offset into PBO glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } } else { Fill2DGLTextureImage(GLFormat, bSRGB, MipIndex, 0, PixelBuffer->GetSize(), ArrayIndex); } } //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. CachedBindPixelUnpackBuffer(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()); CachedBindPixelUnpackBuffer(GL_PIXEL_UNPACK_BUFFER, 0); for (int i = EvictionParamsPtr->MipImageData.Num() - 1; i >= 0; i--) { auto& MipMem = EvictionParamsPtr->MipImageData[i]; if(MipMem.Num()) { Fill2DGLTextureImage(GLFormat, bSRGB, i, MipMem.GetData(), MipMem.Num(), 0); } } // 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; 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. RetainMips = 0; } } // keep the mips for streamable textures EvictionParamsPtr->ReleaseMipData(RetainMips); #if GLDEBUG_LABELS_ENABLED FAnsiCharArray& TextureDebugName = EvictionParamsPtr->GetDebugLabelName(); if(TextureDebugName.Num()) { FOpenGL::LabelObject(GL_TEXTURE, this->GetRawResourceName(), TextureDebugName.GetData()); 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 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::CloneViaCopyImage(FOpenGLTexture* Src, uint32 InNumMips, int32 SrcOffset, int32 DstOffset) { VERIFY_GL_SCOPE(); check(FOpenGL::SupportsCopyImage()); 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); } } } void FOpenGLTexture::CloneViaPBO(FOpenGLTexture* Src, uint32 InNumMips, int32 SrcOffset, int32 DstOffset) { VERIFY_GL_SCOPE(); // 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(this->GetSizeZ() == 0); EPixelFormat PixelFormat = this->GetFormat(); check(PixelFormat == Src->GetFormat()); const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[PixelFormat]; const bool bSRGB = EnumHasAnyFlags(this->GetFlags(), TexCreate_SRGB); check(bSRGB == EnumHasAnyFlags(Src->GetFlags(), TexCreate_SRGB)); const uint32 BlockSizeX = GPixelFormats[PixelFormat].BlockSizeX; const uint32 BlockSizeY = GPixelFormats[PixelFormat].BlockSizeY; const uint32 BlockBytes = GPixelFormats[PixelFormat].BlockBytes; FOpenGLContextState& ContextState = FOpenGLDynamicRHI::Get().GetContextStateForCurrentContext(); for (uint32 ArrayIndex = 0; ArrayIndex < this->GetEffectiveSizeZ(); ArrayIndex++) { // use PBO functionality to copy mip level by mip level for(uint32 MipIndex = 0;MipIndex < InNumMips;++MipIndex) { // Actual mip levels const uint32 DstMipIndex = MipIndex + DstOffset; const uint32 SrcMipIndex = MipIndex + SrcOffset; // Calculate the dimensions of the mip-map. const uint32 MipSizeX = FMath::Max(this->GetSizeX() >> DstMipIndex,1u); const uint32 MipSizeY = FMath::Max(this->GetSizeY() >> DstMipIndex,1u); // Then the rounded PBO size required to capture this mip const uint32 DataSizeX = FMath::Max(MipSizeX,BlockSizeX); const uint32 DataSizeY = FMath::Max(MipSizeY,BlockSizeY); uint32 NumBlocksX = (DataSizeX + BlockSizeX - 1) / BlockSizeX; uint32 NumBlocksY = (DataSizeY + BlockSizeY - 1) / BlockSizeY; const uint32 MipBytes = NumBlocksX * NumBlocksY * BlockBytes; const int32 BufferIndex = DstMipIndex * (bCubemap ? 6 : 1) * this->GetEffectiveSizeZ() + ArrayIndex; const int32 SrcBufferIndex = SrcMipIndex * (Src->bCubemap ? 6 : 1) * Src->GetEffectiveSizeZ() + ArrayIndex; // Standard path with a PBO mirroring ever slice of a texture to allow multiple simulataneous maps if (!IsValidRef(PixelBuffers[BufferIndex])) { PixelBuffers[BufferIndex] = new FOpenGLPixelBuffer(GL_PIXEL_UNPACK_BUFFER, 0, MipBytes, BUF_Dynamic, nullptr); } TRefCountPtr PixelBuffer = 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. // Use a texture stage that's not likely to be used for draws, to avoid waiting FOpenGLDynamicRHI::Get().CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Src->Target, Src->GetResource(), -1, this->GetNumMips()); glBindBuffer( GL_PIXEL_PACK_BUFFER, PixelBuffer->Resource ); if (GLFormat.bCompressed) { FOpenGL::GetCompressedTexImage(Src->bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Src->Target, SrcMipIndex, 0); // offset into PBO } else { glPixelStorei(GL_PACK_ALIGNMENT, 1); FOpenGL::GetTexImage(Src->bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Src->Target, SrcMipIndex, GLFormat.Format, GLFormat.Type, 0); // offset into PBO glPixelStorei(GL_PACK_ALIGNMENT, 4); } // copy the texture data // Upload directly into Dst to avoid out-of-band synchronization caused by glMapBuffer! { CachedBindPixelUnpackBuffer( GL_PIXEL_UNPACK_BUFFER, PixelBuffer->Resource ); // Use a texture stage that's not likely to be used for draws, to avoid waiting FOpenGLDynamicRHI::Get().CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Target, GetResource(), -1, this->GetNumMips()); if( this->GetSizeZ() ) { // texture 2D array if (GLFormat.bCompressed) { FOpenGL::CompressedTexSubImage3D(Target, DstMipIndex, 0, 0, ArrayIndex, MipSizeX, MipSizeY, 1, GLFormat.InternalFormat[bSRGB], PixelBuffer->GetSize(), 0); } else { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); check( FOpenGL::SupportsTexture3D() ); FOpenGL::TexSubImage3D(Target, DstMipIndex, 0, 0, ArrayIndex, MipSizeX, MipSizeY, 1, GLFormat.Format, GLFormat.Type, 0); // offset into PBO glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } } else { if (GLFormat.bCompressed) { if (GetAllocatedStorageForMip(DstMipIndex,ArrayIndex)) { glCompressedTexSubImage2D(bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Target, DstMipIndex, 0, 0, MipSizeX, MipSizeY, GLFormat.InternalFormat[bSRGB], PixelBuffer->GetSize(), 0); // offset into PBO } else { glCompressedTexImage2D(bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Target, DstMipIndex, GLFormat.InternalFormat[bSRGB], MipSizeX, MipSizeY, 0, PixelBuffer->GetSize(), 0); // offset into PBO SetAllocatedStorageForMip(DstMipIndex,ArrayIndex); } } else { // All construction paths should have called TexStorage2D or TexImage2D. So we will // always call TexSubImage2D. check(GetAllocatedStorageForMip(DstMipIndex,ArrayIndex) == true); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexSubImage2D(bCubemap ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + ArrayIndex : Target, DstMipIndex, 0, 0, MipSizeX, MipSizeY, GLFormat.Format, GLFormat.Type, 0); // offset into PBO glPixelStorei(GL_UNPACK_ALIGNMENT, 4); } } } // 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. } } // Reset the buffer bindings on exit only glBindBuffer( GL_PIXEL_PACK_BUFFER, 0 ); CachedBindPixelUnpackBuffer(GL_PIXEL_UNPACK_BUFFER, 0); } /*----------------------------------------------------------------------------- 2D texture support. -----------------------------------------------------------------------------*/ FTextureRHIRef FOpenGLDynamicRHI::RHICreateTexture(const FRHITextureCreateDesc& CreateDesc) { return new FOpenGLTexture(CreateDesc); } FTextureRHIRef FOpenGLDynamicRHI::RHIAsyncCreateTexture2D(uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags Flags, ERHIAccess InResourceState, void** InitialMipData, uint32 NumInitialMips) { check(0); return FTexture2DRHIRef(); } void FOpenGLDynamicRHI::RHICopySharedMips(FRHITexture2D* DestTexture2D, FRHITexture2D* SrcTexture2D) { check(0); } FOpenGLShaderResourceView::FOpenGLShaderResourceView(FOpenGLTexture* InTexture, const FRHITextureSRVCreateInfo& CreateInfo) : FRHIShaderResourceView(InTexture) , Target (InTexture->Target) , Texture (InTexture) , LimitMip (CreateInfo.MipLevel) , OwnsResource(false) { const uint8 Format = (CreateInfo.Format == PF_Unknown) ? Texture->GetFormat() : CreateInfo.Format; const bool bFormatsMatch = Format == Texture->GetFormat() || Format == PF_X24_G8; RunOnGLRenderContextThread([this, bFormatsMatch, Format]() { VERIFY_GL_SCOPE(); checkf(bFormatsMatch, TEXT("SRVs cannot modify the pixel format of a texture when texture views are unsupported.")); Resource = Texture->GetResource(); // Handle the custom stencil SRV if (Format == PF_X24_G8) { // Use a texture stage that's not likely to be used for draws, to avoid waiting FOpenGLContextState& ContextState = FOpenGLDynamicRHI::Get().GetContextStateForCurrentContext(); FOpenGLDynamicRHI::Get().CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Target, Resource, LimitMip, Texture->GetNumMips()); //set the texture to return the stencil index, and then force the components to match D3D glTexParameteri(Target, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX); glTexParameteri(Target, GL_TEXTURE_SWIZZLE_R, GL_ZERO); glTexParameteri(Target, GL_TEXTURE_SWIZZLE_G, GL_RED); glTexParameteri(Target, GL_TEXTURE_SWIZZLE_B, GL_ZERO); glTexParameteri(Target, GL_TEXTURE_SWIZZLE_A, GL_ZERO); } }); } FShaderResourceViewRHIRef FOpenGLDynamicRHI::RHICreateShaderResourceView(FRHITexture* Texture, const FRHITextureSRVCreateInfo& CreateInfo) { return new FOpenGLShaderResourceView(GetOpenGLTextureFromRHITexture(Texture), CreateInfo); } /** Generates mip maps for the surface. */ void FOpenGLDynamicRHI::RHIGenerateMips(FRHITexture* SurfaceRHI) { if (FOpenGL::SupportsGenerateMipmap()) { RunOnGLRenderContextThread([=]() { VERIFY_GL_SCOPE(); GPUProfilingData.RegisterGPUWork(0); FOpenGLContextState& ContextState = GetContextStateForCurrentContext(); FOpenGLTexture* Texture = GetOpenGLTextureFromRHITexture(SurfaceRHI); // Setup the texture on a disused unit // need to figure out how to setup mips properly in no views case CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Texture->Target, Texture->GetResource(), -1, Texture->GetNumMips()); FOpenGL::GenerateMipmap(Texture->Target); }); } else { UE_LOG( LogRHI, Fatal, TEXT("Generate Mipmaps unsupported on this OpenGL version")); } } /** * 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 = GetOpenGLTextureFromRHITexture(TextureRHI); return Texture->MemorySize; } FTexture2DRHIRef FOpenGLDynamicRHI::AsyncReallocateTexture2D_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITexture2D* Texture2DRHI, int32 NewMipCount, int32 NewSizeX, int32 NewSizeY, FThreadSafeCounter* RequestStatus) { return this->RHIAsyncReallocateTexture2D(Texture2DRHI, NewMipCount, NewSizeX, NewSizeY, RequestStatus); } FTexture2DRHIRef FOpenGLDynamicRHI::RHIAsyncReallocateTexture2D(FRHITexture2D* Texture2DRHI, int32 NewMipCount, int32 NewSizeX, int32 NewSizeY, FThreadSafeCounter* RequestStatus) { check(IsInRenderingThread()); FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList(); FOpenGLTexture* OldTexture = GetOpenGLTextureFromRHITexture(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") ); FOpenGLTexture* NewTexture = new FOpenGLTexture(CreateDesc); RHICmdList.EnqueueLambda([OldTexture, NewTexture, SourceMipCount, NewMipCount, RequestStatus](FRHICommandListImmediate& RHICmdList) { VERIFY_GL_SCOPE(); // 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; if (FOpenGL::SupportsCopyImage()) { NewTexture->CloneViaCopyImage(OldTexture, NumSharedMips, SourceMipOffset, DestMipOffset); } else { NewTexture->CloneViaPBO(OldTexture, NumSharedMips, SourceMipOffset, DestMipOffset); } RequestStatus->Decrement(); }); return NewTexture; } /** * Returns the status of an ongoing or completed texture reallocation: * TexRealloc_Succeeded - The texture is ok, reallocation is not in progress. * TexRealloc_Failed - The texture is bad, reallocation is not in progress. * TexRealloc_InProgress - The texture is currently being reallocated async. * * @param Texture2D - Texture to check the reallocation status for * @return - Current reallocation status */ ETextureReallocationStatus FOpenGLDynamicRHI::RHIFinalizeAsyncReallocateTexture2D(FRHITexture2D* Texture2D, bool bBlockUntilCompleted ) { return TexRealloc_Succeeded; } /** * Cancels an async reallocation for the specified texture. * This should be called for the new texture, not the original. * * @param Texture Texture to cancel * @param bBlockUntilCompleted If true, blocks until the cancellation is fully completed * @return Reallocation status */ ETextureReallocationStatus FOpenGLDynamicRHI::RHICancelAsyncReallocateTexture2D(FRHITexture2D* Texture2D, bool bBlockUntilCompleted ) { return TexRealloc_Succeeded; } void* FOpenGLDynamicRHI::RHILockTexture2D(FRHITexture2D* TextureRHI,uint32 MipIndex,EResourceLockMode LockMode,uint32& DestStride,bool bLockWithinMiptail) { return GetOpenGLTextureFromRHITexture(TextureRHI)->Lock(MipIndex, 0, LockMode, DestStride); } void FOpenGLDynamicRHI::RHIUnlockTexture2D(FRHITexture2D* TextureRHI, uint32 MipIndex, bool bLockWithinMiptail) { GetOpenGLTextureFromRHITexture(TextureRHI)->Unlock(MipIndex, 0); } void* FOpenGLDynamicRHI::RHILockTexture2DArray(FRHITexture2DArray* TextureRHI, uint32 TextureIndex, uint32 MipIndex, EResourceLockMode LockMode, uint32& DestStride, bool bLockWithinMiptail) { return GetOpenGLTextureFromRHITexture(TextureRHI)->Lock(MipIndex, TextureIndex, LockMode, DestStride); } void FOpenGLDynamicRHI::RHIUnlockTexture2DArray(FRHITexture2DArray* TextureRHI, uint32 TextureIndex, uint32 MipIndex, bool bLockWithinMiptail) { GetOpenGLTextureFromRHITexture(TextureRHI)->Unlock(MipIndex, TextureIndex); } void FOpenGLDynamicRHI::RHIUpdateTexture2D(FRHITexture2D* TextureRHI, uint32 MipIndex, const FUpdateTextureRegion2D& UpdateRegionIn, uint32 SourcePitch, const uint8* SourceDataIn) { FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList(); const FUpdateTextureRegion2D UpdateRegion = UpdateRegionIn; uint8* RHITSourceData = nullptr; if (!ShouldRunGLRenderContextOpOnThisThread(RHICmdList)) { const FPixelFormatInfo& FormatInfo = GPixelFormats[TextureRHI->GetFormat()]; const size_t UpdateHeightInTiles = FMath::DivideAndRoundUp(UpdateRegion.Height, (uint32)FormatInfo.BlockSizeY); const size_t DataSize = static_cast(SourcePitch) * UpdateHeightInTiles; RHITSourceData = (uint8*)FMemory::Malloc(DataSize, 16); FMemory::Memcpy(RHITSourceData, SourceDataIn, DataSize); } const uint8* SourceData = RHITSourceData ? RHITSourceData : SourceDataIn; RunOnGLRenderContextThread([=]() { VERIFY_GL_SCOPE(); FOpenGLTexture* Texture = GetOpenGLTextureFromRHITexture(TextureRHI); // Use a texture stage that's not likely to be used for draws, to avoid waiting FOpenGLContextState& ContextState = GetContextStateForCurrentContext(); CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Texture->Target, Texture->GetResource(), 0, Texture->GetNumMips()); CachedBindPixelUnpackBuffer(ContextState, 0); EPixelFormat PixelFormat = Texture->GetFormat(); check(GPixelFormats[PixelFormat].BlockSizeX == 1); check(GPixelFormats[PixelFormat].BlockSizeY == 1); const FOpenGLTextureFormat& GLFormat = GOpenGLTextureFormats[PixelFormat]; const uint32 FormatBPP = GPixelFormats[PixelFormat].BlockBytes; checkf(!GLFormat.bCompressed, TEXT("RHIUpdateTexture2D not currently supported for compressed (%s) textures by the OpenGL RHI"), GPixelFormats[PixelFormat].Name); glPixelStorei(GL_UNPACK_ROW_LENGTH, SourcePitch / FormatBPP); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexSubImage2D(Texture->Target, MipIndex, UpdateRegion.DestX, UpdateRegion.DestY, UpdateRegion.Width, UpdateRegion.Height, GLFormat.Format, GLFormat.Type, SourceData); 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 (RHITSourceData) { FMemory::Free(RHITSourceData); } }); } void FOpenGLDynamicRHI::RHIUpdateTexture3D(FRHITexture3D* TextureRHI, uint32 MipIndex, const FUpdateTextureRegion3D& UpdateRegion, uint32 SourceRowPitch, uint32 SourceDepthPitch, const uint8* SourceData) { VERIFY_GL_SCOPE(); check( FOpenGL::SupportsTexture3D() ); FOpenGLTexture* Texture = GetOpenGLTextureFromRHITexture(TextureRHI); // Use a texture stage that's not likely to be used for draws, to avoid waiting FOpenGLContextState& ContextState = GetContextStateForCurrentContext(); CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, Texture->Target, Texture->GetResource(), 0, Texture->GetNumMips()); CachedBindPixelUnpackBuffer(ContextState, 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); // 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(); if (SharedContextState.Textures || RenderingContextState.Textures || PendingState.Textures) { for (int32 SamplerIndex = 0; SamplerIndex < FOpenGL::GetMaxCombinedTextureImageUnits(); ++SamplerIndex) { if (SharedContextState.Textures && SharedContextState.Textures[SamplerIndex].Resource == Resource) { SharedContextState.Textures[SamplerIndex].Target = GL_NONE; SharedContextState.Textures[SamplerIndex].Resource = 0; } if (RenderingContextState.Textures && RenderingContextState.Textures[SamplerIndex].Resource == Resource) { RenderingContextState.Textures[SamplerIndex].Target = GL_NONE; RenderingContextState.Textures[SamplerIndex].Resource = 0; } if (PendingState.Textures && PendingState.Textures[SamplerIndex].Resource == Resource) { PendingState.Textures[SamplerIndex].Target = GL_NONE; PendingState.Textures[SamplerIndex].Resource = 0; } } } TextureMipLimits.Remove(Resource); if (PendingState.DepthStencil && PendingState.DepthStencil->GetResource() == Resource) { PendingState.DepthStencil = nullptr; } } void FOpenGLDynamicRHI::InvalidateUAVResourceInCache(GLuint Resource) { VERIFY_GL_SCOPE(); int32 NumUAVs = RenderingContextState.UAVs.Num(); for (int32 UAVIndex = 0; UAVIndex < NumUAVs; ++UAVIndex) { if (SharedContextState.UAVs[UAVIndex].Resource == Resource) { SharedContextState.UAVs[UAVIndex].Format = GL_NONE; SharedContextState.UAVs[UAVIndex].Resource = 0; } if (RenderingContextState.UAVs[UAVIndex].Resource == Resource) { RenderingContextState.UAVs[UAVIndex].Format = GL_NONE; RenderingContextState.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::RHILockTextureCubeFace(FRHITextureCube* TextureCubeRHI, uint32 FaceIndex, uint32 ArrayIndex, uint32 MipIndex, EResourceLockMode LockMode, uint32& DestStride, bool bLockWithinMiptail) { return GetOpenGLTextureFromRHITexture(TextureCubeRHI)->Lock(MipIndex, FaceIndex + 6 * ArrayIndex, LockMode, DestStride); } void FOpenGLDynamicRHI::RHIUnlockTextureCubeFace(FRHITextureCube* TextureCubeRHI, uint32 FaceIndex, uint32 ArrayIndex, uint32 MipIndex, bool bLockWithinMiptail) { GetOpenGLTextureFromRHITexture(TextureCubeRHI)->Unlock(MipIndex, FaceIndex + ArrayIndex * 6); } void FOpenGLDynamicRHI::RHIBindDebugLabelName(FRHITexture* TextureRHI, const TCHAR* Name) { #if GLDEBUG_LABELS_ENABLED FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList(); if (ShouldRunGLRenderContextOpOnThisThread(RHICmdList)) { VERIFY_GL_SCOPE(); FOpenGLTexture* Texture = GetOpenGLTextureFromRHITexture(TextureRHI); if (Texture->IsEvicted()) { Texture->EvictionParamsPtr->SetDebugLabelName(TCHAR_TO_ANSI(Name)); } else { FOpenGL::LabelObject(GL_TEXTURE, Texture->GetResource(), TCHAR_TO_ANSI(Name)); } } else { // copy string name for RHIT version. FAnsiCharArray TextureDebugName; TextureDebugName.Append(TCHAR_TO_ANSI(Name), FCString::Strlen(Name) + 1); RunOnGLRenderContextThread([TextureRHI, TextureDebugName = MoveTemp(TextureDebugName)]() { VERIFY_GL_SCOPE(); FOpenGLTexture* Texture = GetOpenGLTextureFromRHITexture(TextureRHI); if (Texture->IsEvicted()) { Texture->EvictionParamsPtr->SetDebugLabelName(TextureDebugName); } else { FOpenGL::LabelObject(GL_TEXTURE, Texture->GetResource(), TextureDebugName.GetData()); } }); } #endif } void FOpenGLDynamicRHI::RHIVirtualTextureSetFirstMipInMemory(FRHITexture2D* TextureRHI, uint32 FirstMip) { } void FOpenGLDynamicRHI::RHIVirtualTextureSetFirstMipVisible(FRHITexture2D* TextureRHI, uint32 FirstMip) { } void FOpenGLDynamicRHI::RHICopyTexture(FRHITexture* SourceTextureRHI, FRHITexture* DestTextureRHI, const FRHICopyTextureInfo& CopyInfo) { VERIFY_GL_SCOPE(); FOpenGLTexture* SourceTexture = GetOpenGLTextureFromRHITexture(SourceTextureRHI); FOpenGLTexture* DestTexture = GetOpenGLTextureFromRHITexture(DestTextureRHI); checkf(SourceTexture->Target == DestTexture->Target, TEXT("Cannot copy between different texture targets, SourceTexture Target=%x, Format=%d, Flags=%x; DestTexture Target=%x, Format=%d, Flags=%x"), SourceTexture->Target, SourceTextureRHI->GetFormat(), SourceTextureRHI->GetFlags(), DestTexture->Target, DestTextureRHI->GetFormat(), DestTextureRHI->GetFlags() ); checkf((SourceTextureRHI->GetFlags() & TexCreate_SRGB) == (DestTextureRHI->GetFlags() & TexCreate_SRGB), TEXT("Cannot copy between sRGB and linear, SourceTexture Format=%d, Flags=%x; DestTexture Format=%d, Flags=%x"), SourceTextureRHI->GetFormat(), SourceTextureRHI->GetFlags(), DestTextureRHI->GetFormat(), DestTextureRHI->GetFlags() ); 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; if (FOpenGL::SupportsCopyImage()) { for (uint32 MipIndex = 0; MipIndex < CopyInfo.NumMips; ++MipIndex) { GLint SrcZOffset, DestZOffset; switch (SourceTexture->Target) { case GL_TEXTURE_3D: case GL_TEXTURE_CUBE_MAP: // For cube maps, the Z offsets select the starting faces. SrcZOffset = CopyInfo.SourcePosition.Z >> MipIndex; DestZOffset = CopyInfo.DestPosition.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. SrcZOffset = CopyInfo.SourceSliceIndex; DestZOffset = CopyInfo.DestSliceIndex; Depth = CopyInfo.NumSlices; break; default: SrcZOffset = 0; DestZOffset = 0; break; } GLint SourceX = CopyInfo.SourcePosition.X >> MipIndex; GLint SourceY = CopyInfo.SourcePosition.Y >> MipIndex; GLint DestX = CopyInfo.DestPosition.X >> MipIndex; GLint DestY = CopyInfo.DestPosition.Y >> MipIndex; FOpenGL::CopyImageSubData(SourceTexture->GetResource(), SourceTexture->Target, SrcMip, SourceX, SourceY, SrcZOffset, DestTexture->GetResource(), DestTexture->Target, DestMip, DestX, DestY, DestZOffset, 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); } } return; } // Convert sub texture regions to GL types GLint XOffset = CopyInfo.DestPosition.X; GLint YOffset = CopyInfo.DestPosition.Y; GLint ZOffset = CopyInfo.DestPosition.Z; GLint X = CopyInfo.SourcePosition.X; GLint Y = CopyInfo.SourcePosition.Y; GLint Z = CopyInfo.SourcePosition.Z; // Use a texture stage that's not likely to be used for draws, to avoid waiting FOpenGLContextState& ContextState = GetContextStateForCurrentContext(); CachedSetupTextureStage(ContextState, FOpenGL::GetMaxCombinedTextureImageUnits() - 1, DestTexture->Target, DestTexture->GetResource(), 0, DestTextureRHI->GetNumMips()); CachedBindPixelUnpackBuffer(ContextState, 0); // Bind source texture to an FBO to read from for (uint32 SliceIndex = 0; SliceIndex < CopyInfo.NumSlices; ++SliceIndex) { for (uint32 MipIndex = 0; MipIndex < CopyInfo.NumMips; ++MipIndex) { FOpenGLTexture* RenderTargets[1] = { SourceTexture }; uint32 MipLevels[1] = { static_cast(SrcMip) }; uint32 ArrayIndices[1] = { CopyInfo.SourceSliceIndex + SliceIndex }; GLuint SourceFBO = GetOpenGLFramebuffer(1, RenderTargets, ArrayIndices, MipLevels, nullptr); check(SourceFBO != 0); glBindFramebuffer(GL_FRAMEBUFFER, SourceFBO); FOpenGL::ReadBuffer(GL_COLOR_ATTACHMENT0); switch (DestTexture->Target) { case GL_TEXTURE_1D: FOpenGL::CopyTexSubImage1D(DestTexture->Target, DestMip, XOffset >> MipIndex, X >> MipIndex, 0, Width); break; case GL_TEXTURE_1D_ARRAY: FOpenGL::CopyTexSubImage2D(DestTexture->Target, DestMip, XOffset >> MipIndex, CopyInfo.DestSliceIndex + SliceIndex, X >> MipIndex, 0, Width, 1); break; case GL_TEXTURE_2D: case GL_TEXTURE_RECTANGLE: FOpenGL::CopyTexSubImage2D(DestTexture->Target, DestMip, XOffset >> MipIndex, YOffset >> MipIndex, X >> MipIndex, Y >> MipIndex, Width, Height); break; case GL_TEXTURE_2D_ARRAY: FOpenGL::CopyTexSubImage3D(DestTexture->Target, DestMip, XOffset >> MipIndex, YOffset >> MipIndex, CopyInfo.DestSliceIndex + SliceIndex, X >> MipIndex, Y >> MipIndex, Width, Height); break; case GL_TEXTURE_3D: FOpenGL::CopyTexSubImage3D(DestTexture->Target, DestMip, XOffset >> MipIndex, YOffset >> MipIndex, ZOffset >> MipIndex, X >> MipIndex, Y >> MipIndex, Width, Height); break; case GL_TEXTURE_CUBE_MAP: for (int32 FaceIndex = FMath::Min((int32)CopyInfo.NumSlices, 6) - 1; FaceIndex >= 0; FaceIndex--) { FOpenGL::CopyTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + (uint32)FaceIndex, CopyInfo.DestMipIndex, XOffset >> MipIndex, YOffset >> MipIndex, X >> MipIndex, Y >> MipIndex, Width, Height); } break; } } ++SrcMip; ++DestMip; Width = FMath::Max(1, Width >> 1); Height = FMath::Max(1, Height >> 1); } ContextState.Framebuffer = (GLuint)-1; } FTexture2DRHIRef 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); } FTexture2DRHIRef 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); } FTextureCubeRHIRef 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 = GetOpenGLTextureFromRHITexture(DestRHITexture); FOpenGLTexture* SrcTexture = GetOpenGLTextureFromRHITexture(SrcRHITexture); if (DestTexture && SrcTexture) { DestTexture->AliasResources(*SrcTexture); } } FTextureRHIRef FOpenGLDynamicRHI::RHICreateAliasedTexture(FTextureRHIRef& SourceTexture) { const FString Name = SourceTexture->GetName().ToString() + TEXT("Alias"); return new FOpenGLTexture(*GetOpenGLTextureFromRHITexture(SourceTexture), *Name, FOpenGLTexture::AliasResource); } void* FOpenGLDynamicRHI::LockTexture2D_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITexture2D* Texture, uint32 MipIndex, EResourceLockMode LockMode, uint32& DestStride, bool bLockWithinMiptail, bool bNeedsDefaultRHIFlush) { check(IsInRenderingThread()); static auto* CVarRHICmdBufferWriteLocks = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.RHICmdBufferWriteLocks")); bool bBuffer = CVarRHICmdBufferWriteLocks->GetValueOnRenderThread() > 0; void* Result; uint32 MipBytes = 0; if (!bBuffer || LockMode != RLM_WriteOnly || RHICmdList.Bypass() || !IsRunningRHIInSeparateThread()) { RHITHREAD_GLCOMMAND_PROLOGUE(); return this->RHILockTexture2D(Texture, MipIndex, LockMode, DestStride, bLockWithinMiptail); RHITHREAD_GLCOMMAND_EPILOGUE_GET_RETURN(void *); Result = ReturnValue; MipBytes = GetOpenGLTextureFromRHITexture(Texture)->GetLockSize(MipIndex, 0, LockMode, DestStride); } else { MipBytes = GetOpenGLTextureFromRHITexture(Texture)->GetLockSize(MipIndex, 0, LockMode, DestStride); Result = FMemory::Malloc(MipBytes, 16); } check(Result); GLLockTracker.Lock(Texture, Result, 0, MipIndex, DestStride, MipBytes, LockMode); return Result; } void FOpenGLDynamicRHI::UnlockTexture2D_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITexture2D* Texture, uint32 MipIndex, bool bLockWithinMiptail, bool bNeedsDefaultRHIFlush) { check(IsInRenderingThread()); static auto* CVarRHICmdBufferWriteLocks = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.RHICmdBufferWriteLocks")); bool bBuffer = CVarRHICmdBufferWriteLocks->GetValueOnRenderThread() > 0; FTextureLockTracker::FLockParams Params = GLLockTracker.Unlock(Texture, 0, MipIndex); if (!bBuffer || Params.LockMode != RLM_WriteOnly || RHICmdList.Bypass() || !IsRunningRHIInSeparateThread()) { GLLockTracker.TotalMemoryOutstanding = 0; RHITHREAD_GLCOMMAND_PROLOGUE(); this->RHIUnlockTexture2D(Texture, MipIndex, bLockWithinMiptail); RHITHREAD_GLCOMMAND_EPILOGUE(); } else { auto GLCommand = [=]() { uint32 DestStride; uint8* TexMem = (uint8*)this->RHILockTexture2D(Texture, MipIndex, Params.LockMode, DestStride, bLockWithinMiptail); uint8* BuffMem = (uint8*)Params.Buffer; check(DestStride == Params.Stride); FMemory::Memcpy(TexMem, BuffMem, Params.BufferSize); FMemory::Free(Params.Buffer); this->RHIUnlockTexture2D(Texture, MipIndex, bLockWithinMiptail); }; ALLOC_COMMAND_CL(RHICmdList, FRHICommandGLCommand)(MoveTemp(GLCommand)); } } void* FOpenGLDynamicRHI::RHILockTextureCubeFace_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITextureCube* Texture, uint32 FaceIndex, uint32 ArrayIndex, uint32 MipIndex, EResourceLockMode LockMode, uint32& DestStride, bool bLockWithinMiptail) { check(IsInRenderingThread()); static auto* CVarRHICmdBufferWriteLocks = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.RHICmdBufferWriteLocks")); bool bBuffer = CVarRHICmdBufferWriteLocks->GetValueOnRenderThread() > 0; void* Result; uint32 MipBytes = 0; if (!bBuffer || LockMode != RLM_WriteOnly || RHICmdList.Bypass() || !IsRunningRHIInSeparateThread()) { RHITHREAD_GLCOMMAND_PROLOGUE(); return this->RHILockTextureCubeFace(Texture, FaceIndex, ArrayIndex, MipIndex, LockMode, DestStride, bLockWithinMiptail); RHITHREAD_GLCOMMAND_EPILOGUE_GET_RETURN(void *); Result = ReturnValue; MipBytes = GetOpenGLTextureFromRHITexture(Texture)->GetLockSize(MipIndex, 0, LockMode, DestStride); } else { MipBytes = GetOpenGLTextureFromRHITexture(Texture)->GetLockSize(MipIndex, 0, LockMode, DestStride); Result = FMemory::Malloc(MipBytes, 16); } check(Result); GLLockTracker.Lock(Texture, Result, ArrayIndex, MipIndex, DestStride, MipBytes, LockMode); return Result; } void FOpenGLDynamicRHI::RHIUnlockTextureCubeFace_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITextureCube* Texture, uint32 FaceIndex, uint32 ArrayIndex, uint32 MipIndex, bool bLockWithinMiptail) { check(IsInRenderingThread()); static auto* CVarRHICmdBufferWriteLocks = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.RHICmdBufferWriteLocks")); bool bBuffer = CVarRHICmdBufferWriteLocks->GetValueOnRenderThread() > 0; FTextureLockTracker::FLockParams Params = GLLockTracker.Unlock(Texture, ArrayIndex, MipIndex); if (!bBuffer || Params.LockMode != RLM_WriteOnly || RHICmdList.Bypass() || !IsRunningRHIInSeparateThread()) { GLLockTracker.TotalMemoryOutstanding = 0; RHITHREAD_GLCOMMAND_PROLOGUE(); this->RHIUnlockTextureCubeFace(Texture, FaceIndex, ArrayIndex, MipIndex, bLockWithinMiptail); RHITHREAD_GLCOMMAND_EPILOGUE(); } else { auto GLCommand = [=]() { uint32 DestStride; uint8* TexMem = (uint8*)this->RHILockTextureCubeFace(Texture, FaceIndex, ArrayIndex, MipIndex, RLM_WriteOnly, DestStride, bLockWithinMiptail); uint8* BuffMem = (uint8*)Params.Buffer; check(DestStride == Params.Stride); FMemory::Memcpy(TexMem, BuffMem, Params.BufferSize); FMemory::Free(Params.Buffer); this->RHIUnlockTextureCubeFace(Texture, FaceIndex, ArrayIndex, MipIndex, bLockWithinMiptail); }; ALLOC_COMMAND_CL(RHICmdList, FRHICommandGLCommand)(MoveTemp(GLCommand)); } } void* FOpenGLDynamicRHI::LockTexture2DArray_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITexture2DArray* Texture, uint32 ArrayIndex, uint32 MipIndex, EResourceLockMode LockMode, uint32& DestStride, bool bLockWithinMiptail) { check(IsInRenderingThread()); static auto* CVarRHICmdBufferWriteLocks = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.RHICmdBufferWriteLocks")); bool bBuffer = CVarRHICmdBufferWriteLocks->GetValueOnRenderThread() > 0; void* Result; uint32 MipBytes = 0; if (!bBuffer || LockMode != RLM_WriteOnly || RHICmdList.Bypass() || !IsRunningRHIInSeparateThread()) { RHITHREAD_GLCOMMAND_PROLOGUE(); return this->RHILockTexture2DArray(Texture, ArrayIndex, MipIndex, LockMode, DestStride, bLockWithinMiptail); RHITHREAD_GLCOMMAND_EPILOGUE_GET_RETURN(void*); Result = ReturnValue; MipBytes = GetOpenGLTextureFromRHITexture(Texture)->GetLockSize(MipIndex, ArrayIndex, LockMode, DestStride); } else { MipBytes = GetOpenGLTextureFromRHITexture(Texture)->GetLockSize(MipIndex, ArrayIndex, LockMode, DestStride); Result = FMemory::Malloc(MipBytes, 16); } check(Result); GLLockTracker.Lock(Texture, Result, ArrayIndex, MipIndex, DestStride, MipBytes, LockMode); return Result; } void FOpenGLDynamicRHI::UnlockTexture2DArray_RenderThread(class FRHICommandListImmediate& RHICmdList, FRHITexture2DArray* Texture, uint32 ArrayIndex, uint32 MipIndex, bool bLockWithinMiptail) { check(IsInRenderingThread()); static auto* CVarRHICmdBufferWriteLocks = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.RHICmdBufferWriteLocks")); bool bBuffer = CVarRHICmdBufferWriteLocks->GetValueOnRenderThread() > 0; FTextureLockTracker::FLockParams Params = GLLockTracker.Unlock(Texture, ArrayIndex, MipIndex); if (!bBuffer || Params.LockMode != RLM_WriteOnly || RHICmdList.Bypass() || !IsRunningRHIInSeparateThread()) { GLLockTracker.TotalMemoryOutstanding = 0; RHITHREAD_GLCOMMAND_PROLOGUE(); this->RHIUnlockTexture2DArray(Texture, ArrayIndex, MipIndex, bLockWithinMiptail); RHITHREAD_GLCOMMAND_EPILOGUE(); } else { auto GLCommand = [=]() { uint32 DestStride; uint8* TexMem = (uint8*)this->RHILockTexture2DArray(Texture, ArrayIndex, MipIndex, Params.LockMode, DestStride, bLockWithinMiptail); uint8* BuffMem = (uint8*)Params.Buffer; check(DestStride == Params.Stride); FMemory::Memcpy(TexMem, BuffMem, Params.BufferSize); FMemory::Free(Params.Buffer); this->RHIUnlockTexture2DArray(Texture, ArrayIndex, MipIndex, bLockWithinMiptail); }; ALLOC_COMMAND_CL(RHICmdList, FRHICommandGLCommand)(MoveTemp(GLCommand)); } } 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() && (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); VERIFY_GL_SCOPE(); if (MipImageData[MipIndex].Num()) { // already have data?? checkNoEntry(); } else { 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(); } }