Files
libreoffice/vcl/source/opengl/OpenGLHelper.cxx
Markus Mohrhard a55f740045 include the unx part for getting OpenGL driver & device information
desktop/unx/source/glxtest.cxx is taken directly from the Mozilla
project.

THe whole concept is taken from Mozilla and is based on starting an
early process that creates an OpenGL context. This prevents crashing
drivers to crash Libreoffice.

We read the information from the pipe as soon as we create the first vcl
Window. In that place we then decide if the device/driver combination is
blacklisted.

Change-Id: I2624d4ce06d503281a4459cf3174f57cf1f7b733
2014-11-20 13:50:51 +01:00

493 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <vcl/opengl/GLMHelper.hxx>
#include <vcl/opengl/OpenGLHelper.hxx>
#include <osl/file.hxx>
#include <rtl/bootstrap.hxx>
#include <config_folders.h>
#include <vcl/salbtype.hxx>
#include <vcl/bmpacc.hxx>
#include <boost/scoped_array.hpp>
#include <vcl/pngwrite.hxx>
#include <vcl/graph.hxx>
#include <officecfg/Office/Common.hxx>
#include <vector>
#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID
#include "opengl/x11/X11DeviceInfo.hxx"
#endif
namespace {
OUString getShaderFolder()
{
OUString aUrl("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER);
rtl::Bootstrap::expandMacros(aUrl);
return aUrl + "/opengl/";
}
OString loadShader(const OUString& rFilename)
{
OUString aFileURL = getShaderFolder() + rFilename +".glsl";
osl::File aFile(aFileURL);
if(aFile.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None)
{
sal_uInt64 nSize = 0;
aFile.getSize(nSize);
boost::scoped_array<char> content(new char[nSize+1]);
sal_uInt64 nBytesRead = 0;
aFile.read(content.get(), nSize, nBytesRead);
if(nSize != nBytesRead)
assert(false);
content[nSize] = 0;
return OString(content.get());
}
else
{
SAL_WARN("vcl.opengl", "could not load the file: " << aFileURL);
}
return OString();
}
}
namespace {
int LogCompilerError(GLuint nId, const rtl::OUString &rDetail,
const rtl::OUString &rName, bool bShaderNotProgram)
{
int InfoLogLength = 0;
CHECK_GL_ERROR();
if (bShaderNotProgram)
glGetShaderiv (nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
else
glGetProgramiv(nId, GL_INFO_LOG_LENGTH, &InfoLogLength);
CHECK_GL_ERROR();
if ( InfoLogLength > 0 )
{
std::vector<char> ErrorMessage(InfoLogLength+1);
if (bShaderNotProgram)
glGetShaderInfoLog (nId, InfoLogLength, NULL, &ErrorMessage[0]);
else
glGetProgramInfoLog(nId, InfoLogLength, NULL, &ErrorMessage[0]);
CHECK_GL_ERROR();
ErrorMessage.push_back('\0');
SAL_WARN("vcl.opengl", rDetail << " shader " << nId << " compile for " << rName << " failed : " << &ErrorMessage[0]);
}
else
SAL_WARN("vcl.opengl", rDetail << " shader: " << rName << " compile " << nId << "failed without error log");
return 0;
}
}
GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,const OUString& rFragmentShaderName)
{
// Create the shaders
GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
GLint Result = GL_FALSE;
// Compile Vertex Shader
OString aVertexShaderSource = loadShader(rVertexShaderName);
char const * VertexSourcePointer = aVertexShaderSource.getStr();
glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
glCompileShader(VertexShaderID);
// Check Vertex Shader
glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
if (!Result)
return LogCompilerError(VertexShaderID, "vertex",
rVertexShaderName, true);
// Compile Fragment Shader
OString aFragmentShaderSource = loadShader(rFragmentShaderName);
char const * FragmentSourcePointer = aFragmentShaderSource.getStr();
glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
glCompileShader(FragmentShaderID);
// Check Fragment Shader
glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
if (!Result)
return LogCompilerError(FragmentShaderID, "fragment",
rFragmentShaderName, true);
// Link the program
GLint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VertexShaderID);
glAttachShader(ProgramID, FragmentShaderID);
glLinkProgram(ProgramID);
glDeleteShader(VertexShaderID);
glDeleteShader(FragmentShaderID);
// Check the program
glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
if (!Result)
return LogCompilerError(ProgramID, "program", "<both>", false);
CHECK_GL_ERROR();
return ProgramID;
}
void OpenGLHelper::ConvertBitmapExToRGBATextureBuffer(const BitmapEx& rBitmapEx, sal_uInt8* o_pRGBABuffer, const bool bFlip)
{
long nBmpWidth = rBitmapEx.GetSizePixel().Width();
long nBmpHeight = rBitmapEx.GetSizePixel().Height();
Bitmap aBitmap (rBitmapEx.GetBitmap());
AlphaMask aAlpha (rBitmapEx.GetAlpha());
Bitmap::ScopedReadAccess pReadAccces( aBitmap );
AlphaMask::ScopedReadAccess pAlphaReadAccess( aAlpha );
size_t i = 0;
for (long ny = (bFlip ? nBmpHeight - 1 : 0); (bFlip ? ny >= 0 : ny < nBmpHeight); (bFlip ? ny-- : ny++))
{
Scanline pAScan = pAlphaReadAccess ? pAlphaReadAccess->GetScanline(ny) : 0;
for(long nx = 0; nx < nBmpWidth; nx++)
{
BitmapColor aCol = pReadAccces->GetColor( ny, nx );
o_pRGBABuffer[i++] = aCol.GetRed();
o_pRGBABuffer[i++] = aCol.GetGreen();
o_pRGBABuffer[i++] = aCol.GetBlue();
o_pRGBABuffer[i++] = pAScan ? 255 - *pAScan++ : 255;
}
}
}
void OpenGLHelper::renderToFile(long nWidth, long nHeight, const OUString& rFileName)
{
boost::scoped_array<sal_uInt8> pBuffer(new sal_uInt8[nWidth*nHeight*4]);
glReadPixels(0, 0, nWidth, nHeight, GL_BGRA, GL_UNSIGNED_BYTE, pBuffer.get());
BitmapEx aBitmap = ConvertBGRABufferToBitmapEx(pBuffer.get(), nWidth, nHeight);
try {
vcl::PNGWriter aWriter( aBitmap );
SvFileStream sOutput( rFileName, STREAM_WRITE );
aWriter.Write( sOutput );
sOutput.Close();
} catch (...) {
SAL_WARN("vcl.opengl", "Error writing png to " << rFileName);
}
CHECK_GL_ERROR();
}
BitmapEx OpenGLHelper::ConvertBGRABufferToBitmapEx(const sal_uInt8* const pBuffer, long nWidth, long nHeight)
{
assert(pBuffer);
Bitmap aBitmap( Size(nWidth, nHeight), 24 );
AlphaMask aAlpha( Size(nWidth, nHeight) );
{
Bitmap::ScopedWriteAccess pWriteAccess( aBitmap );
AlphaMask::ScopedWriteAccess pAlphaWriteAccess( aAlpha );
size_t nCurPos = 0;
for( int y = 0; y < nHeight; ++y)
{
Scanline pScan = pWriteAccess->GetScanline(y);
Scanline pAlphaScan = pAlphaWriteAccess->GetScanline(y);
for( int x = 0; x < nWidth; ++x )
{
*pScan++ = pBuffer[nCurPos];
*pScan++ = pBuffer[nCurPos+1];
*pScan++ = pBuffer[nCurPos+2];
nCurPos += 3;
*pAlphaScan++ = static_cast<sal_uInt8>( 255 - pBuffer[nCurPos++] );
}
}
}
return BitmapEx(aBitmap, aAlpha);
}
const char* OpenGLHelper::GLErrorString(GLenum errorCode)
{
static const struct {
GLenum code;
const char *string;
} errors[]=
{
/* GL */
{GL_NO_ERROR, "no error"},
{GL_INVALID_ENUM, "invalid enumerant"},
{GL_INVALID_VALUE, "invalid value"},
{GL_INVALID_OPERATION, "invalid operation"},
{GL_STACK_OVERFLOW, "stack overflow"},
{GL_STACK_UNDERFLOW, "stack underflow"},
{GL_OUT_OF_MEMORY, "out of memory"},
{GL_INVALID_FRAMEBUFFER_OPERATION, "invalid framebuffer operation"},
{0, NULL }
};
int i;
for (i=0; errors[i].string; i++)
{
if (errors[i].code == errorCode)
{
return errors[i].string;
}
}
return NULL;
}
std::ostream& operator<<(std::ostream& rStrm, const glm::vec4& rPos)
{
rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ", " << rPos[3] << ")";
return rStrm;
}
std::ostream& operator<<(std::ostream& rStrm, const glm::vec3& rPos)
{
rStrm << "( " << rPos[0] << ", " << rPos[1] << ", " << rPos[2] << ")";
return rStrm;
}
std::ostream& operator<<(std::ostream& rStrm, const glm::mat4& rMatrix)
{
for(int i = 0; i < 4; ++i)
{
rStrm << "\n( ";
for(int j = 0; j < 4; ++j)
{
rStrm << rMatrix[j][i];
rStrm << " ";
}
rStrm << ")\n";
}
return rStrm;
}
void OpenGLHelper::createFramebuffer(long nWidth, long nHeight, GLuint& nFramebufferId,
GLuint& nRenderbufferDepthId, GLuint& nRenderbufferColorId, bool bRenderbuffer)
{
// create a renderbuffer for depth attachment
glGenRenderbuffers(1, &nRenderbufferDepthId);
glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, nWidth, nHeight);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
if(bRenderbuffer)
{
// create a renderbuffer for color attachment
glGenRenderbuffers(1, &nRenderbufferColorId);
glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferColorId);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, nWidth, nHeight);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
}
else
{
glGenTextures(1, &nRenderbufferColorId);
glBindTexture(GL_TEXTURE_2D, nRenderbufferColorId);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight, 0,
GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, nRenderbufferColorId, 0);
}
// create a framebuffer object and attach renderbuffer
glGenFramebuffers(1, &nFramebufferId);
glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, nFramebufferId);
// attach a renderbuffer to FBO color attachement point
glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferColorId);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, nRenderbufferColorId);
glCheckFramebufferStatus(GL_FRAMEBUFFER);
// attach a renderbuffer to depth attachment point
glBindRenderbuffer(GL_RENDERBUFFER, nRenderbufferDepthId);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, nRenderbufferDepthId);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
SAL_WARN("vcl.opengl", "invalid framebuffer status");
}
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
CHECK_GL_ERROR();
}
float OpenGLHelper::getGLVersion()
{
float fVersion = 1.0;
const GLubyte* aVersion = glGetString( GL_VERSION );
if( aVersion && aVersion[0] )
{
fVersion = aVersion[0] - '0';
if( aVersion[1] == '.' && aVersion[2] )
{
fVersion += (aVersion[2] - '0')/10.0;
}
}
CHECK_GL_ERROR();
return fVersion;
}
void OpenGLHelper::checkGLError(const char* pFile, size_t nLine)
{
GLenum glErr = glGetError();
if (glErr != GL_NO_ERROR)
{
const char* sError = OpenGLHelper::GLErrorString(glErr);
if (sError)
SAL_WARN("vcl.opengl", "GL Error #" << glErr << "(" << sError << ") in File " << pFile << " at line: " << nLine);
else
SAL_WARN("vcl.opengl", "GL Error #" << glErr << " (no message available) in File " << pFile << " at line: " << nLine);
glErr = glGetError();
}
}
bool OpenGLHelper::isDeviceBlacklisted()
{
static bool bSet = false;
static bool bBlacklisted = true; // assume the worst
if (!bSet)
{
#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID
X11OpenGLDeviceInfo aInfo;
bBlacklisted = aInfo.isDeviceBlocked();
SAL_INFO("vcl.opengl", "blacklisted: " << bBlacklisted);
#else
bBlacklisted = false;
#endif
bSet = true;
}
return bBlacklisted;
}
bool OpenGLHelper::supportsVCLOpenGL()
{
static bool bDisableGL = !!getenv("SAL_DISABLEGL");
bool bBlacklisted = isDeviceBlacklisted();
if (bDisableGL || bBlacklisted)
return false;
else
return true;
}
bool OpenGLHelper::isVCLOpenGLEnabled()
{
if (!supportsVCLOpenGL())
return false;
static bool bEnableGLEnv = !!getenv("SAL_ENABLEGL");
bool bEnable = bEnableGLEnv;
static bool bDuringBuild = getenv("VCL_HIDE_WINDOWS");
if (bDuringBuild && !bEnable /* env. enable overrides */)
bEnable = false;
else if (officecfg::Office::Common::VCL::UseOpenGL::get())
bEnable = true;
return bEnable;
}
#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID
bool OpenGLHelper::GetVisualInfo(Display* pDisplay, int nScreen, XVisualInfo& rVI)
{
XVisualInfo* pVI;
int aAttrib[] = { GLX_RGBA,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_DEPTH_SIZE, 24,
None };
pVI = glXChooseVisual( pDisplay, nScreen, aAttrib );
if( !pVI )
return false;
rVI = *pVI;
XFree( pVI );
CHECK_GL_ERROR();
return true;
}
GLXFBConfig OpenGLHelper::GetPixmapFBConfig( Display* pDisplay, bool& bInverted )
{
int nScreen = DefaultScreen( pDisplay );
GLXFBConfig *aFbConfigs;
int i, nFbConfigs, nValue;
aFbConfigs = glXGetFBConfigs( pDisplay, nScreen, &nFbConfigs );
for( i = 0; i < nFbConfigs; i++ )
{
glXGetFBConfigAttrib( pDisplay, aFbConfigs[i], GLX_DRAWABLE_TYPE, &nValue );
if( !(nValue & GLX_PIXMAP_BIT) )
continue;
glXGetFBConfigAttrib( pDisplay, aFbConfigs[i], GLX_BIND_TO_TEXTURE_TARGETS_EXT, &nValue );
if( !(nValue & GLX_TEXTURE_2D_BIT_EXT) )
continue;
glXGetFBConfigAttrib( pDisplay, aFbConfigs[i], GLX_DEPTH_SIZE, &nValue );
if( nValue != 24 )
continue;
glXGetFBConfigAttrib( pDisplay, aFbConfigs[i], GLX_RED_SIZE, &nValue );
if( nValue != 8 )
continue;
SAL_INFO( "vcl.opengl", "Red is " << nValue );
// TODO: lfrb: Make it configurable wrt RGB/RGBA
glXGetFBConfigAttrib( pDisplay, aFbConfigs[i], GLX_BIND_TO_TEXTURE_RGB_EXT, &nValue );
if( nValue == False )
{
glXGetFBConfigAttrib( pDisplay, aFbConfigs[i], GLX_BIND_TO_TEXTURE_RGBA_EXT, &nValue );
if( nValue == False )
continue;
}
glXGetFBConfigAttrib( pDisplay, aFbConfigs[i], GLX_Y_INVERTED_EXT, &nValue );
bInverted = (nValue == True) ? true : false;
break;
}
if( i == nFbConfigs )
{
SAL_WARN( "vcl.opengl", "Unable to find FBconfig for pixmap texturing" );
return 0;
}
CHECK_GL_ERROR();
return aFbConfigs[i];
}
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */