Files
UnrealEngineUWP/Engine/Source/Runtime/OpenGLDrv/Private/OpenGLDebugFrameDump.cpp
Ben Marsh 4ba423868f Copying //UE4/Dev-Build to //UE4/Dev-Main (Source: //UE4/Dev-Build @ 3209340)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3209340 on 2016/11/23 by Ben.Marsh

	Convert UE4 codebase to an "include what you use" model - where every header just includes the dependencies it needs, rather than every source file including large monolithic headers like Engine.h and UnrealEd.h.

	Measured full rebuild times around 2x faster using XGE on Windows, and improvements of 25% or more for incremental builds and full rebuilds on most other platforms.

	  * Every header now includes everything it needs to compile.
	        * There's a CoreMinimal.h header that gets you a set of ubiquitous types from Core (eg. FString, FName, TArray, FVector, etc...). Most headers now include this first.
	        * There's a CoreTypes.h header that sets up primitive UE4 types and build macros (int32, PLATFORM_WIN64, etc...). All headers in Core include this first, as does CoreMinimal.h.
	  * Every .cpp file includes its matching .h file first.
	        * This helps validate that each header is including everything it needs to compile.
	  * No engine code includes a monolithic header such as Engine.h or UnrealEd.h any more.
	        * You will get a warning if you try to include one of these from the engine. They still exist for compatibility with game projects and do not produce warnings when included there.
	        * There have only been minor changes to our internal games down to accommodate these changes. The intent is for this to be as seamless as possible.
	  * No engine code explicitly includes a precompiled header any more.
	        * We still use PCHs, but they're force-included on the compiler command line by UnrealBuildTool instead. This lets us tune what they contain without breaking any existing include dependencies.
	        * PCHs are generated by a tool to get a statistical amount of coverage for the source files using it, and I've seeded the new shared PCHs to contain any header included by > 15% of source files.

	Tool used to generate this transform is at Engine\Source\Programs\IncludeTool.

[CL 3209342 by Ben Marsh in Main branch]
2016-11-23 15:48:37 -05:00

5597 lines
194 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
OpenGLDebugFrameDump.cpp: Implementation of debug code that allows creating detailed summaries of OpenGL pipeline state for all draws in a single frame.
=============================================================================*/
#include "CoreMinimal.h"
#include "HAL/FileManager.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Misc/OutputDeviceFile.h"
#include "Modules/ModuleManager.h"
#include "OpenGLDrv.h"
/** This define decreases file size, but substantially increases frame dump time. Also, if you turn it off, make sure your image viewer correctly displays BMP files with alpha. */
#define USE_COMPRESSED_PNG_INSTEAD_OF_BMP_FOR_CONTENT_OUTPUT 1
#if USE_COMPRESSED_PNG_INSTEAD_OF_BMP_FOR_CONTENT_OUTPUT
// For PNG compression
#include "Interfaces/IImageWrapperModule.h"
const GLenum TextureOutputFormat = GL_RGBA;
#else
const GLenum TextureOutputFormat = GL_BGRA;
#endif
#define DEBUG_GL_ERRORS_CAUSED_BY_THIS_CODE 1
#if DEBUG_GL_ERRORS_CAUSED_BY_THIS_CODE
#define ASSERT_NO_GL_ERROR() check( glGetError() == GL_NO_ERROR )
#else
#define ASSERT_NO_GL_ERROR()
#endif
extern bool GDisableOpenGLDebugOutput;
#if USE_COMPRESSED_PNG_INSTEAD_OF_BMP_FOR_CONTENT_OUTPUT
void appCreatePNGWithAlpha( const TCHAR* File, int32 Width, int32 Height, FColor* Data, IFileManager* FileManager = &IFileManager::Get() )
{
// We assume all resources are png for now.
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>( FName("ImageWrapper") );
IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper( EImageFormat::PNG );
if ( ImageWrapper.IsValid() && ImageWrapper->SetRaw( Data, 4 * Width * Height, Width, Height, ERGBFormat::RGBA, 8 ) )
{
FArchive* Ar = FileManager->CreateFileWriter( File );
if( !Ar )
return;
const TArray<uint8>& CompressedData = ImageWrapper->GetCompressed();
int32 CompressedSize = CompressedData.Num();
Ar->Serialize( (void*)CompressedData.GetData(), CompressedSize );
delete Ar;
}
}
#else
void appCreateBitmapWithAlpha( const TCHAR* File, int32 Width, int32 Height, FColor* Data, IFileManager* FileManager = &IFileManager::Get() )
{
FArchive* Ar = FileManager->CreateFileWriter( File );
if( !Ar )
return;
// Types.
#if PLATFORM_SUPPORTS_PRAGMA_PACK
#pragma pack (push,1)
#endif
struct BITMAPFILEHEADER
{
uint16 bfType GCC_PACK(1);
uint32 bfSize GCC_PACK(1);
uint16 bfReserved1 GCC_PACK(1);
uint16 bfReserved2 GCC_PACK(1);
uint32 bfOffBits GCC_PACK(1);
} FH;
struct BITMAPINFOHEADER
{
uint32 biSize GCC_PACK(1);
int32 biWidth GCC_PACK(1);
int32 biHeight GCC_PACK(1);
uint16 biPlanes GCC_PACK(1);
uint16 biBitCount GCC_PACK(1);
uint32 biCompression GCC_PACK(1);
uint32 biSizeImage GCC_PACK(1);
int32 biXPelsPerMeter GCC_PACK(1);
int32 biYPelsPerMeter GCC_PACK(1);
uint32 biClrUsed GCC_PACK(1);
uint32 biClrImportant GCC_PACK(1);
} IH;
#if PLATFORM_SUPPORTS_PRAGMA_PACK
#pragma pack (pop)
#endif
uint32 BytesPerLine = Width * 4;
// File header.
FH.bfType = INTEL_ORDER16((uint16) ('B' + 256*'M'));
FH.bfSize = INTEL_ORDER32((uint32) (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + BytesPerLine * Height));
FH.bfReserved1 = INTEL_ORDER16((uint16) 0);
FH.bfReserved2 = INTEL_ORDER16((uint16) 0);
FH.bfOffBits = INTEL_ORDER32((uint32) (sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)));
Ar->Serialize( &FH, sizeof(FH) );
// Info header.
IH.biSize = INTEL_ORDER32((uint32) sizeof(BITMAPINFOHEADER));
IH.biWidth = INTEL_ORDER32((uint32) Width);
IH.biHeight = INTEL_ORDER32((uint32) Height);
IH.biPlanes = INTEL_ORDER16((uint16) 1);
IH.biBitCount = INTEL_ORDER16((uint16) 32);
IH.biCompression = INTEL_ORDER32((uint32) 0); //BI_RGBA
IH.biSizeImage = INTEL_ORDER32((uint32) BytesPerLine * Height);
IH.biXPelsPerMeter = INTEL_ORDER32((uint32) 0);
IH.biYPelsPerMeter = INTEL_ORDER32((uint32) 0);
IH.biClrUsed = INTEL_ORDER32((uint32) 0);
IH.biClrImportant = INTEL_ORDER32((uint32) 0);
Ar->Serialize( &IH, sizeof(IH) );
// Colors.
for( int32 i=Height-1; i>=0; i-- )
{
for( int32 j=0; j<Width; j++ )
{
Ar->Serialize( &Data[i*Width+j].B, 1 );
Ar->Serialize( &Data[i*Width+j].G, 1 );
Ar->Serialize( &Data[i*Width+j].R, 1 );
Ar->Serialize( &Data[i*Width+j].A, 1 );
}
}
// Success.
delete Ar;
}
#endif
// A simple code to save a single surface into separate DDS file.
// I know there's a TextureFormatDXT module, but it's too intertwined with the rest of the engine,
// and I prefer this small function to be kept independent of everything, as it's only for one simple debug job, and
// to not introduce a lot of UE4 types like FImage that aren't really necessary.
#ifndef MAKEFOURCC
#define MAKEFOURCC(ch0, ch1, ch2, ch3)\
((uint32)(uint8)(ch0) | ((uint32)(uint8)(ch1) << 8) |\
((uint32)(uint8)(ch2) << 16) | ((uint32)(uint8)(ch3) << 24 ))
#endif
void appCreateDDSWithSingleSurface( const TCHAR* File, int32 Width, int32 Height, GLint InternalFormat, const void* Data, uint32 DataSize, IFileManager* FileManager = &IFileManager::Get() )
{
FArchive* Ar = FileManager->CreateFileWriter( File );
if( !Ar )
return;
// Types.
#if PLATFORM_SUPPORTS_PRAGMA_PACK
#pragma pack (push,1)
#endif
struct DDSPixelFormat
{
uint32 Size GCC_PACK(1);
uint32 Flags GCC_PACK(1);
uint32 FourCC GCC_PACK(1);
uint32 RGBBitCount GCC_PACK(1);
uint32 RBitMask GCC_PACK(1);
uint32 GBitMask GCC_PACK(1);
uint32 BBitMask GCC_PACK(1);
uint32 ABitMask GCC_PACK(1);
};
struct DDSHeader
{
uint32 Size GCC_PACK(1);
uint32 Flags GCC_PACK(1);
uint32 Height GCC_PACK(1);
uint32 Width GCC_PACK(1);
uint32 PitchOrLinearSize GCC_PACK(1);
uint32 Depth GCC_PACK(1);
uint32 MipMapCount GCC_PACK(1);
uint32 Reserved[11] GCC_PACK(1);
struct DDSPixelFormat PixelFormat;
uint32 Caps1 GCC_PACK(1);
uint32 Caps2 GCC_PACK(1);
uint32 Reserved2[3] GCC_PACK(1);
} Header;
#if PLATFORM_SUPPORTS_PRAGMA_PACK
#pragma pack (pop)
#endif
uint8 FileType[] = "DDS ";
Ar->Serialize( FileType, sizeof(uint8)*4 );
check( sizeof(Header) == 124 );
memset( &Header, 0, sizeof(Header) );
Header.Size = INTEL_ORDER32((uint32) 124);
Header.Flags = INTEL_ORDER32((uint32) 0x81007);
Header.Width = INTEL_ORDER32((uint32) Width);
Header.Height = INTEL_ORDER32((uint32) Height);
Header.PitchOrLinearSize = INTEL_ORDER32((uint32) DataSize);
Header.Depth = INTEL_ORDER32((uint32) 1);
Header.MipMapCount = INTEL_ORDER32((uint32) 1);
Header.Caps1 = INTEL_ORDER32((uint32) 0x1000);
Header.PixelFormat.Size = INTEL_ORDER32((uint32) 32);
Header.PixelFormat.Flags = INTEL_ORDER32((uint32) 4);
switch( InternalFormat )
{
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
Header.PixelFormat.FourCC = INTEL_ORDER32((uint32) MAKEFOURCC('D','X','T','1') );
break;
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
Header.PixelFormat.FourCC = INTEL_ORDER32((uint32) MAKEFOURCC('D','X','T','3') );
break;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
Header.PixelFormat.FourCC = INTEL_ORDER32((uint32) MAKEFOURCC('D','X','T','5') );
break;
case GL_COMPRESSED_RED_RGTC1:
Header.PixelFormat.FourCC = INTEL_ORDER32((uint32) MAKEFOURCC('B','C','4','U') ); // BC4 UNORM
break;
case GL_COMPRESSED_SIGNED_RED_RGTC1:
Header.PixelFormat.FourCC = INTEL_ORDER32((uint32) MAKEFOURCC('B','C','4','S') ); // BC4 SNORM
break;
case GL_COMPRESSED_RG_RGTC2:
Header.PixelFormat.FourCC = INTEL_ORDER32((uint32) MAKEFOURCC('A','T','I','2') ); // BC5 UNORM
break;
case GL_COMPRESSED_SIGNED_RG_RGTC2:
Header.PixelFormat.FourCC = INTEL_ORDER32((uint32) MAKEFOURCC('B','C','5','S') ); // BC5 SNORM
break;
default:
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Unknown internal format ( 0x%x ) while saving DDS file '%s'. Resulting DDS may be unreadable."), InternalFormat, *File );
break;
}
Ar->Serialize( &Header, sizeof(Header) );
Ar->Serialize( (void*)Data, DataSize );
delete Ar;
}
//-----------------------------------------------------------------------------
#if ENABLE_OPENGL_FRAMEDUMP || ENABLE_UNIFORM_BUFFER_LAYOUT_DUMP
const TCHAR* GetGLUniformTypeString( GLint UniformType )
{
switch( UniformType )
{
case GL_FLOAT: return TEXT("GL_FLOAT");
case GL_FLOAT_VEC2: return TEXT("GL_FLOAT_VEC2");
case GL_FLOAT_VEC3: return TEXT("GL_FLOAT_VEC3");
case GL_FLOAT_VEC4: return TEXT("GL_FLOAT_VEC4");
case GL_INT: return TEXT("GL_INT");
case GL_INT_VEC2: return TEXT("GL_INT_VEC2");
case GL_INT_VEC3: return TEXT("GL_INT_VEC3");
case GL_INT_VEC4: return TEXT("GL_INT_VEC4");
case GL_UNSIGNED_INT: return TEXT("GL_UNSIGNED_INT");
case GL_UNSIGNED_INT_VEC2: return TEXT("GL_UNSIGNED_INT_VEC2");
case GL_UNSIGNED_INT_VEC3: return TEXT("GL_UNSIGNED_INT_VEC3");
case GL_UNSIGNED_INT_VEC4: return TEXT("GL_UNSIGNED_INT_VEC4");
case GL_BOOL: return TEXT("GL_BOOL");
case GL_BOOL_VEC2: return TEXT("GL_BOOL_VEC2");
case GL_BOOL_VEC3: return TEXT("GL_BOOL_VEC3");
case GL_BOOL_VEC4: return TEXT("GL_BOOL_VEC4");
case GL_FLOAT_MAT2: return TEXT("GL_FLOAT_MAT2");
case GL_FLOAT_MAT3: return TEXT("GL_FLOAT_MAT3");
case GL_FLOAT_MAT4: return TEXT("GL_FLOAT_MAT4");
case GL_FLOAT_MAT2x3: return TEXT("GL_FLOAT_MAT2x3");
case GL_FLOAT_MAT2x4: return TEXT("GL_FLOAT_MAT2x4");
case GL_FLOAT_MAT3x2: return TEXT("GL_FLOAT_MAT3x2");
case GL_FLOAT_MAT3x4: return TEXT("GL_FLOAT_MAT3x4");
case GL_FLOAT_MAT4x2: return TEXT("GL_FLOAT_MAT4x2");
case GL_FLOAT_MAT4x3: return TEXT("GL_FLOAT_MAT4x3");
case GL_SAMPLER_1D: return TEXT("GL_SAMPLER_1D");
case GL_SAMPLER_2D: return TEXT("GL_SAMPLER_2D");
case GL_SAMPLER_3D: return TEXT("GL_SAMPLER_3D");
case GL_SAMPLER_CUBE: return TEXT("GL_SAMPLER_CUBE");
case GL_SAMPLER_1D_SHADOW: return TEXT("GL_SAMPLER_1D_SHADOW");
case GL_SAMPLER_2D_SHADOW: return TEXT("GL_SAMPLER_2D_SHADOW");
case GL_SAMPLER_1D_ARRAY: return TEXT("GL_SAMPLER_1D_ARRAY");
case GL_SAMPLER_2D_ARRAY: return TEXT("GL_SAMPLER_2D_ARRAY");
case GL_SAMPLER_1D_ARRAY_SHADOW: return TEXT("GL_SAMPLER_1D_ARRAY_SHADOW");
case GL_SAMPLER_2D_ARRAY_SHADOW: return TEXT("GL_SAMPLER_2D_ARRAY_SHADOW");
case GL_SAMPLER_2D_MULTISAMPLE: return TEXT("GL_SAMPLER_2D_MULTISAMPLE");
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return TEXT("GL_SAMPLER_2D_MULTISAMPLE_ARRAY");
case GL_SAMPLER_CUBE_SHADOW: return TEXT("GL_SAMPLER_CUBE_SHADOW");
case GL_SAMPLER_BUFFER: return TEXT("GL_SAMPLER_BUFFER");
case GL_SAMPLER_2D_RECT: return TEXT("GL_SAMPLER_2D_RECT");
case GL_SAMPLER_2D_RECT_SHADOW: return TEXT("GL_SAMPLER_2D_RECT_SHADOW");
case GL_INT_SAMPLER_1D: return TEXT("GL_INT_SAMPLER_1D");
case GL_INT_SAMPLER_2D: return TEXT("GL_INT_SAMPLER_2D");
case GL_INT_SAMPLER_3D: return TEXT("GL_INT_SAMPLER_3D");
case GL_INT_SAMPLER_CUBE: return TEXT("GL_INT_SAMPLER_CUBE");
case GL_INT_SAMPLER_1D_ARRAY: return TEXT("GL_INT_SAMPLER_1D_ARRAY");
case GL_INT_SAMPLER_2D_ARRAY: return TEXT("GL_INT_SAMPLER_2D_ARRAY");
case GL_INT_SAMPLER_2D_MULTISAMPLE: return TEXT("GL_INT_SAMPLER_2D_MULTISAMPLE");
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return TEXT("GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY");
case GL_INT_SAMPLER_BUFFER: return TEXT("GL_INT_SAMPLER_BUFFER");
case GL_INT_SAMPLER_2D_RECT: return TEXT("GL_INT_SAMPLER_2D_RECT");
case GL_UNSIGNED_INT_SAMPLER_1D: return TEXT("GL_UNSIGNED_INT_SAMPLER_1D");
case GL_UNSIGNED_INT_SAMPLER_2D: return TEXT("GL_UNSIGNED_INT_SAMPLER_2D");
case GL_UNSIGNED_INT_SAMPLER_3D: return TEXT("GL_UNSIGNED_INT_SAMPLER_3D");
case GL_UNSIGNED_INT_SAMPLER_CUBE: return TEXT("GL_UNSIGNED_INT_SAMPLER_CUBE");
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return TEXT("GL_UNSIGNED_INT_SAMPLER_1D_ARRAY");
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return TEXT("GL_UNSIGNED_INT_SAMPLER_2D_ARRAY");
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return TEXT("GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE");
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return TEXT("GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY");
case GL_UNSIGNED_INT_SAMPLER_BUFFER: return TEXT("GL_UNSIGNED_INT_SAMPLER_BUFFER");
case GL_UNSIGNED_INT_SAMPLER_2D_RECT: return TEXT("GL_UNSIGNED_INT_SAMPLER_2D_RECT");
default: return TEXT("!!!unknown!!!");
};
}
#endif
#if ENABLE_OPENGL_FRAMEDUMP
static const TCHAR* GetAttachedBufferName( bool bIsScreenBuffer, GLint DrawBufferIndex )
{
if( bIsScreenBuffer )
{
switch( DrawBufferIndex )
{
case GL_NONE: return TEXT("GL_NONE");
case GL_FRONT_LEFT: return TEXT("GL_FRONT_LEFT");
case GL_FRONT_RIGHT: return TEXT("GL_FRONT_RIGHT");
case GL_BACK_LEFT: return TEXT("GL_BACK_LEFT");
case GL_BACK_RIGHT: return TEXT("GL_BACK_RIGHT");
case GL_FRONT: return TEXT("GL_FRONT");
case GL_BACK: return TEXT("GL_BACK");
case GL_LEFT: return TEXT("GL_LEFT");
case GL_RIGHT: return TEXT("GL_RIGHT");
case GL_FRONT_AND_BACK: return TEXT("GL_FRONT_AND_BACK");
case GL_DEPTH: return TEXT("GL_DEPTH");
case GL_STENCIL: return TEXT("GL_STENCIL");
default: return TEXT("unknown");
}
}
else
{
switch( DrawBufferIndex )
{
case GL_COLOR_ATTACHMENT0: return TEXT("GL_COLOR_ATTACHMENT0");
case GL_COLOR_ATTACHMENT1: return TEXT("GL_COLOR_ATTACHMENT1");
case GL_COLOR_ATTACHMENT2: return TEXT("GL_COLOR_ATTACHMENT2");
case GL_COLOR_ATTACHMENT3: return TEXT("GL_COLOR_ATTACHMENT3");
case GL_COLOR_ATTACHMENT4: return TEXT("GL_COLOR_ATTACHMENT4");
case GL_COLOR_ATTACHMENT5: return TEXT("GL_COLOR_ATTACHMENT5");
case GL_COLOR_ATTACHMENT6: return TEXT("GL_COLOR_ATTACHMENT6");
case GL_COLOR_ATTACHMENT7: return TEXT("GL_COLOR_ATTACHMENT7");
case GL_COLOR_ATTACHMENT8: return TEXT("GL_COLOR_ATTACHMENT8");
case GL_COLOR_ATTACHMENT9: return TEXT("GL_COLOR_ATTACHMENT9");
case GL_COLOR_ATTACHMENT10: return TEXT("GL_COLOR_ATTACHMENT10");
case GL_COLOR_ATTACHMENT11: return TEXT("GL_COLOR_ATTACHMENT11");
case GL_COLOR_ATTACHMENT12: return TEXT("GL_COLOR_ATTACHMENT12");
case GL_COLOR_ATTACHMENT13: return TEXT("GL_COLOR_ATTACHMENT13");
case GL_COLOR_ATTACHMENT14: return TEXT("GL_COLOR_ATTACHMENT14");
case GL_COLOR_ATTACHMENT15: return TEXT("GL_COLOR_ATTACHMENT15");
case GL_DEPTH_ATTACHMENT: return TEXT("GL_DEPTH_ATTACHMENT");
case GL_STENCIL_ATTACHMENT: return TEXT("GL_STENCIL_ATTACHMENT");
default: return TEXT("unknown");
}
}
return NULL; // to shut up the compiler; it can't reach here, ever
}
static const TCHAR* GetGLCompareString( GLint CompareFunction )
{
switch( CompareFunction )
{
case GL_NEVER: return TEXT("GL_NEVER");
case GL_LESS: return TEXT("GL_LESS");
case GL_EQUAL: return TEXT("GL_EQUAL");
case GL_LEQUAL: return TEXT("GL_LEQUAL");
case GL_GREATER: return TEXT("GL_GREATER");
case GL_NOTEQUAL: return TEXT("GL_NOTEQUAL");
case GL_GEQUAL: return TEXT("GL_GEQUAL");
case GL_ALWAYS: return TEXT("GL_ALWAYS");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetGLStencilOpString( GLint StencilOp )
{
switch( StencilOp )
{
case GL_ZERO: return TEXT("GL_ZERO");
case GL_KEEP: return TEXT("GL_KEEP");
case GL_REPLACE: return TEXT("GL_REPLACE");
case GL_INCR: return TEXT("GL_INCR");
case GL_DECR: return TEXT("GL_DECR");
case GL_INCR_WRAP: return TEXT("GL_INCR_WRAP");
case GL_DECR_WRAP: return TEXT("GL_DECR_WRAP");
case GL_INVERT: return TEXT("GL_INVERT");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetGLDataTypeString( GLint DataType )
{
// static FString Unknown;
switch( DataType )
{
case GL_BYTE: return TEXT("GL_BYTE");
case GL_UNSIGNED_BYTE: return TEXT("GL_UNSIGNED_BYTE");
case GL_SHORT: return TEXT("GL_SHORT");
case GL_UNSIGNED_SHORT: return TEXT("GL_UNSIGNED_SHORT");
case GL_INT: return TEXT("GL_INT");
case GL_UNSIGNED_INT: return TEXT("GL_UNSIGNED_INT");
case GL_FLOAT: return TEXT("GL_FLOAT");
case GL_DOUBLE: return TEXT("GL_DOUBLE");
case GL_HALF_FLOAT: return TEXT("GL_HALF_FLOAT");
default: return TEXT("!!!unknown!!!");
// default:
// Unknown = FString::Printf( TEXT("!!!unknown!!!(%d)"), DataType );
// return *Unknown;
}
}
static const TCHAR* GetGLBlendingFactorString( GLint BlendingFactor )
{
switch( BlendingFactor )
{
case GL_ZERO: return TEXT("GL_ZERO");
case GL_ONE: return TEXT("GL_ONE");
case GL_SRC_COLOR: return TEXT("GL_SRC_COLOR");
case GL_ONE_MINUS_SRC_COLOR: return TEXT("GL_ONE_MINUS_SRC_COLOR");
case GL_SRC_ALPHA: return TEXT("GL_SRC_ALPHA");
case GL_ONE_MINUS_SRC_ALPHA: return TEXT("GL_ONE_MINUS_SRC_ALPHA");
case GL_DST_ALPHA: return TEXT("GL_DST_ALPHA");
case GL_ONE_MINUS_DST_ALPHA: return TEXT("GL_ONE_MINUS_DST_ALPHA");
case GL_DST_COLOR: return TEXT("GL_DST_COLOR");
case GL_ONE_MINUS_DST_COLOR: return TEXT("GL_ONE_MINUS_DST_COLOR");
case GL_SRC_ALPHA_SATURATE: return TEXT("GL_SRC_ALPHA_SATURATE");
case GL_CONSTANT_COLOR: return TEXT("GL_CONSTANT_COLOR");
case GL_ONE_MINUS_CONSTANT_COLOR: return TEXT("GL_ONE_MINUS_CONSTANT_COLOR");
case GL_CONSTANT_ALPHA: return TEXT("GL_CONSTANT_ALPHA");
case GL_ONE_MINUS_CONSTANT_ALPHA: return TEXT("GL_ONE_MINUS_CONSTANT_ALPHA");
case GL_BLEND_COLOR: return TEXT("GL_BLEND_COLOR");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetGLBlendFuncString( GLint BlendFunction )
{
switch( BlendFunction )
{
case GL_FUNC_ADD: return TEXT("GL_FUNC_ADD");
case GL_MIN: return TEXT("GL_MIN");
case GL_MAX: return TEXT("GL_MAX");
case GL_FUNC_SUBTRACT: return TEXT("GL_FUNC_SUBTRACT");
case GL_FUNC_REVERSE_SUBTRACT: return TEXT("GL_FUNC_REVERSE_SUBTRACT");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetHintName( GLint Hint )
{
switch( Hint )
{
case GL_DONT_CARE: return TEXT("GL_DONT_CARE");
case GL_FASTEST: return TEXT("GL_FASTEST");
case GL_NICEST: return TEXT("GL_NICEST");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetCompressedTextureFormatName( GLint CompressedTextureFormat )
{
switch( CompressedTextureFormat )
{
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: return TEXT("GL_COMPRESSED_RGB_S3TC_DXT1_EXT");
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return TEXT("GL_COMPRESSED_RGBA_S3TC_DXT1_EXT");
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return TEXT("GL_COMPRESSED_RGBA_S3TC_DXT3_EXT");
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return TEXT("GL_COMPRESSED_RGBA_S3TC_DXT5_EXT");
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: return TEXT("GL_COMPRESSED_SRGB_S3TC_DXT1_EXT");
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: return TEXT("GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT");
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: return TEXT("GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT");
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: return TEXT("GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT");
default: return TEXT("(other)");
};
}
static const TCHAR* GetGLLogicOpString( GLint LogicOp )
{
switch( LogicOp )
{
case GL_CLEAR: return TEXT("GL_CLEAR");
case GL_AND: return TEXT("GL_AND");
case GL_AND_REVERSE: return TEXT("GL_AND_REVERSE");
case GL_COPY: return TEXT("GL_COPY");
case GL_AND_INVERTED: return TEXT("GL_AND_INVERTED");
case GL_NOOP: return TEXT("GL_NOOP");
case GL_XOR: return TEXT("GL_XOR");
case GL_OR: return TEXT("GL_OR");
case GL_NOR: return TEXT("GL_NOR");
case GL_EQUIV: return TEXT("GL_EQUIV");
case GL_INVERT: return TEXT("GL_INVERT");
case GL_OR_REVERSE: return TEXT("GL_OR_REVERSE");
case GL_COPY_INVERTED: return TEXT("GL_COPY_INVERTED");
case GL_OR_INVERTED: return TEXT("GL_OR_INVERTED");
case GL_NAND: return TEXT("GL_NAND");
case GL_SET: return TEXT("GL_SET");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetCullFaceModeName( GLint CullFaceMode )
{
switch( CullFaceMode )
{
case GL_FRONT: return TEXT("GL_FRONT");
case GL_BACK: return TEXT("GL_BACK");
case GL_FRONT_AND_BACK: return TEXT("GL_FRONT_AND_BACK");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetFrontFaceName( GLint FrontFace )
{
switch( FrontFace )
{
case GL_CCW: return TEXT("GL_CCW");
case GL_CW: return TEXT("GL_CW");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetAttachmentSlotName( GLenum AttachmentSlot )
{
switch( AttachmentSlot )
{
case GL_FRONT_LEFT: return TEXT("GL_FRONT_LEFT");
case GL_FRONT_RIGHT: return TEXT("GL_FRONT_RIGHT");
case GL_BACK_LEFT: return TEXT("GL_BACK_LEFT");
case GL_BACK_RIGHT: return TEXT("GL_BACK_RIGHT");
case GL_DEPTH: return TEXT("GL_DEPTH");
case GL_STENCIL: return TEXT("GL_STENCIL");
case GL_COLOR_ATTACHMENT0: return TEXT("GL_COLOR_ATTACHMENT0");
case GL_COLOR_ATTACHMENT1: return TEXT("GL_COLOR_ATTACHMENT1");
case GL_COLOR_ATTACHMENT2: return TEXT("GL_COLOR_ATTACHMENT2");
case GL_COLOR_ATTACHMENT3: return TEXT("GL_COLOR_ATTACHMENT3");
case GL_COLOR_ATTACHMENT4: return TEXT("GL_COLOR_ATTACHMENT4");
case GL_COLOR_ATTACHMENT5: return TEXT("GL_COLOR_ATTACHMENT5");
case GL_COLOR_ATTACHMENT6: return TEXT("GL_COLOR_ATTACHMENT6");
case GL_COLOR_ATTACHMENT7: return TEXT("GL_COLOR_ATTACHMENT7");
case GL_COLOR_ATTACHMENT8: return TEXT("GL_COLOR_ATTACHMENT8");
case GL_COLOR_ATTACHMENT9: return TEXT("GL_COLOR_ATTACHMENT9");
case GL_COLOR_ATTACHMENT10: return TEXT("GL_COLOR_ATTACHMENT10");
case GL_COLOR_ATTACHMENT11: return TEXT("GL_COLOR_ATTACHMENT11");
case GL_COLOR_ATTACHMENT12: return TEXT("GL_COLOR_ATTACHMENT12");
case GL_COLOR_ATTACHMENT13: return TEXT("GL_COLOR_ATTACHMENT13");
case GL_COLOR_ATTACHMENT14: return TEXT("GL_COLOR_ATTACHMENT14");
case GL_COLOR_ATTACHMENT15: return TEXT("GL_COLOR_ATTACHMENT15");
case GL_DEPTH_ATTACHMENT: return TEXT("GL_DEPTH_ATTACHMENT");
case GL_STENCIL_ATTACHMENT: return TEXT("GL_STENCIL_ATTACHMENT");
case GL_DEPTH_STENCIL_ATTACHMENT: return TEXT("GL_DEPTH_STENCIL_ATTACHMENT");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetGLInternalFormatString( GLint InternalFormat )
{
switch( InternalFormat )
{
// Compressed formats
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: return TEXT("GL_COMPRESSED_RGB_S3TC_DXT1_EXT");
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: return TEXT("GL_COMPRESSED_RGBA_S3TC_DXT1_EXT");
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: return TEXT("GL_COMPRESSED_RGBA_S3TC_DXT3_EXT");
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: return TEXT("GL_COMPRESSED_RGBA_S3TC_DXT5_EXT");
// Depth/stencil formats
case GL_DEPTH_COMPONENT16: return TEXT("GL_DEPTH_COMPONENT16");
case GL_DEPTH_COMPONENT24: return TEXT("GL_DEPTH_COMPONENT24");
case GL_DEPTH_COMPONENT32F: return TEXT("GL_DEPTH_COMPONENT32F");
case GL_DEPTH24_STENCIL8: return TEXT("GL_DEPTH24_STENCIL8");
case GL_DEPTH32F_STENCIL8: return TEXT("GL_DEPTH32F_STENCIL8");
// RGBA
case GL_RGBA8: return TEXT("GL_RGBA8");
case GL_RGBA12: return TEXT("GL_RGBA12");
case GL_RGBA16: return TEXT("GL_RGBA16");
case GL_RGBA16F: return TEXT("GL_RGBA16F");
case GL_RGBA32F: return TEXT("GL_RGBA32");
case GL_RGBA16I: return TEXT("GL_RGBA16I");
case GL_RGBA16UI: return TEXT("GL_RGBA16UI");
case GL_RGBA32I: return TEXT("GL_RGBA32I");
case GL_RGBA32UI: return TEXT("GL_RGBA32UI");
case GL_RGB10_A2: return TEXT("GL_RGB10_A2");
case GL_RGBA4: return TEXT("GL_RGBA4");
case GL_RGB5_A1: return TEXT("GL_RGB5_A1");
case GL_SRGB8_ALPHA8: return TEXT("GL_SRGB8_ALPHA8");
// RG
case GL_RG8: return TEXT("GL_RG8");
case GL_RG16: return TEXT("GL_RG16");
case GL_RG16F: return TEXT("GL_RG16F");
case GL_RG32F: return TEXT("GL_RG32F");
case GL_RG8I: return TEXT("GL_RG8I");
case GL_RG8UI: return TEXT("GL_RG8UI");
case GL_RG16I: return TEXT("GL_RG16I");
case GL_RG16UI: return TEXT("GL_RG16UI");
case GL_RG32I: return TEXT("GL_RG32I");
case GL_RG32UI: return TEXT("GL_RG32UI");
// R
case GL_R8: return TEXT("GL_R8");
case GL_R16: return TEXT("GL_R16");
case GL_R16F: return TEXT("GL_R16F");
case GL_R32F: return TEXT("GL_R32F");
case GL_R8I: return TEXT("GL_R8I");
case GL_R8UI: return TEXT("GL_R8UI");
case GL_R16I: return TEXT("GL_R16I");
case GL_R16UI: return TEXT("GL_R16UI");
case GL_R32I: return TEXT("GL_R32I");
case GL_R32UI: return TEXT("GL_R32UI");
// RGB (at the end, as it's not expected to be used)
case GL_RGB8: return TEXT("GL_RGB8");
case GL_RGB5: return TEXT("GL_RGB5");
case GL_R3_G3_B2: return TEXT("GL_R3_G3_B2");
case GL_RGB4: return TEXT("GL_RGB4");
case GL_SRGB8: return TEXT("GL_SRGB8");
case GL_R11F_G11F_B10F: return TEXT("GL_R11F_G11F_B10F");
case GL_RGB9_E5: return TEXT("GL_RGB9_E5");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetComponentType( GLint ComponentType )
{
switch( ComponentType )
{
case GL_FLOAT: return TEXT("GL_FLOAT");
case GL_INT: return TEXT("GL_INT");
case GL_UNSIGNED_INT: return TEXT("GL_UNSIGNED_INT");
case GL_SIGNED_NORMALIZED: return TEXT("GL_SIGNED_NORMALIZED");
case GL_UNSIGNED_NORMALIZED: return TEXT("GL_UNSIGNED_NORMALIZED");
default: return TEXT("!!!unknown!!!");
}
}
static const TCHAR* GetColorEncoding( GLint ColorEncoding )
{
switch( ColorEncoding )
{
case GL_LINEAR: return TEXT("GL_LINEAR");
case GL_SRGB: return TEXT("GL_SRGB");
default: return TEXT("!!!unknown!!!");
}
}
static const TCHAR* GetCubeMapFaceName( GLint CubeMapFace )
{
switch( CubeMapFace )
{
case GL_TEXTURE_CUBE_MAP_POSITIVE_X: return TEXT("GL_TEXTURE_CUBE_MAP_POSITIVE_X");
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: return TEXT("GL_TEXTURE_CUBE_MAP_NEGATIVE_X");
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: return TEXT("GL_TEXTURE_CUBE_MAP_POSITIVE_Y");
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: return TEXT("GL_TEXTURE_CUBE_MAP_NEGATIVE_Y");
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: return TEXT("GL_TEXTURE_CUBE_MAP_POSITIVE_Z");
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: return TEXT("GL_TEXTURE_CUBE_MAP_NEGATIVE_Z");
default: return TEXT("!!!unknown!!!");
}
}
static const TCHAR* GetShaderType( GLint Type )
{
switch( Type )
{
case GL_VERTEX_SHADER: return TEXT("GL_VERTEX_SHADER");
case GL_GEOMETRY_SHADER: return TEXT("GL_GEOMETRY_SHADER");
case GL_FRAGMENT_SHADER: return TEXT("GL_FRAGMENT_SHADER");
default: return TEXT("!!!unknown!!!");
}
}
static const TCHAR* GetGLTextureFilterString( GLint TextureFilter )
{
switch( TextureFilter )
{
case GL_NEAREST: return TEXT("GL_NEAREST");
case GL_LINEAR: return TEXT("GL_LINEAR");
case GL_NEAREST_MIPMAP_NEAREST: return TEXT("GL_NEAREST_MIPMAP_NEAREST");
case GL_LINEAR_MIPMAP_NEAREST: return TEXT("GL_LINEAR_MIPMAP_NEAREST");
case GL_NEAREST_MIPMAP_LINEAR: return TEXT("GL_NEAREST_MIPMAP_LINEAR");
case GL_LINEAR_MIPMAP_LINEAR: return TEXT("GL_LINEAR_MIPMAP_LINEAR");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* GetGLTextureWrapString( GLint TextureWrap )
{
switch( TextureWrap )
{
case GL_REPEAT: return TEXT("GL_REPEAT");
case GL_MIRRORED_REPEAT: return TEXT("GL_MIRRORED_REPEAT");
case GL_CLAMP_TO_EDGE: return TEXT("GL_CLAMP_TO_EDGE");
case GL_CLAMP_TO_BORDER: return TEXT("GL_CLAMP_TO_BORDER");
case GL_MIRROR_CLAMP_EXT: return TEXT("GL_MIRROR_CLAMP_EXT");
default: return TEXT("!!!unknown!!!");
};
}
static const TCHAR* NameOfType( GLint Type )
{
switch( Type )
{
case GL_DOUBLE: return TEXT("GL_DOUBLE");
case GL_FLOAT: return TEXT("GL_FLOAT");
case GL_HALF_FLOAT: return TEXT("GL_HALF_FLOAT");
case GL_UNSIGNED_SHORT: return TEXT("GL_UNSIGNED_SHORT");
case GL_SHORT: return TEXT("GL_SHORT");
case GL_UNSIGNED_BYTE: return TEXT("GL_UNSIGNED_BYTE");
default: return TEXT("!!!unknown!!!");
}
}
static int32 SizeOfType( GLint Type )
{
switch( Type )
{
case GL_DOUBLE: return 8;
case GL_FLOAT: return 4;
case GL_HALF_FLOAT:
case GL_UNSIGNED_SHORT:
case GL_SHORT: return 2;
case GL_UNSIGNED_BYTE: return 1;
default:
check( 0 );
return 0;
}
}
class FOpenGLDebugFrameDumper
{
public:
/** Event call, called from the engine immediately after a draw command */
void SignalDrawEvent( const TCHAR* FolderPart, const TCHAR* DrawCommandDescription, GLint ElementArrayType, GLint StartVertex, GLint VertexCount, GLint InstanceCount );
/** Event call, called from the engine immediately after a clear command */
void SignalClearEvent( int8 ClearType, int8 NumColors, const float* Colors, float Depth, uint32 Stencil );
/** Event call, called from the engine immediately after a framebuffer blit command */
void SignalFramebufferBlitEvent( GLbitfield Mask );
/** Event call, called from the engine immediately after buffer swap/end of frame */
void SignalEndFrameEvent( void );
/** Command to dump information about all events from now until next end frame event (ie. to dump this frame, or partial frame, if frame rendering already started) */
void TriggerFrameDump( void );
static inline FOpenGLDebugFrameDumper* Instance( void )
{
if( !Singleton )
Singleton = new FOpenGLDebugFrameDumper;
return Singleton;
}
protected:
static FOpenGLDebugFrameDumper* Singleton;
struct TextureLevelInfo
{
GLint Width;
GLint Height;
GLint Depth;
GLint Samples;
GLboolean bHasFixedSampleLocations;
GLint InternalFormat;
GLint RedBits;
GLint GreenBits;
GLint BlueBits;
GLint AlphaBits;
GLint DepthBits;
GLint StencilBits;
GLint SharedSize;
GLint RedType;
GLint GreenType;
GLint BlueType;
GLint AlphaType;
GLint DepthType;
GLboolean bIsCompressed;
GLint CompressedSize;
GLint DataStoreBinding;
};
struct EFramebufferAttachmentSlotType
{
enum Type
{
Color,
Depth,
Stencil,
};
};
struct VertexAttribInfo
{
GLint Index;
GLuint Stride;
GLint Type;
GLint Size;
GLint SizeRead;
GLuint Offset;
GLuint OffsetWithinVertex;
bool bInteger;
bool bNormalized;
bool bSkip;
bool bDivisor;
};
static int32 CDECL qsort_compare_VertexAttribInfo( const void* A, const void* B )
{
const VertexAttribInfo* Element1 = (const VertexAttribInfo*)A;
const VertexAttribInfo* Element2 = (const VertexAttribInfo*)B;
return Element1->Offset - Element2->Offset;
}
/** Event counter. Describes how to name the subfolder we dump the next event to. */
uint32 EventCounter;
/** Frame counter. Describes how to name the subfolder we dump the next frame to. */
uint32 FrameCounter;
FString* CachedRootFolder;
FString* CachedFrameFolder;
FString* CachedEventFolder;
bool bDumpingFrame;
FOpenGLDebugFrameDumper( void )
: EventCounter( 0 )
, FrameCounter( 0 )
, CachedRootFolder( NULL )
, CachedFrameFolder( NULL )
, CachedEventFolder( NULL )
, bDumpingFrame( false )
{
}
void DumpRenderTargetsState( FOutputDeviceFile& LogFile );
void DumpDepthState( FOutputDeviceFile& LogFile );
void DumpStencilState( FOutputDeviceFile& LogFile );
void DumpBufferMasks( FOutputDeviceFile& LogFile );
void DumpClearValues( FOutputDeviceFile& LogFile );
void DumpMultisamplingSettings( FOutputDeviceFile& LogFile );
void DumpScissorAndViewport( FOutputDeviceFile& LogFile );
void DumpVertexAttribArraysState( FOutputDeviceFile& LogFile );
void DumpBlendingState( FOutputDeviceFile& LogFile );
void DumpBufferBindings( FOutputDeviceFile& LogFile );
void DumpHintSettings( FOutputDeviceFile& LogFile );
void DumpOpenGLLimits( FOutputDeviceFile& LogFile );
void DumpPointsSettings( FOutputDeviceFile& LogFile );
void DumpLinesSettings( FOutputDeviceFile& LogFile );
void DumpPolygonsSettings( FOutputDeviceFile& LogFile );
void DumpLogicOpsSettings( FOutputDeviceFile& LogFile );
void DumpPixelModeSettings( FOutputDeviceFile& LogFile );
void DumpTextureLimitsAndBindings( FOutputDeviceFile& LogFile );
void DumpProgramSettings( FOutputDeviceFile& LogFile );
void DumpRenderbufferSettings( FOutputDeviceFile& LogFile, GLint RenderbufferID );
void DumpFramebufferAttachmentSettings( FOutputDeviceFile& LogFile, GLenum AttachmentSlot );
void DumpFramebufferSettings( FOutputDeviceFile& LogFile, GLint FramebufferID );
void DumpTextureUnitSettings( FOutputDeviceFile& LogFile, GLint TextureUnitIndex );
void DumpBoundTextureSettings( FOutputDeviceFile& LogFile, GLenum UnitTarget );
void DumpBoundTextureSurfaceSettings( FOutputDeviceFile& LogFile, GLenum SurfaceType, GLint BaseLevel, GLint MaxLevel );
void GetBoundTextureSurfaceLevelSettings( GLenum SurfaceType, GLint Level, TextureLevelInfo& OutInfo );
void DumpBoundSamplerSettings( FOutputDeviceFile& LogFile, GLint SamplerID );
void DumpProgramContents( FOutputDeviceFile& LogFile, GLint ProgramID );
void DumpShaderContents( FOutputDeviceFile& LogFile, GLint ShaderID );
void DumpFramebufferContent( GLint FramebufferID, GLint AttachmentSlot, const TCHAR* TargetFilename, EFramebufferAttachmentSlotType::Type SlotType, bool bShouldFlipImageVertically );
void DumpTextureContentForImageUnit( int32 UnitIndex );
void DumpTextureSurfaceContent( const TCHAR* TargetFilename, GLenum SurfaceType, GLint Level );
void DumpGeneralOpenGLState( const TCHAR* DrawCommandDescription, bool bIsDrawEvent, bool bIsFramebufferBlitEvent );
void DumpFramebufferState( bool bReadFramebuffer );
void DumpProgramAndShaderState( void );
void DumpBoundTextureState( void );
void DumpFramebufferContents( bool bReadFramebuffer );
void DumpBoundTexturesContents( void );
void DumpElementArrayBufferContents( GLenum ElementArrayType );
void DumpRelevantVertexArrayBufferContents( GLint StartVertex, GLint VertexCount, GLint InstanceCount );
void DumpBoundVertexArrayBufferContents( GLint VertexBufferID, GLint StartVertex, GLint VertexCount, GLint InstanceCount );
void InterpretUniform( GLint UniformType, void* DataBuffer, FString& OutAppendString );
void SetNewEventFolder( const FString& EventString );
};
FOpenGLDebugFrameDumper* FOpenGLDebugFrameDumper::Singleton = NULL;
void FOpenGLDebugFrameDumper::DumpRenderTargetsState( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Render Targets") LINE_TERMINATOR );
if ( FOpenGL::SupportsMultipleRenderTargets() )
{
GLint DrawFramebuffer;
glGetIntegerv( GL_DRAW_FRAMEBUFFER_BINDING, &DrawFramebuffer );
ASSERT_NO_GL_ERROR();
GLint MaxDrawBuffers = 0;
glGetIntegerv( GL_MAX_DRAW_BUFFERS, &MaxDrawBuffers );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_DRAW_BUFFERS: %d") LINE_TERMINATOR, MaxDrawBuffers );
GLint AttachedBufferIndex;
for( GLint DrawBufferIndex = 0; DrawBufferIndex < MaxDrawBuffers; ++DrawBufferIndex )
{
glGetIntegerv( GL_DRAW_BUFFER0+DrawBufferIndex, &AttachedBufferIndex );
ASSERT_NO_GL_ERROR();
if( AttachedBufferIndex )
{
const TCHAR* AttachedBufferName = GetAttachedBufferName( ( DrawFramebuffer == 0 ), AttachedBufferIndex );
if( AttachedBufferName )
{
LogFile.Logf( TEXT("\t\tGL_DRAW_BUFFER%d: %s") LINE_TERMINATOR, DrawBufferIndex, AttachedBufferName );
}
else
{
LogFile.Logf( TEXT("\t\tGL_DRAW_BUFFER%d: 0x%x") LINE_TERMINATOR, DrawBufferIndex, AttachedBufferIndex );
}
}
}
GLint ReadFramebuffer;
glGetIntegerv( GL_READ_FRAMEBUFFER_BINDING, &ReadFramebuffer );
ASSERT_NO_GL_ERROR();
glGetIntegerv( GL_READ_BUFFER, &AttachedBufferIndex );
ASSERT_NO_GL_ERROR();
const TCHAR* AttachedBufferName = GetAttachedBufferName( ( ReadFramebuffer == 0 ), AttachedBufferIndex );
if( AttachedBufferName )
{
LogFile.Logf( TEXT("\tGL_READ_BUFFER: %s") LINE_TERMINATOR, AttachedBufferName );
}
else
{
LogFile.Logf( TEXT("\tGL_READ_BUFFER: 0x%x") LINE_TERMINATOR, AttachedBufferIndex );
}
}
else
{
//@todo-mobile: More debug info
GLint CurrentFBO;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &CurrentFBO);
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\GL_FRAMEBUFFER_BINDING: %d") LINE_TERMINATOR, CurrentFBO );
}
}
void FOpenGLDebugFrameDumper::DumpDepthState( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Depth") LINE_TERMINATOR );
GLboolean DepthTestEnabled;
glGetBooleanv( GL_DEPTH_TEST, &DepthTestEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DEPTH_TEST: %s") LINE_TERMINATOR, DepthTestEnabled ? TEXT("Enabled") : TEXT("Disabled") );
GLfloat DepthClearValue;
glGetFloatv( GL_DEPTH_CLEAR_VALUE, &DepthClearValue );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DEPTH_CLEAR_VALUE: %f") LINE_TERMINATOR, DepthClearValue );
GLint DepthFunction;
glGetIntegerv( GL_DEPTH_FUNC, &DepthFunction );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DEPTH_FUNC: %s") LINE_TERMINATOR, GetGLCompareString( DepthFunction ) );
GLfloat DepthRange[2];
glGetFloatv( GL_DEPTH_RANGE, DepthRange );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DEPTH_RANGE: { %f, %f }") LINE_TERMINATOR, DepthRange[0], DepthRange[1] );
GLboolean WriteMask;
glGetBooleanv( GL_DEPTH_WRITEMASK, &WriteMask );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DEPTH_WRITEMASK: %s") LINE_TERMINATOR, WriteMask ? TEXT("TRUE") : TEXT("FALSE") );
}
void FOpenGLDebugFrameDumper::DumpStencilState( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Stencil") LINE_TERMINATOR );
GLboolean StencilTestEnabled;
glGetBooleanv( GL_STENCIL_TEST, &StencilTestEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_TEST: %s") LINE_TERMINATOR, StencilTestEnabled ? TEXT("Enabled") : TEXT("Disabled") );
GLint ClearValue;
glGetIntegerv( GL_STENCIL_CLEAR_VALUE, &ClearValue );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_CLEAR_VALUE: 0x%x") LINE_TERMINATOR, ClearValue );
GLint TestFailResult;
glGetIntegerv( GL_STENCIL_FAIL, &TestFailResult );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_FAIL: %s") LINE_TERMINATOR, GetGLStencilOpString( TestFailResult ) );
GLint TestPassDepthFailResult;
glGetIntegerv( GL_STENCIL_PASS_DEPTH_FAIL, &TestPassDepthFailResult );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_PASS_DEPTH_FAIL: %s") LINE_TERMINATOR, GetGLStencilOpString( TestPassDepthFailResult ) );
GLint TestPassDepthPassResult;
glGetIntegerv( GL_STENCIL_PASS_DEPTH_PASS, &TestPassDepthPassResult );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_PASS_DEPTH_PASS: %s") LINE_TERMINATOR, GetGLStencilOpString( TestPassDepthPassResult ) );
GLint CompareFunction;
glGetIntegerv( GL_STENCIL_FUNC, &CompareFunction );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_FUNC: %s") LINE_TERMINATOR, GetGLCompareString( CompareFunction ) );
GLint CompareReference;
glGetIntegerv( GL_STENCIL_REF, &CompareReference );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_REF: 0x%x") LINE_TERMINATOR, CompareReference );
GLint ValueMask;
glGetIntegerv( GL_STENCIL_VALUE_MASK, &ValueMask );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_VALUE_MASK: 0x%x") LINE_TERMINATOR, ValueMask );
GLint WriteMask;
glGetIntegerv( GL_STENCIL_WRITEMASK, &WriteMask );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_WRITEMASK: 0x%x") LINE_TERMINATOR, WriteMask );
}
void FOpenGLDebugFrameDumper::DumpBufferMasks( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Buffer Masks") LINE_TERMINATOR );
GLint MaxDrawBuffers = 0;
glGetIntegerv( GL_MAX_DRAW_BUFFERS, &MaxDrawBuffers );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_DRAW_BUFFERS: %d") LINE_TERMINATOR, MaxDrawBuffers );
GLboolean ColorWriteMask[4];
for( GLint DrawBufferIndex = 0; DrawBufferIndex < MaxDrawBuffers; ++DrawBufferIndex )
{
glGetBooleani_v( GL_COLOR_WRITEMASK, DrawBufferIndex, ColorWriteMask );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_COLOR_WRITEMASK for buffer %d: ( %s, %s, %s, %s )") LINE_TERMINATOR,
DrawBufferIndex,
ColorWriteMask[0] ? TEXT("TRUE") : TEXT("FALSE"),
ColorWriteMask[1] ? TEXT("TRUE") : TEXT("FALSE"),
ColorWriteMask[2] ? TEXT("TRUE") : TEXT("FALSE"),
ColorWriteMask[3] ? TEXT("TRUE") : TEXT("FALSE") );
}
GLboolean DepthWriteMask;
glGetBooleanv( GL_DEPTH_WRITEMASK, &DepthWriteMask );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DEPTH_WRITEMASK: %s") LINE_TERMINATOR, DepthWriteMask ? TEXT("TRUE") : TEXT("FALSE") );
GLint StencilValueMask;
glGetIntegerv( GL_STENCIL_VALUE_MASK, &StencilValueMask );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_VALUE_MASK: 0x%x") LINE_TERMINATOR, StencilValueMask );
GLint StencilWriteMask;
glGetIntegerv( GL_STENCIL_WRITEMASK, &StencilWriteMask );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_WRITEMASK: 0x%x") LINE_TERMINATOR, StencilWriteMask );
// GLint StencilBackWriteMask;
// glGetIntegerv( GL_STENCIL_BACK_WRITEMASK, &StencilBackWriteMask );
// ASSERT_NO_GL_ERROR();
// LogFile.Logf( TEXT("\tGL_STENCIL_BACK_WRITEMASK: 0x%x") LINE_TERMINATOR, StencilBackWriteMask );
}
void FOpenGLDebugFrameDumper::DumpClearValues( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Clear Values") LINE_TERMINATOR );
GLfloat ColorClearValue[4];
glGetFloatv( GL_COLOR_CLEAR_VALUE, ColorClearValue );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_COLOR_CLEAR_VALUE: ( %f, %f, %f, %f )") LINE_TERMINATOR, ColorClearValue[0], ColorClearValue[1], ColorClearValue[2], ColorClearValue[3] );
GLfloat DepthClearValue;
glGetFloatv( GL_DEPTH_CLEAR_VALUE, &DepthClearValue );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DEPTH_CLEAR_VALUE: %f") LINE_TERMINATOR, DepthClearValue );
GLint StencilClearValue;
glGetIntegerv( GL_STENCIL_CLEAR_VALUE, &StencilClearValue );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_STENCIL_CLEAR_VALUE: %d") LINE_TERMINATOR, StencilClearValue );
}
void FOpenGLDebugFrameDumper::DumpMultisamplingSettings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Multisampling Settings") LINE_TERMINATOR );
GLboolean MultisamplingEnabled;
glGetBooleanv( GL_MULTISAMPLE, &MultisamplingEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MULTISAMPLE: %s") LINE_TERMINATOR, MultisamplingEnabled ? TEXT("Enabled") : TEXT("Disabled") );
GLboolean SampleAlphaToCoverage;
glGetBooleanv( GL_SAMPLE_ALPHA_TO_COVERAGE, &SampleAlphaToCoverage );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SAMPLE_ALPHA_TO_COVERAGE: %s") LINE_TERMINATOR, SampleAlphaToCoverage ? TEXT("Enabled") : TEXT("Disabled") );
GLboolean SampleAlphaToOne;
glGetBooleanv( GL_SAMPLE_ALPHA_TO_ONE, &SampleAlphaToOne );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SAMPLE_ALPHA_TO_ONE: %s") LINE_TERMINATOR, SampleAlphaToOne ? TEXT("Enabled") : TEXT("Disabled") );
GLboolean SampleCoverage;
glGetBooleanv( GL_SAMPLE_COVERAGE, &SampleCoverage );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SAMPLE_COVERAGE: %s") LINE_TERMINATOR, SampleCoverage ? TEXT("Enabled") : TEXT("Disabled") );
GLboolean SampleCoverageInvert;
glGetBooleanv( GL_SAMPLE_COVERAGE_INVERT, &SampleCoverageInvert );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SAMPLE_COVERAGE_INVERT: %s") LINE_TERMINATOR, SampleCoverageInvert ? TEXT("Enabled") : TEXT("Disabled") );
GLfloat SampleCoverageValue;
glGetFloatv( GL_SAMPLE_COVERAGE_VALUE, &SampleCoverageValue );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SAMPLE_COVERAGE_VALUE: %f") LINE_TERMINATOR, SampleCoverageValue );
GLint SampleBuffers;
glGetIntegerv( GL_SAMPLE_BUFFERS, &SampleBuffers );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SAMPLE_BUFFERS: %d") LINE_TERMINATOR, SampleBuffers );
GLint Samples;
glGetIntegerv( GL_SAMPLES, &Samples );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SAMPLES: %d") LINE_TERMINATOR, Samples );
}
void FOpenGLDebugFrameDumper::DumpScissorAndViewport( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Scissor & Viewport") LINE_TERMINATOR );
GLboolean ScissorTestEnabled;
glGetBooleanv( GL_SCISSOR_TEST, &ScissorTestEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SCISSOR_TEST: %s") LINE_TERMINATOR, ScissorTestEnabled ? TEXT("Enabled") : TEXT("Disabled") );
GLint ScissorBox[4];
glGetIntegerv( GL_SCISSOR_BOX, ScissorBox );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SCISSOR_BOX: { %d, %d, %d, %d }") LINE_TERMINATOR, ScissorBox[0], ScissorBox[1], ScissorBox[2], ScissorBox[3] );
GLint Viewport[4];
glGetIntegerv( GL_VIEWPORT, Viewport );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_VIEWPORT: { %d, %d, %d, %d }") LINE_TERMINATOR, Viewport[0], Viewport[1], Viewport[2], Viewport[3] );
GLint MaxClipPlanes;
glGetIntegerv( GL_MAX_CLIP_DISTANCES, &MaxClipPlanes );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_CLIP_DISTANCES: %d") LINE_TERMINATOR, MaxClipPlanes );
for( int32 ClipPlaneIndex = 0; ClipPlaneIndex < MaxClipPlanes; ++ClipPlaneIndex )
{
GLboolean Enabled = glIsEnabled( GL_CLIP_DISTANCE0+ClipPlaneIndex );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_CLIP_DISTANCE%d: %s") LINE_TERMINATOR, ClipPlaneIndex, Enabled ? TEXT("Enabled") : TEXT("Disabled") );
}
}
void FOpenGLDebugFrameDumper::DumpVertexAttribArraysState( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Vertex Attrib Arrays") LINE_TERMINATOR );
// Get generic vertex array count
GLint MaxVertexAttribs = 0;
glGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &MaxVertexAttribs );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_VERTEX_ATTRIBS: %d") LINE_TERMINATOR, MaxVertexAttribs );
// For each generic vertex array, get info
for( GLint VertexAttribArrayIndex = 0; VertexAttribArrayIndex < MaxVertexAttribs; ++VertexAttribArrayIndex )
{
GLint VertexAttribArrayEnabled;
glGetVertexAttribiv( VertexAttribArrayIndex, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &VertexAttribArrayEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tVertex Attrib Array %d") LINE_TERMINATOR, VertexAttribArrayIndex );
if( VertexAttribArrayEnabled )
{
LogFile.Log( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_ENABLED: TRUE") LINE_TERMINATOR );
GLint VertexAttribArraySize;
glGetVertexAttribiv( VertexAttribArrayIndex, GL_VERTEX_ATTRIB_ARRAY_SIZE, &VertexAttribArraySize );
ASSERT_NO_GL_ERROR();
if( VertexAttribArraySize == GL_BGRA )
{
LogFile.Log( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_SIZE: GL_BGRA(4)") LINE_TERMINATOR );
}
else
{
LogFile.Logf( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_SIZE: %d") LINE_TERMINATOR, VertexAttribArraySize );
}
GLint VertexAttribArrayStride;
glGetVertexAttribiv( VertexAttribArrayIndex, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &VertexAttribArrayStride );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_STRIDE: %d") LINE_TERMINATOR, VertexAttribArrayStride );
GLint VertexAttribArrayType;
glGetVertexAttribiv( VertexAttribArrayIndex, GL_VERTEX_ATTRIB_ARRAY_TYPE, &VertexAttribArrayType );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_TYPE: %s") LINE_TERMINATOR, GetGLDataTypeString( VertexAttribArrayType ) );
GLint VertexAttribArrayNormalized;
glGetVertexAttribiv( VertexAttribArrayIndex, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &VertexAttribArrayNormalized );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_NORMALIZED: %s") LINE_TERMINATOR, VertexAttribArrayNormalized ? TEXT("TRUE") : TEXT("FALSE") );
GLvoid* VertexAttribArrayPointer;
glGetVertexAttribPointerv( VertexAttribArrayIndex, GL_VERTEX_ATTRIB_ARRAY_POINTER, &VertexAttribArrayPointer );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_POINTER: 0x%x") LINE_TERMINATOR, VertexAttribArrayPointer );
GLint VertexAttribArrayBufferBinding;
glGetVertexAttribiv( VertexAttribArrayIndex, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &VertexAttribArrayBufferBinding );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: %d") LINE_TERMINATOR, VertexAttribArrayBufferBinding );
GLint VertexAttribArrayIsInteger;
glGetVertexAttribiv( VertexAttribArrayIndex, GL_VERTEX_ATTRIB_ARRAY_INTEGER, &VertexAttribArrayIsInteger );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_INTEGER: %s") LINE_TERMINATOR, VertexAttribArrayIsInteger ? TEXT("TRUE") : TEXT("FALSE") );
GLint VertexAttribArrayDivisor;
glGetVertexAttribiv( VertexAttribArrayIndex, GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB, &VertexAttribArrayDivisor );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_DIVISOR: %d") LINE_TERMINATOR, VertexAttribArrayDivisor );
}
else
{
LogFile.Log( TEXT("\t\tGL_VERTEX_ATTRIB_ARRAY_ENABLED: FALSE") LINE_TERMINATOR );
if( VertexAttribArrayIndex )
{
GLfloat CurrentVertexAttribFloat[4];
glGetVertexAttribfv( VertexAttribArrayIndex, GL_CURRENT_VERTEX_ATTRIB, CurrentVertexAttribFloat );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_CURRENT_VERTEX_ATTRIB (assumming float): { %f, %f, %f, %f }") LINE_TERMINATOR, CurrentVertexAttribFloat[0], CurrentVertexAttribFloat[1], CurrentVertexAttribFloat[2], CurrentVertexAttribFloat[3] );
GLuint CurrentVertexAttribInteger[4];
glGetVertexAttribIuiv( VertexAttribArrayIndex, GL_CURRENT_VERTEX_ATTRIB, CurrentVertexAttribInteger );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_CURRENT_VERTEX_ATTRIB (assumming uint32): { %f, %f, %f, %f }") LINE_TERMINATOR, CurrentVertexAttribInteger[0], CurrentVertexAttribInteger[1], CurrentVertexAttribInteger[2], CurrentVertexAttribInteger[3] );
}
else
{
LogFile.Log( TEXT("\t\tVertex attrib array disabled for vertex array zero. Make sure the shader isn't trying to use gl_Position, as this won't make much sense.") LINE_TERMINATOR );
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Vertex attrib array is disabled for array zero. This makes sense only if the draw doesn't use vertex buffers at all, relying on vertex id and instance id instead.") );
}
}
}
}
void FOpenGLDebugFrameDumper::DumpBlendingState( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Blending State") LINE_TERMINATOR );
GLboolean bIsEnabled = glIsEnabled( GL_BLEND );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_BLEND: %s") LINE_TERMINATOR, bIsEnabled ? TEXT("Enabled") : TEXT("Disabled") );
GLint BlendSourceRGB;
glGetIntegerv( GL_BLEND_SRC_RGB, &BlendSourceRGB );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_BLEND_SRC_RGB: %s") LINE_TERMINATOR, GetGLBlendingFactorString( BlendSourceRGB ) );
GLint BlendSourceAlpha;
glGetIntegerv( GL_BLEND_SRC_ALPHA, &BlendSourceAlpha );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_BLEND_SRC_ALPHA: %s") LINE_TERMINATOR, GetGLBlendingFactorString( BlendSourceAlpha ) );
GLint BlendDestinationRGB;
glGetIntegerv( GL_BLEND_DST_RGB, &BlendDestinationRGB );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_BLEND_DST_RGB: %s") LINE_TERMINATOR, GetGLBlendingFactorString( BlendDestinationRGB ) );
GLint BlendDestinationAlpha;
glGetIntegerv( GL_BLEND_DST_ALPHA, &BlendDestinationAlpha );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_BLEND_DST_ALPHA: %s") LINE_TERMINATOR, GetGLBlendingFactorString( BlendDestinationAlpha ) );
GLfloat BlendColor[4];
glGetFloatv( GL_BLEND_COLOR, BlendColor );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_BLEND_COLOR: ( %f, %f, %f, %f )") LINE_TERMINATOR, BlendColor[0], BlendColor[1], BlendColor[2], BlendColor[3] );
GLint BlendEquationRGB;
glGetIntegerv( GL_BLEND_EQUATION_RGB, &BlendEquationRGB );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_BLEND_EQUATION_RGB: %s") LINE_TERMINATOR, GetGLBlendFuncString( BlendEquationRGB ) );
GLint BlendEquationAlpha;
glGetIntegerv( GL_BLEND_EQUATION_ALPHA, &BlendEquationAlpha );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_BLEND_EQUATION_ALPHA: %s") LINE_TERMINATOR, GetGLBlendFuncString( BlendEquationAlpha ) );
}
void FOpenGLDebugFrameDumper::DumpBufferBindings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Buffer Object Bindings") LINE_TERMINATOR );
GLint ArrayBufferBinding;
glGetIntegerv( GL_ARRAY_BUFFER_BINDING, &ArrayBufferBinding );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_ARRAY_BUFFER_BINDING: %d") LINE_TERMINATOR, ArrayBufferBinding );
GLint ElementArrayBufferBinding;
glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &ElementArrayBufferBinding );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_ELEMENT_ARRAY_BUFFER_BINDING: %d") LINE_TERMINATOR, ElementArrayBufferBinding );
GLint UniformBufferBinding;
glGetIntegerv( GL_UNIFORM_BUFFER_BINDING, &UniformBufferBinding );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_UNIFORM_BUFFER_BINDING: %d") LINE_TERMINATOR, UniformBufferBinding );
GLint MaxUniformBuffers = 0;
glGetIntegerv( GL_MAX_UNIFORM_BUFFER_BINDINGS, &MaxUniformBuffers );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_UNIFORM_BUFFER_BINDINGS: %d") LINE_TERMINATOR, MaxUniformBuffers );
#if PLATFORM_MAC
// A workaround for apparent bug in NVidia GT650M driver, introduced in 10.8.2 Mac update.
if( MaxUniformBuffers > 60 )
MaxUniformBuffers = 60;
#endif
for( GLint UniformBufferIndex = 0; UniformBufferIndex < MaxUniformBuffers; ++UniformBufferIndex )
{
GLint UniformBufferBound;
glGetIntegeri_v( GL_UNIFORM_BUFFER_BINDING, UniformBufferIndex, &UniformBufferBound );
ASSERT_NO_GL_ERROR();
if( UniformBufferBound )
{
GLint UniformBufferStart;
glGetIntegeri_v( GL_UNIFORM_BUFFER_START, UniformBufferIndex, &UniformBufferStart );
ASSERT_NO_GL_ERROR();
GLint UniformBufferSize;
glGetIntegeri_v( GL_UNIFORM_BUFFER_SIZE, UniformBufferIndex, &UniformBufferSize );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tIndexed GL_UNIFORM_BUFFER_BINDING for index %d: %d ( start: %d, size: %d )") LINE_TERMINATOR, UniformBufferIndex, UniformBufferBound, UniformBufferStart, UniformBufferSize );
}
}
GLint PixelPackBufferBinding;
glGetIntegerv( GL_PIXEL_PACK_BUFFER_BINDING, &PixelPackBufferBinding );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PIXEL_PACK_BUFFER_BINDING: %d") LINE_TERMINATOR, PixelPackBufferBinding );
GLint PixelUnpackBufferBinding;
glGetIntegerv( GL_PIXEL_UNPACK_BUFFER_BINDING, &PixelUnpackBufferBinding );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PIXEL_UNPACK_BUFFER_BINDING: %d") LINE_TERMINATOR, PixelUnpackBufferBinding );
GLint DrawFramebufferBinding;
glGetIntegerv( GL_DRAW_FRAMEBUFFER_BINDING, &DrawFramebufferBinding );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DRAW_FRAMEBUFFER_BINDING: %d") LINE_TERMINATOR, DrawFramebufferBinding );
GLint ReadFramebufferBinding;
glGetIntegerv( GL_READ_FRAMEBUFFER_BINDING, &ReadFramebufferBinding );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_READ_FRAMEBUFFER_BINDING: %d") LINE_TERMINATOR, ReadFramebufferBinding );
GLint RenderbufferBinding;
glGetIntegerv( GL_RENDERBUFFER_BINDING, &RenderbufferBinding );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_RENDERBUFFER_BINDING: %d") LINE_TERMINATOR, RenderbufferBinding );
}
void FOpenGLDebugFrameDumper::DumpHintSettings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Hints") LINE_TERMINATOR );
{
GLint LineSmoothHint;
glGetIntegerv( GL_LINE_SMOOTH_HINT, &LineSmoothHint );
ASSERT_NO_GL_ERROR();
const TCHAR* HintName = GetHintName( LineSmoothHint );
if( HintName )
{
LogFile.Logf( TEXT("\tGL_LINE_SMOOTH_HINT: %s") LINE_TERMINATOR, HintName );
}
else
{
LogFile.Logf( TEXT("\tGL_LINE_SMOOTH_HINT: 0x%x") LINE_TERMINATOR, LineSmoothHint );
}
}
{
GLint PolygonSmoothHint;
glGetIntegerv( GL_POLYGON_SMOOTH_HINT, &PolygonSmoothHint );
ASSERT_NO_GL_ERROR();
const TCHAR* HintName = GetHintName( PolygonSmoothHint );
if( HintName )
{
LogFile.Logf( TEXT("\tGL_POLYGON_SMOOTH_HINT: %s") LINE_TERMINATOR, HintName );
}
else
{
LogFile.Logf( TEXT("\tGL_POLYGON_SMOOTH_HINT: 0x%x") LINE_TERMINATOR, PolygonSmoothHint );
}
}
{
GLint TextureCompressionHint;
glGetIntegerv( GL_TEXTURE_COMPRESSION_HINT, &TextureCompressionHint );
ASSERT_NO_GL_ERROR();
const TCHAR* HintName = GetHintName( TextureCompressionHint );
if( HintName )
{
LogFile.Logf( TEXT("\tGL_TEXTURE_COMPRESSION_HINT: %s") LINE_TERMINATOR, HintName );
}
else
{
LogFile.Logf( TEXT("\tGL_TEXTURE_COMPRESSION_HINT: 0x%x") LINE_TERMINATOR, TextureCompressionHint );
}
}
{
GLint FragmentShaderDerivativeHint;
glGetIntegerv( GL_FRAGMENT_SHADER_DERIVATIVE_HINT, &FragmentShaderDerivativeHint );
ASSERT_NO_GL_ERROR();
const TCHAR* HintName = GetHintName( FragmentShaderDerivativeHint );
if( HintName )
{
LogFile.Logf( TEXT("\tGL_FRAGMENT_SHADER_DERIVATIVE_HINT: %s") LINE_TERMINATOR, HintName );
}
else
{
LogFile.Logf( TEXT("\tGL_FRAGMENT_SHADER_DERIVATIVE_HINT: 0x%x") LINE_TERMINATOR, FragmentShaderDerivativeHint );
}
}
}
void FOpenGLDebugFrameDumper::DumpOpenGLLimits( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Limits") LINE_TERMINATOR );
GLint SubpixelBits;
glGetIntegerv( GL_SUBPIXEL_BITS, &SubpixelBits );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_SUBPIXEL_BIT: %d") LINE_TERMINATOR, SubpixelBits );
GLint Max3DTextureSize;
glGetIntegerv( GL_MAX_3D_TEXTURE_SIZE, &Max3DTextureSize );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_3D_TEXTURE_SIZE: %d") LINE_TERMINATOR, Max3DTextureSize );
GLint MaxCombinedTextureImageUnits;
glGetIntegerv( GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &MaxCombinedTextureImageUnits );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: %d") LINE_TERMINATOR, MaxCombinedTextureImageUnits );
GLint MaxCubeMapTextureSize;
glGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE, &MaxCubeMapTextureSize );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_CUBE_MAP_TEXTURE_SIZE: %d") LINE_TERMINATOR, MaxCubeMapTextureSize );
GLint MaxElementsIndices;
glGetIntegerv( GL_MAX_ELEMENTS_INDICES, &MaxElementsIndices );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_ELEMENTS_INDICES: %d") LINE_TERMINATOR, MaxElementsIndices );
GLint MaxElementsVertices;
glGetIntegerv( GL_MAX_ELEMENTS_VERTICES, &MaxElementsVertices );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_ELEMENTS_VERTICES: %d") LINE_TERMINATOR, MaxElementsVertices );
GLint MaxFragmentUniformComponents;
glGetIntegerv( GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &MaxFragmentUniformComponents );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_FRAGMENT_UNIFORM_COMPONENTS: %d") LINE_TERMINATOR, MaxFragmentUniformComponents );
GLint MaxTextureImageUnits;
glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS, &MaxTextureImageUnits );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_TEXTURE_IMAGE_UNITS: %d") LINE_TERMINATOR, MaxTextureImageUnits );
GLint MaxTextureLODBias;
glGetIntegerv( GL_MAX_TEXTURE_LOD_BIAS, &MaxTextureLODBias );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_TEXTURE_LOD_BIAS: %d") LINE_TERMINATOR, MaxTextureLODBias );
if( FOpenGL::SupportsTextureFilterAnisotropic())
{
GLint MaxTextureMaxAnisotropy;
glGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &MaxTextureMaxAnisotropy );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_TEXTURE_MAX_ANISOTROPY_EXT: %d") LINE_TERMINATOR, MaxTextureMaxAnisotropy );
}
GLint MaxTextureSize;
glGetIntegerv( GL_MAX_TEXTURE_SIZE, &MaxTextureSize );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_TEXTURE_SIZE: %d") LINE_TERMINATOR, MaxTextureSize );
GLint MaxVertexAttribs;
glGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &MaxVertexAttribs );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_VERTEX_ATTRIBS: %d") LINE_TERMINATOR, MaxVertexAttribs );
GLint MaxVertexTextureImageUnits;
glGetIntegerv( GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &MaxVertexTextureImageUnits );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: %d") LINE_TERMINATOR, MaxVertexTextureImageUnits );
GLint MaxVertexUniformComponents;
glGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS, &MaxVertexUniformComponents );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_VERTEX_UNIFORM_COMPONENTS: %d") LINE_TERMINATOR, MaxVertexUniformComponents );
GLint MaxViewportDimensions[2];
glGetIntegerv( GL_MAX_VIEWPORT_DIMS, MaxViewportDimensions );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_VIEWPORT_DIMS: { %d, %d }") LINE_TERMINATOR, MaxViewportDimensions[0], MaxViewportDimensions[1] );
GLint NumCompressedTextureFormats;
glGetIntegerv( GL_NUM_COMPRESSED_TEXTURE_FORMATS, &NumCompressedTextureFormats );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_NUM_COMPRESSED_TEXTURE_FORMATS: %d") LINE_TERMINATOR, NumCompressedTextureFormats );
if( NumCompressedTextureFormats )
{
LogFile.Logf( TEXT("\t{") LINE_TERMINATOR );
GLint* CompressedTextureFormatsTable = (GLint*)FMemory::Malloc( NumCompressedTextureFormats * sizeof( GLint ) );
glGetIntegerv( GL_COMPRESSED_TEXTURE_FORMATS, CompressedTextureFormatsTable );
ASSERT_NO_GL_ERROR();
for( GLint CompressedTextureFormatIndex = 0; CompressedTextureFormatIndex < NumCompressedTextureFormats; ++CompressedTextureFormatIndex )
{
const TCHAR* FormatName = GetCompressedTextureFormatName( CompressedTextureFormatsTable[CompressedTextureFormatIndex] );
if( FormatName )
LogFile.Logf( TEXT("\t\t%s") LINE_TERMINATOR, FormatName );
else
LogFile.Logf( TEXT("\t\t0x%x") LINE_TERMINATOR, CompressedTextureFormatsTable[CompressedTextureFormatIndex] );
}
FMemory::Free( CompressedTextureFormatsTable );
LogFile.Logf( TEXT("\t}") LINE_TERMINATOR );
}
}
void FOpenGLDebugFrameDumper::DumpLinesSettings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Lines") LINE_TERMINATOR );
GLboolean bLineSmoothEnabled;
glGetBooleanv( GL_LINE_SMOOTH, &bLineSmoothEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_LINE_SMOOTH: %s") LINE_TERMINATOR, bLineSmoothEnabled ? TEXT("Enabled") : TEXT("Disabled") );
GLfloat LineWidth;
glGetFloatv( GL_LINE_WIDTH, &LineWidth );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_LINE_WIDTH: %f") LINE_TERMINATOR, LineWidth );
GLfloat LineWidthGranularity;
glGetFloatv( GL_LINE_WIDTH_GRANULARITY, &LineWidthGranularity );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_LINE_WIDTH_GRANULARITY: %f") LINE_TERMINATOR, LineWidthGranularity );
GLfloat LineWidthRange[2];
glGetFloatv( GL_LINE_WIDTH_RANGE, LineWidthRange );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_LINE_WIDTH_RANGE: { %f, %f }") LINE_TERMINATOR, LineWidthRange[0], LineWidthRange[1] );
GLfloat AliasedLineWidthRange[2];
glGetFloatv( GL_ALIASED_LINE_WIDTH_RANGE, AliasedLineWidthRange );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_ALIASED_LINE_WIDTH_RANGE: { %f, %f }") LINE_TERMINATOR, AliasedLineWidthRange[0], AliasedLineWidthRange[1] );
}
void FOpenGLDebugFrameDumper::DumpLogicOpsSettings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Logic Ops") LINE_TERMINATOR );
GLboolean ColorLogicOp;
glGetBooleanv( GL_COLOR_LOGIC_OP, &ColorLogicOp );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_COLOR_LOGIC_OP: %s") LINE_TERMINATOR, ColorLogicOp ? TEXT("Enabled") : TEXT("Disabled") );
GLint LogicOpMode;
glGetIntegerv( GL_LOGIC_OP_MODE, &LogicOpMode );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_LOGIC_OP_MODE: %s") LINE_TERMINATOR, GetGLLogicOpString( LogicOpMode ) );
}
void FOpenGLDebugFrameDumper::DumpPixelModeSettings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Pixel Mode") LINE_TERMINATOR );
GLint PackAlignment;
glGetIntegerv( GL_PACK_ALIGNMENT, &PackAlignment );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PACK_ALIGNMENT: %d") LINE_TERMINATOR, PackAlignment );
GLint PackImageHeight;
glGetIntegerv( GL_PACK_IMAGE_HEIGHT, &PackImageHeight );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PACK_IMAGE_HEIGHT: %d") LINE_TERMINATOR, PackImageHeight );
GLboolean bPackLSBFirst;
glGetBooleanv( GL_PACK_LSB_FIRST, &bPackLSBFirst );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PACK_LSB_FIRST: %s") LINE_TERMINATOR, bPackLSBFirst ? TEXT("TRUE") : TEXT("FALSE") );
GLint PackRowLength;
glGetIntegerv( GL_PACK_ROW_LENGTH, &PackRowLength );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PACK_ROW_LENGTH: %d") LINE_TERMINATOR, PackRowLength );
GLint PackSkipImages;
glGetIntegerv( GL_PACK_SKIP_IMAGES, &PackSkipImages );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PACK_SKIP_IMAGES: %d") LINE_TERMINATOR, PackSkipImages );
GLint PackSkipPixels;
glGetIntegerv( GL_PACK_SKIP_PIXELS, &PackSkipPixels );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PACK_SKIP_PIXELS: %d") LINE_TERMINATOR, PackSkipPixels );
GLint PackSkipRows;
glGetIntegerv( GL_PACK_SKIP_ROWS, &PackSkipRows );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PACK_SKIP_ROWS: %d") LINE_TERMINATOR, PackSkipRows );
GLboolean bPackSwapBytes;
glGetBooleanv( GL_PACK_SWAP_BYTES, &bPackSwapBytes );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_PACK_SWAP_BYTES: %s") LINE_TERMINATOR, bPackSwapBytes ? TEXT("TRUE") : TEXT("FALSE") );
GLint UnpackAlignment;
glGetIntegerv( GL_UNPACK_ALIGNMENT, &UnpackAlignment );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_UNPACK_ALIGNMENTS: %d") LINE_TERMINATOR, UnpackAlignment );
#if PLATFORM_MAC
GLboolean bUnpackClientStorageApple;
glGetBooleanv( GL_UNPACK_CLIENT_STORAGE_APPLE, &bUnpackClientStorageApple );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_UNPACK_CLIENT_STORAGE_APPLE: %s") LINE_TERMINATOR, bUnpackClientStorageApple ? TEXT("TRUE") : TEXT("FALSE") );
#endif
GLint UnpackImageHeight;
glGetIntegerv( GL_UNPACK_IMAGE_HEIGHT, &UnpackImageHeight );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_UNPACK_IMAGE_HEIGHT: %d") LINE_TERMINATOR, UnpackImageHeight );
GLboolean bUnpackLSBFirst;
glGetBooleanv( GL_UNPACK_LSB_FIRST, &bUnpackLSBFirst );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_UNPACK_LSB_FIRST: %s") LINE_TERMINATOR, bUnpackLSBFirst ? TEXT("TRUE") : TEXT("FALSE") );
GLint UnpackRowLength;
glGetIntegerv( GL_UNPACK_ROW_LENGTH, &UnpackRowLength );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_UNPACK_ROW_LENGTH: %d") LINE_TERMINATOR, UnpackRowLength );
GLint UnpackSkipImages;
glGetIntegerv( GL_UNPACK_SKIP_IMAGES, &UnpackSkipImages );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_UNPACK_SKIP_IMAGES: %d") LINE_TERMINATOR, UnpackSkipImages );
GLint UnpackSkipRows;
glGetIntegerv( GL_UNPACK_SKIP_ROWS, &UnpackSkipRows );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_UNPACK_SKIP_ROWS: %d") LINE_TERMINATOR, UnpackSkipRows );
GLboolean bUnpackSwapBytes;
glGetBooleanv( GL_UNPACK_SWAP_BYTES, &bUnpackSwapBytes );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_UNPACK_SWAP_BYTES: %s") LINE_TERMINATOR, bUnpackSwapBytes ? TEXT("TRUE") : TEXT("FALSE") );
}
void FOpenGLDebugFrameDumper::DumpPointsSettings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Points") LINE_TERMINATOR );
GLfloat PointSize;
glGetFloatv( GL_POINT_SIZE, &PointSize );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_POINT_SIZE: %f") LINE_TERMINATOR, PointSize );
GLfloat PointSizeRange[2];
glGetFloatv( GL_POINT_SIZE_RANGE, PointSizeRange );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_POINT_SIZE_RANGE: { %f, %f }") LINE_TERMINATOR, PointSizeRange[0], PointSizeRange[1] );
}
void FOpenGLDebugFrameDumper::DumpPolygonsSettings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Polygons") LINE_TERMINATOR );
GLfloat PolygonOffsetFactor;
glGetFloatv( GL_POLYGON_OFFSET_FACTOR, &PolygonOffsetFactor );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_POLYGON_OFFSET_FACTOR: %f") LINE_TERMINATOR, PolygonOffsetFactor );
GLboolean bCullFaceEnabled;
glGetBooleanv( GL_CULL_FACE, &bCullFaceEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_CULL_FACE: %s") LINE_TERMINATOR, bCullFaceEnabled ? TEXT("TRUE") : TEXT("FALSE") );
GLint CullFaceMode;
glGetIntegerv( GL_CULL_FACE_MODE, &CullFaceMode );
ASSERT_NO_GL_ERROR();
const TCHAR* CullFaceModeName = GetCullFaceModeName( CullFaceMode );
if( CullFaceModeName )
LogFile.Logf( TEXT("\tGL_CULL_FACE_MODE: %s") LINE_TERMINATOR, CullFaceModeName );
else
LogFile.Logf( TEXT("\tGL_CULL_FACE_MODE: 0x%x") LINE_TERMINATOR, CullFaceMode );
GLint FrontFace;
glGetIntegerv( GL_FRONT_FACE, &FrontFace );
ASSERT_NO_GL_ERROR();
const TCHAR* FrontFaceName = GetFrontFaceName( FrontFace );
if( FrontFaceName )
LogFile.Logf( TEXT("\tGL_FRONT_FACE: %s") LINE_TERMINATOR, FrontFaceName );
else
LogFile.Logf( TEXT("\tGL_FRONT_FACE: 0x%x") LINE_TERMINATOR, FrontFace );
GLboolean bPolygonOffsetFillEnabled;
glGetBooleanv( GL_POLYGON_OFFSET_FILL, &bPolygonOffsetFillEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_POLYGON_OFFSET_FILL: %s") LINE_TERMINATOR, bPolygonOffsetFillEnabled ? TEXT("Enabled") : TEXT("Disabled") );
GLboolean bPolygonOffsetLineEnabled;
glGetBooleanv( GL_POLYGON_OFFSET_LINE, &bPolygonOffsetLineEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_POLYGON_OFFSET_LINE: %s") LINE_TERMINATOR, bPolygonOffsetLineEnabled ? TEXT("Enabled") : TEXT("Disabled") );
GLboolean bPolygonOffsetPointEnabled;
glGetBooleanv( GL_POLYGON_OFFSET_POINT, &bPolygonOffsetPointEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_POLYGON_OFFSET_POINT: %s") LINE_TERMINATOR, bPolygonOffsetPointEnabled ? TEXT("Enabled") : TEXT("Disabled") );
GLfloat PolygonOffsetUnits;
glGetFloatv( GL_POLYGON_OFFSET_UNITS, &PolygonOffsetUnits );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_POLYGON_OFFSET_UNITS: %f") LINE_TERMINATOR, PolygonOffsetUnits );
GLboolean bPolygonSmoothEnabled;
glGetBooleanv( GL_POLYGON_SMOOTH, &bPolygonSmoothEnabled );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_POLYGON_SMOOTH: %s") LINE_TERMINATOR, bPolygonSmoothEnabled ? TEXT("Enabled") : TEXT("Disabled") );
}
void FOpenGLDebugFrameDumper::DumpTextureLimitsAndBindings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Texture Limits And Bindings") LINE_TERMINATOR );
GLint ActiveTextureUnit;
glGetIntegerv( GL_ACTIVE_TEXTURE, &ActiveTextureUnit );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_ACTIVE_TEXTURE: %d") LINE_TERMINATOR, ActiveTextureUnit-GL_TEXTURE0 );
GLint MaxTextureImageUnits;
glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS, &MaxTextureImageUnits );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_TEXTURE_IMAGE_UNITS: %d") LINE_TERMINATOR, MaxTextureImageUnits );
GLint MaxVertexTextureImageUnits;
glGetIntegerv( GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &MaxVertexTextureImageUnits );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: %d") LINE_TERMINATOR, MaxVertexTextureImageUnits );
for( GLint TextureImageUnitIndex = 0; TextureImageUnitIndex < MaxTextureImageUnits; ++TextureImageUnitIndex )
{
glActiveTexture( GL_TEXTURE0+TextureImageUnitIndex );
ASSERT_NO_GL_ERROR();
GLint TextureBinding1D;
glGetIntegerv( GL_TEXTURE_BINDING_1D, &TextureBinding1D );
ASSERT_NO_GL_ERROR();
if( TextureBinding1D )
{
LogFile.Logf( TEXT("\t\tUnit %2d : GL_TEXTURE_BINDING_1D: %d") LINE_TERMINATOR, TextureImageUnitIndex, TextureBinding1D );
}
GLint TextureBinding2D;
glGetIntegerv( GL_TEXTURE_BINDING_2D, &TextureBinding2D );
ASSERT_NO_GL_ERROR();
if( TextureBinding2D )
{
LogFile.Logf( TEXT("\t\tUnit %2d : GL_TEXTURE_BINDING_2D: %d") LINE_TERMINATOR, TextureImageUnitIndex, TextureBinding2D );
}
GLint TextureBinding3D;
glGetIntegerv( GL_TEXTURE_BINDING_3D, &TextureBinding3D );
ASSERT_NO_GL_ERROR();
if( TextureBinding3D )
{
LogFile.Logf( TEXT("\t\tUnit %2d : GL_TEXTURE_BINDING_3D: %d") LINE_TERMINATOR, TextureImageUnitIndex, TextureBinding3D );
}
GLint TextureBindingCubeMap;
glGetIntegerv( GL_TEXTURE_BINDING_CUBE_MAP, &TextureBindingCubeMap );
ASSERT_NO_GL_ERROR();
if( TextureBindingCubeMap )
{
LogFile.Logf( TEXT("\t\tUnit %2d : GL_TEXTURE_BINDING_CUBE_MAP: %d") LINE_TERMINATOR, TextureImageUnitIndex, TextureBindingCubeMap );
}
}
glActiveTexture( ActiveTextureUnit );
ASSERT_NO_GL_ERROR();
}
void FOpenGLDebugFrameDumper::DumpProgramSettings( FOutputDeviceFile& LogFile )
{
LogFile.Log( TEXT("Program") LINE_TERMINATOR );
GLint CurrentProgram;
glGetIntegerv( GL_CURRENT_PROGRAM, &CurrentProgram );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_CURRENT_PROGRAM: %d") LINE_TERMINATOR, CurrentProgram );
}
void FOpenGLDebugFrameDumper::DumpRenderbufferSettings( FOutputDeviceFile& LogFile, GLint RenderbufferID )
{
GLint CurrentlyBoundRenderbuffer;
glGetIntegerv( GL_RENDERBUFFER_BINDING, &CurrentlyBoundRenderbuffer );
ASSERT_NO_GL_ERROR();
GLboolean bIsRenderbuffer = glIsRenderbuffer( RenderbufferID );
ASSERT_NO_GL_ERROR();
if( !bIsRenderbuffer )
{
LogFile.Logf( TEXT("\t\t\tRenderbuffer ID %d is not a valid renderbuffer ID!") LINE_TERMINATOR, RenderbufferID );
return;
}
if( RenderbufferID != CurrentlyBoundRenderbuffer )
{
glBindRenderbuffer( GL_RENDERBUFFER, RenderbufferID );
ASSERT_NO_GL_ERROR();
}
LogFile.Logf( TEXT("\t\t\tRenderbuffer object %d info") LINE_TERMINATOR, RenderbufferID );
GLint Width;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &Width );
ASSERT_NO_GL_ERROR();
GLint Height;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &Height );
ASSERT_NO_GL_ERROR();
GLint Format;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &Format );
ASSERT_NO_GL_ERROR();
GLint RedSize;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_RED_SIZE, &RedSize );
ASSERT_NO_GL_ERROR();
GLint GreenSize;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_GREEN_SIZE, &GreenSize );
ASSERT_NO_GL_ERROR();
GLint BlueSize;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_BLUE_SIZE, &BlueSize );
ASSERT_NO_GL_ERROR();
GLint AlphaSize;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_ALPHA_SIZE, &AlphaSize );
ASSERT_NO_GL_ERROR();
GLint DepthSize;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_DEPTH_SIZE, &DepthSize );
ASSERT_NO_GL_ERROR();
GLint StencilSize;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_STENCIL_SIZE, &StencilSize );
ASSERT_NO_GL_ERROR();
GLint Samples;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &Samples );
ASSERT_NO_GL_ERROR();
FString RenderbufferInfo = FString::Printf( TEXT("\t\t%d x %d, format: %s, samples: %d"), Width, Height, GetGLInternalFormatString( Format ), Samples );
if( RedSize )
{
RenderbufferInfo += FString::Printf( TEXT(", red: %d"), RedSize );
}
if( GreenSize )
{
RenderbufferInfo += FString::Printf( TEXT(", green: %d"), GreenSize );
}
if( BlueSize )
{
RenderbufferInfo += FString::Printf( TEXT(", blue: %d"), BlueSize );
}
if( AlphaSize )
{
RenderbufferInfo += FString::Printf( TEXT(", alpha: %d"), AlphaSize );
}
if( DepthSize )
{
RenderbufferInfo += FString::Printf( TEXT(", depth: %d"), DepthSize );
}
if( StencilSize )
{
RenderbufferInfo += FString::Printf( TEXT(", stencil: %d"), StencilSize );
}
RenderbufferInfo += LINE_TERMINATOR;
LogFile.Log( RenderbufferInfo );
// Restore previous state
if( RenderbufferID != CurrentlyBoundRenderbuffer )
{
glBindRenderbuffer( GL_RENDERBUFFER, CurrentlyBoundRenderbuffer );
}
}
void FOpenGLDebugFrameDumper::DumpFramebufferAttachmentSettings( FOutputDeviceFile& LogFile, GLenum AttachmentSlot )
{
GLint AttachmentType;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &AttachmentType );
ASSERT_NO_GL_ERROR();
if( AttachmentType == GL_NONE )
return; // nothing attached; no comment
GLint AttachmentName;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &AttachmentName );
ASSERT_NO_GL_ERROR();
if( AttachmentType == GL_FRAMEBUFFER_DEFAULT )
{
LogFile.Logf( TEXT("\t\tattachment %s is default framebuffer attachment ( name is %d )") LINE_TERMINATOR, GetAttachmentSlotName( AttachmentSlot ), AttachmentName );
}
else if( AttachmentType == GL_TEXTURE )
{
GLint TextureLevel;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &TextureLevel );
ASSERT_NO_GL_ERROR();
GLint CubeMapFace;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &CubeMapFace );
ASSERT_NO_GL_ERROR();
GLint TextureLayer;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER, &TextureLayer );
ASSERT_NO_GL_ERROR();
GLint IsLayered;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_LAYERED, &IsLayered );
ASSERT_NO_GL_ERROR();
GLenum TextureTypeAsk;
GLenum TextureTypeSet;
GLenum TextureTypeFace;
const TCHAR* TextureType;
bool bIsCube = ( CubeMapFace != 0 );
if( bIsCube )
{
TextureTypeAsk = GL_TEXTURE_BINDING_CUBE_MAP;
TextureTypeSet = GL_TEXTURE_CUBE_MAP;
TextureTypeFace = CubeMapFace;
TextureType = TEXT("cube map");
}
else
{
TextureTypeAsk = GL_TEXTURE_BINDING_2D;
TextureTypeSet = GL_TEXTURE_2D;
TextureTypeFace = GL_TEXTURE_2D;
TextureType = TEXT("2D");
}
// Remember what's bound to the texture stage we need to use, to restore it later
GLint BoundTextureID;
glGetIntegerv( TextureTypeAsk, &BoundTextureID );
// Bind our texture. Was it GL_TEXTURE_2D?
ASSERT_NO_GL_ERROR();
GDisableOpenGLDebugOutput = true;
glBindTexture( TextureTypeSet, AttachmentName );
glFinish();
GDisableOpenGLDebugOutput = false;
if( glGetError() )
{
// It could be GL_TEXTURE_2D_MULTISAMPLE then?
check(TextureTypeSet == GL_TEXTURE_2D);
check(TextureLevel == 0);
TextureTypeAsk = GL_TEXTURE_BINDING_2D_MULTISAMPLE;
TextureTypeSet = GL_TEXTURE_2D_MULTISAMPLE;
TextureTypeFace = GL_TEXTURE_2D_MULTISAMPLE;
TextureType = TEXT("2D multisample");
glGetIntegerv( TextureTypeAsk, &BoundTextureID );
ASSERT_NO_GL_ERROR();
GDisableOpenGLDebugOutput = true;
glBindTexture( TextureTypeSet, AttachmentName );
glFinish();
GDisableOpenGLDebugOutput = false;
if( glGetError() )
{
// Ok, then GL_TEXTURE_3D?
TextureTypeAsk = GL_TEXTURE_BINDING_3D;
TextureTypeSet = GL_TEXTURE_3D;
TextureTypeFace = GL_TEXTURE_3D;
TextureType = TEXT("3D");
glGetIntegerv( TextureTypeAsk, &BoundTextureID );
ASSERT_NO_GL_ERROR();
glBindTexture( TextureTypeSet, AttachmentName );
ASSERT_NO_GL_ERROR();
}
}
// Get the information we need
GLint Width;
glGetTexLevelParameteriv( TextureTypeFace, TextureLevel, GL_TEXTURE_WIDTH, &Width );
ASSERT_NO_GL_ERROR();
GLint Height;
glGetTexLevelParameteriv( TextureTypeFace, TextureLevel, GL_TEXTURE_HEIGHT, &Height );
ASSERT_NO_GL_ERROR();
GLint Depth;
glGetTexLevelParameteriv( TextureTypeFace, TextureLevel, GL_TEXTURE_DEPTH, &Depth );
ASSERT_NO_GL_ERROR();
GLint InternalFormat;
glGetTexLevelParameteriv( TextureTypeFace, TextureLevel, GL_TEXTURE_INTERNAL_FORMAT, &InternalFormat );
ASSERT_NO_GL_ERROR();
// Restore previous binding to this texture stage
if( BoundTextureID != AttachmentName )
{
glBindTexture( TextureTypeSet, BoundTextureID );
ASSERT_NO_GL_ERROR();
}
LogFile.Logf( TEXT("\t\tattachment %s is a %s texture ( ID %d, level %d, %d x %d x %d, %s%s )") LINE_TERMINATOR,
GetAttachmentSlotName( AttachmentSlot ), TextureType, AttachmentName, TextureLevel, Width, Height, Depth,
GetGLInternalFormatString( InternalFormat ), IsLayered ? TEXT(", layered") : TEXT("") );
if( CubeMapFace != 0 && CubeMapFace != GL_TEXTURE_CUBE_MAP )
{
LogFile.Logf( TEXT("\t\t\tcube map face: %s") LINE_TERMINATOR, GetCubeMapFaceName( CubeMapFace ) );
}
}
else if( AttachmentType == GL_RENDERBUFFER )
{
LogFile.Logf( TEXT("\t\tattachment %s is a renderbuffer ( ID %d )") LINE_TERMINATOR, GetAttachmentSlotName( AttachmentSlot ), AttachmentName );
DumpRenderbufferSettings( LogFile, AttachmentName );
}
GLint RedSize;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE, &RedSize );
ASSERT_NO_GL_ERROR();
GLint GreenSize;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE, &GreenSize );
ASSERT_NO_GL_ERROR();
GLint BlueSize;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE, &BlueSize );
ASSERT_NO_GL_ERROR();
GLint AlphaSize;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, &AlphaSize );
ASSERT_NO_GL_ERROR();
GLint DepthSize;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &DepthSize );
ASSERT_NO_GL_ERROR();
GLint StencilSize;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &StencilSize );
ASSERT_NO_GL_ERROR();
GLint ComponentType;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &ComponentType );
ASSERT_NO_GL_ERROR();
GLint ColorEncoding;
glGetFramebufferAttachmentParameteriv( GL_DRAW_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &ColorEncoding );
ASSERT_NO_GL_ERROR();
{
FString ComponentInfo = FString::Printf( TEXT("\t\t\tComponent type: %s, color encoding: %s"), GetComponentType( ComponentType ), GetColorEncoding( ColorEncoding ) );
if( RedSize )
{
ComponentInfo += FString::Printf( TEXT(", red: %d"), RedSize );
}
if( GreenSize )
{
ComponentInfo += FString::Printf( TEXT(", green: %d"), GreenSize );
}
if( BlueSize )
{
ComponentInfo += FString::Printf( TEXT(", blue: %d"), BlueSize );
}
if( AlphaSize )
{
ComponentInfo += FString::Printf( TEXT(", alpha: %d"), AlphaSize );
}
if( DepthSize )
{
ComponentInfo += FString::Printf( TEXT(", depth: %d"), DepthSize );
}
if( StencilSize )
{
ComponentInfo += FString::Printf( TEXT(", stencil: %d"), StencilSize );
}
ComponentInfo += LINE_TERMINATOR;
LogFile.Log( ComponentInfo );
}
}
void FOpenGLDebugFrameDumper::DumpFramebufferSettings( FOutputDeviceFile& LogFile, GLint FramebufferID )
{
LogFile.Log( TEXT("Framebuffer State") LINE_TERMINATOR );
GLint CurrentlyBoundDrawFramebuffer;
glGetIntegerv( GL_DRAW_FRAMEBUFFER_BINDING, &CurrentlyBoundDrawFramebuffer );
ASSERT_NO_GL_ERROR();
if( FramebufferID )
{
GLboolean bIsFramebuffer = glIsFramebuffer( FramebufferID );
ASSERT_NO_GL_ERROR();
if( !bIsFramebuffer )
{
LogFile.Logf( TEXT("\tFramebuffer ID %d is not a valid framebuffer ID! ( %d )") LINE_TERMINATOR, FramebufferID );
return;
}
}
if( FramebufferID != CurrentlyBoundDrawFramebuffer )
{
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, FramebufferID );
ASSERT_NO_GL_ERROR();
}
if( !FramebufferID )
{
LogFile.Log( TEXT("\tFramebuffer object 0 (screen buffer)") LINE_TERMINATOR );
DumpFramebufferAttachmentSettings( LogFile, GL_FRONT_LEFT );
DumpFramebufferAttachmentSettings( LogFile, GL_FRONT_RIGHT );
DumpFramebufferAttachmentSettings( LogFile, GL_BACK_LEFT );
DumpFramebufferAttachmentSettings( LogFile, GL_BACK_RIGHT );
DumpFramebufferAttachmentSettings( LogFile, GL_DEPTH );
DumpFramebufferAttachmentSettings( LogFile, GL_STENCIL );
}
else
{
LogFile.Logf( TEXT("\tFramebuffer object %d info") LINE_TERMINATOR, FramebufferID );
GLint MaxColorAttachments;
glGetIntegerv( GL_MAX_COLOR_ATTACHMENTS, &MaxColorAttachments );
ASSERT_NO_GL_ERROR();
for( GLint ColorAttachmentIndex = 0; ColorAttachmentIndex < MaxColorAttachments; ++ColorAttachmentIndex )
{
DumpFramebufferAttachmentSettings( LogFile, GL_COLOR_ATTACHMENT0 + ColorAttachmentIndex );
}
DumpFramebufferAttachmentSettings( LogFile, GL_DEPTH_ATTACHMENT );
DumpFramebufferAttachmentSettings( LogFile, GL_STENCIL_ATTACHMENT );
}
// Restore previous state
if( FramebufferID != CurrentlyBoundDrawFramebuffer )
{
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, CurrentlyBoundDrawFramebuffer );
ASSERT_NO_GL_ERROR();
}
}
void FOpenGLDebugFrameDumper::InterpretUniform( GLint UniformType, void* DataBuffer, FString& OutAppendString )
{
switch( UniformType )
{
case GL_FLOAT:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "%f"), Fields[0] );
}
break;
case GL_FLOAT_VEC2:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %f, %f }"), Fields[0], Fields[1] );
}
break;
case GL_FLOAT_VEC3:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %f, %f, %f }"), Fields[0], Fields[1], Fields[2] );
}
break;
case GL_FLOAT_VEC4:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %f, %f, %f, %f }"), Fields[0], Fields[1], Fields[2], Fields[3] );
}
break;
case GL_SAMPLER_1D:
case GL_SAMPLER_2D:
case GL_SAMPLER_3D:
case GL_SAMPLER_CUBE:
case GL_SAMPLER_1D_SHADOW:
case GL_SAMPLER_2D_SHADOW:
case GL_SAMPLER_1D_ARRAY:
case GL_SAMPLER_2D_ARRAY:
case GL_SAMPLER_1D_ARRAY_SHADOW:
case GL_SAMPLER_2D_ARRAY_SHADOW:
case GL_SAMPLER_2D_MULTISAMPLE:
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_SAMPLER_CUBE_SHADOW:
case GL_SAMPLER_BUFFER:
case GL_SAMPLER_2D_RECT:
case GL_SAMPLER_2D_RECT_SHADOW:
case GL_INT_SAMPLER_1D:
case GL_INT_SAMPLER_2D:
case GL_INT_SAMPLER_3D:
case GL_INT_SAMPLER_CUBE:
case GL_INT_SAMPLER_1D_ARRAY:
case GL_INT_SAMPLER_2D_ARRAY:
case GL_INT_SAMPLER_2D_MULTISAMPLE:
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_INT_SAMPLER_BUFFER:
case GL_INT_SAMPLER_2D_RECT:
case GL_UNSIGNED_INT_SAMPLER_1D:
case GL_UNSIGNED_INT_SAMPLER_2D:
case GL_UNSIGNED_INT_SAMPLER_3D:
case GL_UNSIGNED_INT_SAMPLER_CUBE:
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
case GL_UNSIGNED_INT_SAMPLER_BUFFER:
case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
case GL_INT:
{
int32* Fields = (int32*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "%d"), Fields[0] );
}
break;
case GL_INT_VEC2:
{
int32* Fields = (int32*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %d, %d }"), Fields[0], Fields[1] );
}
break;
case GL_INT_VEC3:
{
int32* Fields = (int32*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %d, %d, %d }"), Fields[0], Fields[1], Fields[2] );
}
break;
case GL_INT_VEC4:
{
int32* Fields = (int32*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %d, %d, %d, %d }"), Fields[0], Fields[1], Fields[2], Fields[3] );
}
break;
case GL_UNSIGNED_INT:
{
uint32* Fields = (uint32*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "%u"), Fields[0] );
}
break;
case GL_UNSIGNED_INT_VEC2:
{
uint32* Fields = (uint32*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %u, %u }"), Fields[0], Fields[1] );
}
break;
case GL_UNSIGNED_INT_VEC3:
{
uint32* Fields = (uint32*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %u, %u, %u }"), Fields[0], Fields[1], Fields[2] );
}
break;
case GL_UNSIGNED_INT_VEC4:
{
uint32* Fields = (uint32*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %u, %u, %u, %u }"), Fields[0], Fields[1], Fields[2], Fields[3] );
}
break;
case GL_BOOL:
{
GLboolean* Fields = (GLboolean*)DataBuffer;
OutAppendString += Fields[0] ? TEXT("TRUE") : TEXT("FALSE");
}
break;
case GL_BOOL_VEC2:
{
GLboolean* Fields = (GLboolean*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %s, %s }"),
Fields[0] ? TEXT("TRUE") : TEXT("FALSE"),
Fields[1] ? TEXT("TRUE") : TEXT("FALSE")
);
}
break;
case GL_BOOL_VEC3:
{
GLboolean* Fields = (GLboolean*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %s, %s, %s }"),
Fields[0] ? TEXT("TRUE") : TEXT("FALSE"),
Fields[1] ? TEXT("TRUE") : TEXT("FALSE"),
Fields[2] ? TEXT("TRUE") : TEXT("FALSE")
);
}
break;
case GL_BOOL_VEC4:
{
GLboolean* Fields = (GLboolean*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ %s, %s, %s, %s }"),
Fields[0] ? TEXT("TRUE") : TEXT("FALSE"),
Fields[1] ? TEXT("TRUE") : TEXT("FALSE"),
Fields[2] ? TEXT("TRUE") : TEXT("FALSE"),
Fields[3] ? TEXT("TRUE") : TEXT("FALSE")
);
}
break;
case GL_FLOAT_MAT2:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ { %f, %f }, { %f, %f } }"), Fields[0], Fields[1], Fields[2], Fields[3] );
}
break;
case GL_FLOAT_MAT3:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ { %f, %f, %f }, { %f, %f, %f }, { %f, %f, %f } }"),
Fields[0], Fields[1], Fields[2], Fields[3], Fields[4], Fields[5], Fields[6], Fields[7], Fields[8] );
}
break;
case GL_FLOAT_MAT4:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ { %f, %f, %f, %f }, { %f, %f, %f, %f }, { %f, %f, %f, %f }, { %f, %f, %f, %f } }"),
Fields[0], Fields[1], Fields[2], Fields[3], Fields[4], Fields[5], Fields[6], Fields[7],
Fields[8], Fields[9], Fields[10], Fields[11], Fields[12], Fields[13], Fields[14], Fields[15]
);
}
break;
case GL_FLOAT_MAT2x3:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ { %f, %f }, { %f, %f }, { %f, %f } }"),
Fields[0], Fields[1], Fields[2], Fields[3], Fields[4], Fields[5] );
}
break;
case GL_FLOAT_MAT2x4:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ { %f, %f }, { %f, %f }, { %f, %f }, { %f, %f } }"),
Fields[0], Fields[1], Fields[2], Fields[3], Fields[4], Fields[5], Fields[6], Fields[7] );
}
break;
case GL_FLOAT_MAT3x2:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ { %f, %f, %f }, { %f, %f, %f } }"),
Fields[0], Fields[1], Fields[2], Fields[3], Fields[4], Fields[5] );
}
break;
case GL_FLOAT_MAT3x4:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ { %f, %f, %f }, { %f, %f, %f }, { %f, %f, %f }, { %f, %f, %f } }"),
Fields[0], Fields[1], Fields[2], Fields[3], Fields[4], Fields[5], Fields[6], Fields[7],
Fields[8], Fields[9], Fields[10], Fields[11]
);
}
break;
case GL_FLOAT_MAT4x2:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ { %f, %f, %f, %f }, { %f, %f, %f, %f } }"),
Fields[0], Fields[1], Fields[2], Fields[3], Fields[4], Fields[5], Fields[6], Fields[7] );
}
break;
case GL_FLOAT_MAT4x3:
{
float* Fields = (float*)DataBuffer;
OutAppendString += FString::Printf( TEXT( "{ { %f, %f, %f, %f }, { %f, %f, %f, %f }, { %f, %f, %f, %f } }"),
Fields[0], Fields[1], Fields[2], Fields[3], Fields[4], Fields[5], Fields[6], Fields[7],
Fields[8], Fields[9], Fields[10], Fields[11]
);
}
break;
default:
OutAppendString += TEXT("!!!unknown!!!");
break;
};
}
void FOpenGLDebugFrameDumper::DumpProgramContents( FOutputDeviceFile& LogFile, GLint ProgramID )
{
GLboolean bIsProgram = glIsProgram( ProgramID );
ASSERT_NO_GL_ERROR();
if( !bIsProgram )
{
LogFile.Logf( TEXT("Program ID %d is not a valid program ID!") LINE_TERMINATOR, ProgramID );
return;
}
LogFile.Logf( TEXT("Program %d info") LINE_TERMINATOR, ProgramID );
{
GLint Status;
glGetProgramiv( ProgramID, GL_DELETE_STATUS, &Status );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DELETE_STATUS: %s") LINE_TERMINATOR, Status ? TEXT("TRUE") : TEXT("FALSE") );
}
{
GLint Status;
glGetProgramiv( ProgramID, GL_LINK_STATUS, &Status );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_LINK_STATUS: %s") LINE_TERMINATOR, Status ? TEXT("TRUE") : TEXT("FALSE") );
}
{
GLint Status;
glGetProgramiv( ProgramID, GL_VALIDATE_STATUS, &Status );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_VALIDATE_STATUS: %s") LINE_TERMINATOR, Status ? TEXT("TRUE") : TEXT("FALSE") );
}
GLint AttachedShaderCount;
glGetProgramiv( ProgramID, GL_ATTACHED_SHADERS, &AttachedShaderCount );
ASSERT_NO_GL_ERROR();
if( AttachedShaderCount )
{
GLsizei CountReceived = 0;
GLuint* AttachedShadersTable = (GLuint*)FMemory::Malloc( sizeof(GLuint)*AttachedShaderCount );
glGetAttachedShaders( ProgramID, AttachedShaderCount, &CountReceived, AttachedShadersTable );
ASSERT_NO_GL_ERROR();
FString ShaderNumbers = TEXT("");
for( GLint ShaderIndex = 0; ShaderIndex < CountReceived; ++ShaderIndex )
{
ShaderNumbers += FString::Printf( TEXT("%s%u"), ShaderIndex ? TEXT(", ") : TEXT(""), AttachedShadersTable[ShaderIndex] );
}
LogFile.Logf( TEXT("\tAttached shaders: %d ( %s )") LINE_TERMINATOR, AttachedShaderCount, *ShaderNumbers );
FMemory::Free( AttachedShadersTable );
}
// Attributes
GLint ActiveAttributesCount;
glGetProgramiv( ProgramID, GL_ACTIVE_ATTRIBUTES, &ActiveAttributesCount );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tActive attributes: %d") LINE_TERMINATOR, ActiveAttributesCount );
if( ActiveAttributesCount )
{
GLint MaxActiveAttributeNameLength;
glGetProgramiv( ProgramID, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &MaxActiveAttributeNameLength );
ASSERT_NO_GL_ERROR();
if( MaxActiveAttributeNameLength )
{
ANSICHAR* ActiveAttributeName = (ANSICHAR*)FMemory::Malloc( sizeof(ANSICHAR)*(MaxActiveAttributeNameLength+1) );
ActiveAttributeName[MaxActiveAttributeNameLength] = 0;
for( GLint AttributeIndex = 0; AttributeIndex < ActiveAttributesCount; ++AttributeIndex )
{
GLsizei NameLength = 0;
GLint Size = 0;
GLenum Type = 0;
glGetActiveAttrib( ProgramID, AttributeIndex, MaxActiveAttributeNameLength+1, &NameLength, &Size, &Type, ActiveAttributeName );
ASSERT_NO_GL_ERROR();
GLint AttributeLocation = glGetAttribLocation( ProgramID, ActiveAttributeName );
ASSERT_NO_GL_ERROR();
FString ActiveAttributeNameString( ActiveAttributeName );
LogFile.Logf( TEXT("\t%04d: %s ( type %s, location %d, size %d )") LINE_TERMINATOR, AttributeIndex, *ActiveAttributeNameString, GetGLUniformTypeString( Type ), AttributeLocation, Size );
}
FMemory::Free( ActiveAttributeName );
}
}
// Uniforms
GLint ActiveUniformCount;
glGetProgramiv( ProgramID, GL_ACTIVE_UNIFORMS, &ActiveUniformCount );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tActive uniforms: %d") LINE_TERMINATOR, ActiveUniformCount );
if( ActiveUniformCount )
{
GLint MaxActiveUniformNameLength;
glGetProgramiv( ProgramID, GL_ACTIVE_UNIFORM_MAX_LENGTH, &MaxActiveUniformNameLength );
ASSERT_NO_GL_ERROR();
if( MaxActiveUniformNameLength )
{
ANSICHAR* ActiveUniformName = (ANSICHAR*)FMemory::Malloc( sizeof(ANSICHAR)*(MaxActiveUniformNameLength+1) );
ActiveUniformName[MaxActiveUniformNameLength] = 0;
for( GLint ActiveUniformIndex = 0; ActiveUniformIndex < ActiveUniformCount; ++ActiveUniformIndex )
{
GLsizei NameLengthReceived;
glGetActiveUniformName( ProgramID, ActiveUniformIndex, MaxActiveUniformNameLength+1, &NameLengthReceived, ActiveUniformName );
ASSERT_NO_GL_ERROR();
GLuint TempUniformIndex = ActiveUniformIndex;
GLint UniformType;
glGetActiveUniformsiv( ProgramID, 1, &TempUniformIndex, GL_UNIFORM_TYPE, &UniformType );
ASSERT_NO_GL_ERROR();
GLint UniformSize;
glGetActiveUniformsiv( ProgramID, 1, &TempUniformIndex, GL_UNIFORM_SIZE, &UniformSize );
ASSERT_NO_GL_ERROR();
GLint UniformIsRowMajor;
glGetActiveUniformsiv( ProgramID, 1, &TempUniformIndex, GL_UNIFORM_IS_ROW_MAJOR, &UniformIsRowMajor );
ASSERT_NO_GL_ERROR();
FString ActiveUniformNameString( ActiveUniformName );
LogFile.Logf( TEXT("\t%04d: %s ( type %s )") LINE_TERMINATOR, ActiveUniformIndex, *ActiveUniformNameString, GetGLUniformTypeString( UniformType ) );
GLint UniformBlockIndex;
glGetActiveUniformsiv( ProgramID, 1, &TempUniformIndex, GL_UNIFORM_BLOCK_INDEX, &UniformBlockIndex );
ASSERT_NO_GL_ERROR();
if( UniformBlockIndex == -1 ) // default uniform
{
GLint UniformOffset = glGetUniformLocation( ProgramID, ActiveUniformName );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t ( size %d, DEFAULT uniform block : location %d, is row major %d )") LINE_TERMINATOR,
UniformSize, UniformOffset, UniformIsRowMajor );
for( int32 UniformArrayMemberIndex = 0; UniformArrayMemberIndex < UniformSize; ++UniformArrayMemberIndex )
{
float DataBuffer[16]; // so it can fit all types
switch( UniformType )
{
case GL_FLOAT:
case GL_FLOAT_VEC2:
case GL_FLOAT_VEC3:
case GL_FLOAT_VEC4:
case GL_FLOAT_MAT2:
case GL_FLOAT_MAT3:
case GL_FLOAT_MAT4:
case GL_FLOAT_MAT2x3:
case GL_FLOAT_MAT2x4:
case GL_FLOAT_MAT3x2:
case GL_FLOAT_MAT3x4:
case GL_FLOAT_MAT4x2:
case GL_FLOAT_MAT4x3:
glGetUniformfv( ProgramID, UniformOffset, DataBuffer );
ASSERT_NO_GL_ERROR();
break;
case GL_UNSIGNED_INT:
case GL_UNSIGNED_INT_VEC2:
case GL_UNSIGNED_INT_VEC3:
case GL_UNSIGNED_INT_VEC4:
glGetUniformuiv( ProgramID, UniformOffset, (GLuint*)DataBuffer );
ASSERT_NO_GL_ERROR();
break;
default:
glGetUniformiv( ProgramID, UniformOffset, (GLint*)DataBuffer );
ASSERT_NO_GL_ERROR();
break;
}
FString Line = TEXT("\t ");
InterpretUniform( UniformType, DataBuffer, Line );
Line += LINE_TERMINATOR;
LogFile.Log( Line );
++UniformOffset;
}
}
else
{
GLint UniformOffset;
glGetActiveUniformsiv( ProgramID, 1, &TempUniformIndex, GL_UNIFORM_OFFSET, &UniformOffset );
ASSERT_NO_GL_ERROR();
GLint UniformArrayStride;
glGetActiveUniformsiv( ProgramID, 1, &TempUniformIndex, GL_UNIFORM_ARRAY_STRIDE, &UniformArrayStride );
ASSERT_NO_GL_ERROR();
GLint UniformMatrixStride;
glGetActiveUniformsiv( ProgramID, 1, &TempUniformIndex, GL_UNIFORM_MATRIX_STRIDE, &UniformMatrixStride );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t ( size %d, uniform block %d : offset %d array stride %d, matrix stride %d, is row major %d )") LINE_TERMINATOR,
UniformSize, UniformBlockIndex, UniformOffset, UniformArrayStride, UniformMatrixStride, UniformIsRowMajor );
GLint UniformBlockBinding;
glGetActiveUniformBlockiv( ProgramID, UniformBlockIndex, GL_UNIFORM_BLOCK_BINDING, &UniformBlockBinding );
ASSERT_NO_GL_ERROR();
GLint UniformBufferID;
glGetIntegeri_v( GL_UNIFORM_BUFFER_BINDING, UniformBlockBinding, &UniformBufferID );
ASSERT_NO_GL_ERROR();
if( UniformBufferID )
{
GLint CurrentUniformBufferBinding;
glGetIntegerv( GL_UNIFORM_BUFFER_BINDING, &CurrentUniformBufferBinding );
ASSERT_NO_GL_ERROR();
glBindBuffer( GL_UNIFORM_BUFFER, UniformBufferID );
ASSERT_NO_GL_ERROR();
GLint64 TotalBufferSize = 0LL; // getters apparently like to overwrite just some bytes of this on Mac, if they return small number, so we need to put 0 in both dwords initially
glGetBufferParameteri64v( GL_UNIFORM_BUFFER, GL_BUFFER_SIZE, &TotalBufferSize );
ASSERT_NO_GL_ERROR();
GLuint BufferSize = (GLuint)TotalBufferSize; // yes, I know it's conversion to shorter variable, I don't expect buffers > 4GB in size.
int32 SizeToMap = UniformArrayStride ? UniformSize*UniformArrayStride : 16*sizeof(float);
int32 MaxSizeToMap = BufferSize-UniformOffset;
if( MaxSizeToMap < SizeToMap )
{
SizeToMap = MaxSizeToMap;
}
if( SizeToMap <= 0 )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Active uniform is beyond the end of the uniform buffer!") );
}
else
{
void* BufferPtr = glMapBufferRange( GL_UNIFORM_BUFFER, UniformOffset, SizeToMap, GL_MAP_READ_BIT );
ASSERT_NO_GL_ERROR();
if( BufferPtr )
{
for( int32 UniformArrayMemberIndex = 0; UniformArrayMemberIndex < UniformSize; ++UniformArrayMemberIndex )
{
uint8* DataBuffer = (uint8*)BufferPtr + UniformArrayMemberIndex * UniformArrayStride;
FString Line = TEXT("\t ");
InterpretUniform( UniformType, DataBuffer, Line );
Line += LINE_TERMINATOR;
LogFile.Log( Line );
}
glUnmapBuffer( GL_UNIFORM_BUFFER );
ASSERT_NO_GL_ERROR();
}
else
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Failed to map uniform buffer %d!"), UniformBlockBinding );
}
}
glBindBuffer( GL_UNIFORM_BUFFER, CurrentUniformBufferBinding );
ASSERT_NO_GL_ERROR();
}
else
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Nothing bound to active uniform block right after draw!") );
}
}
}
FMemory::Free( ActiveUniformName );
}
}
// Uniform blocks
GLint ActiveUniformBlockCount;
glGetProgramiv( ProgramID, GL_ACTIVE_UNIFORM_BLOCKS, &ActiveUniformBlockCount );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tActive uniform blocks: %d") LINE_TERMINATOR, ActiveUniformBlockCount );
if( ActiveUniformBlockCount )
{
GLint MaxActiveUniformBlockNameLength;
glGetProgramiv( ProgramID, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, &MaxActiveUniformBlockNameLength );
ASSERT_NO_GL_ERROR();
if( MaxActiveUniformBlockNameLength )
{
ANSICHAR* ActiveUniformBlockName = (ANSICHAR*)FMemory::Malloc( sizeof(ANSICHAR)*(MaxActiveUniformBlockNameLength+1) );
ActiveUniformBlockName[MaxActiveUniformBlockNameLength] = 0;
for( GLint ActiveUniformBlockIndex = 0; ActiveUniformBlockIndex < ActiveUniformBlockCount; ++ActiveUniformBlockIndex )
{
GLsizei NameLengthReceived;
glGetActiveUniformBlockName( ProgramID, ActiveUniformBlockIndex, MaxActiveUniformBlockNameLength+1, &NameLengthReceived, ActiveUniformBlockName );
ASSERT_NO_GL_ERROR();
GLint UniformBlockBinding;
glGetActiveUniformBlockiv( ProgramID, ActiveUniformBlockIndex, GL_UNIFORM_BLOCK_BINDING, &UniformBlockBinding );
ASSERT_NO_GL_ERROR();
GLint UniformBlockDataSize;
glGetActiveUniformBlockiv( ProgramID, ActiveUniformBlockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &UniformBlockDataSize );
ASSERT_NO_GL_ERROR();
GLint ActiveUniformsInBlockCount;
glGetActiveUniformBlockiv( ProgramID, ActiveUniformBlockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &ActiveUniformsInBlockCount );
ASSERT_NO_GL_ERROR();
GLint IsReferencedByVertexShader;
glGetActiveUniformBlockiv( ProgramID, ActiveUniformBlockIndex, GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER, &IsReferencedByVertexShader );
ASSERT_NO_GL_ERROR();
GLint IsReferencedByGeometryShader;
glGetActiveUniformBlockiv( ProgramID, ActiveUniformBlockIndex, GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER, &IsReferencedByGeometryShader );
ASSERT_NO_GL_ERROR();
GLint IsReferencedByFragmentShader;
glGetActiveUniformBlockiv( ProgramID, ActiveUniformBlockIndex, GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER, &IsReferencedByFragmentShader );
ASSERT_NO_GL_ERROR();
// TO DO someday, if ever needed - get a list of active uniform indices for the block
// gl3GetActiveUniformBlockiv( ProgramID, ActiveUniformBlockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, &TableOfIndices );
FString ActiveUniformBlockNameString( ActiveUniformBlockName );
LogFile.Logf( TEXT("\t%02d: %s ( binding %d, size %d, active uniforms %d, referenced by: %s%s%s )") LINE_TERMINATOR,
ActiveUniformBlockIndex,
*ActiveUniformBlockNameString,
UniformBlockBinding,
UniformBlockDataSize,
ActiveUniformsInBlockCount,
IsReferencedByVertexShader ? TEXT("V") : TEXT("_"),
IsReferencedByGeometryShader ? TEXT("G") : TEXT("_"),
IsReferencedByFragmentShader ? TEXT("F") : TEXT("_")
);
}
FMemory::Free( ActiveUniformBlockName );
}
}
// glValidateProgram
glValidateProgram( ProgramID );
GLint ValidationStatus;
glGetProgramiv( ProgramID, GL_VALIDATE_STATUS, &ValidationStatus );
LogFile.Logf( TEXT("\tProgram validation status: %s") LINE_TERMINATOR, ( ValidationStatus == GL_FALSE ) ? TEXT("FALSE") : TEXT("TRUE") );
// At the end, info log
GLint InfoLogLength;
glGetProgramiv( ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength );
ASSERT_NO_GL_ERROR();
if( InfoLogLength )
{
ANSICHAR* ANSIBuffer = (ANSICHAR*)FMemory::Malloc( sizeof(ANSICHAR)*InfoLogLength ); // OpenGL gives back char*
glGetProgramInfoLog( ProgramID, InfoLogLength, NULL, ANSIBuffer );
FString Buffer( ANSIBuffer );
LogFile.Logf( TEXT("\tProgram info log:") LINE_TERMINATOR
TEXT("==============================================") LINE_TERMINATOR
TEXT("%s==============================================") LINE_TERMINATOR,
*Buffer );
FMemory::Free( ANSIBuffer );
}
else
{
LogFile.Log( TEXT("\tNo program info log") LINE_TERMINATOR );
}
// TO DO someday - add geometry shader and transform feedback information here. Skipping it for now.
}
void FOpenGLDebugFrameDumper::DumpShaderContents( FOutputDeviceFile& LogFile, GLint ShaderID )
{
GLboolean bIsShader = glIsShader( ShaderID );
ASSERT_NO_GL_ERROR();
if( !bIsShader )
{
LogFile.Logf( TEXT("Shader ID %d is not a valid shader ID!") LINE_TERMINATOR, ShaderID );
return;
}
LogFile.Logf( TEXT("Shader %d info") LINE_TERMINATOR, ShaderID );
GLint ShaderType;
glGetShaderiv( ShaderID, GL_SHADER_TYPE, &ShaderType );
ASSERT_NO_GL_ERROR();
const TCHAR* ShaderTypeString = GetShaderType( ShaderType );
LogFile.Logf( TEXT("\tGL_SHADER_TYPE: %s") LINE_TERMINATOR, ShaderTypeString );
{
GLint Status;
glGetShaderiv( ShaderID, GL_DELETE_STATUS, &Status );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_DELETE_STATUS: %s") LINE_TERMINATOR, Status ? TEXT("TRUE") : TEXT("FALSE") );
}
{
GLint Status;
glGetShaderiv( ShaderID, GL_COMPILE_STATUS, &Status );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_COMPILE_STATUS: %s") LINE_TERMINATOR, Status ? TEXT("TRUE") : TEXT("FALSE") );
}
GLint ShaderSourceLength = 0;
glGetShaderiv( ShaderID, GL_SHADER_SOURCE_LENGTH, &ShaderSourceLength );
ASSERT_NO_GL_ERROR();
if( ShaderSourceLength )
{
ANSICHAR* SourceCode = (ANSICHAR*)FMemory::Malloc( ShaderSourceLength );
glGetShaderSource( ShaderID, ShaderSourceLength, 0, SourceCode );
ASSERT_NO_GL_ERROR();
uint32 CRC = FCrc::MemCrc_DEPRECATED( SourceCode, ShaderSourceLength );
LogFile.Logf( TEXT("\tShader source code (length %u characters, CRC: 0x%x):") LINE_TERMINATOR
TEXT("==============================================") LINE_TERMINATOR
TEXT("%s==============================================") LINE_TERMINATOR,
ShaderSourceLength-1, CRC, *FString( SourceCode ) );
FMemory::Free( SourceCode );
}
else
{
LogFile.Logf( TEXT("\tNo shader source code") LINE_TERMINATOR );
}
GLint ShaderInfoLogLength = 0;
glGetShaderiv( ShaderID, GL_INFO_LOG_LENGTH, &ShaderInfoLogLength );
ASSERT_NO_GL_ERROR();
if( ShaderInfoLogLength )
{
ANSICHAR* InfoLog = (ANSICHAR*)FMemory::Malloc( ShaderInfoLogLength+1 );
glGetShaderInfoLog( ShaderID, ShaderInfoLogLength+1, 0, InfoLog );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tShader info log:") LINE_TERMINATOR
TEXT("==============================================") LINE_TERMINATOR
TEXT("%s==============================================") LINE_TERMINATOR,
*FString( InfoLog ) );
FMemory::Free( InfoLog );
}
else
{
LogFile.Logf( TEXT("\tNo shader info log") LINE_TERMINATOR );
}
}
void FOpenGLDebugFrameDumper::GetBoundTextureSurfaceLevelSettings( GLenum SurfaceType, GLint Level, TextureLevelInfo& OutInfo )
{
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_WIDTH, &OutInfo.Width );
ASSERT_NO_GL_ERROR();
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_HEIGHT, &OutInfo.Height );
ASSERT_NO_GL_ERROR();
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_DEPTH, &OutInfo.Depth );
ASSERT_NO_GL_ERROR();
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_SAMPLES, &OutInfo.Samples );
ASSERT_NO_GL_ERROR();
GLint FixedSampleLocations;
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_FIXED_SAMPLE_LOCATIONS, &FixedSampleLocations );
ASSERT_NO_GL_ERROR();
OutInfo.bHasFixedSampleLocations = ( FixedSampleLocations != 0 );
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_INTERNAL_FORMAT, &OutInfo.InternalFormat );
ASSERT_NO_GL_ERROR();
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_RED_SIZE, &OutInfo.RedBits );
ASSERT_NO_GL_ERROR();
if( OutInfo.RedBits )
{
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_RED_TYPE, &OutInfo.RedType );
ASSERT_NO_GL_ERROR();
}
else
{
OutInfo.RedType = 0;
}
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_GREEN_SIZE, &OutInfo.GreenBits );
ASSERT_NO_GL_ERROR();
if( OutInfo.GreenBits )
{
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_GREEN_TYPE, &OutInfo.GreenType );
ASSERT_NO_GL_ERROR();
}
else
{
OutInfo.GreenType = 0;
}
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_BLUE_SIZE, &OutInfo.BlueBits );
ASSERT_NO_GL_ERROR();
if( OutInfo.BlueBits )
{
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_BLUE_TYPE, &OutInfo.BlueType );
ASSERT_NO_GL_ERROR();
}
else
{
OutInfo.BlueType = 0;
}
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_ALPHA_SIZE, &OutInfo.AlphaBits );
ASSERT_NO_GL_ERROR();
if( OutInfo.AlphaBits )
{
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_ALPHA_TYPE, &OutInfo.AlphaType );
ASSERT_NO_GL_ERROR();
}
else
{
OutInfo.AlphaType = 0;
}
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_DEPTH_SIZE, &OutInfo.DepthBits );
ASSERT_NO_GL_ERROR();
if( OutInfo.DepthBits )
{
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_DEPTH_TYPE, &OutInfo.DepthType );
ASSERT_NO_GL_ERROR();
}
else
{
OutInfo.DepthType = 0;
}
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_STENCIL_SIZE, &OutInfo.StencilBits );
ASSERT_NO_GL_ERROR();
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_SHARED_SIZE, &OutInfo.SharedSize );
ASSERT_NO_GL_ERROR();
GLint TextureCompressed;
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_COMPRESSED, &TextureCompressed );
ASSERT_NO_GL_ERROR();
OutInfo.bIsCompressed = ( TextureCompressed != 0 );
if( OutInfo.bIsCompressed )
{
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &OutInfo.CompressedSize );
ASSERT_NO_GL_ERROR();
}
else
{
OutInfo.CompressedSize = 0;
}
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_BUFFER_DATA_STORE_BINDING, &OutInfo.DataStoreBinding );
ASSERT_NO_GL_ERROR();
}
void FOpenGLDebugFrameDumper::DumpBoundTextureSurfaceSettings( FOutputDeviceFile& LogFile, GLenum SurfaceType, GLint BaseLevel, GLint MaxLevel )
{
TextureLevelInfo BaseInfo;
GetBoundTextureSurfaceLevelSettings( SurfaceType, BaseLevel, BaseInfo );
// Log base level
LogFile.Logf( TEXT("\tBase level ( %d ) info") LINE_TERMINATOR, BaseLevel );
LogFile.Logf( TEXT("\t\t%d x %d x %d ( %d samples, shared size %d )") LINE_TERMINATOR, BaseInfo.Width, BaseInfo.Height, BaseInfo.Depth, BaseInfo.Samples, BaseInfo.SharedSize );
if( BaseInfo.bHasFixedSampleLocations )
{
LogFile.Log( TEXT("\t\tfixed sample locations") LINE_TERMINATOR );
}
LogFile.Logf( TEXT("\t\tInternal format: %s") LINE_TERMINATOR, GetGLInternalFormatString( BaseInfo.InternalFormat ) );
if( BaseInfo.RedBits )
{
LogFile.Logf( TEXT("\t\tR bits: %d, component type: %s") LINE_TERMINATOR, BaseInfo.RedBits, GetComponentType( BaseInfo.RedType ) );
}
if( BaseInfo.GreenBits )
{
LogFile.Logf( TEXT("\t\tG bits: %d, component type: %s") LINE_TERMINATOR, BaseInfo.GreenBits, GetComponentType( BaseInfo.GreenType ) );
}
if( BaseInfo.BlueBits )
{
LogFile.Logf( TEXT("\t\tB bits: %d, component type: %s") LINE_TERMINATOR, BaseInfo.BlueBits, GetComponentType( BaseInfo.BlueType ) );
}
if( BaseInfo.AlphaBits )
{
LogFile.Logf( TEXT("\t\tA bits: %d, component type: %s") LINE_TERMINATOR, BaseInfo.AlphaBits, GetComponentType( BaseInfo.AlphaType ) );
}
if( BaseInfo.DepthBits )
{
LogFile.Logf( TEXT("\t\tDepth bits: %d, component type: %s") LINE_TERMINATOR, BaseInfo.DepthBits, GetComponentType( BaseInfo.DepthType ) );
}
if( BaseInfo.StencilBits )
{
LogFile.Logf( TEXT("\t\tStencil bits: %d") LINE_TERMINATOR, BaseInfo.StencilBits );
}
if( BaseInfo.bIsCompressed )
{
LogFile.Logf( TEXT("\t\tTexture compressed, size: %d") LINE_TERMINATOR, BaseInfo.CompressedSize );
}
if( BaseInfo.DataStoreBinding )
{
LogFile.Logf( TEXT("\t\tData store binding: %d") LINE_TERMINATOR, BaseInfo.DataStoreBinding );
}
if( MaxLevel > BaseLevel )
LogFile.Log( TEXT("\t") LINE_TERMINATOR );
TextureLevelInfo PreviousLevelInfo = BaseInfo;
for( GLint Level = BaseLevel+1; Level <= MaxLevel; ++Level )
{
TextureLevelInfo NewInfo;
GetBoundTextureSurfaceLevelSettings( SurfaceType, Level, NewInfo );
LogFile.Logf( TEXT("\tLevel %d: %d x %d x %d ( %d samples, shared size: %d )") LINE_TERMINATOR, Level, NewInfo.Width, NewInfo.Height, NewInfo.Depth, NewInfo.Samples, NewInfo.SharedSize );
if( NewInfo.bHasFixedSampleLocations != PreviousLevelInfo.bHasFixedSampleLocations )
{
LogFile.Logf( TEXT("\t\tfixed sample locations: %s") LINE_TERMINATOR, NewInfo.bHasFixedSampleLocations ? TEXT("TRUE") : TEXT("FALSE") );
}
if( NewInfo.InternalFormat != PreviousLevelInfo.InternalFormat )
{
LogFile.Logf( TEXT("\t\tInternal format: %s") LINE_TERMINATOR, GetGLInternalFormatString( NewInfo.InternalFormat ) );
}
if( NewInfo.RedBits != PreviousLevelInfo.RedBits )
{
if( NewInfo.RedBits )
{
LogFile.Logf( TEXT("\t\tR bits: %d, component type: %s") LINE_TERMINATOR, NewInfo.RedBits, GetComponentType( NewInfo.RedType ) );
}
else
{
LogFile.Log( TEXT("\t\tR bits gone!") );
}
}
if( NewInfo.GreenBits != PreviousLevelInfo.GreenBits )
{
if( NewInfo.GreenBits )
{
LogFile.Logf( TEXT("\t\tG bits: %d, component type: %s") LINE_TERMINATOR, NewInfo.GreenBits, GetComponentType( NewInfo.GreenType ) );
}
else
{
LogFile.Log( TEXT("\t\tG bits gone!") );
}
}
if( NewInfo.BlueBits != PreviousLevelInfo.BlueBits )
{
if( NewInfo.BlueBits )
{
LogFile.Logf( TEXT("\t\tB bits: %d, component type: %s") LINE_TERMINATOR, NewInfo.BlueBits, GetComponentType( NewInfo.BlueType ) );
}
else
{
LogFile.Log( TEXT("\t\tB bits gone!") );
}
}
if( NewInfo.AlphaBits != PreviousLevelInfo.AlphaBits )
{
if( NewInfo.AlphaBits )
{
LogFile.Logf( TEXT("\t\tA bits: %d, component type: %s") LINE_TERMINATOR, NewInfo.AlphaBits, GetComponentType( NewInfo.AlphaType ) );
}
else
{
LogFile.Log( TEXT("\t\tA bits gone!") );
}
}
if( NewInfo.DepthBits != PreviousLevelInfo.DepthBits )
{
if( NewInfo.DepthBits )
{
LogFile.Logf( TEXT("\t\tDepth bits: %d, component type: %s") LINE_TERMINATOR, NewInfo.DepthBits, GetComponentType( NewInfo.DepthType ) );
}
else
{
LogFile.Log( TEXT("\t\tDepth bits gone!") );
}
}
if( NewInfo.StencilBits != PreviousLevelInfo.StencilBits )
{
if( NewInfo.StencilBits )
{
LogFile.Logf( TEXT("\t\tStencil bits: %d") LINE_TERMINATOR, NewInfo.StencilBits );
}
else
{
LogFile.Log( TEXT("\t\tStencil bits gone!") );
}
}
if( NewInfo.bIsCompressed != PreviousLevelInfo.bIsCompressed )
{
if( NewInfo.bIsCompressed )
{
LogFile.Logf( TEXT("\t\tTexture compressed, size: %d") LINE_TERMINATOR, NewInfo.CompressedSize );
}
else
{
LogFile.Log( TEXT("\t\tTexture not compressed now!") LINE_TERMINATOR );
}
}
if( NewInfo.DataStoreBinding )
{
LogFile.Logf( TEXT("\t\tData store binding: %d") LINE_TERMINATOR, NewInfo.DataStoreBinding );
}
PreviousLevelInfo = NewInfo;
}
LogFile.Log( TEXT("\t") LINE_TERMINATOR );
}
void FOpenGLDebugFrameDumper::DumpBoundTextureSettings( FOutputDeviceFile& LogFile, GLenum UnitTarget )
{
GLfloat BorderColor[4];
glGetTexParameterfv( UnitTarget, GL_TEXTURE_BORDER_COLOR, BorderColor );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_BORDER_COLOR: ( %f, %f, %f, %f )") LINE_TERMINATOR, BorderColor[0], BorderColor[1], BorderColor[2], BorderColor[3] );
GLint TextureMinFilter;
glGetTexParameteriv( UnitTarget, GL_TEXTURE_MIN_FILTER, &TextureMinFilter );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_MIN_FILTER: %s") LINE_TERMINATOR, GetGLTextureFilterString( TextureMinFilter ) );
GLint TextureMagFilter;
glGetTexParameteriv( UnitTarget, GL_TEXTURE_MAG_FILTER, &TextureMagFilter );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_MAG_FILTER: %s") LINE_TERMINATOR, GetGLTextureFilterString( TextureMagFilter ) );
GLint TextureWrapS;
glGetTexParameteriv( UnitTarget, GL_TEXTURE_WRAP_S, &TextureWrapS );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_WRAP_S: %s") LINE_TERMINATOR, GetGLTextureWrapString( TextureWrapS ) );
GLint TextureWrapT;
glGetTexParameteriv( UnitTarget, GL_TEXTURE_WRAP_T, &TextureWrapT );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_WRAP_T: %s") LINE_TERMINATOR, GetGLTextureWrapString( TextureWrapT ) );
GLint TextureWrapR;
glGetTexParameteriv( UnitTarget, GL_TEXTURE_WRAP_R, &TextureWrapR );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_WRAP_R: %s") LINE_TERMINATOR, GetGLTextureWrapString( TextureWrapR ) );
GLfloat TextureMinLOD;
glGetTexParameterfv( UnitTarget, GL_TEXTURE_MIN_LOD, &TextureMinLOD );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_MIN_LOD: %f") LINE_TERMINATOR, TextureMinLOD );
GLfloat TextureMaxLOD;
glGetTexParameterfv( UnitTarget, GL_TEXTURE_MAX_LOD, &TextureMaxLOD );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_MAX_LOD: %f") LINE_TERMINATOR, TextureMaxLOD );
GLint TextureBaseLevel;
glGetTexParameteriv( UnitTarget, GL_TEXTURE_BASE_LEVEL, &TextureBaseLevel );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_BASE_LEVEL: %d") LINE_TERMINATOR, TextureBaseLevel );
GLint TextureMaxLevel;
glGetTexParameteriv( UnitTarget, GL_TEXTURE_MAX_LEVEL, &TextureMaxLevel );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_MAX_LEVEL: %d") LINE_TERMINATOR, TextureMaxLevel );
GLfloat TextureLODBias;
glGetTexParameterfv( UnitTarget, GL_TEXTURE_LOD_BIAS, &TextureLODBias );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_LOD_BIAS: %f") LINE_TERMINATOR, TextureLODBias );
GLint TextureCompareMode;
glGetTexParameteriv( UnitTarget, GL_TEXTURE_COMPARE_MODE, &TextureCompareMode );
ASSERT_NO_GL_ERROR();
if( TextureCompareMode != GL_NONE )
{
LogFile.Logf( TEXT("\tGL_TEXTURE_COMPARE_MODE: unknown value ( 0x%x )") LINE_TERMINATOR, TextureCompareMode );
GLint TextureCompareFunc;
glGetTexParameteriv( UnitTarget, GL_TEXTURE_COMPARE_FUNC, &TextureCompareFunc );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_TEXTURE_COMPARE_FUNC: %s") LINE_TERMINATOR, GetGLCompareString( TextureCompareFunc ) );
}
else
{
LogFile.Log( TEXT("\tGL_TEXTURE_COMPARE_MODE: GL_NONE") LINE_TERMINATOR );
}
LogFile.Log( TEXT("\t") LINE_TERMINATOR );
if( TextureBaseLevel > TextureMaxLevel )
{
LogFile.Logf( TEXT("\tBase texture level > max level, data makes no sense!") LINE_TERMINATOR TEXT("\t") LINE_TERMINATOR );
}
else if( UnitTarget == GL_TEXTURE_CUBE_MAP )
{
for( GLint TextureFace = GL_TEXTURE_CUBE_MAP_POSITIVE_X; TextureFace <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z; ++TextureFace )
{
LogFile.Logf( TEXT("\tTexture face: %s") LINE_TERMINATOR TEXT("\t") LINE_TERMINATOR, GetCubeMapFaceName( TextureFace ) );
DumpBoundTextureSurfaceSettings( LogFile, TextureFace, TextureBaseLevel, TextureMaxLevel );
}
}
else
{
DumpBoundTextureSurfaceSettings( LogFile, UnitTarget, TextureBaseLevel, TextureMaxLevel );
}
}
void FOpenGLDebugFrameDumper::DumpBoundSamplerSettings( FOutputDeviceFile& LogFile, GLint SamplerID )
{
GLfloat BorderColor[4];
glGetSamplerParameterfv( SamplerID, GL_TEXTURE_BORDER_COLOR, BorderColor );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_BORDER_COLOR: ( %f, %f, %f, %f )") LINE_TERMINATOR, BorderColor[0], BorderColor[1], BorderColor[2], BorderColor[3] );
GLint MinFilter;
glGetSamplerParameteriv( SamplerID, GL_TEXTURE_MIN_FILTER, &MinFilter );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_MIN_FILTER: %s") LINE_TERMINATOR, GetGLTextureFilterString( MinFilter ) );
GLint MagFilter;
glGetSamplerParameteriv( SamplerID, GL_TEXTURE_MAG_FILTER, &MagFilter );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_MAG_FILTER: %s") LINE_TERMINATOR, GetGLTextureFilterString( MagFilter ) );
GLint WrapS;
glGetSamplerParameteriv( SamplerID, GL_TEXTURE_WRAP_S, &WrapS );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_WRAP_S: %s") LINE_TERMINATOR, GetGLTextureWrapString( WrapS ) );
GLint WrapT;
glGetSamplerParameteriv( SamplerID, GL_TEXTURE_WRAP_T, &WrapT );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_WRAP_T: %s") LINE_TERMINATOR, GetGLTextureWrapString( WrapT ) );
GLint WrapR;
glGetSamplerParameteriv( SamplerID, GL_TEXTURE_WRAP_R, &WrapR );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_WRAP_R: %s") LINE_TERMINATOR, GetGLTextureWrapString( WrapR ) );
GLfloat MinLOD;
glGetSamplerParameterfv( SamplerID, GL_TEXTURE_MIN_LOD, &MinLOD );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_MIN_LOD: %f") LINE_TERMINATOR, MinLOD );
GLfloat MaxLOD;
glGetSamplerParameterfv( SamplerID, GL_TEXTURE_MAX_LOD, &MaxLOD );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_MAX_LOD: %f") LINE_TERMINATOR, MaxLOD );
GLfloat LODBias;
glGetSamplerParameterfv( SamplerID, GL_TEXTURE_LOD_BIAS, &LODBias );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\tGL_TEXTURE_LOD_BIAS: %f") LINE_TERMINATOR, LODBias );
GLint CompareMode;
glGetSamplerParameteriv( SamplerID, GL_TEXTURE_COMPARE_MODE, &CompareMode );
ASSERT_NO_GL_ERROR();
if( CompareMode != GL_NONE )
{
LogFile.Logf( TEXT("\tGL_TEXTURE_COMPARE_MODE: unknown value ( 0x%x )") LINE_TERMINATOR, CompareMode );
GLint CompareFunc;
glGetSamplerParameteriv( SamplerID, GL_TEXTURE_COMPARE_FUNC, &CompareFunc );
ASSERT_NO_GL_ERROR();
LogFile.Logf( TEXT("\t\tGL_TEXTURE_COMPARE_FUNC: %s") LINE_TERMINATOR, GetGLCompareString( CompareFunc ) );
}
else
{
LogFile.Log( TEXT("\tGL_TEXTURE_COMPARE_MODE: GL_NONE") LINE_TERMINATOR );
}
LogFile.Log( TEXT("\t") LINE_TERMINATOR );
}
void FOpenGLDebugFrameDumper::DumpTextureUnitSettings( FOutputDeviceFile& LogFile, GLint TextureUnitIndex )
{
glActiveTexture( GL_TEXTURE0+TextureUnitIndex );
ASSERT_NO_GL_ERROR();
bool bIsTextureBound = false;
GLint Binding;
glGetIntegerv( GL_TEXTURE_BINDING_1D, &Binding );
ASSERT_NO_GL_ERROR();
if( Binding != 0 )
{
LogFile.Logf( TEXT("Unit %2d : GL_TEXTURE_BINDING_1D: %d") LINE_TERMINATOR, TextureUnitIndex, Binding );
DumpBoundTextureSettings( LogFile, GL_TEXTURE_1D );
bIsTextureBound = true;
}
glGetIntegerv( GL_TEXTURE_BINDING_2D, &Binding );
ASSERT_NO_GL_ERROR();
if( Binding != 0 )
{
LogFile.Logf( TEXT("Unit %2d : GL_TEXTURE_BINDING_2D: %d") LINE_TERMINATOR, TextureUnitIndex, Binding );
DumpBoundTextureSettings( LogFile, GL_TEXTURE_2D );
bIsTextureBound = true;
}
glGetIntegerv( GL_TEXTURE_BINDING_3D, &Binding );
ASSERT_NO_GL_ERROR();
if( Binding != 0 )
{
LogFile.Logf( TEXT("Unit %2d : GL_TEXTURE_BINDING_3D: %d") LINE_TERMINATOR, TextureUnitIndex, Binding );
DumpBoundTextureSettings( LogFile, GL_TEXTURE_3D );
bIsTextureBound = true;
}
glGetIntegerv( GL_TEXTURE_BINDING_CUBE_MAP, &Binding );
ASSERT_NO_GL_ERROR();
if( Binding != 0 )
{
LogFile.Logf( TEXT("Unit %2d : GL_TEXTURE_BINDING_CUBE_MAP: %d") LINE_TERMINATOR, TextureUnitIndex, Binding );
DumpBoundTextureSettings( LogFile, GL_TEXTURE_CUBE_MAP );
bIsTextureBound = true;
}
glGetIntegerv( GL_TEXTURE_BINDING_2D_MULTISAMPLE, &Binding );
ASSERT_NO_GL_ERROR();
if( Binding != 0 )
{
LogFile.Logf( TEXT("Unit %2d : GL_TEXTURE_BINDING_2D_MULTISAMPLE: %d") LINE_TERMINATOR, TextureUnitIndex, Binding );
DumpBoundTextureSurfaceSettings( LogFile, GL_TEXTURE_2D_MULTISAMPLE, 0, 0 );
bIsTextureBound = true;
}
glGetIntegerv( GL_TEXTURE_BINDING_BUFFER, &Binding );
ASSERT_NO_GL_ERROR();
if( Binding != 0 )
{
GLint DataStoreBinding;
glGetTexLevelParameteriv( GL_TEXTURE_BUFFER, 0, GL_TEXTURE_BUFFER_DATA_STORE_BINDING, &DataStoreBinding );
LogFile.Logf( TEXT("Unit %2d : GL_TEXTURE_BINDING_BUFFER: %d (bound buffer: %d)") LINE_TERMINATOR, TextureUnitIndex, Binding, DataStoreBinding );
}
if( bIsTextureBound )
{
GLint SamplerBinding;
glGetIntegerv( GL_SAMPLER_BINDING, &SamplerBinding );
ASSERT_NO_GL_ERROR();
if( SamplerBinding )
{
LogFile.Logf( TEXT("Unit %2d : GL_SAMPLER_BINDING: %d") LINE_TERMINATOR, TextureUnitIndex, SamplerBinding );
DumpBoundSamplerSettings( LogFile, SamplerBinding );
}
}
}
void FOpenGLDebugFrameDumper::DumpGeneralOpenGLState( const TCHAR* DrawCommandDescription, bool bIsDrawEvent, bool bIsFramebufferBlitEvent )
{
FString LogFileName = *CachedEventFolder * TEXT("state.log");
FOutputDeviceFile LogFile( *LogFileName );
LogFile.SetAutoEmitLineTerminator( false );
LogFile.Log( LINE_TERMINATOR ); // to end "log start" line
// Extract and log current event info
LogFile.Logf( TEXT( "Event: %s" ) LINE_TERMINATOR, DrawCommandDescription );
// Extract and log current OpenGL error
GLenum OpenGLError = glGetError();
const TCHAR* OpenGLErrorString = 0;
switch( OpenGLError )
{
case GL_NO_ERROR: OpenGLErrorString = TEXT("GL_NO_ERROR" ); break;
case GL_INVALID_ENUM: OpenGLErrorString = TEXT("GL_INVALID_ENUM" ); break;
case GL_INVALID_VALUE: OpenGLErrorString = TEXT("GL_INVALID_VALUE" ); break;
case GL_INVALID_OPERATION: OpenGLErrorString = TEXT("GL_INVALID_OPERATION" ); break;
case GL_OUT_OF_MEMORY: OpenGLErrorString = TEXT("GL_OUT_OF_MEMORY" ); break;
// case GL_STACK_OVERFLOW: OpenGLErrorString = TEXT("GL_STACK_OVERFLOW" ); break;
// case GL_STACK_UNDERFLOW: OpenGLErrorString = TEXT("GL_STACK_UNDERFLOW" ); break;
// case GL_TABLE_TOO_LARGE: OpenGLErrorString = TEXT("GL_TABLE_TOO_LARGE" ); break;
default: OpenGLErrorString = TEXT("unknown" ); break;
}
LogFile.Logf( TEXT( "OpenGL Error: %s ( 0x%x )" ) LINE_TERMINATOR, OpenGLErrorString, OpenGLError );
DumpRenderTargetsState( LogFile );
DumpDepthState( LogFile );
DumpStencilState( LogFile );
DumpBufferMasks( LogFile );
DumpClearValues( LogFile );
DumpMultisamplingSettings( LogFile );
DumpScissorAndViewport( LogFile );
if( bIsFramebufferBlitEvent || bIsDrawEvent )
{
DumpBufferBindings( LogFile );
}
if( bIsDrawEvent )
{
DumpVertexAttribArraysState( LogFile );
DumpBlendingState( LogFile );
DumpHintSettings( LogFile );
DumpOpenGLLimits( LogFile );
DumpPointsSettings( LogFile );
DumpLinesSettings( LogFile );
DumpPolygonsSettings( LogFile );
DumpTextureLimitsAndBindings( LogFile );
DumpProgramSettings( LogFile );
DumpLogicOpsSettings( LogFile );
DumpPixelModeSettings( LogFile );
}
LogFile.TearDown();
}
void FOpenGLDebugFrameDumper::DumpFramebufferState( bool bReadFramebuffer )
{
FString LogFileName;
GLenum FramebufferBindingEnum;
if( bReadFramebuffer )
{
LogFileName = TEXT("framebufferRead.log");
FramebufferBindingEnum = GL_READ_FRAMEBUFFER_BINDING;
}
else
{
LogFileName = TEXT("framebufferDraw.log");
FramebufferBindingEnum = GL_DRAW_FRAMEBUFFER_BINDING;
}
FString LogFilePath = *CachedEventFolder * LogFileName;
FOutputDeviceFile LogFile( *LogFilePath );
LogFile.SetAutoEmitLineTerminator( false );
LogFile.Log( LINE_TERMINATOR ); // to end "log start" line
GLint CurrentlyBoundFramebuffer;
glGetIntegerv( FramebufferBindingEnum, &CurrentlyBoundFramebuffer );
ASSERT_NO_GL_ERROR();
DumpFramebufferSettings( LogFile, CurrentlyBoundFramebuffer );
LogFile.TearDown();
}
void FOpenGLDebugFrameDumper::DumpProgramAndShaderState( void )
{
// Dump current program and its shaders state
GLint ProgramID;
glGetIntegerv( GL_CURRENT_PROGRAM, &ProgramID );
ASSERT_NO_GL_ERROR();
FString ProgramLogFileName = *CachedEventFolder * *FString::Printf( TEXT("program%d.log"), ProgramID );
FOutputDeviceFile ProgramLogFile( *ProgramLogFileName );
ProgramLogFile.SetAutoEmitLineTerminator( false );
ProgramLogFile.Log( LINE_TERMINATOR ); // to end "log start" line
DumpProgramContents( ProgramLogFile, ProgramID );
ProgramLogFile.TearDown();
GLint AttachedShaderCount;
glGetProgramiv( ProgramID, GL_ATTACHED_SHADERS, &AttachedShaderCount );
ASSERT_NO_GL_ERROR();
if( !AttachedShaderCount )
{
// we're done here
return;
}
// Log attached shaders, into their own files
GLsizei CountReceived = 0;
GLuint* AttachedShadersTable = (GLuint*)FMemory::Malloc( sizeof(GLuint)*AttachedShaderCount );
glGetAttachedShaders( ProgramID, AttachedShaderCount, &CountReceived, AttachedShadersTable );
ASSERT_NO_GL_ERROR();
for( GLint AttachedShaderIndex = 0; AttachedShaderIndex < CountReceived; ++AttachedShaderIndex )
{
FString ShaderLogFileName = *CachedEventFolder * *FString::Printf( TEXT("shader%d.log"), AttachedShadersTable[AttachedShaderIndex] );
FOutputDeviceFile ShaderLogFile( *ShaderLogFileName );
ShaderLogFile.SetAutoEmitLineTerminator( false );
ShaderLogFile.Log( LINE_TERMINATOR ); // to end "log start" line
DumpShaderContents( ShaderLogFile, AttachedShadersTable[AttachedShaderIndex] );
ShaderLogFile.TearDown();
}
FMemory::Free( AttachedShadersTable );
}
void FOpenGLDebugFrameDumper::DumpBoundTextureState( void )
{
FString LogFileName = *CachedEventFolder * TEXT("textureUnits.log");
FOutputDeviceFile LogFile( *LogFileName );
LogFile.SetAutoEmitLineTerminator( false );
LogFile.Log( LINE_TERMINATOR ); // to end "log start" line
// Remember this, as DumpTextureUnitSettings() modifies it
GLint ActiveTextureUnit;
glGetIntegerv( GL_ACTIVE_TEXTURE, &ActiveTextureUnit );
ASSERT_NO_GL_ERROR();
GLint MaxTextureImageUnits;
glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS, &MaxTextureImageUnits );
ASSERT_NO_GL_ERROR();
for( GLint TextureUnitIndex = 0; TextureUnitIndex < MaxTextureImageUnits; ++TextureUnitIndex )
{
DumpTextureUnitSettings( LogFile, TextureUnitIndex );
}
// And restore previous OpenGL state
glActiveTexture( ActiveTextureUnit );
ASSERT_NO_GL_ERROR();
LogFile.TearDown();
}
void FOpenGLDebugFrameDumper::DumpFramebufferContent( GLint FramebufferID, GLint AttachmentSlot, const TCHAR* TargetFilename, EFramebufferAttachmentSlotType::Type SlotType, bool bShouldFlipImageVertically )
{
// Remember those, as we'll need to restore them later
GLint CurrentlyBoundReadFramebuffer;
glGetIntegerv( GL_READ_FRAMEBUFFER_BINDING, &CurrentlyBoundReadFramebuffer );
ASSERT_NO_GL_ERROR();
GLint CurrentReadBuffer;
glGetIntegerv( GL_READ_BUFFER, &CurrentReadBuffer );
ASSERT_NO_GL_ERROR();
if( FramebufferID != CurrentlyBoundReadFramebuffer )
{
glBindFramebuffer( GL_READ_FRAMEBUFFER, FramebufferID );
ASSERT_NO_GL_ERROR();
}
if( ( SlotType == EFramebufferAttachmentSlotType::Color ) && ( AttachmentSlot != CurrentReadBuffer ) )
{
FOpenGL::ReadBuffer( AttachmentSlot ); // this only needs to be selected for reading from one of color buffers
ASSERT_NO_GL_ERROR();
}
GLint AttachmentType = GL_FRAMEBUFFER_DEFAULT;
if( FramebufferID )
{
glGetFramebufferAttachmentParameteriv( GL_READ_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &AttachmentType );
ASSERT_NO_GL_ERROR();
}
GLint Width = 0;
GLint Height = 0;
GLint Depth = 0;
GLint InternalFormat = 0;
bool bIsAllOk = true;
if( AttachmentType == GL_FRAMEBUFFER_DEFAULT )
{
uint32 BackbufferWidth;
uint32 BackbufferHeight;
PlatformGetBackbufferDimensions( BackbufferWidth, BackbufferHeight );
Width = BackbufferWidth;
Height = BackbufferHeight;
check( Width > 0 && Height > 0 );
// no matter what the real format is, this is only to select target format later.
if( SlotType == EFramebufferAttachmentSlotType::Depth )
{
InternalFormat = GL_DEPTH_COMPONENT32F;
}
else if( SlotType == EFramebufferAttachmentSlotType::Stencil )
{
InternalFormat = GL_DEPTH24_STENCIL8;
}
else
{
InternalFormat = GL_RGBA8;
}
}
else if( AttachmentType == GL_TEXTURE )
{
// We need to get texture information now
// Get texture ID for binding
GLint TextureID = 0;
glGetFramebufferAttachmentParameteriv( GL_READ_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &TextureID );
ASSERT_NO_GL_ERROR();
// Determine the level we need to ask about
GLint TextureLevel;
glGetFramebufferAttachmentParameteriv( GL_READ_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &TextureLevel );
ASSERT_NO_GL_ERROR();
// Determine if the texture we have attached is 2D or Cube ( I assume 3D and 1D make no sense )
GLint TextureCubeMapFace;
glGetFramebufferAttachmentParameteriv( GL_READ_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE, &TextureCubeMapFace );
ASSERT_NO_GL_ERROR();
bool bIsCube = ( TextureCubeMapFace != 0 );
// Setup various types of function call parameters, depending on whether it is cube or not
GLenum TextureTypeAsk;
GLenum TextureTypeSet;
GLenum TextureTypeFace;
if( bIsCube )
{
TextureTypeAsk = GL_TEXTURE_BINDING_CUBE_MAP;
TextureTypeSet = GL_TEXTURE_CUBE_MAP;
TextureTypeFace = TextureCubeMapFace;
}
else
{
TextureTypeAsk = GL_TEXTURE_BINDING_2D;
TextureTypeSet = GL_TEXTURE_2D;
TextureTypeFace = GL_TEXTURE_2D;
}
// Remember what's bound to the stage we'll bind our texture to
GLint BoundTextureID;
glGetIntegerv( TextureTypeAsk, &BoundTextureID );
ASSERT_NO_GL_ERROR();
// Bind our texture. Was it GL_TEXTURE_2D?
ASSERT_NO_GL_ERROR();
GDisableOpenGLDebugOutput = true;
glBindTexture( TextureTypeSet, TextureID );
glFinish();
GDisableOpenGLDebugOutput = false;
if( glGetError() )
{
bIsAllOk = false;
// It could be GL_TEXTURE_2D_MULTISAMPLE then?
check(TextureTypeSet == GL_TEXTURE_2D);
check(TextureLevel == 0);
TextureTypeAsk = GL_TEXTURE_BINDING_2D_MULTISAMPLE;
TextureTypeSet = GL_TEXTURE_2D_MULTISAMPLE;
TextureTypeFace = GL_TEXTURE_2D_MULTISAMPLE;
glGetIntegerv( TextureTypeAsk, &BoundTextureID );
ASSERT_NO_GL_ERROR();
GDisableOpenGLDebugOutput = true;
glBindTexture( TextureTypeSet, TextureID );
glFinish();
GDisableOpenGLDebugOutput = false;
if( glGetError() )
{
// Ok, then GL_TEXTURE_3D?
TextureTypeAsk = GL_TEXTURE_BINDING_3D;
TextureTypeSet = GL_TEXTURE_3D;
TextureTypeFace = GL_TEXTURE_3D;
glGetIntegerv( TextureTypeAsk, &BoundTextureID );
ASSERT_NO_GL_ERROR();
glBindTexture( TextureTypeSet, TextureID );
ASSERT_NO_GL_ERROR();
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Texture %d is 3D - dumping data from such is unhandled atm. Add code?"), TextureID );
}
else
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Texture %d is multisampled - dumping data from such is unhandled atm. Add code?"), TextureID );
}
}
// Get the information we need
glGetTexLevelParameteriv( TextureTypeFace, TextureLevel, GL_TEXTURE_WIDTH, &Width );
ASSERT_NO_GL_ERROR();
glGetTexLevelParameteriv( TextureTypeFace, TextureLevel, GL_TEXTURE_HEIGHT, &Height );
ASSERT_NO_GL_ERROR();
glGetTexLevelParameteriv( TextureTypeFace, TextureLevel, GL_TEXTURE_DEPTH, &Depth );
ASSERT_NO_GL_ERROR();
glGetTexLevelParameteriv( TextureTypeFace, TextureLevel, GL_TEXTURE_INTERNAL_FORMAT, &InternalFormat );
ASSERT_NO_GL_ERROR();
// Restore previous binding to this stage
if( BoundTextureID != TextureID )
{
glBindTexture( TextureTypeSet, BoundTextureID );
ASSERT_NO_GL_ERROR();
}
}
else if( AttachmentType == GL_RENDERBUFFER )
{
// We need to get renderbuffer information now
// Get renderbuffer ID for binding
GLint RenderbufferID = 0;
glGetFramebufferAttachmentParameteriv( GL_READ_FRAMEBUFFER, AttachmentSlot, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &RenderbufferID );
ASSERT_NO_GL_ERROR();
// Remember what's bound as renderbuffer now, to restore it later
GLint CurrentlyBoundRenderbuffer;
glGetIntegerv( GL_RENDERBUFFER_BINDING, &CurrentlyBoundRenderbuffer );
ASSERT_NO_GL_ERROR();
// Bind our renderbuffer
if( RenderbufferID != CurrentlyBoundRenderbuffer )
{
glBindRenderbuffer( GL_RENDERBUFFER, RenderbufferID );
ASSERT_NO_GL_ERROR();
}
GLint Samples;
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &Samples );
ASSERT_NO_GL_ERROR();
if( Samples != 0 )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Renderbuffer %d is multisampled - dumping data from such is unhandled atm. Add code?"), RenderbufferID );
bIsAllOk = false;
}
else
{
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &Width );
ASSERT_NO_GL_ERROR();
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &Height );
ASSERT_NO_GL_ERROR();
glGetRenderbufferParameteriv( GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &InternalFormat );
ASSERT_NO_GL_ERROR();
}
// Restore previous state
if( RenderbufferID != CurrentlyBoundRenderbuffer )
{
glBindRenderbuffer( GL_RENDERBUFFER, CurrentlyBoundRenderbuffer );
ASSERT_NO_GL_ERROR();
}
}
else if( AttachmentType == GL_NONE )
{
bIsAllOk = false;
}
else
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Unrecognized framebuffer attachment type: %d! Debug this to add handling for it?."), AttachmentType );
bIsAllOk = false;
}
if( bIsAllOk )
{
FString FilenameString( TargetFilename );
int32 RGBADataSize = 4 * Width * Height;
uint8* RGBAData = (uint8*)FMemory::Malloc( RGBADataSize );
bool bIgnoreAlpha = false;
switch( InternalFormat )
{
// These formats can reasonably be expected never to contain values from outside 0-1 range,
// and can be taken into buffer directly. All missing R,G,B components will be filled with 0s, missing alpha with 1s.
case GL_RG8:
case GL_RG16:
case GL_R8:
case GL_R16:
case GL_RGB8:
case GL_RGB5:
case GL_R3_G3_B2:
case GL_RGB4:
bIgnoreAlpha = true;
// pass-through
case GL_RGBA8:
case GL_RGBA12:
case GL_RGBA16:
case GL_RGB10_A2:
case GL_RGBA4:
case GL_RGB5_A1:
case GL_SRGB8_ALPHA8:
if( SlotType != EFramebufferAttachmentSlotType::Color )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Trying to receive depth or stencil information from color attachment! Internal format: %s"), GetGLInternalFormatString( InternalFormat ) );
bIsAllOk = false;
}
else
{
glReadPixels( 0, 0, Width, Height, TextureOutputFormat, GL_UNSIGNED_INT_8_8_8_8_REV, RGBAData );
ASSERT_NO_GL_ERROR();
}
break;
case GL_RG16F:
case GL_RG32F:
case GL_R16F:
case GL_R32F:
case GL_R11F_G11F_B10F:
bIgnoreAlpha = true;
// pass-through
case GL_RGBA32F:
case GL_RGBA16F:
if( SlotType != EFramebufferAttachmentSlotType::Color )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Trying to receive depth or stencil information from color attachment! Internal format: %s"), GetGLInternalFormatString( InternalFormat ) );
bIsAllOk = false;
}
else
{
// These formats are stored internally as floats, and might conceivably contain values from outside 0-1 range.
// Let's take the data from them into float buffer, check up on it, and if we detect values from outside 0-1 range,
// report it and scale all components down by the same amount, so they fit in 0-1 range.
int32 FloatRGBADataSize = sizeof(float) * RGBADataSize;
float* FloatRGBAData = (float*)FMemory::Malloc( FloatRGBADataSize );
glReadPixels( 0, 0, Width, Height, TextureOutputFormat, GL_FLOAT, FloatRGBAData );
ASSERT_NO_GL_ERROR();
int32 PixelComponentCount = RGBADataSize;
// Determine minimal and maximal float value present in received data. Treat alpha separately.
float MinValue[2] = { FLT_MAX };
float MaxValue[2] = { FLT_MIN };
float* DataPtr = FloatRGBAData;
for( int32 PixelComponentIndex = 0; PixelComponentIndex < PixelComponentCount; ++PixelComponentIndex, ++DataPtr )
{
int32 AlphaValue = ( PixelComponentIndex % 4 == 3 ) ? 1 : 0;
if( *DataPtr < MinValue[AlphaValue] )
{
MinValue[AlphaValue] = *DataPtr;
}
if( *DataPtr > MaxValue[AlphaValue] )
{
MaxValue[AlphaValue] = *DataPtr;
}
}
// If necessary, rescale the data, announcing this fact.
if( ( MinValue[0] < 0.f ) || ( MaxValue[0] > 1.f ) )
{
DataPtr = FloatRGBAData;
float RescaleFactor = MaxValue[0] - MinValue[0];
for( int32 PixelComponentIndex = 0; PixelComponentIndex < PixelComponentCount; ++PixelComponentIndex, ++DataPtr )
{
if( PixelComponentIndex % 4 != 3 )
{
*DataPtr = ( *DataPtr - MinValue[0] ) / RescaleFactor;
}
}
// Add '_min%f_max%f' to end of file name, to let people know it's been rescaled.
FilenameString += FString::Printf( TEXT("_min%f_max%f"), MinValue[0], MaxValue[0] );
}
if( !bIgnoreAlpha && ( ( MinValue[1] < 0.f ) || ( MaxValue[1] > 1.f ) ) )
{
DataPtr = FloatRGBAData;
float RescaleFactor = MaxValue[1] - MinValue[1];
for( int32 PixelComponentIndex = 0; PixelComponentIndex < PixelComponentCount; ++PixelComponentIndex, ++DataPtr )
{
if( PixelComponentIndex % 4 == 3 )
{
*DataPtr = ( *DataPtr - MinValue[1] ) / RescaleFactor;
}
}
// Add '_amin%f_amax%f' to end of file name, to let people know it's been rescaled.
FilenameString += FString::Printf( TEXT("_amin%f_amax%f"), MinValue[1], MaxValue[1] );
}
// Convert the data into RGBA8 buffer
DataPtr = FloatRGBAData;
uint8* TargetPtr = RGBAData;
for( int32 PixelComponentIndex = 0; PixelComponentIndex < PixelComponentCount; ++PixelComponentIndex )
{
*TargetPtr++ = (uint8)( *DataPtr++ * 255.0f );
}
FMemory::Free( FloatRGBAData );
}
break;
case GL_DEPTH_COMPONENT16:
case GL_DEPTH_COMPONENT24:
case GL_DEPTH_COMPONENT32F:
if( SlotType != EFramebufferAttachmentSlotType::Depth )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Trying to receive color or stencil information from depth attachment! Internal format: %s"), GetGLInternalFormatString( InternalFormat ) );
bIsAllOk = false;
}
else
{
// However these formats are stored internally, they can be received as floats.
int32 DepthValueCount = RGBADataSize/4;
int32 FloatDepthDataSize = sizeof(float) * DepthValueCount;
float* FloatDepthData = (float*)FMemory::Malloc( FloatDepthDataSize );
glReadPixels( 0, 0, Width, Height, GL_DEPTH_COMPONENT, GL_FLOAT, FloatDepthData );
ASSERT_NO_GL_ERROR();
// 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;
}
// Add '_min%f_max%f' to end of file name, to let people know it's been rescaled.
FilenameString += FString::Printf( TEXT("_min%f_max%f"), MinValue, MaxValue );
}
// Convert the data into rgba8 buffer
DataPtr = FloatDepthData;
uint8* TargetPtr = RGBAData;
for( int32 DepthValueIndex = 0; DepthValueIndex < DepthValueCount; ++DepthValueIndex )
{
uint8 Value = (uint8)( *DataPtr++ * 255.0f );
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = 255;
}
FMemory::Free( FloatDepthData );
}
break;
case GL_DEPTH24_STENCIL8:
case GL_DEPTH32F_STENCIL8:
if( SlotType == EFramebufferAttachmentSlotType::Color )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Trying to receive color information from depth stencil attachment! Internal format: %s"), GetGLInternalFormatString( InternalFormat ) );
bIsAllOk = false;
}
else if( SlotType == EFramebufferAttachmentSlotType::Depth )
{
// However these formats are stored internally, they can be received as floats.
int32 DepthValueCount = RGBADataSize/4;
int32 FloatDepthDataSize = sizeof(float) * DepthValueCount;
float* FloatDepthData = (float*)FMemory::Malloc( FloatDepthDataSize );
glReadPixels( 0, 0, Width, Height, GL_DEPTH_COMPONENT, GL_FLOAT, FloatDepthData );
ASSERT_NO_GL_ERROR();
// 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;
}
// Add '_min%f_max%f' to end of file name, to let people know it's been rescaled.
FilenameString += FString::Printf( TEXT("_min%f_max%f"), MinValue, MaxValue );
}
// Convert the data into rgba8 buffer
DataPtr = FloatDepthData;
uint8* TargetPtr = RGBAData;
for( int32 DepthValueIndex = 0; DepthValueIndex < DepthValueCount; ++DepthValueIndex )
{
uint8 Value = (uint8)( *DataPtr++ * 255.0f );
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = 255;
}
FMemory::Free( FloatDepthData );
}
else if( SlotType == EFramebufferAttachmentSlotType::Stencil )
{
int32 StencilValueCount = RGBADataSize/4;
uint8* StencilData = (uint8*)FMemory::Malloc( StencilValueCount );
glReadPixels( 0, 0, Width, Height, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, StencilData ); // or GL_STENCIL_INDEX8 ?
ASSERT_NO_GL_ERROR();
// Convert the data into rgba8 buffer
uint8* DataPtr = StencilData;
uint8* TargetPtr = RGBAData;
for( int32 StencilValueIndex = 0; StencilValueIndex < StencilValueCount; ++StencilValueIndex )
{
uint8 Value = *DataPtr++;
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = 255;
}
FMemory::Free( StencilData );
}
break;
default:
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Unhandled internal texture format: %s (0x%x)!"), GetGLInternalFormatString( InternalFormat ), InternalFormat );
bIsAllOk = false;
break;
}
if( bIsAllOk )
{
if( bShouldFlipImageVertically )
{
// Flip image vertically, in-place
uint32 Pitch = 4 * Width;
uint8* LineBuffer = (uint8*)FMemory::Malloc( Pitch );
for( int32 ImageRowIndex = 0; ImageRowIndex < Height / 2; ++ImageRowIndex )
{
memcpy( LineBuffer, RGBAData + ImageRowIndex * Pitch, Pitch );
memcpy( RGBAData + ImageRowIndex * Pitch, RGBAData + ( Height - 1 - ImageRowIndex ) * Pitch, Pitch );
memcpy( RGBAData + ( Height - 1 - ImageRowIndex ) * Pitch, LineBuffer, Pitch );
}
FMemory::Free( LineBuffer );
}
#if USE_COMPRESSED_PNG_INSTEAD_OF_BMP_FOR_CONTENT_OUTPUT
FilenameString += TEXT(".png");
FString FilePath = *CachedEventFolder * FilenameString;
if( bIgnoreAlpha && ( SlotType == EFramebufferAttachmentSlotType::Color ) ) // Alpha is 255 in depth and stencil already
{
// Make image non-transparent
uint8* DataPtr = RGBAData+3; // first alpha offset
for( int32 PixelComponentIndex = 0; PixelComponentIndex < RGBADataSize/4; ++PixelComponentIndex )
{
*DataPtr = 255;
DataPtr += 4;
}
}
appCreatePNGWithAlpha( *FilePath, Width, Height, (FColor*)RGBAData );
#else
FilenameString += TEXT(".bmp");
FString FilePath = *CachedEventFolder * FilenameString;
if( bIgnoreAlpha )
{
FFileHelper::CreateBitmap( *FilePath, Width, Height, (FColor*)RGBAData );
}
else
{
appCreateBitmapWithAlpha( *FilePath, Width, Height, (FColor*)RGBAData );
}
#endif
}
FMemory::Free( RGBAData );
}
// Restore previous state
if( FramebufferID != CurrentlyBoundReadFramebuffer )
{
glBindFramebuffer( GL_READ_FRAMEBUFFER, CurrentlyBoundReadFramebuffer );
ASSERT_NO_GL_ERROR();
}
FOpenGL::ReadBuffer( CurrentReadBuffer );
ASSERT_NO_GL_ERROR();
}
void FOpenGLDebugFrameDumper::DumpFramebufferContents( bool bReadFramebuffer )
{
FString LogFileEnding;
GLenum FramebufferBindingEnum;
GLenum FramebufferTypeEnum;
if( bReadFramebuffer )
{
LogFileEnding = TEXT("Read");
FramebufferBindingEnum = GL_READ_FRAMEBUFFER_BINDING;
FramebufferTypeEnum = GL_READ_FRAMEBUFFER;
}
else
{
LogFileEnding = TEXT("Draw");
FramebufferBindingEnum = GL_DRAW_FRAMEBUFFER_BINDING;
FramebufferTypeEnum = GL_DRAW_FRAMEBUFFER;
}
GLint CurrentlyBoundFramebuffer;
glGetIntegerv( FramebufferBindingEnum, &CurrentlyBoundFramebuffer );
ASSERT_NO_GL_ERROR();
if( CurrentlyBoundFramebuffer == 0 )
{
// Screen buffer. Assumming it always has a front and back buffer.
DumpFramebufferContent( 0, GL_FRONT_LEFT, *FString::Printf( TEXT("fbScreenFront%s"), *LogFileEnding ), EFramebufferAttachmentSlotType::Color, true );
DumpFramebufferContent( 0, GL_BACK_LEFT, *FString::Printf( TEXT("fbScreenBack%s"), *LogFileEnding ), EFramebufferAttachmentSlotType::Color, true );
#if PLATFORM_MAC
GLint AttachmentType;
glGetFramebufferAttachmentParameteriv( FramebufferTypeEnum, GL_DEPTH, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &AttachmentType );
ASSERT_NO_GL_ERROR();
if( AttachmentType != GL_NONE )
{
DumpFramebufferContent( 0, GL_DEPTH, *FString::Printf( TEXT("fbScreenDepth%s"), *LogFileEnding ), EFramebufferAttachmentSlotType::Depth, true );
}
glGetFramebufferAttachmentParameteriv( FramebufferTypeEnum, GL_STENCIL, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &AttachmentType );
ASSERT_NO_GL_ERROR();
if( AttachmentType != GL_NONE )
{
DumpFramebufferContent( 0, GL_STENCIL, *FString::Printf( TEXT("fbScreenStencil%s"), *LogFileEnding ), EFramebufferAttachmentSlotType::Stencil, true );
}
#else
DumpFramebufferContent( 0, GL_DEPTH, *FString::Printf( TEXT("fbScreenDepth%s"), *LogFileEnding ), EFramebufferAttachmentSlotType::Depth, true );
// Commented out as Windows OpenGL provides no way to find out if screen buffer contains stencil, and causes OpenGL errors when it doesn't contain it.
// DumpFramebufferContent( 0, GL_STENCIL, *FString::Printf( TEXT("fbScreenStencil%s"), *LogFileEnding ), EFramebufferAttachmentSlotType::Stencil, true );
#endif
}
else
{
GLint MaxColorAttachments;
glGetIntegerv( GL_MAX_COLOR_ATTACHMENTS, &MaxColorAttachments );
ASSERT_NO_GL_ERROR();
GLint AttachmentType;
for( int32 AttachmentIndex = 0; AttachmentIndex < MaxColorAttachments; ++AttachmentIndex )
{
glGetFramebufferAttachmentParameteriv( FramebufferTypeEnum, GL_COLOR_ATTACHMENT0 + AttachmentIndex, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &AttachmentType );
ASSERT_NO_GL_ERROR();
if( AttachmentType != GL_NONE )
{
DumpFramebufferContent( CurrentlyBoundFramebuffer, GL_COLOR_ATTACHMENT0 + AttachmentIndex, *FString::Printf( TEXT("fb%d%s"), AttachmentIndex, *LogFileEnding ), EFramebufferAttachmentSlotType::Color, false );
}
}
glGetFramebufferAttachmentParameteriv( FramebufferTypeEnum, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &AttachmentType );
ASSERT_NO_GL_ERROR();
if( AttachmentType != GL_NONE )
{
DumpFramebufferContent( CurrentlyBoundFramebuffer, GL_DEPTH_ATTACHMENT, *FString::Printf( TEXT("fbDepth%s"), *LogFileEnding ), EFramebufferAttachmentSlotType::Depth, false );
}
glGetFramebufferAttachmentParameteriv( FramebufferTypeEnum, GL_STENCIL_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &AttachmentType );
ASSERT_NO_GL_ERROR();
if( AttachmentType != GL_NONE )
{
DumpFramebufferContent( CurrentlyBoundFramebuffer, GL_STENCIL_ATTACHMENT, *FString::Printf( TEXT("fbStencil%s"), *LogFileEnding ), EFramebufferAttachmentSlotType::Stencil, false );
}
}
}
void FOpenGLDebugFrameDumper::DumpTextureSurfaceContent( const TCHAR* TargetFilename, GLenum SurfaceType, GLint Level )
{
GLint Samples;
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_SAMPLES, &Samples );
ASSERT_NO_GL_ERROR();
if( Samples != 0 )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Texture surface we try to get data for is multisampled! Add code to handle this when you need it.") );
return;
}
GLint InternalFormat;
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_INTERNAL_FORMAT, &InternalFormat );
ASSERT_NO_GL_ERROR();
GLint Width;
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_WIDTH, &Width );
ASSERT_NO_GL_ERROR();
GLint Height;
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_HEIGHT, &Height );
ASSERT_NO_GL_ERROR();
GLint Compressed;
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_COMPRESSED, &Compressed );
ASSERT_NO_GL_ERROR();
if( Compressed != 0 )
{
// Save specific level and target (face) of a texture in a DDS file
GLint CompressedSize;
glGetTexLevelParameteriv( SurfaceType, Level, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &CompressedSize );
ASSERT_NO_GL_ERROR();
uint8* CompressedData = (uint8*)FMemory::Malloc( CompressedSize );
glGetCompressedTexImage( SurfaceType, Level, CompressedData );
ASSERT_NO_GL_ERROR();
FString FilenameString( TargetFilename );
FString FilePath = *CachedEventFolder * ( FilenameString + TEXT(".dds") );
appCreateDDSWithSingleSurface( *FilePath, Width, Height, InternalFormat, CompressedData, CompressedSize );
FMemory::Free( CompressedData );
}
else
{
FString FilenameString( TargetFilename );
// Save non-compressed format
int32 RGBADataSize = 4 * Width * Height;
uint8* RGBAData = (uint8*)FMemory::Malloc( RGBADataSize );
bool bIgnoreAlpha = false;
bool bTextureTypeIsColor = false;
switch( InternalFormat )
{
// These texture formats can reasonably be expected never to contain values from outside 0-1 range,
// and can be taken into buffer directly. All missing R,G,B components will be filled with 0s, missing alpha with 1s.
case GL_RG8:
case GL_RG16:
case GL_R8:
case GL_R16:
case GL_RGB8:
case GL_RGB5:
case GL_R3_G3_B2:
case GL_RGB4:
bIgnoreAlpha = true;
// pass-through
case GL_RGBA8:
case GL_RGBA12:
case GL_RGBA16:
case GL_RGB10_A2:
case GL_RGBA4:
case GL_RGB5_A1:
case GL_SRGB8_ALPHA8:
glGetTexImage( SurfaceType, Level, TextureOutputFormat, GL_UNSIGNED_INT_8_8_8_8_REV, RGBAData );
ASSERT_NO_GL_ERROR();
bTextureTypeIsColor = true;
break;
case GL_RG16F:
case GL_RG32F:
case GL_R16F:
case GL_R32F:
case GL_R11F_G11F_B10F:
bIgnoreAlpha = true;
// pass-through
case GL_RGBA32F:
case GL_RGBA16F:
// These texture formats are stored internally as floats, and might conceivably contain values from outside 0-1 range.
// Let's take the data from them into float buffer, check up on it, and if we detect values from outside 0-1 range,
// report it and scale all components down by the same amount, so they fit in 0-1 range.
bTextureTypeIsColor = true;
{
// Determine minimal and maximal float value present in received data. Treat alpha separately.
int32 FloatRGBADataSize = sizeof(float) * RGBADataSize;
float* FloatRGBAData = (float*)FMemory::Malloc( FloatRGBADataSize );
glGetTexImage( SurfaceType, Level, TextureOutputFormat, GL_FLOAT, FloatRGBAData );
ASSERT_NO_GL_ERROR();
int32 PixelComponentCount = RGBADataSize;
// Determine minimal and maximal float value present in received data. Treat alpha separately.
float MinValue[2] = { FLT_MAX };
float MaxValue[2] = { FLT_MIN };
float* DataPtr = FloatRGBAData;
for( int32 PixelComponentIndex = 0; PixelComponentIndex < PixelComponentCount; ++PixelComponentIndex, ++DataPtr )
{
int32 AlphaValue = ( PixelComponentIndex % 4 == 3 ) ? 1 : 0;
if( *DataPtr < MinValue[AlphaValue] )
{
MinValue[AlphaValue] = *DataPtr;
}
if( *DataPtr > MaxValue[AlphaValue] )
{
MaxValue[AlphaValue] = *DataPtr;
}
}
// If necessary, rescale the data.
if( ( MinValue[0] < 0.f ) || ( MaxValue[0] > 1.f ) )
{
DataPtr = FloatRGBAData;
float RescaleFactor = MaxValue[0] - MinValue[0];
for( int32 PixelComponentIndex = 0; PixelComponentIndex < PixelComponentCount; ++PixelComponentIndex, ++DataPtr )
{
if( PixelComponentIndex % 4 != 3 )
{
*DataPtr = ( *DataPtr - MinValue[0] ) / RescaleFactor;
}
}
// Add '_min%f_max%f' to end of file name, to let people know it's been rescaled.
FilenameString += FString::Printf( TEXT("_min%f_max%f"), MinValue[0], MaxValue[0] );
}
if( !bIgnoreAlpha && ( ( MinValue[1] < 0.f ) || ( MaxValue[1] > 1.f ) ) )
{
DataPtr = FloatRGBAData;
float RescaleFactor = MaxValue[1] - MinValue[1];
for( int32 PixelComponentIndex = 0; PixelComponentIndex < PixelComponentCount; ++PixelComponentIndex, ++DataPtr )
{
if( PixelComponentIndex % 4 == 3 )
{
*DataPtr = ( *DataPtr - MinValue[1] ) / RescaleFactor;
}
}
// Add '_amin%f_amax%f' to end of file name, to let people know it's been rescaled.
FilenameString += FString::Printf( TEXT("_amin%f_amax%f"), MinValue[1], MaxValue[1] );
}
// Convert the data into RGBA8 buffer
DataPtr = FloatRGBAData;
uint8* TargetPtr = RGBAData;
for( int32 PixelComponentIndex = 0; PixelComponentIndex < PixelComponentCount; ++PixelComponentIndex )
{
*TargetPtr++ = (uint8)( *DataPtr++ * 255.0f );
}
FMemory::Free( FloatRGBAData );
}
break;
case GL_DEPTH_COMPONENT32F:
case GL_DEPTH32F_STENCIL8:
case GL_DEPTH_COMPONENT16:
case GL_DEPTH_COMPONENT24:
case GL_DEPTH24_STENCIL8:
// However these formats are stored internally, they can be received as floats.
// Not trying to get a stencil component out of a texture - assumming it's always depth that's interesting.
{
int32 DepthValueCount = RGBADataSize/4;
int32 FloatDepthDataSize = sizeof(float) * DepthValueCount;
float* FloatDepthData = (float*)FMemory::Malloc( FloatDepthDataSize );
glGetTexImage( SurfaceType, Level, GL_DEPTH_COMPONENT, GL_FLOAT, FloatDepthData );
ASSERT_NO_GL_ERROR();
// 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;
}
// Add '_min%f_max%f' to end of file name, to let people know it's been rescaled.
FilenameString += FString::Printf( TEXT("_min%f_max%f"), MinValue, MaxValue );
}
// Convert the data into rgba8 buffer
DataPtr = FloatDepthData;
uint8* TargetPtr = RGBAData;
for( int32 DepthValueIndex = 0; DepthValueIndex < DepthValueCount; ++DepthValueIndex )
{
uint8 Value = (uint8)( *DataPtr++ * 255.0f );
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = Value;
*TargetPtr++ = 255;
}
FMemory::Free( FloatDepthData );
bIgnoreAlpha = true;
}
break;
default:
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Unhandled internal texture format: %s!"), GetGLInternalFormatString( InternalFormat ) );
return;
}
{
// Flip image vertically (texture image data is stored in different row order than on Windows)
uint32 Pitch = 4 * Width;
uint8* LineBuffer = (uint8*)FMemory::Malloc( Pitch );
for( int32 ImageRowIndex = 0; ImageRowIndex < Height / 2; ++ImageRowIndex )
{
memcpy( LineBuffer, RGBAData + ImageRowIndex * Pitch, Pitch );
memcpy( RGBAData + ImageRowIndex * Pitch, RGBAData + ( Height - 1 - ImageRowIndex ) * Pitch, Pitch );
memcpy( RGBAData + ( Height - 1 - ImageRowIndex ) * Pitch, LineBuffer, Pitch );
}
FMemory::Free( LineBuffer );
}
#if USE_COMPRESSED_PNG_INSTEAD_OF_BMP_FOR_CONTENT_OUTPUT
FilenameString += TEXT(".png");
FString FilePath = *CachedEventFolder * FilenameString;
if( bIgnoreAlpha && bTextureTypeIsColor )
{
// Make image non-transparent
uint8* DataPtr = RGBAData+3; // first alpha offset
for( int32 PixelComponentIndex = 0; PixelComponentIndex < RGBADataSize/4; ++PixelComponentIndex )
{
*DataPtr = 255;
DataPtr += 4;
}
}
appCreatePNGWithAlpha( *FilePath, Width, Height, (FColor*)RGBAData );
#else
FilenameString += TEXT(".bmp");
FString FilePath = *CachedEventFolder * FilenameString;
if( bIgnoreAlpha )
{
FFileHelper::CreateBitmap( *FilePath, Width, Height, (FColor*)RGBAData );
}
else
{
appCreateBitmapWithAlpha( *FilePath, Width, Height, (FColor*)RGBAData );
}
#endif
FMemory::Free( RGBAData );
}
}
void FOpenGLDebugFrameDumper::DumpTextureContentForImageUnit( int32 UnitIndex )
{
glActiveTexture( GL_TEXTURE0+UnitIndex );
ASSERT_NO_GL_ERROR();
GLint TextureID;
glGetIntegerv( GL_TEXTURE_BINDING_1D, &TextureID );
ASSERT_NO_GL_ERROR();
if( TextureID )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Unit %d, texture binding 1D = %d, texture dump unhandled!"), UnitIndex, TextureID );
}
glGetIntegerv( GL_TEXTURE_BINDING_2D, &TextureID );
ASSERT_NO_GL_ERROR();
if( TextureID )
{
GLint BaseLevel;
glGetTexParameteriv( GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, &BaseLevel );
ASSERT_NO_GL_ERROR();
GLint MaxLevel;
glGetTexParameteriv( GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, &MaxLevel );
ASSERT_NO_GL_ERROR();
for( GLint Level = BaseLevel; Level <= MaxLevel; ++Level )
{
DumpTextureSurfaceContent( *FString::Printf( TEXT("tex%d_2D_id%d_lvl%d"), UnitIndex, TextureID, Level ), GL_TEXTURE_2D, Level );
}
}
glGetIntegerv( GL_TEXTURE_BINDING_2D_MULTISAMPLE, &TextureID );
ASSERT_NO_GL_ERROR();
if( TextureID )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Unit %d, texture binding 2D multisample = %d, texture dump unhandled!"), UnitIndex, TextureID );
}
glGetIntegerv( GL_TEXTURE_BINDING_3D, &TextureID );
ASSERT_NO_GL_ERROR();
if( TextureID )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Unit %d, texture binding 3D = %d, texture dump unhandled!"), UnitIndex, TextureID );
}
glGetIntegerv( GL_TEXTURE_BINDING_BUFFER, &TextureID );
ASSERT_NO_GL_ERROR();
if( TextureID )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Unit %d, texture binding buffer = %d, texture dump unhandled!"), UnitIndex, TextureID );
}
glGetIntegerv( GL_TEXTURE_BINDING_CUBE_MAP, &TextureID );
ASSERT_NO_GL_ERROR();
if( TextureID )
{
GLint BaseLevel;
glGetTexParameteriv( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_BASE_LEVEL, &BaseLevel );
ASSERT_NO_GL_ERROR();
GLint MaxLevel;
glGetTexParameteriv( GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_LEVEL, &MaxLevel );
ASSERT_NO_GL_ERROR();
for( GLint Level = BaseLevel; Level <= MaxLevel; ++Level )
{
for( GLint Face = 0; Face < 6; ++Face )
{
DumpTextureSurfaceContent( *FString::Printf( TEXT("tex%d_2D_id%d_lvl%d_face%d"), UnitIndex, TextureID, Level, Face ), GL_TEXTURE_CUBE_MAP_POSITIVE_X+Face, Level );
}
}
}
}
void FOpenGLDebugFrameDumper::DumpBoundTexturesContents( void )
{
// Remember this value, as we need to restore it
GLint ActiveTextureUnit;
glGetIntegerv( GL_ACTIVE_TEXTURE, &ActiveTextureUnit );
ASSERT_NO_GL_ERROR();
GLint MaxTextureImageUnits;
glGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS, &MaxTextureImageUnits );
ASSERT_NO_GL_ERROR();
for( int32 TextureImageUnitIndex = 0; TextureImageUnitIndex < MaxTextureImageUnits; ++TextureImageUnitIndex )
{
DumpTextureContentForImageUnit( TextureImageUnitIndex );
}
// Now restore value
glActiveTexture( ActiveTextureUnit );
ASSERT_NO_GL_ERROR();
}
void FOpenGLDebugFrameDumper::DumpElementArrayBufferContents( GLenum ElementArrayType )
{
GLint ElementArrayBufferBinding;
glGetIntegerv( GL_ELEMENT_ARRAY_BUFFER_BINDING, &ElementArrayBufferBinding );
ASSERT_NO_GL_ERROR();
if( ElementArrayBufferBinding == 0 )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: No valid OpenGL buffer bound to element array buffer binding point!") );
return;
}
GLint BufferMapped;
glGetBufferParameteriv( GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_MAPPED, &BufferMapped );
ASSERT_NO_GL_ERROR();
if( BufferMapped )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Can't map element array buffer %d for reading contents, as it's currently mapped!"), ElementArrayBufferBinding );
return;
}
bool bIs32Bit = ( ElementArrayType == GL_UNSIGNED_INT );
GLint64 TotalBufferSize = 0LL; // getters apparently like to overwrite just some bytes of this on Mac, if they return small number, so we need to put 0 in both dwords initially
glGetBufferParameteri64v( GL_ELEMENT_ARRAY_BUFFER, GL_BUFFER_SIZE, &TotalBufferSize );
ASSERT_NO_GL_ERROR();
GLuint BufferSize = (GLuint)TotalBufferSize; // yes, I know it's conversion to shorter variable, I don't expect buffers > 4GB in size.
void* BufferPtr = glMapBufferRange( GL_ELEMENT_ARRAY_BUFFER, 0, BufferSize, GL_MAP_READ_BIT );
ASSERT_NO_GL_ERROR();
if( !BufferPtr )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Failed to map element array buffer %d!"), ElementArrayBufferBinding );
return;
}
FString LogFileName = *CachedEventFolder * TEXT("elementArrayBuffer.log");
FOutputDeviceFile LogFile( *LogFileName );
LogFile.SetAutoEmitLineTerminator( false );
LogFile.Log( LINE_TERMINATOR ); // to end "log start" line
uint32 ElementCount = BufferSize / ( bIs32Bit ? sizeof(uint32) : sizeof(uint16) );
LogFile.Logf( TEXT("Index buffer %d, size %u, element count %d, %s") LINE_TERMINATOR, ElementArrayBufferBinding, BufferSize, ElementCount, bIs32Bit ? TEXT("32-bit") : TEXT("16-bit") );
LogFile.Log( TEXT("=========================================================================") LINE_TERMINATOR );
FString Line = TEXT("");
int32 ValuesInLine = 0;
uint32* Ptr32 = (uint32*)BufferPtr;
uint16* Ptr16 = (uint16*)BufferPtr;
uint32 LowestValue = 0xFFFFFFFF;
uint32 HighestValue = 0L;
for( uint32 ElementIndex = 0; ElementIndex < ElementCount; ++ElementIndex )
{
Line += ( ValuesInLine ? TEXT(", ") : TEXT( "\t" ) );
uint32 Value = ( bIs32Bit ? *Ptr32++ : *Ptr16++ );
if( LowestValue > Value )
{
LowestValue = Value;
}
if( HighestValue < Value )
{
HighestValue = Value;
}
Line += FString::Printf( TEXT("%u"), Value );
++ValuesInLine;
if( ValuesInLine >= 20 )
{
// Log line
Line += TEXT( ",\n" );
LogFile.Log( *Line );
Line = TEXT("");
ValuesInLine = 0;
}
}
if( ValuesInLine )
{
// Move last line to file
Line += LINE_TERMINATOR;
LogFile.Log( *Line );
}
LogFile.Log( TEXT("=========================================================================") LINE_TERMINATOR );
LogFile.Logf( TEXT("Lowest value in buffer: %u, highest: %u") LINE_TERMINATOR, LowestValue, HighestValue );
LogFile.TearDown();
glUnmapBuffer( GL_ELEMENT_ARRAY_BUFFER );
ASSERT_NO_GL_ERROR();
}
inline uint32 HalfFloatToFloatInteger( uint16 HalfFloat )
{
register uint32 Sign = ( HalfFloat >> 15 ) & 0x00000001;
register uint32 Exponent = ( HalfFloat >> 10 ) & 0x0000001f;
register 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 FOpenGLDebugFrameDumper::DumpBoundVertexArrayBufferContents( GLint VertexBufferID, GLint StartVertex, GLint VertexCount, GLint InstanceCount )
{
GLint BufferMapped;
glGetBufferParameteriv( GL_ARRAY_BUFFER, GL_BUFFER_MAPPED, &BufferMapped );
ASSERT_NO_GL_ERROR();
if( BufferMapped )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Can't map vertex array buffer %d for reading contents, as it's currently mapped!"), VertexBufferID );
return;
}
GLint64 TotalBufferSize = 0LL; // getters apparently like to overwrite just some bytes of this on Mac, if they return small number, so we need to put 0 in both dwords initially
glGetBufferParameteri64v( GL_ARRAY_BUFFER, GL_BUFFER_SIZE, &TotalBufferSize );
ASSERT_NO_GL_ERROR();
GLuint BufferSize = (GLuint)TotalBufferSize; // yes, I know it's conversion to shorter variable, I don't expect buffers > 4GB in size.
void* BufferPtr = glMapBufferRange( GL_ARRAY_BUFFER, 0, BufferSize, GL_MAP_READ_BIT );
ASSERT_NO_GL_ERROR();
if( !BufferPtr )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Failed to map vertex array buffer %d!"), VertexBufferID );
return;
}
// Now gather information for this buffer from vertex attrib arrays
GLint MaxVertexAttribs = 0;
glGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &MaxVertexAttribs );
ASSERT_NO_GL_ERROR();
VertexAttribInfo* VertexAttribInfoTable = (VertexAttribInfo*)FMemory::Malloc( sizeof(VertexAttribInfo) * MaxVertexAttribs );
if( !VertexAttribInfoTable )
{
glUnmapBuffer( GL_ARRAY_BUFFER );
ASSERT_NO_GL_ERROR();
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Failed to allocate memory for vertex attrib info table!") );
return;
}
GLint CommonStrideForAllAttributes = -1;
GLint CommonDivisorForAllAttributes = -1;
bool bCanUseCommonStrideAndDivisor = true;
uint32 RelevantVertexAttribs = 0;
for( GLint VertexAttribIndex = 0; VertexAttribIndex < MaxVertexAttribs; ++VertexAttribIndex )
{
GLint VertexAttribArrayEnabled;
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &VertexAttribArrayEnabled );
ASSERT_NO_GL_ERROR();
if( !VertexAttribArrayEnabled )
{
continue;
}
GLint VertexAttribArrayBufferBinding;
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &VertexAttribArrayBufferBinding );
ASSERT_NO_GL_ERROR();
if( VertexAttribArrayBufferBinding != VertexBufferID )
{
continue;
}
bool bDifferentStrideOrDivisor = false;
VertexAttribInfoTable[RelevantVertexAttribs].Index = VertexAttribIndex;
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_SIZE, &VertexAttribInfoTable[RelevantVertexAttribs].SizeRead );
ASSERT_NO_GL_ERROR();
VertexAttribInfoTable[RelevantVertexAttribs].Size = ( VertexAttribInfoTable[RelevantVertexAttribs].SizeRead != GL_BGRA ) ? VertexAttribInfoTable[RelevantVertexAttribs].SizeRead : 4;
check( VertexAttribInfoTable[RelevantVertexAttribs].Size <= 4 );
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_STRIDE, (GLint*)&VertexAttribInfoTable[RelevantVertexAttribs].Stride );
ASSERT_NO_GL_ERROR();
if( CommonStrideForAllAttributes == -1 )
{
CommonStrideForAllAttributes = VertexAttribInfoTable[RelevantVertexAttribs].Stride;
}
else if( VertexAttribInfoTable[RelevantVertexAttribs].Stride != CommonStrideForAllAttributes )
{
bDifferentStrideOrDivisor = true;
}
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_TYPE, &VertexAttribInfoTable[RelevantVertexAttribs].Type );
ASSERT_NO_GL_ERROR();
GLint VertexAttribArrayNormalized;
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &VertexAttribArrayNormalized );
ASSERT_NO_GL_ERROR();
VertexAttribInfoTable[RelevantVertexAttribs].bNormalized = ( VertexAttribArrayNormalized != 0 );
GLvoid* VertexAttribArrayPointer;
glGetVertexAttribPointerv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_POINTER, &VertexAttribArrayPointer );
ASSERT_NO_GL_ERROR();
VertexAttribInfoTable[RelevantVertexAttribs].Offset = (GLuint)((GLuint64)VertexAttribArrayPointer & 0xFFFFFFFF);
GLint VertexAttribArrayInteger;
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_INTEGER, &VertexAttribArrayInteger );
ASSERT_NO_GL_ERROR();
VertexAttribInfoTable[RelevantVertexAttribs].bInteger = ( VertexAttribArrayInteger != 0 );
GLint VertexAttribArrayDivisor;
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_DIVISOR_ARB, &VertexAttribArrayDivisor );
ASSERT_NO_GL_ERROR();
if( ( VertexAttribArrayDivisor != 0 ) && ( VertexAttribArrayDivisor != 1 ) )
{
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Vertex array buffer %d has non-typical divisor: %d"), VertexBufferID, VertexAttribArrayDivisor );
}
VertexAttribInfoTable[RelevantVertexAttribs].bDivisor = ( VertexAttribArrayDivisor != 0 );
if( CommonDivisorForAllAttributes == -1 )
{
CommonDivisorForAllAttributes = VertexAttribInfoTable[RelevantVertexAttribs].bDivisor;
}
else if( VertexAttribInfoTable[RelevantVertexAttribs].bDivisor != ( CommonDivisorForAllAttributes != 0 ) )
{
bDifferentStrideOrDivisor = true;
}
if( bDifferentStrideOrDivisor && bCanUseCommonStrideAndDivisor )
{
bCanUseCommonStrideAndDivisor = false;
}
++RelevantVertexAttribs;
}
if( RelevantVertexAttribs == 0 )
{
glUnmapBuffer( GL_ARRAY_BUFFER );
ASSERT_NO_GL_ERROR();
FMemory::Free( VertexAttribInfoTable );
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Vertex array buffer %d isn't bound to any vertex attribs, despite it being chosen."), VertexBufferID );
return;
}
// Sort vertex attribs info by increasing offset into vertex
qsort( VertexAttribInfoTable, RelevantVertexAttribs, sizeof( VertexAttribInfo ), qsort_compare_VertexAttribInfo );
// Adjust offset within vertex, assumming that one of those is at zero offset within vertex
{
GLuint BaseOffset = VertexAttribInfoTable[0].Offset;
for( uint32 VertexAttribIndex = 0; VertexAttribIndex < RelevantVertexAttribs; ++VertexAttribIndex )
{
VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex = VertexAttribInfoTable[VertexAttribIndex].Offset - BaseOffset;
}
}
// Determine if the buffer is indexed, or instanced
if( InstanceCount && ( CommonDivisorForAllAttributes == 1 ) )
{
// Instanced buffer
StartVertex = 0;
VertexCount = InstanceCount;
}
FString LogFileName = *CachedEventFolder * FString::Printf( TEXT("vertexArrayBuffer%d.log"), VertexBufferID );
FOutputDeviceFile LogFile( *LogFileName );
LogFile.SetAutoEmitLineTerminator( false );
LogFile.Log( LINE_TERMINATOR ); // to end "log start" line
// Output header info ( bufferID, stride, size, vertexCount )
LogFile.Logf( TEXT("Vertex buffer %d, size %u, start vertex for the draw within buffer %u, vertex count for the draw %d:") LINE_TERMINATOR, VertexBufferID, BufferSize, StartVertex, VertexCount );
if( !bCanUseCommonStrideAndDivisor )
{
LogFile.Log( TEXT("(different attributes of the same buffer are placed with different stride or divisor, so it's impossible to determine unused parts of vertex)") LINE_TERMINATOR );
}
LogFile.Log( TEXT("============================ VERTEX BUFFER INFO SET UP IN VERTEX ATTRIBS =======================================") LINE_TERMINATOR );
// Go through vertex attrib info and output offset, size, normalized, integer, type for each attrib.
// If stride differs, or attrib is integer, say it won't be logged
GLuint OffsetCovered = 0;
for( uint32 VertexAttribIndex = 0; VertexAttribIndex < RelevantVertexAttribs; ++VertexAttribIndex )
{
VertexAttribInfoTable[VertexAttribIndex].bSkip = false;
if( bCanUseCommonStrideAndDivisor )
{
if( OffsetCovered < VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex )
{
LogFile.Logf( TEXT("\tOffset: %d - %d unidentified bytes") LINE_TERMINATOR, VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex, VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex - OffsetCovered );
}
else if( OffsetCovered > VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex )
{
LogFile.Logf( TEXT("\t\t%d BYTES ARE SHARED WITH THE FOLLOWING ATTRIBUTE!") LINE_TERMINATOR, OffsetCovered - VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex );
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Event %d, vertex array buffer %d, vertex attrib at offset %d using exact same data as another attribute!"), EventCounter, VertexBufferID, VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex );
}
}
LogFile.Logf( TEXT("\tOffset: %d (in buffer: %d ), Size: %d, type: %s, stride: %d, normalized: %s") LINE_TERMINATOR, VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex, VertexAttribInfoTable[VertexAttribIndex].Offset,
VertexAttribInfoTable[VertexAttribIndex].Size, NameOfType( VertexAttribInfoTable[VertexAttribIndex].Type ), VertexAttribInfoTable[VertexAttribIndex].Stride, VertexAttribInfoTable[VertexAttribIndex].bNormalized ? TEXT("Yes") : TEXT("No") );
if( VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex > VertexAttribInfoTable[VertexAttribIndex].Stride )
{
LogFile.Log( TEXT("\t\tTHIS ATTRIBUTE STARTS BEYOND THE END OF VERTEX DATA! IT WILL BE SKIPPED.") LINE_TERMINATOR );
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Event %d, ertex array buffer %d, vertex attrib at offset %d starts beyond end of vertex data!"), EventCounter, VertexBufferID, VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex );
VertexAttribInfoTable[VertexAttribIndex].bSkip = true;
}
int32 SizeOfMember = VertexAttribInfoTable[VertexAttribIndex].Size * SizeOfType( VertexAttribInfoTable[VertexAttribIndex].Type );
OffsetCovered = VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex + SizeOfMember;
if( OffsetCovered > VertexAttribInfoTable[VertexAttribIndex].Stride )
{
LogFile.Log( TEXT("\t\tTHIS ATTRIBUTE ENDS BEYOND THE END OF VERTEX DATA! IT WILL BE SKIPPED.") LINE_TERMINATOR );
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Event %d, vertex array buffer %d, vertex attrib at offset %d ends beyond end of vertex data!"), EventCounter, VertexBufferID, VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex );
VertexAttribInfoTable[VertexAttribIndex].bSkip = true;
}
if( VertexAttribInfoTable[VertexAttribIndex].Offset + StartVertex * VertexAttribInfoTable[VertexAttribIndex].Stride > BufferSize )
{
LogFile.Log( TEXT("\t\tVALUES FROM THIS ATTRIBUTE SUBMITTED FOR OPENGL TO DRAW START BEYOND BUFFER END! IT WILL BE SKIPPED.") LINE_TERMINATOR );
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Event %d, vertex array buffer %d, vertex attrib at offset %d - values from it submitted for OpenGL to draw start beyond buffer end!"), EventCounter, VertexBufferID, VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex );
VertexAttribInfoTable[VertexAttribIndex].bSkip = true;
}
else if( VertexAttribInfoTable[VertexAttribIndex].Offset + ( StartVertex + VertexCount - 1 ) * VertexAttribInfoTable[VertexAttribIndex].Stride + SizeOfMember > BufferSize )
{
LogFile.Log( TEXT("\t\tVALUES FROM THIS ATTRIBUTE SUBMITTED FOR OPENGL TO DRAW EXTEND BEYOND BUFFER END! IT WILL BE SKIPPED.") LINE_TERMINATOR );
UE_LOG( LogRHI, Warning, TEXT("DEBUG FRAME DUMPER: Event %d, vertex array buffer %d, vertex attrib at offset %d - values from it submitted for OpenGL to draw extend beyond buffer end!"), EventCounter, VertexBufferID, VertexAttribInfoTable[VertexAttribIndex].OffsetWithinVertex );
VertexAttribInfoTable[VertexAttribIndex].bSkip = true;
}
}
LogFile.Log( TEXT("================================= INTERPRETED VERTEX BUFFER CONTENTS ===========================================") LINE_TERMINATOR );
// For each vertex, prepare a line with contributions from each attrib, and output it
FString Line;
for( int32 VertexIndex = 0; VertexIndex < VertexCount; ++VertexIndex )
{
Line = TEXT("");
for( uint32 VertexAttribIndex = 0; VertexAttribIndex < RelevantVertexAttribs; ++VertexAttribIndex )
{
if( VertexAttribInfoTable[VertexAttribIndex].bSkip )
{
continue;
}
if( Line.Len() )
{
Line += TEXT(", ");
}
else
{
Line = FString::Printf( TEXT("%08d: "), VertexIndex );
}
uint32 Offset = VertexAttribInfoTable[VertexAttribIndex].Offset + ( StartVertex + VertexIndex ) * VertexAttribInfoTable[VertexAttribIndex].Stride;
int32 SizeOfMember = VertexAttribInfoTable[VertexAttribIndex].Size * SizeOfType( VertexAttribInfoTable[VertexAttribIndex].Type );
if( Offset + SizeOfMember > BufferSize )
{
Line += TEXT("(beyond end of buffer)");
}
else
{
const uint8* ValuePtr = (const uint8*)BufferPtr + Offset;
switch( VertexAttribInfoTable[VertexAttribIndex].Type )
{
case GL_FLOAT:
{
const float* FloatPtr = (const float*)ValuePtr;
if( VertexAttribInfoTable[VertexAttribIndex].Size == 1 )
{
Line += FString::Printf( TEXT("%f"), FloatPtr[0] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 2 )
{
Line += FString::Printf( TEXT("{ %f, %f }"), FloatPtr[0], FloatPtr[1] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 3 )
{
Line += FString::Printf( TEXT("{ %f, %f, %f }"), FloatPtr[0], FloatPtr[1], FloatPtr[2] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 4 )
{
Line += FString::Printf( TEXT("{ %f, %f, %f, %f }"), FloatPtr[0], FloatPtr[1], FloatPtr[2], FloatPtr[3] );
}
else
{
Line += TEXT("(unhandled float count)");
}
}
break;
case GL_UNSIGNED_BYTE:
{
const uint8* Uint8Ptr = (const uint8*)ValuePtr;
if( VertexAttribInfoTable[VertexAttribIndex].Size == 1 )
{
Line += FString::Printf( TEXT("%u"), Uint8Ptr[0] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 2 )
{
Line += FString::Printf( TEXT("{ %u, %u }"), Uint8Ptr[0], Uint8Ptr[1] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 3 )
{
Line += FString::Printf( TEXT("{ %u, %u, %u }"), Uint8Ptr[0], Uint8Ptr[1], Uint8Ptr[2] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 4 )
{
Line += FString::Printf( TEXT("{ %u, %u, %u, %u }"), Uint8Ptr[0], Uint8Ptr[1], Uint8Ptr[2], Uint8Ptr[3] );
}
else
{
Line += TEXT("(unhandled unsigned char count)");
}
}
break;
case GL_UNSIGNED_SHORT:
{
const uint16* UInt16Ptr = (const uint16*)ValuePtr;
if( VertexAttribInfoTable[VertexAttribIndex].Size == 1 )
{
Line += FString::Printf( TEXT("%u"), UInt16Ptr[0] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 2 )
{
Line += FString::Printf( TEXT("{ %u, %u }"), UInt16Ptr[0], UInt16Ptr[1] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 3 )
{
Line += FString::Printf( TEXT("{ %u, %u, %u }"), UInt16Ptr[0], UInt16Ptr[1], UInt16Ptr[2] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 4 )
{
Line += FString::Printf( TEXT("{ %u, %u, %u, %u }"), UInt16Ptr[0], UInt16Ptr[1], UInt16Ptr[2], UInt16Ptr[3] );
}
else
{
Line += TEXT("(unhandled unsigned short count)");
}
}
break;
case GL_SHORT:
{
const int16* Int16Ptr = (const int16*)ValuePtr;
if( VertexAttribInfoTable[VertexAttribIndex].Size == 1 )
{
Line += FString::Printf( TEXT("%d"), Int16Ptr[0] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 2 )
{
Line += FString::Printf( TEXT("{ %d, %d }"), Int16Ptr[0], Int16Ptr[1] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 3 )
{
Line += FString::Printf( TEXT("{ %d, %d, %d }"), Int16Ptr[0], Int16Ptr[1], Int16Ptr[2] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 4 )
{
Line += FString::Printf( TEXT("{ %d, %d, %d, %d }"), Int16Ptr[0], Int16Ptr[1], Int16Ptr[2], Int16Ptr[3] );
}
else
{
Line += TEXT("(unhandled short count)");
}
}
break;
case GL_HALF_FLOAT:
if( VertexAttribInfoTable[VertexAttribIndex].Size > 4 )
{
Line += TEXT("(unhandled float count)");
}
else
{
float Floats[4];
const uint16* UInt16Ptr = (const uint16*)ValuePtr;
for( int32 MemberIndex = 0; MemberIndex < VertexAttribInfoTable[VertexAttribIndex].Size; ++MemberIndex )
Floats[MemberIndex] = HalfFloatToFloat( UInt16Ptr[MemberIndex] );
if( VertexAttribInfoTable[VertexAttribIndex].Size == 1 )
{
Line += FString::Printf( TEXT("%f"), Floats[0] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 2 )
{
Line += FString::Printf( TEXT("{ %f, %f }"), Floats[0], Floats[1] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 3 )
{
Line += FString::Printf( TEXT("{ %f, %f, %f }"), Floats[0], Floats[1], Floats[2] );
}
else if( VertexAttribInfoTable[VertexAttribIndex].Size == 4 )
{
Line += FString::Printf( TEXT("{ %f, %f, %f, %f }"), Floats[0], Floats[1], Floats[2], Floats[3] );
}
else
{
Line += TEXT("(unhandled float count)");
}
}
break;
default:
Line += TEXT("(unhandled type)");
break;
}
}
}
if( Line.Len() )
{
Line += LINE_TERMINATOR;
LogFile.Log( *Line );
}
}
LogFile.Log( TEXT("================================================================================================================") LINE_TERMINATOR );
// Clean up
LogFile.TearDown();
glUnmapBuffer( GL_ARRAY_BUFFER );
ASSERT_NO_GL_ERROR();
FMemory::Free( VertexAttribInfoTable );
}
void FOpenGLDebugFrameDumper::DumpRelevantVertexArrayBufferContents( GLint StartVertex, GLint VertexCount, GLint InstanceCount )
{
GLint MaxVertexAttribs = 0;
glGetIntegerv( GL_MAX_VERTEX_ATTRIBS, &MaxVertexAttribs );
ASSERT_NO_GL_ERROR();
GLint IndicesToDump[64];
GLint AttribsToDump[64];
GLint DumpCount = 0;
for( GLint VertexAttribIndex = 0; VertexAttribIndex < MaxVertexAttribs; ++VertexAttribIndex )
{
GLint VertexAttribArrayEnabled;
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &VertexAttribArrayEnabled );
ASSERT_NO_GL_ERROR();
if( !VertexAttribArrayEnabled )
{
continue;
}
GLint VertexAttribArrayBufferBinding;
glGetVertexAttribiv( VertexAttribIndex, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &VertexAttribArrayBufferBinding );
ASSERT_NO_GL_ERROR();
bool bIsFound = false;
for( GLint AlreadyThereIndex = 0; AlreadyThereIndex < DumpCount; ++AlreadyThereIndex )
{
if( IndicesToDump[AlreadyThereIndex] == VertexAttribArrayBufferBinding )
{
bIsFound = true;
break;
}
}
if( bIsFound )
{
continue;
}
IndicesToDump[DumpCount] = VertexAttribArrayBufferBinding;
AttribsToDump[DumpCount] = VertexAttribIndex;
++DumpCount;
check( DumpCount < 64 );
}
if( DumpCount )
{
GLint PreviousVertexBuffer;
glGetIntegerv( GL_ARRAY_BUFFER_BINDING, &PreviousVertexBuffer );
ASSERT_NO_GL_ERROR();
GLint CurrentVertexBuffer = PreviousVertexBuffer;
for( GLint ArrayToDumpIndex = 0; ArrayToDumpIndex < DumpCount; ++ArrayToDumpIndex )
{
GLint VertexBufferID = IndicesToDump[ArrayToDumpIndex];
if( VertexBufferID != CurrentVertexBuffer )
{
glBindBuffer( GL_ARRAY_BUFFER, VertexBufferID );
ASSERT_NO_GL_ERROR();
CurrentVertexBuffer = VertexBufferID;
}
DumpBoundVertexArrayBufferContents( VertexBufferID, StartVertex, VertexCount, InstanceCount );
}
if( CurrentVertexBuffer != PreviousVertexBuffer )
{
glBindBuffer( GL_ARRAY_BUFFER, PreviousVertexBuffer );
ASSERT_NO_GL_ERROR();
}
}
}
void FOpenGLDebugFrameDumper::TriggerFrameDump( void )
{
if( bDumpingFrame )
return;
if( CachedRootFolder == NULL )
{
// Delete entire root frame dump folder with everything in it, if it exists.
CachedRootFolder = new FString( FPaths::ProfilingDir() * TEXT("OpenGLDebugFrameDump") );
IFileManager::Get().DeleteDirectory( **CachedRootFolder, false, true );
// Create new root frame dump folder
IFileManager::Get().MakeDirectory( **CachedRootFolder );
}
// Create new frame folder
if( CachedFrameFolder )
delete CachedFrameFolder;
CachedFrameFolder = new FString( *CachedRootFolder * FString::Printf( TEXT( "Frame_%08u" ), FrameCounter ) );
UE_LOG( LogRHI, Log, TEXT("DEBUG FRAME DUMPER: Frame %d dump started."), FrameCounter );
EventCounter = 0;
bDumpingFrame = true;
}
void FOpenGLDebugFrameDumper::SetNewEventFolder( const FString& EventString )
{
// Create new event folder
if( CachedEventFolder )
delete CachedEventFolder;
CachedEventFolder = new FString( *CachedFrameFolder * ( FString::Printf( TEXT( "Event_%08u-" ), EventCounter ) + EventString ) );
}
void FOpenGLDebugFrameDumper::SignalDrawEvent( const TCHAR* FolderPart, const TCHAR* DrawCommandDescription, GLint ElementArrayType, GLint StartVertex, GLint VertexCount, GLint InstanceCount )
{
if( !bDumpingFrame )
return;
// if( ![NSOpenGLContext currentContext] )
// {
// UE_LOG(LogRHI, Log, TEXT("DEBUG FRAME DUMPER: No valid OpenGL context set in calling thread!"));
// return;
// }
SetNewEventFolder( FolderPart );
DumpGeneralOpenGLState( DrawCommandDescription, true, false );
DumpFramebufferState( false );
DumpProgramAndShaderState();
DumpBoundTextureState();
DumpFramebufferContents( false );
DumpBoundTexturesContents();
if( ElementArrayType != 0 )
{
DumpElementArrayBufferContents( ElementArrayType );
}
DumpRelevantVertexArrayBufferContents( StartVertex, VertexCount, InstanceCount );
// TO DO - dump the rest of OpenGL state
++EventCounter;
}
void FOpenGLDebugFrameDumper::SignalClearEvent( int8 ClearType, int8 NumColors, const float* Colors, float Depth, uint32 Stencil )
{
if( !bDumpingFrame )
return;
// if( ![NSOpenGLContext currentContext] )
// {
// UE_LOG(LogRHI, Log, TEXT("DEBUG FRAME DUMPER: No valid OpenGL context set in calling thread!"));
// return;
// }
SetNewEventFolder( TEXT("glClearBuffer(s)") );
FString MaskString;
{
bool bHasText = false;
MaskString = TEXT("");
if( ClearType & 4 )
{
MaskString = FString::Printf( TEXT( "%d color buffers( " ), NumColors );
for( int32 ColorIndex = 0; ColorIndex < NumColors; ++ColorIndex )
{
if( ColorIndex )
{
MaskString += TEXT(", ");
}
MaskString += *FString::Printf( TEXT("(%f,%f,%f,%f)"), Colors[4*ColorIndex], Colors[4*ColorIndex+1], Colors[4*ColorIndex+2], Colors[4*ColorIndex+3] );
}
MaskString += TEXT(" )");
bHasText = true;
}
if( ClearType & 1 )
{
if( bHasText )
{
MaskString += TEXT(", ");
}
MaskString += *FString::Printf( TEXT( "depth(%f)" ), Depth );
bHasText = true;
}
if( ClearType & 2 )
{
if( bHasText )
{
MaskString += TEXT(", ");
}
MaskString += *FString::Printf( TEXT( "stencil(0x%x)" ), Stencil );
}
}
FString DrawCommandDescription = FString( TEXT("glClearBuffer*( ") ) + MaskString + TEXT(" )");
DumpGeneralOpenGLState( *DrawCommandDescription, false, false );
DumpFramebufferState( false );
DumpFramebufferContents( false );
++EventCounter;
}
void FOpenGLDebugFrameDumper::SignalFramebufferBlitEvent( GLbitfield Mask )
{
if( !bDumpingFrame )
return;
// if( ![NSOpenGLContext currentContext] )
// {
// UE_LOG(LogRHI, Log, TEXT("DEBUG FRAME DUMPER: No valid OpenGL context set in calling thread!"));
// return;
// }
SetNewEventFolder( TEXT("glFramebufferBlit") );
FString MaskString;
{
MaskString = ( Mask & GL_COLOR_BUFFER_BIT ) ? TEXT( "GL_COLOR_BUFFER_BIT" ) : TEXT("");
bool bHasText = ( ( Mask & GL_COLOR_BUFFER_BIT ) != 0 );
if( Mask & GL_DEPTH_BUFFER_BIT )
{
if( bHasText )
MaskString += TEXT("|");
MaskString += TEXT( "GL_DEPTH_BUFFER_BIT" );
bHasText = true;
}
if( Mask & GL_STENCIL_BUFFER_BIT )
{
if( bHasText )
MaskString += TEXT("|");
MaskString += TEXT( "GL_STENCIL_BUFFER_BIT" );
}
}
FString DrawCommandDescription = FString( TEXT("glFramebufferBlit(") ) + MaskString + TEXT(")");
DumpGeneralOpenGLState( *DrawCommandDescription, false, true );
DumpFramebufferState( false );
DumpFramebufferState( true );
DumpFramebufferContents( false );
DumpFramebufferContents( true );
++EventCounter;
}
void FOpenGLDebugFrameDumper::SignalEndFrameEvent( void )
{
if( !bDumpingFrame )
return;
// if( ![NSOpenGLContext currentContext] )
// {
// UE_LOG(LogRHI, Log, TEXT("DEBUG FRAME DUMPER: No valid OpenGL context set in calling thread!"));
// return;
// }
SetNewEventFolder( TEXT("BufferFlush") );
DumpGeneralOpenGLState( TEXT("(BufferFlush)"), false, false );
DumpFramebufferContents( false );
// TO DO - dump the rest of OpenGL state
UE_LOG( LogRHI, Log, TEXT("DEBUG FRAME DUMPER: Frame %d dump ended."), FrameCounter );
bDumpingFrame = false;
EventCounter = 0;
++FrameCounter;
}
/*=============================================================================
Implementation of C methods that serve as the only connection all external code may depend on.
=============================================================================*/
static const TCHAR* GetPrimitiveTypeString( GLint type )
{
switch( type )
{
case GL_TRIANGLES: return TEXT("GL_TRIANGLES");
case GL_POINTS: return TEXT("GL_POINTS");
case GL_LINES: return TEXT("GL_LINES");
case GL_LINE_STRIP: return TEXT("GL_LINE_STRIP");
case GL_TRIANGLE_STRIP: return TEXT("GL_TRIANGLE_STRIP");
case GL_TRIANGLE_FAN: return TEXT("GL_TRIANGLE_FAN");
default: return TEXT("!!!unknown!!!");
}
}
extern "C" {
void SignalOpenGLDrawArraysEvent( GLenum Mode, GLint First, GLsizei Count )
{
FOpenGLDebugFrameDumper::Instance()->SignalDrawEvent(
TEXT("glDrawArrays"),
*FString::Printf( TEXT( "glDrawArrays( Mode = %s, First = %d, Count = %u )" ),
GetPrimitiveTypeString( Mode ),
First,
Count
),
0, // no index buffer
First,
Count,
0
);
}
void SignalOpenGLDrawArraysInstancedEvent( GLenum Mode, GLint First, GLsizei Count, GLsizei PrimCount )
{
FOpenGLDebugFrameDumper::Instance()->SignalDrawEvent(
TEXT("glDrawArraysInstanced"),
*FString::Printf(
TEXT( "glDrawArraysInstanced( Mode = %s, First = %d, Count = %u, PrimCount = %u )" ),
GetPrimitiveTypeString( Mode ),
First,
Count,
PrimCount
),
0, // no index buffer
First,
Count,
PrimCount
);
}
void SignalOpenGLDrawRangeElementsEvent( GLenum Mode, GLuint Start, GLuint End, GLsizei Count, GLenum Type, const GLvoid* Indices )
{
FOpenGLDebugFrameDumper::Instance()->SignalDrawEvent(
TEXT("glDrawRangeElements"),
*FString::Printf(
TEXT( "glDrawRangeElements( Mode = %s, Start = %d, End = %d, Count = %u, Type = %s, Indices = %p )" ),
GetPrimitiveTypeString( Mode ),
Start,
End,
Count,
( ( Type == GL_UNSIGNED_INT ) ? TEXT("GL_UNSIGNED_INT") : TEXT("GL_UNSIGNED_SHORT") ),
Indices
),
(GLint)Type,
Start,
End - Start,
0
);
}
void SignalOpenGLDrawRangeElementsInstancedEvent( GLenum Mode, GLsizei Count, GLenum Type, const GLvoid* Indices, GLsizei PrimCount )
{
FOpenGLDebugFrameDumper::Instance()->SignalDrawEvent(
TEXT("glDrawElementsInstanced"),
*FString::Printf(
TEXT( "glDrawElementsInstanced( Mode = %s, Count = %u, Type = %s, Indices = %p, PrimCount = %u )" ),
GetPrimitiveTypeString( Mode ),
Count,
( ( Type == GL_UNSIGNED_INT ) ? TEXT("GL_UNSIGNED_INT") : TEXT("GL_UNSIGNED_SHORT") ),
Indices,
PrimCount
),
(GLint)Type,
0,
Count,
PrimCount
);
}
void SignalOpenGLClearEvent( int8 ClearType, int8 NumColors, const float* Colors, float Depth, uint32 Stencil )
{
FOpenGLDebugFrameDumper::Instance()->SignalClearEvent( ClearType, NumColors, Colors, Depth, Stencil );
}
void SignalOpenGLFramebufferBlitEvent( GLbitfield Mask )
{
FOpenGLDebugFrameDumper::Instance()->SignalFramebufferBlitEvent( Mask );
}
void SignalOpenGLEndFrameEvent( void )
{
FOpenGLDebugFrameDumper::Instance()->SignalEndFrameEvent();
}
void TriggerOpenGLFrameDump( void )
{
FOpenGLDebugFrameDumper::Instance()->TriggerFrameDump();
}
void TriggerOpenGLFrameDumpEveryXCalls( int32 X )
{
static int32 Counter = 0;
if( Counter >= X )
{
FOpenGLDebugFrameDumper::Instance()->TriggerFrameDump();
Counter = 0;
}
++Counter;
}
} // extern "C"
#endif // ENABLE_OPENGL_FRAMEDUMP