1
0
Fork 0
libreoffice/canvas/source/opengl/ogl_spritedevicehelper.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

549 lines
20 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 <sal/config.h>
#include <sal/log.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/utils/canvastools.hxx>
#include <basegfx/utils/unopolypolygon.hxx>
#include <com/sun/star/awt/XTopWindow.hpp>
#include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp>
#include <com/sun/star/uno/Reference.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/opengl/OpenGLHelper.hxx>
#include <vcl/syschild.hxx>
#include "ogl_spritedevicehelper.hxx"
#include "ogl_spritecanvas.hxx"
#include "ogl_canvasbitmap.hxx"
#include "ogl_canvastools.hxx"
#include "ogl_canvascustomsprite.hxx"
#include "ogl_texturecache.hxx"
using namespace ::com::sun::star;
static void initContext()
{
// need the backside for mirror effects
glDisable(GL_CULL_FACE);
// no perspective, we're 2D
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// misc preferences
glEnable(GL_POINT_SMOOTH);
glEnable(GL_LINE_SMOOTH);
glEnable(GL_POLYGON_SMOOTH);
glHint(GL_POINT_SMOOTH_HINT,GL_NICEST);
glHint(GL_LINE_SMOOTH_HINT,GL_NICEST);
glHint(GL_POLYGON_SMOOTH_HINT,GL_NICEST);
glShadeModel(GL_FLAT);
}
static void initTransformation(const ::Size& rSize)
{
// use whole window
glViewport( 0,0,
static_cast<GLsizei>(rSize.Width()),
static_cast<GLsizei>(rSize.Height()) );
// model coordinate system is already in device pixel
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslated(-1.0, 1.0, 0.0);
glScaled( 2.0 / rSize.Width(),
-2.0 / rSize.Height(),
1.0 );
// clear to black
glClearColor(0,0,0,0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
namespace oglcanvas
{
SpriteDeviceHelper::SpriteDeviceHelper() :
mpSpriteCanvas(nullptr),
mpTextureCache(std::make_shared<TextureCache>()),
mnLinearTwoColorGradientProgram(0),
mnLinearMultiColorGradientProgram(0),
mnRadialTwoColorGradientProgram(0),
mnRadialMultiColorGradientProgram(0),
mnRectangularTwoColorGradientProgram(0),
mnRectangularMultiColorGradientProgram(0),
mxContext(OpenGLContext::Create())
{}
SpriteDeviceHelper::~SpriteDeviceHelper()
{ mxContext->dispose(); }
void SpriteDeviceHelper::init( vcl::Window& rWindow,
SpriteCanvas& rSpriteCanvas,
const awt::Rectangle& rViewArea )
{
mpSpriteCanvas = &rSpriteCanvas;
rSpriteCanvas.setWindow(
uno::Reference<awt::XWindow2>(
VCLUnoHelper::GetInterface(&rWindow),
uno::UNO_QUERY_THROW) );
mxContext->requestLegacyContext();
mxContext->init(&rWindow);
// init window context
initContext();
mnLinearMultiColorGradientProgram =
OpenGLHelper::LoadShaders(u"dummyVertexShader"_ustr, u"linearMultiColorGradientFragmentShader"_ustr);
mnLinearTwoColorGradientProgram =
OpenGLHelper::LoadShaders(u"dummyVertexShader"_ustr, u"linearTwoColorGradientFragmentShader"_ustr);
mnRadialMultiColorGradientProgram =
OpenGLHelper::LoadShaders(u"dummyVertexShader"_ustr, u"radialMultiColorGradientFragmentShader"_ustr);
mnRadialTwoColorGradientProgram =
OpenGLHelper::LoadShaders(u"dummyVertexShader"_ustr, u"radialTwoColorGradientFragmentShader"_ustr);
mnRectangularMultiColorGradientProgram =
OpenGLHelper::LoadShaders(u"dummyVertexShader"_ustr, u"rectangularMultiColorGradientFragmentShader"_ustr);
mnRectangularTwoColorGradientProgram =
OpenGLHelper::LoadShaders(u"dummyVertexShader"_ustr, u"rectangularTwoColorGradientFragmentShader"_ustr);
mxContext->makeCurrent();
notifySizeUpdate(rViewArea);
// TODO(E3): check for GL_ARB_imaging extension
}
void SpriteDeviceHelper::disposing()
{
// release all references
mpSpriteCanvas = nullptr;
mpTextureCache.reset();
if( mxContext->isInitialized() )
{
glDeleteProgram( mnRectangularTwoColorGradientProgram );
glDeleteProgram( mnRectangularMultiColorGradientProgram );
glDeleteProgram( mnRadialTwoColorGradientProgram );
glDeleteProgram( mnRadialMultiColorGradientProgram );
glDeleteProgram( mnLinearTwoColorGradientProgram );
glDeleteProgram( mnLinearMultiColorGradientProgram );
}
}
geometry::RealSize2D SpriteDeviceHelper::getPhysicalResolution()
{
if( !mxContext->isInitialized() )
return ::canvas::tools::createInfiniteSize2D(); // we're disposed
// Map a one-by-one millimeter box to pixel
SystemChildWindow* pChildWindow = mxContext->getChildWindow();
const MapMode aOldMapMode( pChildWindow->GetMapMode() );
pChildWindow->SetMapMode( MapMode(MapUnit::MapMM) );
const Size aPixelSize( pChildWindow->LogicToPixel(Size(1,1)) );
pChildWindow->SetMapMode( aOldMapMode );
return vcl::unotools::size2DFromSize( aPixelSize );
}
geometry::RealSize2D SpriteDeviceHelper::getPhysicalSize()
{
if( !mxContext->isInitialized() )
return ::canvas::tools::createInfiniteSize2D(); // we're disposed
// Map the pixel dimensions of the output window to millimeter
SystemChildWindow* pChildWindow = mxContext->getChildWindow();
const MapMode aOldMapMode( pChildWindow->GetMapMode() );
pChildWindow->SetMapMode( MapMode(MapUnit::MapMM) );
const Size aLogSize( pChildWindow->PixelToLogic(pChildWindow->GetOutputSizePixel()) );
pChildWindow->SetMapMode( aOldMapMode );
return vcl::unotools::size2DFromSize( aLogSize );
}
uno::Reference< rendering::XLinePolyPolygon2D > SpriteDeviceHelper::createCompatibleLinePolyPolygon(
const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/,
const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points )
{
// disposed?
if( !mpSpriteCanvas )
return uno::Reference< rendering::XLinePolyPolygon2D >(); // we're disposed
return uno::Reference< rendering::XLinePolyPolygon2D >(
new ::basegfx::unotools::UnoPolyPolygon(
::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( points )));
}
uno::Reference< rendering::XBezierPolyPolygon2D > SpriteDeviceHelper::createCompatibleBezierPolyPolygon(
const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/,
const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points )
{
// disposed?
if( !mpSpriteCanvas )
return uno::Reference< rendering::XBezierPolyPolygon2D >(); // we're disposed
return uno::Reference< rendering::XBezierPolyPolygon2D >(
new ::basegfx::unotools::UnoPolyPolygon(
::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( points ) ) );
}
uno::Reference< rendering::XBitmap > SpriteDeviceHelper::createCompatibleBitmap(
const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/,
const geometry::IntegerSize2D& size )
{
// disposed?
if( !mpSpriteCanvas )
return uno::Reference< rendering::XBitmap >(); // we're disposed
return uno::Reference< rendering::XBitmap >(
new CanvasBitmap( size,
mpSpriteCanvas,
*this ) );
}
uno::Reference< rendering::XVolatileBitmap > SpriteDeviceHelper::createVolatileBitmap(
const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/,
const geometry::IntegerSize2D& /*size*/ )
{
return uno::Reference< rendering::XVolatileBitmap >();
}
uno::Reference< rendering::XBitmap > SpriteDeviceHelper::createCompatibleAlphaBitmap(
const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/,
const geometry::IntegerSize2D& size )
{
// disposed?
if( !mpSpriteCanvas )
return uno::Reference< rendering::XBitmap >(); // we're disposed
return uno::Reference< rendering::XBitmap >(
new CanvasBitmap( size,
mpSpriteCanvas,
*this ) );
}
uno::Reference< rendering::XVolatileBitmap > SpriteDeviceHelper::createVolatileAlphaBitmap(
const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/,
const geometry::IntegerSize2D& /*size*/ )
{
return uno::Reference< rendering::XVolatileBitmap >();
}
namespace
{
/** Functor providing a StrictWeakOrdering for XSprites (over
priority)
*/
struct SpriteComparator
{
bool operator()( const ::rtl::Reference<CanvasCustomSprite>& rLHS,
const ::rtl::Reference<CanvasCustomSprite>& rRHS ) const
{
const double nPrioL( rLHS->getPriority() );
const double nPrioR( rRHS->getPriority() );
// if prios are equal, tie-break on ptr value
return nPrioL == nPrioR ? rLHS.get() < rRHS.get() : nPrioL < nPrioR;
}
};
}
bool SpriteDeviceHelper::showBuffer( bool bIsVisible, SAL_UNUSED_PARAMETER bool /*bUpdateAll*/ )
{
// hidden or disposed?
if( !bIsVisible || !mxContext->isInitialized() || !mpSpriteCanvas )
return false;
mxContext->makeCurrent();
SystemChildWindow* pChildWindow = mxContext->getChildWindow();
const ::Size aOutputSize = pChildWindow->GetSizePixel();
initTransformation(aOutputSize);
// render the actual spritecanvas content
mpSpriteCanvas->renderRecordedActions();
// render all sprites (in order of priority) on top of that
std::vector< ::rtl::Reference<CanvasCustomSprite> > aSprites(
maActiveSprites.begin(),
maActiveSprites.end());
std::sort(aSprites.begin(),
aSprites.end(),
SpriteComparator());
for( const auto& rSprite : aSprites )
rSprite->renderSprite();
// frame counter, other info
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslated(-1.0, 1.0, 0.0);
glScaled( 2.0 / aOutputSize.Width(),
-2.0 / aOutputSize.Height(),
1.0 );
const double denominator( maLastUpdate.getElapsedTime() );
maLastUpdate.reset();
const double fps(denominator == 0.0 ? 100.0 : 1.0/denominator);
std::vector<double> aVec { fps, static_cast<double>(maActiveSprites.size()),
static_cast<double>(mpTextureCache->getCacheSize()),
static_cast<double>(mpTextureCache->getCacheMissCount()),
static_cast<double>(mpTextureCache->getCacheHitCount()) };
renderOSD( aVec, 20 );
/*
* TODO: moggi: fix it!
// switch buffer, sync etc.
const unx::Window aXWindow=pChildWindow->GetSystemData()->aWindow;
unx::glXSwapBuffers(reinterpret_cast<unx::Display*>(mpDisplay),
aXWindow);
pChildWindow->Show();
unx::glXWaitGL();
XSync( reinterpret_cast<unx::Display*>(mpDisplay), false );
*/
mxContext->swapBuffers();
// flush texture cache, such that it does not build up
// indefinitely.
// TODO: have max cache size/LRU time in config, prune only on
// demand
mpTextureCache->prune();
return true;
}
bool SpriteDeviceHelper::switchBuffer( bool bIsVisible, bool bUpdateAll )
{
// no difference for VCL canvas
return showBuffer( bIsVisible, bUpdateAll );
}
uno::Any SpriteDeviceHelper::isAccelerated() const
{
return css::uno::Any(false);
}
uno::Any SpriteDeviceHelper::getDeviceHandle() const
{
const SystemChildWindow* pChildWindow = mxContext->getChildWindow();
const OutputDevice* pDevice = pChildWindow ? pChildWindow->GetOutDev() : nullptr;
return uno::Any(reinterpret_cast<sal_Int64>(pDevice));
}
uno::Any SpriteDeviceHelper::getSurfaceHandle() const
{
return uno::Any();
}
uno::Reference<rendering::XColorSpace> SpriteDeviceHelper::getColorSpace() const
{
// always the same
return ::canvas::tools::getStdColorSpace();
}
void SpriteDeviceHelper::notifySizeUpdate( const awt::Rectangle& rBounds )
{
if( mxContext->isInitialized() )
{
SystemChildWindow* pChildWindow = mxContext->getChildWindow();
pChildWindow->setPosSizePixel(
0,0,rBounds.Width,rBounds.Height);
}
}
void SpriteDeviceHelper::dumpScreenContent() const
{
SAL_INFO("canvas.ogl", __func__ );
}
void SpriteDeviceHelper::show( const ::rtl::Reference< CanvasCustomSprite >& xSprite )
{
maActiveSprites.insert(xSprite);
}
void SpriteDeviceHelper::hide( const ::rtl::Reference< CanvasCustomSprite >& xSprite )
{
maActiveSprites.erase(xSprite);
}
static void setupUniforms( unsigned int nProgramId,
const ::basegfx::B2DHomMatrix& rTexTransform )
{
const GLint nTransformLocation = glGetUniformLocation(nProgramId,
"m_transform" );
// OGL is column-major
float aTexTransform[] =
{
float(rTexTransform.get(0,0)), float(rTexTransform.get(1,0)),
float(rTexTransform.get(0,1)), float(rTexTransform.get(1,1)),
float(rTexTransform.get(0,2)), float(rTexTransform.get(1,2))
};
glUniformMatrix3x2fv(nTransformLocation,1,false,aTexTransform);
}
static void setupUniforms( unsigned int nProgramId,
const rendering::ARGBColor* pColors,
const uno::Sequence< double >& rStops,
const ::basegfx::B2DHomMatrix& rTexTransform )
{
glUseProgram(nProgramId);
GLuint nColorsTexture;
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &nColorsTexture);
glBindTexture(GL_TEXTURE_1D, nColorsTexture);
const sal_Int32 nColors=rStops.getLength();
glTexImage1D( GL_TEXTURE_1D, 0, GL_RGBA, nColors, 0, GL_RGBA, GL_DOUBLE, pColors );
glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
GLuint nStopsTexture;
glActiveTexture(GL_TEXTURE1);
glGenTextures(1, &nStopsTexture);
glBindTexture(GL_TEXTURE_1D, nStopsTexture);
glTexImage1D( GL_TEXTURE_1D, 0, GL_ALPHA, nColors, 0, GL_ALPHA, GL_DOUBLE, rStops.getConstArray() );
glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
const GLint nColorArrayLocation = glGetUniformLocation(nProgramId,
"t_colorArray4d" );
glUniform1i( nColorArrayLocation, 0 ); // unit 0
const GLint nStopArrayLocation = glGetUniformLocation(nProgramId,
"t_stopArray1d" );
glUniform1i( nStopArrayLocation, 1 ); // unit 1
const GLint nNumColorLocation = glGetUniformLocation(nProgramId,
"i_nColors" );
glUniform1i( nNumColorLocation, nColors-1 );
setupUniforms(nProgramId,rTexTransform);
}
static void setupUniforms( unsigned int nProgramId,
const rendering::ARGBColor& rStartColor,
const rendering::ARGBColor& rEndColor,
const ::basegfx::B2DHomMatrix& rTexTransform )
{
glUseProgram(nProgramId);
const GLint nStartColorLocation = glGetUniformLocation(nProgramId,
"v_startColor4d" );
glUniform4f(nStartColorLocation,
rStartColor.Red,
rStartColor.Green,
rStartColor.Blue,
rStartColor.Alpha);
const GLint nEndColorLocation = glGetUniformLocation(nProgramId,
"v_endColor4d" );
glUniform4f(nEndColorLocation,
rEndColor.Red,
rEndColor.Green,
rEndColor.Blue,
rEndColor.Alpha);
setupUniforms(nProgramId,rTexTransform);
}
void SpriteDeviceHelper::useLinearGradientShader( const rendering::ARGBColor* pColors,
const uno::Sequence< double >& rStops,
const ::basegfx::B2DHomMatrix& rTexTransform )
{
if( rStops.getLength() > 2 )
setupUniforms(mnLinearMultiColorGradientProgram, pColors, rStops, rTexTransform);
else
setupUniforms(mnLinearTwoColorGradientProgram, pColors[0], pColors[1], rTexTransform);
}
void SpriteDeviceHelper::useRadialGradientShader( const rendering::ARGBColor* pColors,
const uno::Sequence< double >& rStops,
const ::basegfx::B2DHomMatrix& rTexTransform )
{
if( rStops.getLength() > 2 )
setupUniforms(mnRadialMultiColorGradientProgram, pColors, rStops, rTexTransform);
else
setupUniforms(mnRadialTwoColorGradientProgram, pColors[0], pColors[1], rTexTransform);
}
void SpriteDeviceHelper::useRectangularGradientShader( const rendering::ARGBColor* pColors,
const uno::Sequence< double >& rStops,
const ::basegfx::B2DHomMatrix& rTexTransform )
{
if( rStops.getLength() > 2 )
setupUniforms(mnRectangularMultiColorGradientProgram, pColors, rStops, rTexTransform);
else
setupUniforms(mnRectangularTwoColorGradientProgram, pColors[0], pColors[1], rTexTransform);
}
namespace
{
class BufferContextImpl : public IBufferContext
{
GLuint mnFramebufferId;
GLuint mnDepthId;
GLuint mnTextureId;
virtual void startBufferRendering() override
{
glBindFramebuffer(GL_FRAMEBUFFER, mnFramebufferId);
}
virtual void endBufferRendering() override
{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
virtual GLuint getTextureId() override
{
return mnTextureId;
}
public:
explicit BufferContextImpl(const ::basegfx::B2IVector& rSize) :
mnFramebufferId(0),
mnDepthId(0),
mnTextureId(0)
{
OpenGLHelper::createFramebuffer(rSize.getX(), rSize.getY(), mnFramebufferId,
mnDepthId, mnTextureId);
}
virtual ~BufferContextImpl() override
{
glDeleteTextures(1, &mnTextureId);
glDeleteRenderbuffers(1, &mnDepthId);
glDeleteFramebuffers(1, &mnFramebufferId);
}
};
}
IBufferContextSharedPtr SpriteDeviceHelper::createBufferContext(const ::basegfx::B2IVector& rSize) const
{
return std::make_shared<BufferContextImpl>(rSize);
}
TextureCache& SpriteDeviceHelper::getTextureCache() const
{
return *mpTextureCache;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */