Files
libreoffice/vcl/source/opengl/OpenGLHelper.cxx

499 lines
15 KiB
C++
Raw Normal View History

/* -*- 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"
#elif defined (_WIN32)
#include "opengl/win/WinDeviceInfo.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);
#elif defined( _WIN32 )
WinOpenGLDeviceInfo aInfo;
bBlacklisted = aInfo.isDeviceBlocked();
#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,
GLX_STENCIL_SIZE, 8,
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: */