[Libreoffice-commits] core.git: Branch 'libreoffice-5-0-2' - include/vcl vcl/inc vcl/opengl vcl/source
Marco Cecchetti
marco.cecchetti at collabora.com
Wed Sep 16 10:49:18 PDT 2015
include/vcl/opengl/OpenGLContext.hxx | 18 +-
include/vcl/opengl/OpenGLHelper.hxx | 7
vcl/inc/opengl/program.hxx | 3
vcl/inc/opengl/win/WinDeviceInfo.hxx | 45 +++++
vcl/inc/opengl/x11/X11DeviceInfo.hxx | 26 +++
vcl/opengl/program.cxx | 7
vcl/source/opengl/OpenGLContext.cxx | 37 +---
vcl/source/opengl/OpenGLHelper.cxx | 267 ++++++++++++++++++++++++++++++++++-
8 files changed, 369 insertions(+), 41 deletions(-)
New commits:
commit 9861e8c174bb950a3b7d2af1f17dcfcb430e4055
Author: Marco Cecchetti <marco.cecchetti at collabora.com>
Date: Sun Sep 13 12:15:13 2015 +0200
tdf#93814: Added support for caching shader program binaries.
Reviewed-on: https://gerrit.libreoffice.org/18555
Tested-by: Jenkins <ci at libreoffice.org>
Reviewed-by: Tomaž Vajngerl <quikee at gmail.com>
Signed-off-by: Michael Meeks <michael.meeks at collabora.com>
Conflicts:
include/vcl/opengl/OpenGLContext.hxx
include/vcl/opengl/OpenGLHelper.hxx
vcl/inc/opengl/win/WinDeviceInfo.hxx
vcl/source/opengl/OpenGLContext.cxx
Change-Id: I21c844b47282f6b3eec443933a86421a074e24df
Reviewed-on: https://gerrit.libreoffice.org/18632
Reviewed-by: Jan Holesovsky <kendy at collabora.com>
Reviewed-by: Marco Cecchetti <mrcekets at gmail.com>
Tested-by: Jan Holesovsky <kendy at collabora.com>
diff --git a/include/vcl/opengl/OpenGLContext.hxx b/include/vcl/opengl/OpenGLContext.hxx
index e706df5..68d7209 100644
--- a/include/vcl/opengl/OpenGLContext.hxx
+++ b/include/vcl/opengl/OpenGLContext.hxx
@@ -56,9 +56,13 @@ class NSOpenGLView;
#include <vcl/window.hxx>
#include <tools/gen.hxx>
#include <vcl/syschild.hxx>
+#include <rtl/crc.h>
#include <rtl/ref.hxx>
+#include <map>
+#include <memory>
#include <set>
+#include <unordered_map>
class OpenGLFramebuffer;
class OpenGLProgram;
@@ -281,15 +285,15 @@ private:
OpenGLFramebuffer* mpFirstFramebuffer;
OpenGLFramebuffer* mpLastFramebuffer;
- struct ProgramKey
+ struct ProgramHash
{
- ProgramKey( const OUString& vertexShader, const OUString& fragmentShader, const OString& preamble );
- bool operator< ( const ProgramKey& other ) const;
- OUString vertexShader;
- OUString fragmentShader;
- OString preamble;
+ size_t operator()( const rtl::OString& aDigest ) const
+ {
+ return (size_t)( rtl_crc32( 0, aDigest.getStr(), aDigest.getLength() ) );
+ }
};
- std::map<ProgramKey, boost::shared_ptr<OpenGLProgram> > maPrograms;
+ typedef std::unordered_map< rtl::OString, std::shared_ptr<OpenGLProgram>, ProgramHash > ProgramCollection;
+ ProgramCollection maPrograms;
OpenGLProgram* mpCurrentProgram;
#ifdef DBG_UTIL
std::set<SalGraphicsImpl*> maParents;
diff --git a/include/vcl/opengl/OpenGLHelper.hxx b/include/vcl/opengl/OpenGLHelper.hxx
index 646a2d3..788b056 100644
--- a/include/vcl/opengl/OpenGLHelper.hxx
+++ b/include/vcl/opengl/OpenGLHelper.hxx
@@ -36,8 +36,13 @@
class VCL_DLLPUBLIC OpenGLHelper
{
+ OpenGLHelper() SAL_DELETED_FUNCTION; // Should not be instantiated
+
public:
- static GLint LoadShaders(const OUString& rVertexShaderName, const OUString& rFragmentShaderName, const OString& preamble = "" );
+
+ static rtl::OString GetDigest(const OUString& rVertexShaderName, const OUString& rFragmentShaderName, const rtl::OString& preamble = "" );
+
+ static GLint LoadShaders(const OUString& rVertexShaderName, const OUString& rFragmentShaderName, const rtl::OString& preamble = "", const rtl::OString& rDigest = "" );
/**
* The caller is responsible for allocate the memory for the RGBA buffer, before call
diff --git a/vcl/inc/opengl/program.hxx b/vcl/inc/opengl/program.hxx
index 7bdd43d..3a47512 100644
--- a/vcl/inc/opengl/program.hxx
+++ b/vcl/inc/opengl/program.hxx
@@ -50,7 +50,8 @@ public:
OpenGLProgram();
~OpenGLProgram();
- bool Load( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble = "" );
+ bool Load( const OUString& rVertexShader, const OUString& rFragmentShader,
+ const rtl::OString& preamble = "", const rtl::OString& rDigest = "" );
bool Use();
bool Clean();
diff --git a/vcl/inc/opengl/win/WinDeviceInfo.hxx b/vcl/inc/opengl/win/WinDeviceInfo.hxx
index 21f14d9..662967f 100644
--- a/vcl/inc/opengl/win/WinDeviceInfo.hxx
+++ b/vcl/inc/opengl/win/WinDeviceInfo.hxx
@@ -183,6 +183,51 @@ public:
virtual ~WinOpenGLDeviceInfo();
virtual bool isDeviceBlocked();
+
+ const OUString& GetDriverVersion() const
+ {
+ return maDriverVersion;
+ }
+
+ const OUString& GetDriverDate() const
+ {
+ return maDriverDate;
+ }
+
+ const OUString& GetDeviceID() const
+ {
+ return maDeviceID;
+ }
+
+ const OUString& GetAdapterVendorID() const
+ {
+ return maAdapterVendorID;
+ }
+
+ const OUString& GetAdapterDeviceID() const
+ {
+ return maAdapterDeviceID;
+ }
+
+ const OUString& GetAdapterSubsysID() const
+ {
+ return maAdapterSubsysID;
+ }
+ const OUString& GetDeviceKey() const
+ {
+ return maDeviceKey;
+ }
+
+ const OUString& GetDeviceString() const
+ {
+ return maDeviceString;
+ }
+
+ sal_uInt32 GetWindowsVersion() const
+ {
+ return mnWindowsVersion;
+ }
+
};
#endif
diff --git a/vcl/inc/opengl/x11/X11DeviceInfo.hxx b/vcl/inc/opengl/x11/X11DeviceInfo.hxx
index dfb7204..c34deb3 100644
--- a/vcl/inc/opengl/x11/X11DeviceInfo.hxx
+++ b/vcl/inc/opengl/x11/X11DeviceInfo.hxx
@@ -44,6 +44,32 @@ public:
virtual ~X11OpenGLDeviceInfo();
virtual bool isDeviceBlocked() SAL_OVERRIDE;
+
+ const OString& GetVendor() const
+ {
+ return maVendor;
+ }
+
+ const OString& GetRenderer() const
+ {
+ return maRenderer;
+ }
+
+ const OString& GetVersion() const
+ {
+ return maVersion;
+ }
+
+ const OString& GetOS() const
+ {
+ return maOS;
+ }
+
+ const OString& GetOSRelease() const
+ {
+ return maOSRelease;
+ }
+
};
#endif
diff --git a/vcl/opengl/program.cxx b/vcl/opengl/program.cxx
index eec4e92..0919c1a 100644
--- a/vcl/opengl/program.cxx
+++ b/vcl/opengl/program.cxx
@@ -36,9 +36,12 @@ OpenGLProgram::~OpenGLProgram()
glDeleteProgram( mnId );
}
-bool OpenGLProgram::Load( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble )
+bool OpenGLProgram::Load( const OUString& rVertexShader,
+ const OUString& rFragmentShader,
+ const rtl::OString& preamble,
+ const rtl::OString& rDigest )
{
- mnId = OpenGLHelper::LoadShaders( rVertexShader, rFragmentShader, preamble );
+ mnId = OpenGLHelper::LoadShaders( rVertexShader, rFragmentShader, preamble, rDigest );
return ( mnId != 0 );
}
diff --git a/vcl/source/opengl/OpenGLContext.cxx b/vcl/source/opengl/OpenGLContext.cxx
index 2502a83..251be21 100644
--- a/vcl/source/opengl/OpenGLContext.cxx
+++ b/vcl/source/opengl/OpenGLContext.cxx
@@ -1725,22 +1725,25 @@ void OpenGLContext::ReleaseFramebuffers()
BindFramebuffer( NULL );
}
-OpenGLProgram* OpenGLContext::GetProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble )
+OpenGLProgram* OpenGLContext::GetProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const rtl::OString& preamble )
{
OpenGLZone aZone;
- ProgramKey aKey( rVertexShader, rFragmentShader, preamble );
+ rtl::OString aKey = OpenGLHelper::GetDigest( rVertexShader, rFragmentShader, preamble );
- std::map< ProgramKey, boost::shared_ptr<OpenGLProgram> >::iterator
- it = maPrograms.find( aKey );
- if( it != maPrograms.end() )
- return it->second.get();
+ if( !aKey.isEmpty() )
+ {
+ ProgramCollection::iterator it = maPrograms.find( aKey );
+ if( it != maPrograms.end() )
+ return it->second.get();
+ }
- boost::shared_ptr<OpenGLProgram> pProgram = boost::make_shared<OpenGLProgram>();
- if( !pProgram->Load( rVertexShader, rFragmentShader, preamble ) )
+ std::shared_ptr<OpenGLProgram> pProgram = std::make_shared<OpenGLProgram>();
+ if( !pProgram->Load( rVertexShader, rFragmentShader, preamble, aKey ) )
return NULL;
- maPrograms.insert(std::pair<ProgramKey, boost::shared_ptr<OpenGLProgram> >(aKey, pProgram));
+ maPrograms.insert(std::make_pair(aKey, pProgram));
+
return pProgram.get();
}
@@ -1766,20 +1769,4 @@ OpenGLProgram* OpenGLContext::UseProgram( const OUString& rVertexShader, const O
return mpCurrentProgram;
}
-inline
-OpenGLContext::ProgramKey::ProgramKey( const OUString& v, const OUString& f, const OString& p )
-: vertexShader( v ), fragmentShader( f ), preamble( p )
-{
-}
-
-inline
-bool OpenGLContext::ProgramKey::operator< ( const ProgramKey& other ) const
-{
- if( vertexShader != other.vertexShader )
- return vertexShader < other.vertexShader;
- if( fragmentShader != other.fragmentShader )
- return fragmentShader < other.fragmentShader;
- return preamble < other.preamble;
-}
-
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/opengl/OpenGLHelper.cxx b/vcl/source/opengl/OpenGLHelper.cxx
index 5840853..538e467 100644
--- a/vcl/source/opengl/OpenGLHelper.cxx
+++ b/vcl/source/opengl/OpenGLHelper.cxx
@@ -12,6 +12,9 @@
#include <osl/file.hxx>
#include <rtl/bootstrap.hxx>
+#include <rtl/digest.h>
+#include <rtl/strbuf.hxx>
+#include <rtl/ustring.hxx>
#include <config_folders.h>
#include <vcl/salbtype.hxx>
#include <vcl/bmpacc.hxx>
@@ -25,6 +28,8 @@
#include <stdarg.h>
#include <vector>
+#include <deque>
+#include <unordered_map>
#include "svdata.hxx"
#include "salgdi.hxx"
@@ -47,6 +52,8 @@ sal_uInt64 volatile OpenGLZone::gnLeaveCount = 0;
namespace {
+using namespace rtl;
+
OUString getShaderFolder()
{
OUString aUrl("$BRAND_BASE_DIR/" LIBO_ETC_FOLDER);
@@ -143,12 +150,247 @@ static void addPreamble(OString& rShaderSource, const OString& rPreamble)
}
}
-GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,const OUString& rFragmentShaderName, const OString& preamble)
+namespace
+{
+ static const sal_uInt32 GLenumSize = sizeof(GLenum);
+
+ OString getHexString(const sal_uInt8* pData, sal_uInt32 nLength)
+ {
+ static const char* pHexData = "0123456789ABCDEF";
+
+ bool bIsZero = true;
+ OStringBuffer aHexStr;
+ for(size_t i = 0; i < nLength; ++i)
+ {
+ sal_uInt8 val = pData[i];
+ if( val != 0 )
+ bIsZero = false;
+ aHexStr.append( pHexData[ val & 0xf ] );
+ aHexStr.append( pHexData[ val >> 4 ] );
+ }
+ if( bIsZero )
+ return OString();
+ else
+ return aHexStr.makeStringAndClear();
+ }
+
+ OString generateMD5(const void* pData, size_t length)
+ {
+ sal_uInt8 pBuffer[RTL_DIGEST_LENGTH_MD5];
+ rtlDigestError aError = rtl_digest_MD5(pData, length,
+ pBuffer, RTL_DIGEST_LENGTH_MD5);
+ SAL_WARN_IF(aError != rtl_Digest_E_None, "vcl.opengl", "md5 generation failed");
+
+ return getHexString(pBuffer, RTL_DIGEST_LENGTH_MD5);
+ }
+
+ OString getStringDigest( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OString& rPreamble )
+ {
+ // read shaders source
+ OString aVertexShaderSource = loadShader( rVertexShaderName );
+ OString aFragmentShaderSource = loadShader( rFragmentShaderName );
+
+ // get info about the graphic device
+#if defined( SAL_UNX ) && !defined( MACOSX ) && !defined( IOS )&& !defined( ANDROID )
+ static const X11OpenGLDeviceInfo aInfo;
+ static const OString aDeviceInfo (
+ aInfo.GetOS() +
+ aInfo.GetOSRelease() +
+ aInfo.GetRenderer() +
+ aInfo.GetVendor() +
+ aInfo.GetVersion() );
+#elif defined( _WIN32 )
+ static const WinOpenGLDeviceInfo aInfo;
+ static const OString aDeviceInfo (
+ OUStringToOString( aInfo.GetAdapterVendorID(), RTL_TEXTENCODING_UTF8 ) +
+ OUStringToOString( aInfo.GetAdapterDeviceID(), RTL_TEXTENCODING_UTF8 ) +
+ OUStringToOString( aInfo.GetDriverVersion(), RTL_TEXTENCODING_UTF8 ) +
+ OString::number( aInfo.GetWindowsVersion() ) );
+#else
+ static const OString aDeviceInfo (
+ OString( (const char*)(glGetString(GL_VENDOR)) ) +
+ OString( (const char*)(glGetString(GL_RENDERER)) ) +
+ OString( (const char*)(glGetString(GL_VERSION)) ) );
+#endif
+
+ OString aMessage;
+ aMessage += rPreamble;
+ aMessage += aVertexShaderSource;
+ aMessage += aFragmentShaderSource;
+ aMessage += aDeviceInfo;
+
+ return generateMD5(aMessage.getStr(), aMessage.getLength());
+ }
+
+ OString getCacheFolder()
+ {
+ OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/");
+ rtl::Bootstrap::expandMacros(url);
+
+ osl::Directory::create(url);
+
+ return rtl::OUStringToOString(url, RTL_TEXTENCODING_UTF8);
+ }
+
+
+ bool writeProgramBinary( const OString& rBinaryFileName,
+ const std::vector<sal_uInt8>& rBinary )
+ {
+ osl::File aFile(rtl::OStringToOUString(rBinaryFileName, RTL_TEXTENCODING_UTF8));
+ osl::FileBase::RC eStatus = aFile.open(
+ osl_File_OpenFlag_Write | osl_File_OpenFlag_Create );
+
+ if( eStatus != osl::FileBase::E_None )
+ {
+ // when file already exists we do not have to save it:
+ // we can be sure that the binary to save is exactly equal
+ // to the already saved binary, since they have the same hash value
+ if( eStatus == osl::FileBase::E_EXIST )
+ {
+ SAL_WARN( "vcl.opengl",
+ "No binary program saved. A file with the same hash already exists: '" << rBinaryFileName << "'" );
+ return true;
+ }
+ return false;
+ }
+
+ sal_uInt64 nBytesWritten = 0;
+ aFile.write( rBinary.data(), rBinary.size(), nBytesWritten );
+
+ assert( rBinary.size() == nBytesWritten );
+
+ return true;
+ }
+
+ bool readProgramBinary( const OString& rBinaryFileName,
+ std::vector<sal_uInt8>& rBinary )
+ {
+ osl::File aFile( rtl::OStringToOUString( rBinaryFileName, RTL_TEXTENCODING_UTF8 ) );
+ if(aFile.open( osl_File_OpenFlag_Read ) == osl::FileBase::E_None)
+ {
+ sal_uInt64 nSize = 0;
+ aFile.getSize( nSize );
+ rBinary.resize( nSize );
+ sal_uInt64 nBytesRead = 0;
+ aFile.read( rBinary.data(), nSize, nBytesRead );
+ assert( nSize == nBytesRead );
+ SAL_WARN("vcl.opengl", "Loading file: '" << rBinaryFileName << "': success" );
+ return true;
+ }
+ else
+ {
+ SAL_WARN("vcl.opengl", "Loading file: '" << rBinaryFileName << "': FAIL");
+ }
+
+ return false;
+ }
+
+ OString createFileName( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OString& rDigest )
+ {
+ OString aFileName;
+ aFileName += getCacheFolder();
+ aFileName += rtl::OUStringToOString( rVertexShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
+ aFileName += rtl::OUStringToOString( rFragmentShaderName, RTL_TEXTENCODING_UTF8 ) + "-";
+ aFileName += rDigest + ".bin";
+ return aFileName;
+ }
+
+ GLint loadProgramBinary( GLuint nProgramID, const OString& rBinaryFileName )
+ {
+ GLint nResult = GL_FALSE;
+ GLenum nBinaryFormat;
+ std::vector<sal_uInt8> aBinary;
+ if( readProgramBinary( rBinaryFileName, aBinary ) && aBinary.size() > GLenumSize )
+ {
+ GLint nBinaryLength = aBinary.size() - GLenumSize;
+
+ // Extract binary format
+ sal_uInt8* pBF = (sal_uInt8*)(&nBinaryFormat);
+ for( size_t i = 0; i < GLenumSize; ++i )
+ {
+ pBF[i] = aBinary[nBinaryLength + i];
+ }
+
+ // Load the program
+ glProgramBinary( nProgramID, nBinaryFormat, (void*)(aBinary.data()), nBinaryLength );
+
+ // Check the program
+ glGetProgramiv(nProgramID, GL_LINK_STATUS, &nResult);
+ }
+ return nResult;
+ }
+
+ void saveProgramBinary( GLint nProgramID, const OString& rBinaryFileName )
+ {
+ GLint nBinaryLength = 0;
+ GLenum nBinaryFormat = GL_NONE;
+
+ glGetProgramiv( nProgramID, GL_PROGRAM_BINARY_LENGTH, &nBinaryLength );
+ if( !( nBinaryLength > 0 ) )
+ {
+ SAL_WARN( "vcl.opengl", "Binary size is zero" );
+ return;
+ }
+
+ std::vector<sal_uInt8> aBinary( nBinaryLength + GLenumSize );
+
+ glGetProgramBinary( nProgramID, nBinaryLength, NULL, &nBinaryFormat, (void*)(aBinary.data()) );
+
+ const sal_uInt8* pBF = (const sal_uInt8*)(&nBinaryFormat);
+ aBinary.insert( aBinary.end(), pBF, pBF + GLenumSize );
+
+ SAL_INFO("vcl.opengl", "Program id: " << nProgramID );
+ SAL_INFO("vcl.opengl", "Binary length: " << nBinaryLength );
+ SAL_INFO("vcl.opengl", "Binary format: " << nBinaryFormat );
+
+ if( !writeProgramBinary( rBinaryFileName, aBinary ) )
+ SAL_WARN("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': FAIL");
+ else
+ SAL_WARN("vcl.opengl", "Writing binary file '" << rBinaryFileName << "': success");
+ }
+}
+
+rtl::OString OpenGLHelper::GetDigest( const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OString& rPreamble )
+{
+ return getStringDigest(rVertexShaderName, rFragmentShaderName, rPreamble);
+}
+
+GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,
+ const OUString& rFragmentShaderName,
+ const OString& preamble,
+ const OString& rDigest)
{
OpenGLZone aZone;
gbInShaderCompile = true;
+ // create the program object
+ GLint ProgramID = glCreateProgram();
+
+ // read shaders from file
+ OString aVertexShaderSource = loadShader(rVertexShaderName);
+ OString aFragmentShaderSource = loadShader(rFragmentShaderName);
+
+
+ GLint bBinaryResult = GL_FALSE;
+ if( GLEW_ARB_get_program_binary && !rDigest.isEmpty() )
+ {
+ OString aFileName =
+ createFileName(rVertexShaderName, rFragmentShaderName, rDigest);
+ bBinaryResult = loadProgramBinary(ProgramID, aFileName);
+ VCL_GL_INFO("vcl.opengl", "Load binary shader from '" << aFileName << "'" << bBinaryResult);
+ CHECK_GL_ERROR();
+ }
+
+ if( bBinaryResult != GL_FALSE )
+ return ProgramID;
+
VCL_GL_INFO("vcl.opengl", "Load shader: vertex " << rVertexShaderName << " fragment " << rFragmentShaderName);
// Create the shaders
GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
@@ -157,7 +399,6 @@ GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,const OUString
GLint Result = GL_FALSE;
// Compile Vertex Shader
- OString aVertexShaderSource = loadShader(rVertexShaderName);
if( !preamble.isEmpty())
addPreamble( aVertexShaderSource, preamble );
char const * VertexSourcePointer = aVertexShaderSource.getStr();
@@ -171,7 +412,6 @@ GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,const OUString
rVertexShaderName, true);
// Compile Fragment Shader
- OString aFragmentShaderSource = loadShader(rFragmentShaderName);
if( !preamble.isEmpty())
addPreamble( aFragmentShaderSource, preamble );
char const * FragmentSourcePointer = aFragmentShaderSource.getStr();
@@ -185,10 +425,27 @@ GLint OpenGLHelper::LoadShaders(const OUString& rVertexShaderName,const OUString
rFragmentShaderName, true);
// Link the program
- GLint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VertexShaderID);
glAttachShader(ProgramID, FragmentShaderID);
- glLinkProgram(ProgramID);
+
+ if( GLEW_ARB_get_program_binary && !rDigest.isEmpty() )
+ {
+ glProgramParameteri(ProgramID, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
+ glLinkProgram(ProgramID);
+ glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
+ if (!Result)
+ {
+ SAL_WARN("vcl.opengl", "linking failed: " << Result );
+ return LogCompilerError(ProgramID, "program", "<both>", false);
+ }
+ OString aFileName =
+ createFileName(rVertexShaderName, rFragmentShaderName, rDigest);
+ saveProgramBinary(ProgramID, aFileName);
+ }
+ else
+ {
+ glLinkProgram(ProgramID);
+ }
glDeleteShader(VertexShaderID);
glDeleteShader(FragmentShaderID);
More information about the Libreoffice-commits
mailing list