diff options
Diffstat (limited to '')
47 files changed, 9383 insertions, 0 deletions
diff --git a/vcl/opengl/DeviceInfo.cxx b/vcl/opengl/DeviceInfo.cxx new file mode 100644 index 000000000..24f42e34e --- /dev/null +++ b/vcl/opengl/DeviceInfo.cxx @@ -0,0 +1,16 @@ +/* -*- 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 <opengl/DeviceInfo.hxx> + +OpenGLDeviceInfo::~OpenGLDeviceInfo() +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/FixedTextureAtlas.cxx b/vcl/opengl/FixedTextureAtlas.cxx new file mode 100644 index 000000000..7425942d1 --- /dev/null +++ b/vcl/opengl/FixedTextureAtlas.cxx @@ -0,0 +1,137 @@ +/* -*- 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 <memory> +#include <sal/config.h> +#include <vcl/opengl/OpenGLContext.hxx> +#include <vcl/opengl/OpenGLHelper.hxx> + +#include <opengl/framebuffer.hxx> +#include <opengl/texture.hxx> + +#include <opengl/FixedTextureAtlas.hxx> + +struct FixedTexture +{ + std::shared_ptr<ImplOpenGLTexture> mpTexture; + int mnFreeSlots; + std::vector<bool> maAllocatedSlots; + + FixedTexture(int nTextureWidth, int nTextureHeight, int nNumberOfSlots) + : mpTexture(std::make_shared<ImplOpenGLTexture>(nTextureWidth, nTextureHeight, true)) + , mnFreeSlots(nNumberOfSlots) + , maAllocatedSlots(nNumberOfSlots, false) + { + auto aDeallocateFunction = [this] (int nSlotNumber) + { + deallocateSlot(nSlotNumber); + }; + + mpTexture->SetSlotDeallocateCallback(aDeallocateFunction); + mpTexture->InitializeSlotMechanism(nNumberOfSlots); + } + + ~FixedTexture() + { + mpTexture->ResetSlotDeallocateCallback(); + } + + void allocateSlot(int nSlot) + { + maAllocatedSlots[nSlot] = true; + mnFreeSlots--; + } + + void deallocateSlot(int nSlot) + { + maAllocatedSlots[nSlot] = false; + mnFreeSlots++; + } + + int findAndAllocateFreeSlot() + { + for (size_t i = 0; i < maAllocatedSlots.size(); ++i) + { + if (!maAllocatedSlots[i]) + { + allocateSlot(i); + return i; + } + } + return -1; + } + +private: + FixedTexture(const FixedTexture&) = delete; + FixedTexture& operator=(const FixedTexture&) = delete; +}; + +FixedTextureAtlasManager::FixedTextureAtlasManager(int nWidthFactor, int nHeightFactor, int nSubTextureSize) + : mWidthFactor(nWidthFactor) + , mHeightFactor(nHeightFactor) + , mSubTextureSize(nSubTextureSize) +{ +} + +FixedTextureAtlasManager::~FixedTextureAtlasManager() +{ +} + +void FixedTextureAtlasManager::CreateNewTexture() +{ + int nTextureWidth = mWidthFactor * mSubTextureSize; + int nTextureHeight = mHeightFactor * mSubTextureSize; + maFixedTextures.push_back(std::make_unique<FixedTexture>(nTextureWidth, nTextureHeight, mWidthFactor * mHeightFactor)); +} + +OpenGLTexture FixedTextureAtlasManager::Reserve(int nWidth, int nHeight) +{ + FixedTexture* pFixedTexture = nullptr; + + auto funFreeSlot = [] (std::unique_ptr<FixedTexture>& inFixedTexture) + { + return inFixedTexture->mnFreeSlots > 0; + }; + + auto it = std::find_if(maFixedTextures.begin(), maFixedTextures.end(), funFreeSlot); + + if (it != maFixedTextures.end()) + { + pFixedTexture = (*it).get(); + } + else + { + CreateNewTexture(); + pFixedTexture = maFixedTextures.back().get(); + } + + int nSlot = pFixedTexture->findAndAllocateFreeSlot(); + + // Calculate coordinates in texture + int nX = (nSlot % mWidthFactor) * mSubTextureSize; + int nY = (nSlot / mWidthFactor) * mSubTextureSize; + + tools::Rectangle aRectangle(Point(nX, nY), Size(nWidth, nHeight)); + + return OpenGLTexture(pFixedTexture->mpTexture, aRectangle, nSlot); +} + +OpenGLTexture FixedTextureAtlasManager::InsertBuffer(int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData) +{ + OpenGLTexture aTexture = Reserve(nWidth, nHeight); + if (pData == nullptr) + return aTexture; + + aTexture.CopyData(nWidth, nHeight, nFormat, nType, pData); + + return aTexture; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/LineRenderUtils.cxx b/vcl/opengl/LineRenderUtils.cxx new file mode 100644 index 000000000..e130fb93b --- /dev/null +++ b/vcl/opengl/LineRenderUtils.cxx @@ -0,0 +1,189 @@ +/* -*- 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 <opengl/LineRenderUtils.hxx> +#include <opengl/VertexUtils.hxx> + +namespace vcl +{ + +LineBuilder::LineBuilder(std::vector<Vertex>& rVertices, std::vector<GLuint>& rIndices, + Color nColor, GLfloat fTransparency, + GLfloat fLineWidth, bool bUseAA) + : mrVertices(rVertices) + , mrIndices(rIndices) + , mR(nColor.GetRed()) + , mG(nColor.GetGreen()) + , mB(nColor.GetBlue()) + , mA((1.0f - fTransparency) * 255.0f) + , mfLineWidth(fLineWidth) + , mfLineWidthAndAA(bUseAA ? fLineWidth : -fLineWidth) + , mnInitialIndexSize(rIndices.size()) + , mbIncomplete(false) +{ +} + +void LineBuilder::appendLineSegment(const glm::vec2& rPoint1, const glm::vec2& rNormal1, GLfloat aExtrusion1, + const glm::vec2& rPoint2, const glm::vec2& rNormal2, GLfloat aExtrusion2) +{ + GLuint zero = mrVertices.size(); + + mrVertices.insert(mrVertices.end(), { + {rPoint1, glm::vec4{mR, mG, mB, mA}, glm::vec4{-rNormal1.x, -rNormal1.y, -aExtrusion1, mfLineWidthAndAA}}, + {rPoint1, glm::vec4{mR, mG, mB, mA}, glm::vec4{ rNormal1.x, rNormal1.y, aExtrusion1, mfLineWidthAndAA}}, + {rPoint2, glm::vec4{mR, mG, mB, mA}, glm::vec4{-rNormal2.x, -rNormal2.y, -aExtrusion2, mfLineWidthAndAA}}, + {rPoint2, glm::vec4{mR, mG, mB, mA}, glm::vec4{ rNormal2.x, rNormal2.y, aExtrusion2, mfLineWidthAndAA}}, + }); + + mrIndices.insert(mrIndices.end(), { + zero + 0, zero + 1, zero + 2, + zero + 2, zero + 1, zero + 3 + }); + +} + +void LineBuilder::appendLine(const glm::vec2& rPoint1, const glm::vec2& rPoint2) +{ + glm::vec2 aLineVector = vcl::vertex::normalize(rPoint2 - rPoint1); + glm::vec2 aNormal = vcl::vertex::perpendicular(aLineVector); + + appendLineSegment(rPoint1, aNormal, 1.0f, + rPoint2, aNormal, 1.0f); +} + +void LineBuilder::appendAndConnectLinePoint(const glm::vec2& rPoint, const glm::vec2& aNormal, GLfloat aExtrusion) +{ + GLuint zero = mrVertices.size(); + + mrVertices.insert(mrVertices.end(), { + {rPoint, glm::vec4{mR, mG, mB, mA}, glm::vec4{-aNormal.x, -aNormal.y, -aExtrusion, mfLineWidthAndAA}}, + {rPoint, glm::vec4{mR, mG, mB, mA}, glm::vec4{ aNormal.x, aNormal.y, aExtrusion, mfLineWidthAndAA}}, + }); + + if (mnInitialIndexSize == mrIndices.size()) + { + mrIndices.insert(mrIndices.end(), { + zero + 0, zero + 1 + }); + mbIncomplete = true; + } + else + { + if (mbIncomplete) + { + mrIndices.insert(mrIndices.end(), { + zero + 0, + zero + 0, zero - 1, zero + 1 + }); + mbIncomplete = false; + } + else + { + mrIndices.insert(mrIndices.end(), { + zero - 2, zero - 1, zero + 0, + zero + 0, zero - 1, zero + 1 + }); + } + } +} + +void LineBuilder::appendMiterJoint(glm::vec2 const& point, const glm::vec2& prevLineVector, + glm::vec2 const& nextLineVector) +{ + // With miter join we calculate the extrusion vector by adding normals of + // previous and next line segment. The vector shows the way but we also + // need the length (otherwise the line will be deformed). Length factor is + // calculated as dot product of extrusion vector and one of the normals. + // The value we get is the inverse length (used in the shader): + // length = line_width / dot(extrusionVector, normal) + + glm::vec2 normal(-prevLineVector.y, prevLineVector.x); + + glm::vec2 tangent = vcl::vertex::normalize(nextLineVector + prevLineVector); + glm::vec2 extrusionVector(-tangent.y, tangent.x); + GLfloat length = glm::dot(extrusionVector, normal); + + appendAndConnectLinePoint(point, extrusionVector, length); +} + +void LineBuilder::appendBevelJoint(glm::vec2 const& point, const glm::vec2& prevLineVector, + const glm::vec2& nextLineVector) +{ + // For bevel join we just add 2 additional vertices and use previous + // line segment normal and next line segment normal as extrusion vector. + // All the magic is done by the fact that we draw triangle strips, so we + // cover the joins correctly. + + glm::vec2 prevNormal(-prevLineVector.y, prevLineVector.x); + glm::vec2 nextNormal(-nextLineVector.y, nextLineVector.x); + + appendAndConnectLinePoint(point, prevNormal, 1.0f); + appendAndConnectLinePoint(point, nextNormal, 1.0f); +} + +void LineBuilder::appendRoundJoint(glm::vec2 const& point, const glm::vec2& prevLineVector, + const glm::vec2& nextLineVector) +{ + // For round join we do a similar thing as in bevel, we add more intermediate + // vertices and add normals to get extrusion vectors in the between the + // both normals. + + // 3 additional extrusion vectors + normals are enough to make most + // line joins look round. Ideally the number of vectors could be + // calculated. + + glm::vec2 prevNormal(-prevLineVector.y, prevLineVector.x); + glm::vec2 nextNormal(-nextLineVector.y, nextLineVector.x); + + glm::vec2 middle = vcl::vertex::normalize(prevNormal + nextNormal); + glm::vec2 middleLeft = vcl::vertex::normalize(prevNormal + middle); + glm::vec2 middleRight = vcl::vertex::normalize(middle + nextNormal); + + appendAndConnectLinePoint(point, prevNormal, 1.0f); + appendAndConnectLinePoint(point, middleLeft, 1.0f); + appendAndConnectLinePoint(point, middle, 1.0f); + appendAndConnectLinePoint(point, middleRight, 1.0f); + appendAndConnectLinePoint(point, nextNormal, 1.0f); +} + +void LineBuilder::appendRoundLineCapVertices(const glm::vec2& rPoint1, const glm::vec2& rPoint2) +{ + constexpr int nRoundCapIteration = 12; + + glm::vec2 lineVector = vcl::vertex::normalize(rPoint2 - rPoint1); + glm::vec2 normal(-lineVector.y, lineVector.x); + glm::vec2 previousRoundNormal = normal; + + for (int nFactor = 1; nFactor <= nRoundCapIteration; nFactor++) + { + float angle = float(nFactor) * (M_PI / float(nRoundCapIteration)); + glm::vec2 roundNormal(normal.x * glm::cos(angle) - normal.y * glm::sin(angle), + normal.x * glm::sin(angle) + normal.y * glm::cos(angle)); + + appendLineSegment(rPoint1, previousRoundNormal, 1.0f, + rPoint1, roundNormal, 1.0f); + previousRoundNormal = roundNormal; + } +} + +void LineBuilder::appendSquareLineCapVertices(const glm::vec2& rPoint1, const glm::vec2& rPoint2) +{ + glm::vec2 lineVector = vcl::vertex::normalize(rPoint2 - rPoint1); + glm::vec2 normal(-lineVector.y, lineVector.x); + + glm::vec2 extrudedPoint = rPoint1 + -lineVector * (mfLineWidth / 2.0f); + + appendLineSegment(extrudedPoint, normal, 1.0f, + rPoint1, normal, 1.0f); +} + +} // end vcl + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/PackedTextureAtlas.cxx b/vcl/opengl/PackedTextureAtlas.cxx new file mode 100644 index 000000000..8508bbe3c --- /dev/null +++ b/vcl/opengl/PackedTextureAtlas.cxx @@ -0,0 +1,192 @@ +/* -*- 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 <memory> +#include <sal/config.h> +#include <vcl/opengl/OpenGLContext.hxx> +#include <vcl/opengl/OpenGLHelper.hxx> + +#include <opengl/framebuffer.hxx> +#include <opengl/texture.hxx> + +#include <opengl/PackedTextureAtlas.hxx> + +namespace { + +struct Node +{ + tools::Rectangle mRectangle; + std::unique_ptr<Node> mLeftNode; + std::unique_ptr<Node> mRightNode; + bool mOccupied; + + explicit Node(int nWidth, int nHeight); + explicit Node(tools::Rectangle const & aRectangle); + + bool isLeaf() const; + Node* insert(int nWidth, int nHeight, int nPadding); +}; + +} + +Node::Node(int nWidth, int nHeight) + : mRectangle(tools::Rectangle(Point(), Size(nWidth, nHeight))) + , mLeftNode() + , mRightNode() + , mOccupied(false) +{} + +Node::Node(tools::Rectangle const & aRectangle) + : mRectangle(aRectangle) + , mLeftNode() + , mRightNode() + , mOccupied(false) +{} + +bool Node::isLeaf() const { return mLeftNode == nullptr && mRightNode == nullptr; } + +Node* Node::insert(int nWidth, int nHeight, int nPadding) +{ + if (!isLeaf()) + { + Node* pNewNode = mLeftNode->insert(nWidth, nHeight, nPadding); + + if (pNewNode != nullptr) + return pNewNode; + + return mRightNode->insert(nWidth, nHeight, nPadding); + } + else + { + if (mOccupied) + { + return nullptr; + } + + if (nWidth > mRectangle.GetWidth() || nHeight > mRectangle.GetHeight()) + { // does not fit + return nullptr; + } + + if (nWidth == mRectangle.GetWidth() && nHeight == mRectangle.GetHeight()) + { // perfect fit + mOccupied = true; + return this; + } + + int dw = mRectangle.GetWidth() - nWidth; + int dh = mRectangle.GetHeight() - nHeight; + + tools::Rectangle aLeftRect; + tools::Rectangle aRightRect; + if (dw > dh) + { + aLeftRect = tools::Rectangle(Point(mRectangle.Left(), mRectangle.Top()), + Size(nWidth, mRectangle.GetHeight())); + aRightRect = tools::Rectangle(Point(nPadding + mRectangle.Left() + nWidth, mRectangle.Top()), + Size(mRectangle.GetWidth() - nWidth - nPadding, mRectangle.GetHeight())); + } + else + { + aLeftRect = tools::Rectangle(Point(mRectangle.Left(), mRectangle.Top()), + Size(mRectangle.GetWidth(), nHeight)); + aRightRect = tools::Rectangle(Point(mRectangle.Left(), nPadding + mRectangle.Top() + nHeight), + Size(mRectangle.GetWidth(), mRectangle.GetHeight() - nHeight - nPadding)); + } + + mLeftNode.reset(new Node(aLeftRect)); + mRightNode.reset(new Node(aRightRect)); + + return mLeftNode->insert(nWidth, nHeight, nPadding); + } +} + +struct PackedTexture +{ + std::shared_ptr<ImplOpenGLTexture> mpTexture; + std::unique_ptr<Node> mpRootNode; + + PackedTexture(int nWidth, int nHeight) + : mpTexture(std::make_shared<ImplOpenGLTexture>(nWidth, nHeight, true)) + , mpRootNode(new Node(nWidth, nHeight)) + {} +}; + +PackedTextureAtlasManager::PackedTextureAtlasManager(int nTextureWidth, int nTextureHeight) + : mnTextureWidth(nTextureWidth) + , mnTextureHeight(nTextureHeight) +{ +} + +PackedTextureAtlasManager::~PackedTextureAtlasManager() +{ + for (std::unique_ptr<PackedTexture>& pPackedTexture : maPackedTextures) + { + // Free texture early in VCL shutdown while we have a context. + pPackedTexture->mpTexture.reset(); + } +} + +void PackedTextureAtlasManager::CreateNewTexture() +{ + std::unique_ptr<PackedTexture> pPackedTexture(new PackedTexture(mnTextureWidth, mnTextureHeight)); + GLuint nTextureID = pPackedTexture->mpTexture->mnTexture; + maPackedTextures.push_back(std::move(pPackedTexture)); + VCL_GL_INFO("PackedTextureAtlas::CreateNewTexture adding texture: " << nTextureID << + " atlases: " << maPackedTextures.size()); +} + +OpenGLTexture PackedTextureAtlasManager::Reserve(int nWidth, int nHeight) +{ + for (std::unique_ptr<PackedTexture>& pPackedTexture : maPackedTextures) + { + Node* pNode = pPackedTexture->mpRootNode->insert(nWidth, nHeight, 1); + if (pNode != nullptr) + { + return OpenGLTexture(pPackedTexture->mpTexture, pNode->mRectangle, -1); + } + } + CreateNewTexture(); + std::unique_ptr<PackedTexture>& pPackedTexture = maPackedTextures.back(); + Node* pNode = pPackedTexture->mpRootNode->insert(nWidth, nHeight, 1); + if (pNode != nullptr) + { + return OpenGLTexture(pPackedTexture->mpTexture, pNode->mRectangle, -1); + } + return OpenGLTexture(); +} + +OpenGLTexture PackedTextureAtlasManager::InsertBuffer(int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData) +{ + OpenGLTexture aTexture = Reserve(nWidth, nHeight); + if (aTexture && pData == nullptr) + return aTexture; + + aTexture.CopyData(nWidth, nHeight, nFormat, nType, pData); + + return aTexture; +} + +std::vector<GLuint> PackedTextureAtlasManager::ReduceTextureNumber(int nMaxNumberOfTextures) +{ + std::vector<GLuint> aTextureIDs; + while (int(maPackedTextures.size()) > nMaxNumberOfTextures) + { + // Remove oldest created texture + GLuint nTextureID = maPackedTextures[0]->mpTexture->mnTexture; + aTextureIDs.push_back(nTextureID); + maPackedTextures.erase(maPackedTextures.begin()); + VCL_GL_INFO("PackedTextureAtlas::ReduceTextureNumber removing texture: " << nTextureID << + " atlases: " << maPackedTextures.size()); + } + return aTextureIDs; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/README.deprecated b/vcl/opengl/README.deprecated new file mode 100644 index 000000000..eb033a0fd --- /dev/null +++ b/vcl/opengl/README.deprecated @@ -0,0 +1,23 @@ +deprecated features + +GL_LIGHTING +GL_TEXTURE_2D +GL_POINT_SMOOTH +GL_TEXTURE_WRAP_S +GL_TEXTURE_WRAP_T +glBegin +glEnd + + +GLSL + +texture*D +varying +attribute +missing version string + +gl_FragColor +gl_FragData +gl_Normal +gl_NormalMatrix +gl_Vertex diff --git a/vcl/opengl/README.opengl b/vcl/opengl/README.opengl new file mode 100644 index 000000000..231abbf36 --- /dev/null +++ b/vcl/opengl/README.opengl @@ -0,0 +1,26 @@ +Run LO with OpenGL enabled +-------------------------- +SAL_USE_VCLPLUGIN=gen SAL_FORCEGL=1 ./soffice + +Environment variables used: + +SAL_USE_VCLPLUGIN - use the specified VCL plugin (GTK2 in this case - currently +needed on Linux because the default GTK3 doesn't support OpenGL yet) + +SAL_FORCEGL - enable OpenGL even if the card is blacklisted. + +Other variables: + +LIBGL_ALWAYS_SOFTWARE=1 - on Linux+Mesa forces the software renderer to be used +(this is useful as an alternative to spot driver specific bugs) + +SAL_LOG=+INFO.vcl.opengl - if "--enable-dbgutil" is used, this can show OpenGL +various rendering messages. + +LD_PRELOAD=/usr/lib64/apitrace/wrappers/glxtrace.so - preload the wrapper for +APItrace. The path is the default used in Fedora 21+. + +Run VCLDemo +----------- + +SAL_USE_VCLPLUGIN=gen SAL_FORCEGL=1 ./bin/run vcldemo diff --git a/vcl/opengl/RenderList.cxx b/vcl/opengl/RenderList.cxx new file mode 100644 index 000000000..4830f1040 --- /dev/null +++ b/vcl/opengl/RenderList.cxx @@ -0,0 +1,403 @@ +/* -*- 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 <opengl/RenderList.hxx> +#include <opengl/VertexUtils.hxx> +#include <opengl/LineRenderUtils.hxx> + +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygontriangulator.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dtrapezoid.hxx> + +namespace +{ + +/** Append vertices for the polyline + * + * OpenGL polyline drawing algorithm inspired by: + * - http://mattdesl.svbtle.com/drawing-lines-is-hard + * - https://www.mapbox.com/blog/drawing-antialiased-lines/ + * - https://cesiumjs.org/2013/04/22/Robust-Polyline-Rendering-with-WebGL/ + * - http://artgrammer.blogspot.si/2011/05/drawing-nearly-perfect-2d-line-segments.html + * - http://artgrammer.blogspot.si/2011/07/drawing-polylines-by-tessellation.html + * + */ +void appendPolyLine(vcl::LineBuilder& rBuilder, const basegfx::B2DPolygon& rPolygon, + basegfx::B2DLineJoin eLineJoin, css::drawing::LineCap eLineCap, + double fMiterMinimumAngle) +{ + sal_uInt32 nPoints = rPolygon.count(); + bool bClosed = rPolygon.isClosed(); + + if (nPoints == 2 || eLineJoin == basegfx::B2DLineJoin::NONE) + { + // If line joint is NONE or a simple line with 2 points, draw the polyline + // each line segment separately. + + for (sal_uInt32 i = 0; i < (bClosed ? nPoints : nPoints - 1); ++i) + { + sal_uInt32 index1 = (i + 0) % nPoints; // loop indices - important when polyline is closed + sal_uInt32 index2 = (i + 1) % nPoints; + + glm::vec2 aPoint1(rPolygon.getB2DPoint(index1).getX(), rPolygon.getB2DPoint(index1).getY()); + glm::vec2 aPoint2(rPolygon.getB2DPoint(index2).getX(), rPolygon.getB2DPoint(index2).getY()); + + rBuilder.appendLine(aPoint1, aPoint2); + } + } + else if (nPoints > 2) + { + int i = 0; + int lastPoint = int(nPoints); + + glm::vec2 p0(rPolygon.getB2DPoint(nPoints - 1).getX(), rPolygon.getB2DPoint(nPoints - 1).getY()); + glm::vec2 p1(rPolygon.getB2DPoint(0).getX(), rPolygon.getB2DPoint(0).getY()); + glm::vec2 p2(rPolygon.getB2DPoint(1).getX(), rPolygon.getB2DPoint(1).getY()); + + glm::vec2 nextLineVector; + glm::vec2 previousLineVector; + glm::vec2 normal; // perpendicular to the line vector + + nextLineVector = vcl::vertex::normalize(p2 - p1); + + if (!bClosed) + { + normal = glm::vec2(-nextLineVector.y, nextLineVector.x); // make perpendicular + rBuilder.appendAndConnectLinePoint(p1, normal, 1.0f); + + i++; // first point done already + lastPoint--; // last point will be calculated separately from the loop + + p0 = p1; + previousLineVector = nextLineVector; + } + else + { + lastPoint++; // we need to connect last point to first point so one more line segment to calculate + previousLineVector = vcl::vertex::normalize(p1 - p0); + } + + for (; i < lastPoint; ++i) + { + int index1 = (i + 0) % nPoints; // loop indices - important when polyline is closed + int index2 = (i + 1) % nPoints; + + p1 = glm::vec2(rPolygon.getB2DPoint(index1).getX(), rPolygon.getB2DPoint(index1).getY()); + p2 = glm::vec2(rPolygon.getB2DPoint(index2).getX(), rPolygon.getB2DPoint(index2).getY()); + + if (p1 == p2) // skip equal points, normals could div-by-0 + continue; + + nextLineVector = vcl::vertex::normalize(p2 - p1); + + if (eLineJoin == basegfx::B2DLineJoin::Miter) + { + if (vcl::vertex::lineVectorAngle(previousLineVector, nextLineVector) < fMiterMinimumAngle) + rBuilder.appendBevelJoint(p1, previousLineVector, nextLineVector); + else + rBuilder.appendMiterJoint(p1, previousLineVector, nextLineVector); + } + else if (eLineJoin == basegfx::B2DLineJoin::Bevel) + { + rBuilder.appendBevelJoint(p1, previousLineVector, nextLineVector); + } + else if (eLineJoin == basegfx::B2DLineJoin::Round) + { + rBuilder.appendRoundJoint(p1, previousLineVector, nextLineVector); + } + p0 = p1; + previousLineVector = nextLineVector; + } + + if (!bClosed) + { + // Create vertices for the last point. There is no line join so just + // use the last line segment normal as the extrusion vector. + p1 = glm::vec2(rPolygon.getB2DPoint(nPoints - 1).getX(), rPolygon.getB2DPoint(nPoints - 1).getY()); + normal = glm::vec2(-previousLineVector.y, previousLineVector.x); + rBuilder.appendAndConnectLinePoint(p1, normal, 1.0f); + } + } + + if (!bClosed && nPoints >= 2 && (eLineCap == css::drawing::LineCap_ROUND || eLineCap == css::drawing::LineCap_SQUARE)) + { + glm::vec2 aBeginCapPoint1(rPolygon.getB2DPoint(0).getX(), rPolygon.getB2DPoint(0).getY()); + glm::vec2 aBeginCapPoint2(rPolygon.getB2DPoint(1).getX(), rPolygon.getB2DPoint(1).getY()); + + glm::vec2 aEndCapPoint1(rPolygon.getB2DPoint(nPoints - 1).getX(), rPolygon.getB2DPoint(nPoints - 1).getY()); + glm::vec2 aEndCapPoint2(rPolygon.getB2DPoint(nPoints - 2).getX(), rPolygon.getB2DPoint(nPoints - 2).getY()); + + if (eLineCap == css::drawing::LineCap_ROUND) + { + rBuilder.appendRoundLineCapVertices(aBeginCapPoint1, aBeginCapPoint2); + rBuilder.appendRoundLineCapVertices(aEndCapPoint1, aEndCapPoint2); + } + else if (eLineCap == css::drawing::LineCap_SQUARE) + { + rBuilder.appendSquareLineCapVertices(aBeginCapPoint1, aBeginCapPoint2); + rBuilder.appendSquareLineCapVertices(aEndCapPoint1, aEndCapPoint2); + } + } +} + +void appendTrapezoid(std::vector<Vertex>& rVertices, std::vector<GLuint>& rIndices, + GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, + GLfloat x3, GLfloat y3, GLfloat x4, GLfloat y4, + Color nColor, GLfloat fTransparency) +{ + GLubyte nR, nG, nB, nA; + vcl::vertex::createColor(nColor, fTransparency, nR, nG, nB, nA); + + GLuint zero = rVertices.size(); + + rVertices.insert(rVertices.end(), { + {glm::vec2{x1, y1}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}}, + {glm::vec2{x2, y2}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}}, + {glm::vec2{x3, y3}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}}, + {glm::vec2{x4, y4}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}}, + }); + + rIndices.insert(rIndices.end(), { + zero + 0, zero + 1, zero + 2, + zero + 2, zero + 1, zero + 3 + }); +} + +void appendRectangle(std::vector<Vertex>& rVertices, std::vector<GLuint>& rIndices, + GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2, + Color nColor, GLfloat fTransparency) +{ + GLubyte nR, nG, nB, nA; + vcl::vertex::createColor(nColor, fTransparency, nR, nG, nB, nA); + + GLuint zero = rVertices.size(); + + rVertices.insert(rVertices.end(), { + {glm::vec2{x1, y1}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}}, + {glm::vec2{x2, y1}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}}, + {glm::vec2{x1, y2}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}}, + {glm::vec2{x2, y2}, glm::vec4{nR, nG, nB, nA}, glm::vec4{0.0f, 0.0f, 0.0f, 0.0f}}, + }); + + rIndices.insert(rIndices.end(), { + zero + 0, zero + 1, zero + 2, + zero + 2, zero + 1, zero + 3 + }); +} + +} // end anonymous namespace + +void RenderList::addDrawPixel(long nX, long nY, Color nColor) +{ + if (nColor == SALCOLOR_NONE) + return; + + checkOverlapping(basegfx::B2DRange(nX, nY, nX, nY)); + + RenderParameters& rRenderParameter = maRenderEntries.back().maTriangleParameters; + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + nX - 0.5f, nY - 0.5f, nX + 0.5f, nY + 0.5f, nColor, 0.0f); +} + +void RenderList::addDrawRectangle(long nX, long nY, long nWidth, long nHeight, double fTransparency, + Color nLineColor, Color nFillColor) +{ + if (nLineColor == SALCOLOR_NONE && nFillColor == SALCOLOR_NONE) + return; + if (fTransparency == 1.0f) + return; + + GLfloat fX1(nX); + GLfloat fY1(nY); + GLfloat fX2(nX + nWidth - 1); + GLfloat fY2(nY + nHeight - 1); + + checkOverlapping(basegfx::B2DRange(fX1, fY1, fX2, fY2)); + + RenderParameters& rRenderParameter = maRenderEntries.back().maTriangleParameters; + + // Draw rectangle stroke with line color + if (nLineColor != SALCOLOR_NONE) + { + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + fX1 - 0.5f, fY1 - 0.5f, fX1 + 0.5f, fY2 + 0.5f, nLineColor, fTransparency); + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + fX1 - 0.5f, fY1 - 0.5f, fX2 + 0.5f, fY1 + 0.5f, nLineColor, fTransparency); + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + fX2 - 0.5f, fY1 - 0.5f, fX2 + 0.5f, fY2 + 0.5f, nLineColor, fTransparency); + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + fX1 - 0.5f, fY2 - 0.5f, fX2 + 0.5f, fY2 + 0.5f, nLineColor, fTransparency); + } + + if (nFillColor != SALCOLOR_NONE) + { + if (nLineColor == SALCOLOR_NONE) + { + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + fX1 - 0.5f, fY1 - 0.5f, fX1 + 0.5f, fY2 + 0.5f, nFillColor, fTransparency); + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + fX1 - 0.5f, fY1 - 0.5f, fX2 + 0.5f, fY1 + 0.5f, nFillColor, fTransparency); + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + fX2 - 0.5f, fY1 - 0.5f, fX2 + 0.5f, fY2 + 0.5f, nFillColor, fTransparency); + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + fX1 - 0.5f, fY2 - 0.5f, fX2 + 0.5f, fY2 + 0.5f, nFillColor, fTransparency); + } + // Draw rectangle fill with fill color + appendRectangle(rRenderParameter.maVertices, rRenderParameter.maIndices, + fX1 + 0.5f, fY1 + 0.5f, fX2 - 0.5f, fY2 - 0.5f, nFillColor, fTransparency); + } +} + +void RenderList::addDrawLine(long nX1, long nY1, long nX2, long nY2, Color nLineColor, bool bUseAA) +{ + if (nLineColor == SALCOLOR_NONE) + return; + + checkOverlapping(basegfx::B2DRange(nX1, nY1, nX2, nY2)); + + RenderParameters& rRenderParameter = maRenderEntries.back().maLineParameters; + + glm::vec2 aPoint1(nX1, nY1); + glm::vec2 aPoint2(nX2, nY2); + + vcl::LineBuilder aBuilder(rRenderParameter.maVertices, rRenderParameter.maIndices, nLineColor, 0.0f, 1.0f, bUseAA); + aBuilder.appendLine(aPoint1, aPoint2); +} + +void RenderList::addDrawPolyPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon, double fTransparency, + Color nLineColor, Color nFillColor, bool bUseAA) +{ + if (rPolyPolygon.count() <= 0) + return; + if (nLineColor == SALCOLOR_NONE && nFillColor == SALCOLOR_NONE) + return; + if (fTransparency == 1.0) + return; + + checkOverlapping(rPolyPolygon.getB2DRange()); + + if (nFillColor != SALCOLOR_NONE) + { + basegfx::B2DTrapezoidVector aTrapezoidVector; + basegfx::utils::trapezoidSubdivide(aTrapezoidVector, rPolyPolygon); + + if (!aTrapezoidVector.empty()) + { + RenderParameters& rTriangleRenderParameter = maRenderEntries.back().maTriangleParameters; + + for (const basegfx::B2DTrapezoid & rTrapezoid : aTrapezoidVector) + { + GLfloat topX1 = rTrapezoid.getTopXLeft(); + GLfloat topX2 = rTrapezoid.getTopXRight(); + GLfloat topY = rTrapezoid.getTopY(); + + GLfloat bottomX1 = rTrapezoid.getBottomXLeft(); + GLfloat bottomX2 = rTrapezoid.getBottomXRight(); + GLfloat bottomY = rTrapezoid.getBottomY(); + + appendTrapezoid(rTriangleRenderParameter.maVertices, rTriangleRenderParameter.maIndices, + topX1, topY, topX2, topY, + bottomX1, bottomY, bottomX2, bottomY, + nFillColor, fTransparency); + } + } + } + + if (nLineColor != SALCOLOR_NONE || bUseAA) + { + RenderParameters& rLineRenderParameter = maRenderEntries.back().maLineParameters; + Color nColor = (nLineColor == SALCOLOR_NONE) ? nFillColor : nLineColor; + + vcl::LineBuilder aBuilder(rLineRenderParameter.maVertices, rLineRenderParameter.maIndices, + nColor, fTransparency, 1.0f, bUseAA); + + for (const basegfx::B2DPolygon& rPolygon : rPolyPolygon) + { + basegfx::B2DPolygon aPolygon(rPolygon); + if (rPolygon.areControlPointsUsed()) + aPolygon = rPolygon.getDefaultAdaptiveSubdivision(); + + sal_uInt32 nPoints = aPolygon.count(); + if (nPoints <= 1) + continue; + + GLfloat x1, y1, x2, y2; + sal_uInt32 index1, index2; + + for (sal_uInt32 i = 0; i <= nPoints; ++i) + { + index1 = i % nPoints; + index2 = (i + 1) % nPoints; + + x1 = aPolygon.getB2DPoint(index1).getX(); + y1 = aPolygon.getB2DPoint(index1).getY(); + x2 = aPolygon.getB2DPoint(index2).getX(); + y2 = aPolygon.getB2DPoint(index2).getY(); + + aBuilder.appendLine(glm::vec2(x1, y1), glm::vec2(x2, y2)); + } + } + } +} + +void RenderList::addDrawTextureWithMaskColor(OpenGLTexture const & rTexture, Color nColor, const SalTwoRect& r2Rect) +{ + if (!rTexture) + return; + + GLfloat fX1 = r2Rect.mnDestX; + GLfloat fY1 = r2Rect.mnDestY; + GLfloat fX2 = fX1 + r2Rect.mnDestWidth; + GLfloat fY2 = fY1 + r2Rect.mnDestHeight; + + checkOverlapping(basegfx::B2DRange(fX1, fY1, fX2, fY2)); + + GLuint nTextureId = rTexture.Id(); + + RenderTextureParameters& rTextureParameter = maRenderEntries.back().maTextureParametersMap[nTextureId]; + rTextureParameter.maTexture = rTexture; + + rTexture.FillCoords<GL_TRIANGLES>(rTextureParameter.maTextureCoords, r2Rect); + + vcl::vertex::addRectangle<GL_TRIANGLES>(rTextureParameter.maVertices, fX1, fY1, fX2, fY2); + vcl::vertex::addQuadColors<GL_TRIANGLES>(rTextureParameter.maColors, nColor, 0.0f); +} + +void RenderList::addDrawPolyLine(const basegfx::B2DPolygon& rPolygon, double fTransparency, + double fLineWidth, basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, double fMiterMinimumAngle, + Color nLineColor, bool bUseAA) +{ + if (rPolygon.count() <= 1) + return; + if (nLineColor == SALCOLOR_NONE) + return; + if (fTransparency == 1.0) + return; + + const bool bIsHairline = fLineWidth <= 1.2; + fLineWidth = bIsHairline ? 1.0f : fLineWidth; + + basegfx::B2DPolygon aPolygon(rPolygon); + if (rPolygon.areControlPointsUsed()) + aPolygon = rPolygon.getDefaultAdaptiveSubdivision(); + + checkOverlapping(aPolygon.getB2DRange()); + + RenderParameters& rParameter = maRenderEntries.back().maLineParameters; + + vcl::LineBuilder aBuilder(rParameter.maVertices, rParameter.maIndices, + nLineColor, fTransparency, fLineWidth, bUseAA); + + appendPolyLine(aBuilder, aPolygon, eLineJoin, eLineCap, fMiterMinimumAngle); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/framebuffer.cxx b/vcl/opengl/framebuffer.cxx new file mode 100644 index 000000000..db957d1a6 --- /dev/null +++ b/vcl/opengl/framebuffer.cxx @@ -0,0 +1,108 @@ +/* -*- 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/log.hxx> + +#include <opengl/framebuffer.hxx> + +#include <vcl/opengl/OpenGLHelper.hxx> + +OpenGLFramebuffer::OpenGLFramebuffer() : + mnId( 0 ), + mnWidth( 0 ), + mnHeight( 0 ), + mnAttachedTexture( 0 ), + mpPrevFramebuffer( nullptr ) +{ + glGenFramebuffers( 1, &mnId ); + CHECK_GL_ERROR(); + VCL_GL_INFO( "Created framebuffer " << static_cast<int>(mnId) ); +} + +OpenGLFramebuffer::~OpenGLFramebuffer() +{ + glDeleteFramebuffers( 1, &mnId ); + VCL_GL_INFO( "Deleted framebuffer " << static_cast<int>(mnId) ); + CHECK_GL_ERROR(); +} + +void OpenGLFramebuffer::Bind(GLenum eTarget) +{ + VCL_GL_INFO( "Binding framebuffer " << static_cast<int>(mnId) ); + glBindFramebuffer(eTarget, mnId); + CHECK_GL_ERROR(); +} + +void OpenGLFramebuffer::Unbind(GLenum eTarget) +{ + glBindFramebuffer(eTarget, 0); + CHECK_GL_ERROR(); + VCL_GL_INFO( "Binding default framebuffer" ); +} + +bool OpenGLFramebuffer::IsFree() const +{ + return !mnAttachedTexture; +} + +bool OpenGLFramebuffer::IsAttached( GLuint nTexture ) const +{ + return mnAttachedTexture == nTexture; +} + +bool OpenGLFramebuffer::IsAttached( const OpenGLTexture& rTexture ) const +{ + return mnAttachedTexture == rTexture.Id(); +} + +void OpenGLFramebuffer::AttachTexture( const OpenGLTexture& rTexture ) +{ + if( rTexture.Id() == mnAttachedTexture ) + return; + + VCL_GL_INFO( "Attaching texture " << rTexture.Id() << " to framebuffer " << static_cast<int>(mnId) ); + mnAttachedTexture = rTexture.Id(); + mnWidth = rTexture.GetWidth(); + mnHeight = rTexture.GetHeight(); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mnAttachedTexture, 0); + CHECK_GL_ERROR(); + + GLuint nStencil = rTexture.StencilId(); + if( nStencil ) + { + VCL_GL_INFO( "Attaching stencil " << nStencil << " to framebuffer " << static_cast<int>(mnId) ); + glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, nStencil ); + CHECK_GL_ERROR(); + } + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + CHECK_GL_ERROR(); + if (status != GL_FRAMEBUFFER_COMPLETE) + { + SAL_WARN("vcl.opengl", "Framebuffer incomplete"); + } +} + +void OpenGLFramebuffer::DetachTexture() +{ + if( mnAttachedTexture != 0 ) + { + mnAttachedTexture = 0; + glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0 ); + CHECK_GL_ERROR(); + + // FIXME: we could make this conditional on having a stencil ? + glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, 0 ); + CHECK_GL_ERROR(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/gdiimpl.cxx b/vcl/opengl/gdiimpl.cxx new file mode 100644 index 000000000..6c76154ea --- /dev/null +++ b/vcl/opengl/gdiimpl.cxx @@ -0,0 +1,2315 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <opengl/gdiimpl.hxx> +#include <opengl/framebuffer.hxx> + +#include <vcl/gradient.hxx> +#include <vcl/idle.hxx> +#include <salframe.hxx> +#include <salvd.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontriangulator.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <basegfx/polygon/b2dtrapezoid.hxx> +#include <sal/log.hxx> + +#include <vcl/opengl/OpenGLHelper.hxx> +#include <salgdi.hxx> +#include <svdata.hxx> +#include <opengl/zone.hxx> +#include <opengl/salbmp.hxx> +#include <opengl/RenderState.hxx> +#include <opengl/VertexUtils.hxx> +#include <opengl/BufferObject.hxx> + +#include <cmath> +#include <vector> +#include <numeric> + +#include <glm/gtc/type_ptr.hpp> +#include <glm/gtx/norm.hpp> + +#include <stdlib.h> + +class OpenGLFlushIdle : public Idle +{ + OpenGLSalGraphicsImpl *m_pImpl; +public: + explicit OpenGLFlushIdle( OpenGLSalGraphicsImpl *pImpl ) + : Idle( "gl idle swap" ) + , m_pImpl( pImpl ) + { + // We don't want to be swapping before we've painted. + SetPriority( TaskPriority::POST_PAINT ); + } + + virtual void Invoke() override + { + m_pImpl->doFlush(); + Stop(); + SetPriority(TaskPriority::HIGHEST); + } +}; + +OpenGLSalGraphicsImpl::OpenGLSalGraphicsImpl(SalGraphics& rParent, SalGeometryProvider *pProvider) + : mrParent(rParent) + , mpProvider(pProvider) + , mpProgram(nullptr) + , mpFlush(new OpenGLFlushIdle(this)) + , mbUseScissor(false) + , mbUseStencil(false) + , mbXORMode(false) + , mbAcquiringOpenGLContext(false) + , mnLineColor(SALCOLOR_NONE) + , mnFillColor(SALCOLOR_NONE) +#ifdef DBG_UTIL + , mProgramIsSolidColor(false) +#endif + , mnDrawCount(0) + , mnDrawCountAtFlush(0) + , mProgramSolidColor(SALCOLOR_NONE) + , mProgramSolidTransparency(0.0) + , mpRenderList(new RenderList) +{ +} + +OpenGLSalGraphicsImpl::~OpenGLSalGraphicsImpl() +{ + if( !IsOffscreen() && mnDrawCountAtFlush != mnDrawCount ) + VCL_GL_INFO( "Destroying un-flushed on-screen graphics" ); + + mpFlush.reset(); + + ReleaseContext(); +} + +rtl::Reference<OpenGLContext> OpenGLSalGraphicsImpl::GetOpenGLContext() +{ + if (mbAcquiringOpenGLContext) + return nullptr; + mbAcquiringOpenGLContext = true; + bool bSuccess = AcquireContext(true); + mbAcquiringOpenGLContext = false; + if (!bSuccess) + return nullptr; + return mpContext; +} + +bool OpenGLSalGraphicsImpl::AcquireContext( bool bForceCreate ) +{ + mpContext = OpenGLContext::getVCLContext( false ); + + if( !mpContext.is() && mpWindowContext.is() ) + { + mpContext = mpWindowContext; + } + else if( bForceCreate && !IsOffscreen() ) + { + mpWindowContext = CreateWinContext(); + mpContext = mpWindowContext; + } + + if( !mpContext.is() ) + mpContext = OpenGLContext::getVCLContext(); + + return mpContext.is(); +} + +void OpenGLSalGraphicsImpl::ReleaseContext() +{ + mpContext.clear(); +} + +void OpenGLSalGraphicsImpl::Init() +{ + // Our init phase is strange ::Init is called twice for vdevs. + // the first time around with a NULL geometry provider. + if( !mpProvider ) + return; + + // check if we can simply re-use the same context + if( mpContext.is() ) + { + if( !UseContext( mpContext ) ) + ReleaseContext(); + } + + // Always create the offscreen texture + if( maOffscreenTex.GetWidth() != GetWidth() || + maOffscreenTex.GetHeight() != GetHeight() ) + { + // We don't want to be swapping before we've painted. + mpFlush->SetPriority( TaskPriority::POST_PAINT ); + + if( maOffscreenTex && // don't work to release empty textures + mpContext.is() ) // valid context + { + mpContext->makeCurrent(); + mpContext->ReleaseFramebuffer( maOffscreenTex ); + } + maOffscreenTex = OpenGLTexture(); + VCL_GL_INFO("::Init - re-size offscreen texture"); + } + + if( mpWindowContext.is() ) + { + mpWindowContext->reset(); + mpWindowContext.clear(); + } +} + +// Currently only used to get windows ordering right. +void OpenGLSalGraphicsImpl::DeInit() +{ + VCL_GL_INFO("::DeInit"); + + FlushDeferredDrawing(); + + // tdf#93839: + // Our window handles and resources are being free underneath us. + // These can be bound into a context, which relies on them. So + // let it know. Other eg. VirtualDevice contexts which have + // references on and rely on this context continuing to work will + // get a shiny new context in AcquireContext:: next PreDraw. + if( mpWindowContext.is() ) + { + mpWindowContext->reset(); + mpWindowContext.clear(); + } + mpContext.clear(); +} + +void OpenGLSalGraphicsImpl::PreDraw(XOROption eOpt) +{ + FlushDeferredDrawing(); + + InitializePreDrawState(eOpt); +} + +void OpenGLSalGraphicsImpl::InitializePreDrawState(XOROption eOpt) +{ + OpenGLZone::enter(); + + mnDrawCount++; + + if( !AcquireContext() ) + { + SAL_WARN( "vcl.opengl", "Couldn't acquire context" ); + return; + } + + mpContext->makeCurrent(); + CHECK_GL_ERROR(); + + CheckOffscreenTexture(); + CHECK_GL_ERROR(); + + mpContext->state().viewport(tools::Rectangle(Point(0, 0), Size(GetWidth(), GetHeight()))); + + ImplInitClipRegion(); + CHECK_GL_ERROR(); + + if (eOpt == IMPLEMENT_XOR && mbXORMode) + { + glEnable(GL_COLOR_LOGIC_OP); + CHECK_GL_ERROR(); + + glLogicOp(GL_XOR); + CHECK_GL_ERROR(); + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); + CHECK_GL_ERROR(); + } +} + +void OpenGLSalGraphicsImpl::PostDraw() +{ + if (mbXORMode) + { + glDisable(GL_COLOR_LOGIC_OP); + CHECK_GL_ERROR(); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + CHECK_GL_ERROR(); + } + + if( mpProgram ) + { + mpProgram->Clean(); + mpProgram = nullptr; +#ifdef DBG_UTIL + mProgramIsSolidColor = false; +#endif + } + + assert (maOffscreenTex); + + // Always queue the flush. + if( !IsOffscreen() ) + flush(); + + OpenGLZone::leave(); +} + +void OpenGLSalGraphicsImpl::PostBatchDraw() +{ + if (IsOffscreen()) + return; + + if (!mpFlush->IsActive()) + mpFlush->Start(); +} + +void OpenGLSalGraphicsImpl::ApplyProgramMatrices(float fPixelOffset) +{ + mpProgram->ApplyMatrix(GetWidth(), GetHeight(), fPixelOffset); +} + +void OpenGLSalGraphicsImpl::freeResources() +{ + // TODO Delete shaders, programs and textures if not shared + if( mpContext.is() && mpContext->isInitialized() ) + { + VCL_GL_INFO( "freeResources" ); + mpContext->makeCurrent(); + FlushDeferredDrawing(); + mpContext->ReleaseFramebuffer( maOffscreenTex ); + } + ReleaseContext(); +} + +void OpenGLSalGraphicsImpl::ImplSetClipBit( const vcl::Region& rClip, GLuint nMask ) +{ + mpContext->state().scissor().disable(); + mpContext->state().stencil().enable(); + + VCL_GL_INFO( "Adding complex clip / stencil" ); + GLuint nStencil = maOffscreenTex.StencilId(); + if( nStencil == 0 ) + { + nStencil = maOffscreenTex.AddStencil(); + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, nStencil ); + CHECK_GL_ERROR(); + } + // else - we associated the stencil in + // AcquireFrameBuffer / AttachTexture + + CHECK_GL_ERROR(); + glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + CHECK_GL_ERROR(); + glStencilMask( nMask ); + CHECK_GL_ERROR(); + glStencilFunc( GL_NEVER, nMask, 0xFF ); + CHECK_GL_ERROR(); + glStencilOp( GL_REPLACE, GL_KEEP, GL_KEEP ); + CHECK_GL_ERROR(); + + glClear( GL_STENCIL_BUFFER_BIT ); + CHECK_GL_ERROR(); + if( UseSolid( Color( 0xFF, 0xFF, 0xFF ) ) ) + { + if( rClip.getRegionBand() ) + DrawRegionBand( *rClip.getRegionBand() ); + else + DrawPolyPolygon( rClip.GetAsB2DPolyPolygon(), true ); + } + + glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + CHECK_GL_ERROR(); + glStencilMask( 0x00 ); + CHECK_GL_ERROR(); + + mpContext->state().stencil().disable(); +} + +void OpenGLSalGraphicsImpl::ImplInitClipRegion() +{ + // make sure the context has the right clipping set + if (maClipRegion != mpContext->maClipRegion) + { + mpContext->maClipRegion = maClipRegion; + if (mbUseStencil) + { + ImplSetClipBit(maClipRegion, 0x01); + } + } + + if (mbUseScissor) + { + tools::Rectangle aRect(maClipRegion.GetBoundRect()); + mpContext->state().scissor().set(aRect.Left(), GetHeight() - aRect.Bottom() - 1, aRect.GetWidth(), aRect.GetHeight()); + mpContext->state().scissor().enable(); + } + else + { + mpContext->state().scissor().disable(); + } + + if (mbUseStencil) + { + glStencilFunc( GL_EQUAL, 1, 0x1 ); + CHECK_GL_ERROR(); + mpContext->state().stencil().enable(); + } + else + { + mpContext->state().stencil().disable(); + } +} + +const vcl::Region& OpenGLSalGraphicsImpl::getClipRegion() const +{ + return maClipRegion; +} + +bool OpenGLSalGraphicsImpl::setClipRegion( const vcl::Region& rClip ) +{ + if (maClipRegion == rClip) + { + VCL_GL_INFO("::setClipRegion (no change) " << rClip); + return true; + } + + FlushDeferredDrawing(); + + VCL_GL_INFO("::setClipRegion " << rClip); + + maClipRegion = rClip; + + mbUseStencil = false; + mbUseScissor = false; + if (maClipRegion.IsRectangle()) + mbUseScissor = true; + else if (!maClipRegion.IsEmpty()) + mbUseStencil = true; + + return true; +} + +// set the clip region to empty +void OpenGLSalGraphicsImpl::ResetClipRegion() +{ + if (maClipRegion.IsEmpty()) + { + VCL_GL_INFO("::ResetClipRegion (no change) "); + return; + } + + FlushDeferredDrawing(); + + VCL_GL_INFO("::ResetClipRegion"); + + maClipRegion.SetEmpty(); + mbUseScissor = false; + mbUseStencil = false; +} + +// get the depth of the device +sal_uInt16 OpenGLSalGraphicsImpl::GetBitCount() const +{ + return 32; +} + +// get the width of the device +long OpenGLSalGraphicsImpl::GetGraphicsWidth() const +{ + return GetWidth(); +} + +// set the line color to transparent (= don't draw lines) +void OpenGLSalGraphicsImpl::SetLineColor() +{ + if( mnLineColor != SALCOLOR_NONE ) + { + mnLineColor = SALCOLOR_NONE; + } +} + +// set the line color to a specific color +void OpenGLSalGraphicsImpl::SetLineColor( Color nColor ) +{ + if( mnLineColor != nColor ) + { + mnLineColor = nColor; + } +} + +// set the fill color to transparent (= don't fill) +void OpenGLSalGraphicsImpl::SetFillColor() +{ + if( mnFillColor != SALCOLOR_NONE ) + { + mnFillColor = SALCOLOR_NONE; + } +} + +// set the fill color to a specific color, shapes will be +// filled accordingly +void OpenGLSalGraphicsImpl::SetFillColor( Color nColor ) +{ + if( mnFillColor != nColor ) + { + mnFillColor = nColor; + } +} + +// enable/disable XOR drawing +void OpenGLSalGraphicsImpl::SetXORMode( bool bSet, bool ) +{ + if (mbXORMode != bSet) + { + FlushDeferredDrawing(); + mbXORMode = bSet; + } +} + +void OpenGLSalGraphicsImpl::SetROPLineColor(SalROPColor nROPColor) +{ + switch (nROPColor) + { + case SalROPColor::N0: + mnLineColor = Color(0, 0, 0); + break; + case SalROPColor::N1: + mnLineColor = Color(0xff, 0xff, 0xff); + break; + case SalROPColor::Invert: + mnLineColor = Color(0xff, 0xff, 0xff); + break; + } +} + +void OpenGLSalGraphicsImpl::SetROPFillColor(SalROPColor nROPColor) +{ + switch (nROPColor) + { + case SalROPColor::N0: + mnFillColor = Color(0, 0, 0); + break; + case SalROPColor::N1: + mnFillColor = Color(0xff, 0xff, 0xff); + break; + case SalROPColor::Invert: + mnFillColor = Color(0xff, 0xff, 0xff); + break; + } +} + +void OpenGLSalGraphicsImpl::CheckOffscreenTexture() +{ + bool bClearTexture = false; + + VCL_GL_INFO( "Check Offscreen texture" ); + + // Always create the offscreen texture + if( maOffscreenTex ) + { + if( maOffscreenTex.GetWidth() != GetWidth() || + maOffscreenTex.GetHeight() != GetHeight() ) + { + VCL_GL_INFO( "re-size offscreen texture " << maOffscreenTex.Id() ); + mpFlush->SetPriority( TaskPriority::POST_PAINT ); + mpContext->ReleaseFramebuffer( maOffscreenTex ); + maOffscreenTex = OpenGLTexture(); + } + } + + if( !maOffscreenTex ) + { + VCL_GL_INFO( "create texture of size " + << GetWidth() << " x " << GetHeight() ); + maOffscreenTex = OpenGLTexture( GetWidth(), GetHeight() ); + bClearTexture = true; + } + + if( !maOffscreenTex.IsUnique() ) + { + GLfloat fWidth = GetWidth(); + GLfloat fHeight = GetHeight(); + SalTwoRect aPosAry(0, 0, fWidth, fHeight, 0,0, fWidth, fHeight); + + // TODO: lfrb: User GL_ARB_copy_image? + OpenGLTexture aNewTex( GetWidth(), GetHeight() ); + + mpContext->state().scissor().disable(); + mpContext->state().stencil().disable(); + + mpContext->AcquireFramebuffer( aNewTex ); + DrawTexture( maOffscreenTex, aPosAry ); + maOffscreenTex = aNewTex; + } + else + { + mpContext->AcquireFramebuffer( maOffscreenTex ); + CHECK_GL_ERROR(); + + if( bClearTexture ) + { + glDrawBuffer( GL_COLOR_ATTACHMENT0 ); +#if OSL_DEBUG_LEVEL > 0 // lets have some red debugging background. + GLfloat const clearColor[4] = { 1.0, 0, 0, 0 }; +#else + GLfloat const clearColor[4] = { 1.0, 1.0, 1.0, 0 }; +#endif + glClearBufferfv( GL_COLOR, 0, clearColor ); + // FIXME: use glClearTexImage if we have it ? + } + } + + assert( maOffscreenTex ); + + CHECK_GL_ERROR(); +} + +bool OpenGLSalGraphicsImpl::UseProgram( const OUString& rVertexShader, const OUString& rFragmentShader, const OString& preamble ) +{ + if( mpProgram != nullptr ) + mpProgram->Clean(); + mpProgram = mpContext->UseProgram( rVertexShader, rFragmentShader, preamble ); +#ifdef DBG_UTIL + mProgramIsSolidColor = false; // UseSolid() will set to true if needed +#endif + return ( mpProgram != nullptr ); +} + +bool OpenGLSalGraphicsImpl::UseSolid( Color nColor, sal_uInt8 nTransparency ) +{ + if( nColor == SALCOLOR_NONE ) + return false; + UseSolid(); + mpProgram->SetColor( "color", nColor, nTransparency ); +#ifdef DBG_UTIL + mProgramIsSolidColor = true; +#endif + mProgramSolidColor = nColor; + mProgramSolidTransparency = nTransparency / 100.0; + + return true; +} + +bool OpenGLSalGraphicsImpl::UseSolid( Color nColor, double fTransparency ) +{ + if( nColor == SALCOLOR_NONE ) + return false; + UseSolid(); + mpProgram->SetColorf( "color", nColor, fTransparency ); +#ifdef DBG_UTIL + mProgramIsSolidColor = true; +#endif + mProgramSolidColor = nColor; + mProgramSolidTransparency = fTransparency; + return true; +} + +void OpenGLSalGraphicsImpl::UseSolid() +{ + if (!UseProgram("combinedVertexShader", "combinedFragmentShader")) + return; + mpProgram->SetShaderType(DrawShaderType::Normal); +} + +bool OpenGLSalGraphicsImpl::UseInvert50() +{ + return UseProgram( "dumbVertexShader", "invert50FragmentShader" ); +} + +bool OpenGLSalGraphicsImpl::UseSolid( Color nColor ) +{ + return UseSolid( nColor, 0.0f ); +} + +bool OpenGLSalGraphicsImpl::UseInvert( SalInvert nFlags ) +{ + OpenGLZone aZone; + + if( ( nFlags & SalInvert::N50 ) || + ( nFlags & SalInvert::TrackFrame ) ) + { + // FIXME: Trackframe really should be 2 pix. on/off stipple. + if( !UseInvert50() ) + return false; + mpProgram->SetBlendMode( GL_ONE_MINUS_DST_COLOR, + GL_ONE_MINUS_SRC_COLOR ); + } + else + { + if( !UseSolid( Color( 255, 255, 255 ) ) ) + return false; + mpProgram->SetBlendMode( GL_ONE_MINUS_DST_COLOR, GL_ZERO ); + } + return true; +} + +void OpenGLSalGraphicsImpl::DrawLineSegment(float x1, float y1, float x2, float y2) +{ + std::vector<GLfloat> aVertices; + std::vector<GLfloat> aExtrusionVectors; + + OpenGLZone aZone; + + glm::vec2 aPoint1(x1, y1); + glm::vec2 aPoint2(x2, y2); + + glm::vec2 aLineVector = vcl::vertex::normalize(aPoint2 - aPoint1); + glm::vec2 aNormal(-aLineVector.y, aLineVector.x); + + vcl::vertex::addLineSegmentVertices(aVertices, aExtrusionVectors, + aPoint1, aNormal, 1.0f, + aPoint2, aNormal, 1.0f); + + ApplyProgramMatrices(0.5f); + mpProgram->SetExtrusionVectors(aExtrusionVectors.data()); + mpProgram->DrawArrays(GL_TRIANGLES, aVertices); + + CHECK_GL_ERROR(); +} + +bool OpenGLSalGraphicsImpl::UseLine(Color nColor, double fTransparency, GLfloat fLineWidth, bool bUseAA) +{ + if( nColor == SALCOLOR_NONE ) + return false; + UseLine(fLineWidth, bUseAA); + mpProgram->SetColorf("color", nColor, fTransparency); +#ifdef DBG_UTIL + mProgramIsSolidColor = true; +#endif + mProgramSolidColor = nColor; + mProgramSolidTransparency = fTransparency; + return true; +} + +void OpenGLSalGraphicsImpl::UseLine(GLfloat fLineWidth, bool bUseAA) +{ + if (!UseProgram("combinedVertexShader", "combinedFragmentShader")) + return; + mpProgram->SetShaderType(DrawShaderType::Line); + mpProgram->SetUniform1f("line_width", fLineWidth); + // The width of the feather - area we make linearly transparent in VS. + // Good AA value is 0.5f, no AA if feather 0.0f + mpProgram->SetUniform1f("feather", bUseAA ? 0.5f : 0.0f); + // We need blending or AA won't work correctly + mpProgram->SetBlendMode(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +void OpenGLSalGraphicsImpl::DrawConvexPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry, bool blockAA ) +{ + OpenGLZone aZone; + + std::vector<GLfloat> aVertices(nPoints * 2); + sal_uInt32 i, j; + + for( i = 0, j = 0; i < nPoints; i++, j += 2 ) + { + aVertices[j] = GLfloat(pPtAry[i].mnX); + aVertices[j+1] = GLfloat(pPtAry[i].mnY); + } + + ApplyProgramMatrices(); + std::vector<GLfloat> aExtrusion(nPoints * 3, 0); + mpProgram->SetExtrusionVectors(aExtrusion.data()); + mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices); + CHECK_GL_ERROR(); + + if( !blockAA && mrParent.getAntiAliasB2DDraw()) + { + // Make the edges antialiased by drawing the edge lines again with AA. + // TODO: If transparent drawing is set up, drawing the lines themselves twice + // may be a problem, if that is a real problem, the polygon areas itself needs to be + // masked out for this or something. +#ifdef DBG_UTIL + assert( mProgramIsSolidColor ); +#endif + Color lastSolidColor = mProgramSolidColor; + double lastSolidTransparency = mProgramSolidTransparency; + if (UseLine(lastSolidColor, lastSolidTransparency, 1.0f, true)) + { + for( i = 0; i < nPoints; ++i ) + { + const SalPoint& rPt1 = pPtAry[ i ]; + const SalPoint& rPt2 = pPtAry[ ( i + 1 ) % nPoints ]; + DrawLineSegment(rPt1.mnX, rPt1.mnY, rPt2.mnX, rPt2.mnY); + } + UseSolid( lastSolidColor, lastSolidTransparency ); + } + } +} + +void OpenGLSalGraphicsImpl::DrawConvexPolygon( const tools::Polygon& rPolygon, bool blockAA ) +{ + OpenGLZone aZone; + + sal_uInt16 nPoints = rPolygon.GetSize() - 1; + std::vector<GLfloat> aVertices(nPoints * 2); + sal_uInt32 i, j; + + for( i = 0, j = 0; i < nPoints; i++, j += 2 ) + { + const Point& rPt = rPolygon.GetPoint( i ); + aVertices[j] = GLfloat(rPt.X()); + aVertices[j+1] = GLfloat(rPt.Y()); + } + + ApplyProgramMatrices(); + std::vector<GLfloat> aExtrusion(nPoints * 3, 0); + mpProgram->SetExtrusionVectors(aExtrusion.data()); + mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices); + CHECK_GL_ERROR(); + + if( !blockAA && mrParent.getAntiAliasB2DDraw()) + { + // Make the edges antialiased by drawing the edge lines again with AA. + // TODO: If transparent drawing is set up, drawing the lines themselves twice + // may be a problem, if that is a real problem, the polygon areas itself needs to be + // masked out for this or something. +#ifdef DBG_UTIL + assert( mProgramIsSolidColor ); +#endif + Color lastSolidColor = mProgramSolidColor; + double lastSolidTransparency = mProgramSolidTransparency; + if (UseLine(lastSolidColor, lastSolidTransparency, 1.0f, true)) + { + for( i = 0; i < nPoints; ++i ) + { + const Point& rPt1 = rPolygon.GetPoint( i ); + const Point& rPt2 = rPolygon.GetPoint(( i + 1 ) % nPoints ); + DrawLineSegment(rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY()); + } + UseSolid( lastSolidColor, lastSolidTransparency ); + } + } +} + +void OpenGLSalGraphicsImpl::DrawTrapezoid( const basegfx::B2DTrapezoid& trapezoid, bool blockAA ) +{ + OpenGLZone aZone; + + const basegfx::B2DPolygon& rPolygon = trapezoid.getB2DPolygon(); + sal_uInt16 nPoints = rPolygon.count(); + std::vector<GLfloat> aVertices(nPoints * 2); + sal_uInt32 i, j; + + for( i = 0, j = 0; i < nPoints; i++, j += 2 ) + { + const basegfx::B2DPoint& rPt = rPolygon.getB2DPoint( i ); + aVertices[j] = GLfloat(rPt.getX()); + aVertices[j+1] = GLfloat(rPt.getY()); + } + + if (!mpProgram) + { + SAL_WARN("vcl.opengl", "OpenGLSalGraphicsImpl::DrawTrapezoid: mpProgram is 0"); + return; + } + + ApplyProgramMatrices(); + std::vector<GLfloat> aExtrusion(nPoints * 3, 0); + mpProgram->SetExtrusionVectors(aExtrusion.data()); + mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices); + CHECK_GL_ERROR(); + + if( !blockAA && mrParent.getAntiAliasB2DDraw()) + { + // Make the edges antialiased by drawing the edge lines again with AA. + // TODO: If transparent drawing is set up, drawing the lines themselves twice + // may be a problem, if that is a real problem, the polygon areas itself needs to be + // masked out for this or something. +#ifdef DBG_UTIL + assert( mProgramIsSolidColor ); +#endif + Color lastSolidColor = mProgramSolidColor; + double lastSolidTransparency = mProgramSolidTransparency; + if (UseLine(lastSolidColor, lastSolidTransparency, 1.0f, true)) + { + for( i = 0; i < nPoints; ++i ) + { + const basegfx::B2DPoint& rPt1 = rPolygon.getB2DPoint( i ); + const basegfx::B2DPoint& rPt2 = rPolygon.getB2DPoint(( i + 1 ) % nPoints ); + DrawLineSegment(rPt1.getX(), rPt1.getY(), rPt2.getX(), rPt2.getY()); + } + UseSolid( lastSolidColor, lastSolidTransparency ); + } + } +} + +void OpenGLSalGraphicsImpl::DrawRect( long nX, long nY, long nWidth, long nHeight ) +{ + long nX1( nX ); + long nY1( nY ); + long nX2( nX + nWidth ); + long nY2( nY + nHeight ); + const SalPoint aPoints[] = { { nX1, nY2 }, { nX1, nY1 }, + { nX2, nY1 }, { nX2, nY2 }}; + + DrawConvexPolygon( 4, aPoints, true ); +} + +void OpenGLSalGraphicsImpl::DrawRect( const tools::Rectangle& rRect ) +{ + long nX1( rRect.Left() ); + long nY1( rRect.Top() ); + long nX2( rRect.Right() ); + long nY2( rRect.Bottom() ); + const SalPoint aPoints[] = { { nX1, nY2 }, { nX1, nY1 }, + { nX2, nY1 }, { nX2, nY2 }}; + + DrawConvexPolygon( 4, aPoints, true ); +} + +void OpenGLSalGraphicsImpl::DrawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) +{ + basegfx::B2DPolygon aPolygon; + + for( sal_uInt32 i = 0; i < nPoints; i++ ) + aPolygon.append( basegfx::B2DPoint( pPtAry[i].mnX, pPtAry[i].mnY ) ); + aPolygon.setClosed( true ); + + if( basegfx::utils::isConvex( aPolygon ) ) + { + if( nPoints > 2 ) + DrawConvexPolygon( nPoints, pPtAry ); + } + else + { + const basegfx::B2DPolyPolygon aPolyPolygon( aPolygon ); + DrawPolyPolygon( aPolyPolygon ); + } +} + +void OpenGLSalGraphicsImpl::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon, bool blockAA ) +{ + const basegfx::B2DPolyPolygon& aSimplePolyPolygon = ::basegfx::utils::solveCrossovers( rPolyPolygon ); + basegfx::B2DTrapezoidVector aB2DTrapVector; + basegfx::utils::trapezoidSubdivide( aB2DTrapVector, aSimplePolyPolygon ); + // draw tessellation result + for(const basegfx::B2DTrapezoid & i : aB2DTrapVector) + DrawTrapezoid( i, blockAA ); +} + +void OpenGLSalGraphicsImpl::DrawRegionBand( const RegionBand& rRegion ) +{ + OpenGLZone aZone; + + RectangleVector aRects; + std::vector<GLfloat> aVertices; + rRegion.GetRegionRectangles( aRects ); + + if( aRects.empty() ) + return; + +#define ADD_VERTICE(pt) \ + aVertices.push_back(GLfloat(pt.X())); \ + aVertices.push_back(GLfloat(pt.Y())); + + for(tools::Rectangle & rRect : aRects) + { + rRect.AdjustBottom(1 ); + rRect.AdjustRight(1 ); + ADD_VERTICE( rRect.TopLeft() ); + ADD_VERTICE( rRect.TopRight() ); + ADD_VERTICE( rRect.BottomLeft() ); + ADD_VERTICE( rRect.BottomLeft() ); + ADD_VERTICE( rRect.TopRight() ); + ADD_VERTICE( rRect.BottomRight() ); + } +#undef ADD_VERTICE + std::vector<GLfloat> aExtrusion(aRects.size() * 6 * 3, 0); + mpProgram->SetExtrusionVectors(aExtrusion.data()); + ApplyProgramMatrices(); + mpProgram->DrawArrays(GL_TRIANGLES, aVertices); + CHECK_GL_ERROR(); +} + +void OpenGLSalGraphicsImpl::DrawTextureRect( const SalTwoRect& rPosAry ) +{ + OpenGLZone aZone; + + SAL_INFO("vcl.opengl", "draw texture rect"); + + long nX = rPosAry.mnDestX; + long nY = rPosAry.mnDestY; + long nWidth = rPosAry.mnDestWidth; + long nHeight = rPosAry.mnDestHeight; + + std::vector<GLfloat> aVertices; + aVertices.reserve(8); + vcl::vertex::addRectangle<GL_TRIANGLE_FAN>(aVertices, nX, nY, nX + nWidth, nY + nHeight); + + ApplyProgramMatrices(); + mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices); + CHECK_GL_ERROR(); +} + +void OpenGLSalGraphicsImpl::DrawTexture( OpenGLTexture& rTexture, const SalTwoRect& rPosAry, bool bInverted ) +{ + OpenGLZone aZone; + + SAL_INFO("vcl.opengl", "draw texture"); + + if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader")) + return; + mpProgram->SetShaderType(TextureShaderType::Normal); + mpProgram->SetIdentityTransform("transform"); + mpProgram->SetTexture("texture", rTexture); + + GLfloat aTexCoord[8]; + rTexture.GetCoord(aTexCoord, rPosAry, bInverted); + mpProgram->SetTextureCoord(aTexCoord); + mpProgram->SetMaskCoord(aTexCoord); + mpProgram->SetAlphaCoord(aTexCoord); + + DrawTextureRect( rPosAry ); + mpProgram->Clean(); +} + +namespace { + +bool scaleTexture(const rtl::Reference< OpenGLContext > &xContext, + OpenGLTexture& rOutTexture, const double& ixscale, const double& iyscale, OpenGLTexture& rTexture) +{ + int nWidth = rTexture.GetWidth(); + int nHeight = rTexture.GetHeight(); + if (nWidth == 0 || nHeight == 0) + return false; + + int nNewWidth = nWidth / ixscale; + int nNewHeight = nHeight / iyscale; + + OString sUseReducedRegisterVariantDefine; + if (xContext->getOpenGLCapabilitySwitch().mbLimitedShaderRegisters) + sUseReducedRegisterVariantDefine = OString("#define USE_REDUCED_REGISTER_VARIANT\n"); + + OpenGLProgram* pProgram = xContext->UseProgram("textureVertexShader", "areaScaleFragmentShader", sUseReducedRegisterVariantDefine); + if (pProgram == nullptr) + return false; + + OpenGLTexture aScratchTex(nNewWidth, nNewHeight); + OpenGLFramebuffer* pFramebuffer = xContext->AcquireFramebuffer(aScratchTex); + + // From OpenGLSalBitmap::ImplScaleArea(). + pProgram->SetUniform1f("xscale", ixscale); + pProgram->SetUniform1f("yscale", iyscale); + pProgram->SetUniform1i("swidth", nWidth); + pProgram->SetUniform1i("sheight", nHeight); + // For converting between <0,nWidth> and <0.0,1.0> coordinate systems. + GLfloat srcCoords[ 8 ]; + rTexture.GetWholeCoord( srcCoords ); + pProgram->SetUniform1f( "xoffset", srcCoords[ 0 ] ); + pProgram->SetUniform1f( "yoffset", srcCoords[ 1 ] ); + pProgram->SetUniform1f( "xtopixelratio", nNewWidth / ( srcCoords[ 4 ] - srcCoords[ 0 ] )); + pProgram->SetUniform1f( "ytopixelratio", nNewHeight / ( srcCoords[ 5 ] - srcCoords[ 1 ] )); + pProgram->SetUniform1f( "xfrompixelratio", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / nWidth ); + pProgram->SetUniform1f( "yfrompixelratio", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / nHeight ); + + pProgram->SetTexture("sampler", rTexture); + pProgram->DrawTexture(rTexture); + pProgram->Clean(); + + OpenGLContext::ReleaseFramebuffer(pFramebuffer); + + CHECK_GL_ERROR(); + + rOutTexture = aScratchTex; + return true; +} + +} + +void OpenGLSalGraphicsImpl::DrawTransformedTexture( + OpenGLTexture& rTexture, + OpenGLTexture& rMask, + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY ) +{ + OpenGLZone aZone; + + std::vector<GLfloat> aVertices = { + 0, GLfloat(rTexture.GetHeight()), + 0, 0, + GLfloat(rTexture.GetWidth()), 0, + GLfloat(rTexture.GetWidth()), GLfloat(rTexture.GetHeight()) + }; + + GLfloat aTexCoord[8]; + + const long nDestWidth = basegfx::fround(basegfx::B2DVector(rX - rNull).getLength()); + const long nDestHeight = basegfx::fround(basegfx::B2DVector(rY - rNull).getLength()); + + // Invisibly small images shouldn't divide by zero. + if( nDestHeight == 0 || nDestWidth == 0 ) + return; + + // inverted scale ratios + double ixscale = rTexture.GetWidth() / double(nDestWidth); + double iyscale = rTexture.GetHeight() / double(nDestHeight); + + // If downscaling at a higher scale ratio, use the area scaling algorithm rather + // than plain OpenGL's scaling (texture mapping), for better results. + // See OpenGLSalBitmap::ImplScaleArea(). + bool areaScaling = false; + bool fastAreaScaling = false; + + OString sUseReducedRegisterVariantDefine; + if (mpContext->getOpenGLCapabilitySwitch().mbLimitedShaderRegisters) + sUseReducedRegisterVariantDefine = OString("#define USE_REDUCED_REGISTER_VARIANT\n"); + + OUString textureFragmentShader; + if( ixscale >= 2 && iyscale >= 2 ) // scale ratio less than 50% + { + areaScaling = true; + fastAreaScaling = ( ixscale == std::trunc( ixscale ) && iyscale == std::trunc( iyscale )); + // The generic case has arrays only up to 16 ratio downscaling and is performed in 2 passes, + // when the ratio is in the 16-100 range, which is hopefully enough in practice, but protect + // against buffer overflows in case such an extreme case happens (and in such case the precision + // of the generic algorithm probably doesn't matter anyway). + if( ixscale > 100 || iyscale > 100 ) + fastAreaScaling = true; + if( fastAreaScaling ) + textureFragmentShader = "areaScaleFastFragmentShader"; + else + textureFragmentShader = "areaScaleFragmentShader"; + } + + OpenGLTexture aInTexture = rTexture; + OpenGLTexture aInMask = rMask; + + // When using the area scaling algorithm we need to reduce the texture size in 2 passes + // in order to not use a big array inside the fragment shader. + if (areaScaling && !fastAreaScaling) + { + // Perform a first texture downscaling by an inverted scale ratio equal to + // the square root of the whole inverted scale ratio. + if (ixscale > 16 || iyscale > 16) + { + // The scissor area is set to the current window size in PreDraw, + // so if we do not disable the scissor test, the texture produced + // by the first downscaling is clipped to the current window size. + mpContext->state().scissor().disable(); + mpContext->state().stencil().disable(); + + // the square root of the whole inverted scale ratio + double ixscalesqrt = std::floor(std::sqrt(ixscale)); + double iyscalesqrt = std::floor(std::sqrt(iyscale)); + ixscale /= ixscalesqrt; // second pass inverted x-scale factor + iyscale /= iyscalesqrt; // second pass inverted y-scale factor + + scaleTexture(mpContext, aInTexture, ixscalesqrt, iyscalesqrt, rTexture); + + if (rMask) // we need to downscale the mask too + { + scaleTexture(mpContext, aInMask, ixscalesqrt, iyscalesqrt, rMask); + } + + // We need to re-acquire the off-screen texture. + CheckOffscreenTexture(); + CHECK_GL_ERROR(); + + // Re-enable scissor and stencil tests if needed. + if (mbUseScissor) + mpContext->state().scissor().enable(); + + if (mbUseStencil) + mpContext->state().stencil().enable(); + } + } + + if( aInMask ) + { + if( !UseProgram( "transformedTextureVertexShader", + textureFragmentShader.isEmpty() ? "maskedTextureFragmentShader" : textureFragmentShader, + "#define MASKED\n" + sUseReducedRegisterVariantDefine)) + return; + mpProgram->SetTexture( "mask", aInMask ); + GLfloat aMaskCoord[8]; + aInMask.GetWholeCoord(aMaskCoord); + mpProgram->SetMaskCoord(aMaskCoord); + aInMask.SetFilter( GL_LINEAR ); + mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + } + else + { + if( !UseProgram( "transformedTextureVertexShader", + textureFragmentShader.isEmpty() ? "textureFragmentShader" : textureFragmentShader, + sUseReducedRegisterVariantDefine)) + return; + } + + if(areaScaling) + { + int nWidth = aInTexture.GetWidth(); + int nHeight = aInTexture.GetHeight(); + + // From OpenGLSalBitmap::ImplScaleArea(). + if (fastAreaScaling && nWidth && nHeight) + { + mpProgram->SetUniform1i( "xscale", ixscale ); + mpProgram->SetUniform1i( "yscale", iyscale ); + GLfloat srcCoords[ 8 ]; + aInTexture.GetWholeCoord( srcCoords ); + mpProgram->SetUniform1f( "xstep", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / nWidth ); + mpProgram->SetUniform1f( "ystep", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / nHeight ); + mpProgram->SetUniform1f( "ratio", 1.0 / ( ixscale * iyscale )); + } + else if (nHeight > 1 && nWidth > 1) + { + mpProgram->SetUniform1f( "xscale", ixscale ); + mpProgram->SetUniform1f( "yscale", iyscale ); + mpProgram->SetUniform1i( "swidth", nWidth ); + mpProgram->SetUniform1i( "sheight", nHeight ); + // For converting between <0,nWidth-1> and <0.0,1.0> coordinate systems. + GLfloat srcCoords[ 8 ]; + aInTexture.GetWholeCoord( srcCoords ); + mpProgram->SetUniform1f( "xoffset", srcCoords[ 0 ] ); + mpProgram->SetUniform1f( "yoffset", srcCoords[ 1 ] ); + mpProgram->SetUniform1f( "xtopixelratio", ( nWidth / ixscale ) / ( srcCoords[ 4 ] - srcCoords[ 0 ] )); + mpProgram->SetUniform1f( "ytopixelratio", ( nHeight / iyscale ) / ( srcCoords[ 5 ] - srcCoords[ 1 ] )); + mpProgram->SetUniform1f( "xfrompixelratio", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / nWidth ); + mpProgram->SetUniform1f( "yfrompixelratio", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / nHeight ); + } + } + + ApplyProgramMatrices(); + mpProgram->SetUniform2f( "viewport", GetWidth(), GetHeight() ); + // Here, in order to get the correct transformation we need to pass the original texture, + // since it has been used for initializing the rectangle vertices. + mpProgram->SetTransform( "transform", rTexture, rNull, rX, rY ); + aInTexture.GetWholeCoord(aTexCoord); + mpProgram->SetTexture("sampler", aInTexture); + aInTexture.SetFilter(GL_LINEAR); + mpProgram->SetTextureCoord( aTexCoord ); + mpProgram->DrawArrays(GL_TRIANGLE_FAN, aVertices); + + CHECK_GL_ERROR(); + mpProgram->Clean(); +} + +void OpenGLSalGraphicsImpl::DrawAlphaTexture( OpenGLTexture& rTexture, const SalTwoRect& rPosAry, bool bInverted, bool bPremultiplied ) +{ + OpenGLZone aZone; + + if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader")) + return; + mpProgram->SetShaderType(TextureShaderType::Normal); + mpProgram->SetIdentityTransform("transform"); + mpProgram->SetTexture("texture", rTexture); + mpProgram->SetBlendMode( bPremultiplied ? GL_ONE : GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA ); + + GLfloat aTexCoord[8]; + rTexture.GetCoord(aTexCoord, rPosAry, bInverted); + mpProgram->SetTextureCoord(aTexCoord); + mpProgram->SetMaskCoord(aTexCoord); + mpProgram->SetAlphaCoord(aTexCoord); + + DrawTextureRect( rPosAry ); + mpProgram->Clean(); +} + +void OpenGLSalGraphicsImpl::DrawTextureDiff( OpenGLTexture& rTexture, OpenGLTexture& rMask, const SalTwoRect& rPosAry, bool bInverted ) +{ + OpenGLZone aZone; + + if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader")) + return; + mpProgram->SetShaderType(TextureShaderType::Diff); + mpProgram->SetIdentityTransform("transform"); + mpProgram->SetTexture( "texture", rTexture ); + mpProgram->SetTexture( "mask", rMask ); + mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + GLfloat aTexCoord[8]; + rTexture.GetCoord(aTexCoord, rPosAry, bInverted); + mpProgram->SetTextureCoord(aTexCoord); + mpProgram->SetAlphaCoord(aTexCoord); + + GLfloat aMaskCoord[8]; + rMask.GetCoord(aMaskCoord, rPosAry, bInverted); + mpProgram->SetMaskCoord(aMaskCoord); + + DrawTextureRect( rPosAry ); + mpProgram->Clean(); +} + +void OpenGLSalGraphicsImpl::DrawTextureWithMask( OpenGLTexture& rTexture, OpenGLTexture& rMask, const SalTwoRect& rPosAry ) +{ + OpenGLZone aZone; + + if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader")) + return; + mpProgram->SetShaderType(TextureShaderType::Masked); + mpProgram->SetIdentityTransform("transform"); + mpProgram->SetTexture( "texture", rTexture ); + mpProgram->SetTexture( "mask", rMask ); + mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + GLfloat aTexCoord[8]; + rTexture.GetCoord(aTexCoord, rPosAry); + mpProgram->SetTextureCoord(aTexCoord); + mpProgram->SetAlphaCoord(aTexCoord); + + GLfloat aMaskCoord[8]; + rMask.GetCoord(aMaskCoord, rPosAry); + mpProgram->SetMaskCoord(aMaskCoord); + + DrawTextureRect(rPosAry); + mpProgram->Clean(); +} + +void OpenGLSalGraphicsImpl::DrawBlendedTexture( OpenGLTexture& rTexture, OpenGLTexture& rMask, OpenGLTexture& rAlpha, const SalTwoRect& rPosAry ) +{ + OpenGLZone aZone; + + if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader")) + return; + mpProgram->SetShaderType(TextureShaderType::Blend); + mpProgram->SetTexture( "texture", rTexture ); + mpProgram->SetTexture( "mask", rMask ); + mpProgram->SetTexture( "alpha", rAlpha ); + + GLfloat aTexCoord[8]; + rTexture.GetCoord(aTexCoord, rPosAry); + mpProgram->SetTextureCoord(aTexCoord); + + GLfloat aAlphaCoord[8]; + rAlpha.GetCoord(aAlphaCoord, rPosAry); + mpProgram->SetAlphaCoord(aAlphaCoord); + + GLfloat aMaskCoord[8]; + rMask.GetCoord(aMaskCoord, rPosAry); + mpProgram->SetMaskCoord(aMaskCoord); + + mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + DrawTextureRect( rPosAry ); + mpProgram->Clean(); +} + +void OpenGLSalGraphicsImpl::DrawMask( OpenGLTexture& rMask, Color nMaskColor, const SalTwoRect& rPosAry ) +{ + OpenGLZone aZone; + + if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader")) + return; + mpProgram->SetShaderType(TextureShaderType::MaskedColor); + mpProgram->SetIdentityTransform("transform"); + mpProgram->SetColor( "color", nMaskColor, 0 ); + mpProgram->SetTexture("texture", rMask); + + GLfloat aTexCoord[8]; + rMask.GetCoord(aTexCoord, rPosAry); + mpProgram->SetTextureCoord(aTexCoord); + mpProgram->SetMaskCoord(aTexCoord); + mpProgram->SetAlphaCoord(aTexCoord); + + mpProgram->SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + DrawTextureRect(rPosAry); + mpProgram->Clean(); +} + +void OpenGLSalGraphicsImpl::FlushLinesOrTriangles(DrawShaderType eType, RenderParameters const & rParameters) +{ + if (!UseProgram("combinedVertexShader", "combinedFragmentShader", "#define USE_VERTEX_COLORS")) + return; + + mpProgram->SetShaderType(eType); + mpProgram->SetBlendMode(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + ApplyProgramMatrices(0.5f); + + vcl::VertexBufferObject<Vertex> vbo; + vbo.upload(rParameters.maVertices); + + GLuint positionAttrib = SAL_MAX_UINT32; + GLuint colorAttrib = SAL_MAX_UINT32; + GLuint lineDataAttrib = SAL_MAX_UINT32; + + mpProgram->SetVertexAttrib(positionAttrib, "position", 2, GL_FLOAT, GL_FALSE, + sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, position))); + + mpProgram->SetVertexAttrib(colorAttrib, "vertex_color_in", 4, GL_FLOAT, GL_FALSE, + sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, color))); + + mpProgram->SetVertexAttrib(lineDataAttrib, "extrusion_vectors", 4, GL_FLOAT, GL_FALSE, + sizeof(Vertex), reinterpret_cast<void*>(offsetof(Vertex, lineData))); + + vcl::IndexBufferObject ibo; + ibo.upload(rParameters.maIndices); + ibo.bind(); + + mpProgram->DrawElements(GL_TRIANGLES, rParameters.maIndices.size()); + CHECK_GL_ERROR(); + + mpProgram->Clean(); +} + +void OpenGLSalGraphicsImpl::FlushDeferredDrawing() +{ + if (mpRenderList->empty()) + return; + + VCL_GL_INFO("FlushDeferredDrawing: " << mpRenderList->getEntries().size()); + + InitializePreDrawState(XOROption::IMPLEMENT_XOR); + + OpenGLZone aZone; + for (RenderEntry& rRenderEntry : mpRenderList->getEntries()) + { + if (rRenderEntry.hasTriangles()) + { + RenderParameters& rParameters = rRenderEntry.maTriangleParameters; + VCL_GL_INFO("Flush Triangles: " << rParameters.maVertices.size()); + FlushLinesOrTriangles(DrawShaderType::Normal, rParameters); + } + if (rRenderEntry.hasLines()) + { + RenderParameters& rParameters = rRenderEntry.maLineParameters; + VCL_GL_INFO("Flush Lines: " << rParameters.maVertices.size()); + FlushLinesOrTriangles(DrawShaderType::Line, rParameters); + } + if (rRenderEntry.hasTextures() && UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader", "#define USE_VERTEX_COLORS")) + { + mpProgram->SetShaderType(TextureShaderType::MaskedColor); + mpProgram->SetIdentityTransform("transform"); + mpProgram->SetBlendMode(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + for (auto& rPair : rRenderEntry.maTextureParametersMap) + { + RenderTextureParameters& rParameters = rPair.second; + mpProgram->SetTexture("texture", rParameters.maTexture); + ApplyProgramMatrices(); + mpProgram->SetTextureCoord(rParameters.maTextureCoords.data()); + mpProgram->SetMaskCoord(rParameters.maTextureCoords.data()); + mpProgram->SetAlphaCoord(rParameters.maTextureCoords.data()); + mpProgram->SetVertexColors(rParameters.maColors); + mpProgram->DrawArrays(GL_TRIANGLES, rParameters.maVertices); + CHECK_GL_ERROR(); + } + mpProgram->Clean(); + } + } + + mpRenderList->clear(); + PostDraw(); + + VCL_GL_INFO("End FlushDeferredDrawing"); +} + +void OpenGLSalGraphicsImpl::DrawLinearGradient( const Gradient& rGradient, const tools::Rectangle& rRect ) +{ + OpenGLZone aZone; + + if( !UseProgram( "textureVertexShader", "linearGradientFragmentShader" ) ) + return; + Color aStartCol = rGradient.GetStartColor(); + Color aEndCol = rGradient.GetEndColor(); + long nFactor = rGradient.GetStartIntensity(); + mpProgram->SetColorWithIntensity( "start_color", aStartCol, nFactor ); + nFactor = rGradient.GetEndIntensity(); + mpProgram->SetColorWithIntensity( "end_color", aEndCol, nFactor ); + + tools::Rectangle aBoundRect; + Point aCenter; + rGradient.GetBoundRect( rRect, aBoundRect, aCenter ); + tools::Polygon aPoly( aBoundRect ); + aPoly.Rotate( aCenter, rGradient.GetAngle() % 3600 ); + + GLfloat aTexCoord[8] = { 0, 1, 1, 1, 1, 0, 0, 0 }; + GLfloat fMin = 1.0 - 100.0 / (100.0 - rGradient.GetBorder()); + aTexCoord[5] = aTexCoord[7] = fMin; + mpProgram->SetTextureCoord( aTexCoord ); + DrawConvexPolygon( aPoly, true ); +} + +void OpenGLSalGraphicsImpl::DrawAxialGradient( const Gradient& rGradient, const tools::Rectangle& rRect ) +{ + OpenGLZone aZone; + + if( !UseProgram( "textureVertexShader", "linearGradientFragmentShader" ) ) + return; + Color aStartCol = rGradient.GetStartColor(); + Color aEndCol = rGradient.GetEndColor(); + long nFactor = rGradient.GetStartIntensity(); + mpProgram->SetColorWithIntensity( "start_color", aStartCol, nFactor ); + nFactor = rGradient.GetEndIntensity(); + mpProgram->SetColorWithIntensity( "end_color", aEndCol, nFactor ); + + /** + * Draw two rectangles with linear gradient. + * + * 1 *---* 2 + * | /| + * | / | Points 0 and 3 have start color + * 0 |/__| 3 Points 1, 2, 4 and 5 have end color + * |\ | + * | \ | + * | \| + * 5 *---* 4 + * + */ + + tools::Rectangle aRect; + Point aCenter; + rGradient.GetBoundRect( rRect, aRect, aCenter ); + + // determine points 0 and 3 + Point aPt0( aRect.Left(), (aRect.Top() + aRect.Bottom() + 1) / 2 ); + Point aPt3( aRect.Right(), (aRect.Top() + aRect.Bottom() + 1) / 2 ); + + tools::Polygon aPoly( 7 ); + aPoly.SetPoint( aPt0, 0 ); + aPoly.SetPoint( aRect.TopLeft(), 1 ); + aPoly.SetPoint( aRect.TopRight(), 2 ); + aPoly.SetPoint( aPt3, 3 ); + aPoly.SetPoint( aRect.BottomRight(), 4 ); + aPoly.SetPoint( aRect.BottomLeft(), 5 ); + aPoly.SetPoint( aPt0, 6 ); + aPoly.Rotate( aCenter, rGradient.GetAngle() % 3600 ); + + GLfloat aTexCoord[12] = { 0, 1, 1, 0, 2, 0, 3, 1, 4, 0, 5, 0 }; + GLfloat fMin = 1.0 - 100.0 / (100.0 - rGradient.GetBorder()); + aTexCoord[3] = aTexCoord[5] = aTexCoord[9] = aTexCoord[11] = fMin; + mpProgram->SetTextureCoord( aTexCoord ); + DrawConvexPolygon( aPoly, true ); +} + +void OpenGLSalGraphicsImpl::DrawRadialGradient( const Gradient& rGradient, const tools::Rectangle& rRect ) +{ + OpenGLZone aZone; + + if( !UseProgram( "textureVertexShader", "radialGradientFragmentShader" ) ) + return; + Color aStartCol = rGradient.GetStartColor(); + Color aEndCol = rGradient.GetEndColor(); + long nFactor = rGradient.GetStartIntensity(); + mpProgram->SetColorWithIntensity( "start_color", aStartCol, nFactor ); + nFactor = rGradient.GetEndIntensity(); + mpProgram->SetColorWithIntensity( "end_color", aEndCol, nFactor ); + + tools::Rectangle aRect; + Point aCenter; + rGradient.GetBoundRect( rRect, aRect, aCenter ); + + // adjust coordinates so that radius has distance equals to 1.0 + double fRadius = aRect.GetWidth() / 2.0f; + GLfloat fWidth = rRect.GetWidth() / fRadius; + GLfloat fHeight = rRect.GetHeight() / fRadius; + GLfloat aTexCoord[8] = { 0, 0, 0, fHeight, fWidth, fHeight, fWidth, 0 }; + mpProgram->SetTextureCoord( aTexCoord ); + mpProgram->SetUniform2f( "center", (aCenter.X() - rRect.Left()) / fRadius, + (aCenter.Y() - rRect.Top()) / fRadius ); + DrawRect( rRect ); +} + +void OpenGLSalGraphicsImpl::drawPixel(long nX, long nY) +{ + VCL_GL_INFO("::drawPixel: (" << nX << ", " << nY << ")"); + mpRenderList->addDrawPixel(nX, nY, mnLineColor); + PostBatchDraw(); +} + +void OpenGLSalGraphicsImpl::drawPixel(long nX, long nY, Color nColor) +{ + VCL_GL_INFO("::drawPixel: (" << nX << ", " << nY << ")"); + mpRenderList->addDrawPixel(nX, nY, nColor); + PostBatchDraw(); +} + +void OpenGLSalGraphicsImpl::drawLine(long nX1, long nY1, long nX2, long nY2) +{ + VCL_GL_INFO("::drawLine (" << nX1 << ", " << nY1 << ") (" << nX2 << ", " << nY2 << ")"); + mpRenderList->addDrawLine(nX1, nY1, nX2, nY2, mnLineColor, mrParent.getAntiAliasB2DDraw()); + PostBatchDraw(); +} + +void OpenGLSalGraphicsImpl::drawRect( long nX, long nY, long nWidth, long nHeight ) +{ + VCL_GL_INFO("::drawRect (" << nX << ", " << nY << ") [" << nWidth << ", " << nHeight << "]"); + mpRenderList->addDrawRectangle(nX, nY, nWidth, nHeight, 0.0, mnLineColor, mnFillColor); + PostBatchDraw(); +} + +void OpenGLSalGraphicsImpl::drawPolyLine( sal_uInt32 nPoints, const SalPoint* pPtAry ) +{ + VCL_GL_INFO("::drawPolyLine legacy -> redirecting to drawPolyLine"); + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY)); + aPoly.setClosed(false); + + drawPolyLine( + basegfx::B2DHomMatrix(), + aPoly, + 0.0, + 1.0, + nullptr, // MM01 + basegfx::B2DLineJoin::Miter, + css::drawing::LineCap_BUTT, + basegfx::deg2rad(15.0) /*default*/, + false); +} + +void OpenGLSalGraphicsImpl::drawPolygon( sal_uInt32 nPoints, const SalPoint* pPtAry ) +{ + VCL_GL_INFO("::drawPolygon legacy -> redirecting to drawPolyPolygon with transparency"); + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(pPtAry->mnX, pPtAry->mnY), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint(pPtAry[i].mnX, pPtAry[i].mnY)); + + drawPolyPolygon( + basegfx::B2DHomMatrix(), + basegfx::B2DPolyPolygon(aPoly), + 0.0); +} + +void OpenGLSalGraphicsImpl::drawPolyPolygon( sal_uInt32 nPoly, const sal_uInt32* pPointCounts, PCONSTSALPOINT* pPtAry ) +{ + VCL_GL_INFO("::drawPolyPolygon legacy -> redirecting to drawPolyPolygon with transparency"); + basegfx::B2DPolyPolygon aPolyPoly; + for(sal_uInt32 nPolygon = 0; nPolygon < nPoly; ++nPolygon) + { + sal_uInt32 nPoints = pPointCounts[nPolygon]; + if (nPoints) + { + PCONSTSALPOINT pPoints = pPtAry[nPolygon]; + basegfx::B2DPolygon aPoly; + aPoly.append( basegfx::B2DPoint(pPoints->mnX, pPoints->mnY), nPoints); + for (sal_uInt32 i = 1; i < nPoints; ++i) + aPoly.setB2DPoint(i, basegfx::B2DPoint( pPoints[i].mnX, pPoints[i].mnY)); + + aPolyPoly.append(aPoly); + } + } + + drawPolyPolygon( + basegfx::B2DHomMatrix(), + aPolyPoly, + 0.0); +} + +bool OpenGLSalGraphicsImpl::drawPolyPolygon( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolyPolygon& rPolyPolygon, + double fTransparency) +{ + VCL_GL_INFO("::drawPolyPolygon " << rPolyPolygon.getB2DRange()); + + // Fallback: Transform to DeviceCoordinates + basegfx::B2DPolyPolygon aPolyPolygon(rPolyPolygon); + aPolyPolygon.transform(rObjectToDevice); + + // FlushLinesOrTriangles() works with a 0.5 pixel offset, compensate for that here. + basegfx::B2DHomMatrix aMatrix; + aMatrix.translate(-0.5f, -0.5f); + aPolyPolygon.transform(aMatrix); + + mpRenderList->addDrawPolyPolygon( + aPolyPolygon, + fTransparency, + mnLineColor, + mnFillColor, + mrParent.getAntiAliasB2DDraw()); + + PostBatchDraw(); + return true; +} + +bool OpenGLSalGraphicsImpl::drawPolyLine( + const basegfx::B2DHomMatrix& rObjectToDevice, + const basegfx::B2DPolygon& rPolygon, + double fTransparency, + double fLineWidth, + const std::vector< double >* pStroke, // MM01 + basegfx::B2DLineJoin eLineJoin, + css::drawing::LineCap eLineCap, + double fMiterMinimumAngle, + bool bPixelSnapHairline) +{ + VCL_GL_INFO("::drawPolyLine " << rPolygon.getB2DRange()); + + // MM01 check done for simple reasons + if(!rPolygon.count() || fTransparency < 0.0 || fTransparency > 1.0) + { + return true; + } + + // MM01 need to do line dashing as fallback stuff here now + const double fDotDashLength(nullptr != pStroke ? std::accumulate(pStroke->begin(), pStroke->end(), 0.0) : 0.0); + const bool bStrokeUsed(0.0 != fDotDashLength); + assert(!bStrokeUsed || (bStrokeUsed && pStroke)); + basegfx::B2DPolyPolygon aPolyPolygonLine; + + if(bStrokeUsed) + { + // apply LineStyle + basegfx::utils::applyLineDashing( + rPolygon, // source + *pStroke, // pattern + &aPolyPolygonLine, // target for lines + nullptr, // target for gaps + fDotDashLength); // full length if available + } + else + { + // no line dashing, just copy + aPolyPolygonLine.append(rPolygon); + } + + // Transform to DeviceCoordinates, get DeviceLineWidth, execute PixelSnapHairline + aPolyPolygonLine.transform(rObjectToDevice); + if(bPixelSnapHairline) { aPolyPolygonLine = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aPolyPolygonLine); } + + // tdf#124848 get correct LineWidth in discrete coordinates, + if(fLineWidth == 0) // hairline + fLineWidth = 1.0; + else // Adjust line width for object-to-device scale. + fLineWidth = (rObjectToDevice * basegfx::B2DVector(fLineWidth, 0)).getLength(); + + for(sal_uInt32 a(0); a < aPolyPolygonLine.count(); a++) + { + // addDrawPolyLine() assumes that there are no duplicate points in the polygon + basegfx::B2DPolygon aPolyLine(aPolyPolygonLine.getB2DPolygon(a)); + basegfx::utils::simplifyCurveSegments(aPolyLine); + aPolyLine.removeDoublePoints(); + + mpRenderList->addDrawPolyLine( + aPolyLine, + fTransparency, + fLineWidth, + eLineJoin, + eLineCap, + fMiterMinimumAngle, + mnLineColor, + mrParent.getAntiAliasB2DDraw()); + + // MM01: not sure - maybe this can be moved out of this loop, but to + // keep on the safe side for now, do not really change something for now + PostBatchDraw(); + } + + return true; +} + +bool OpenGLSalGraphicsImpl::drawPolyLineBezier( + sal_uInt32 /*nPoints*/, + const SalPoint* /*pPtAry*/, + const PolyFlags* /*pFlgAry*/ ) +{ + return false; +} + +bool OpenGLSalGraphicsImpl::drawPolygonBezier( + sal_uInt32 /*nPoints*/, + const SalPoint* /*pPtAry*/, + const PolyFlags* /*pFlgAry*/ ) +{ + return false; +} + +bool OpenGLSalGraphicsImpl::drawPolyPolygonBezier( + sal_uInt32 /*nPoly*/, + const sal_uInt32* /*pPoints*/, + const SalPoint* const* /*pPtAry*/, + const PolyFlags* const* /*pFlgAry*/ ) +{ + return false; +} + +// CopyArea --> No RasterOp, but ClipRegion +void OpenGLSalGraphicsImpl::copyArea( + long nDestX, long nDestY, + long nSrcX, long nSrcY, + long nSrcWidth, long nSrcHeight, bool /*bWindowInvalidate*/ ) +{ + VCL_GL_INFO( "::copyArea " << nSrcX << "," << nSrcY << " >> " << nDestX << "," << nDestY << " (" << nSrcWidth << "," << nSrcHeight << ")" ); + OpenGLTexture aTexture; + SalTwoRect aPosAry(0, 0, nSrcWidth, nSrcHeight, nDestX, nDestY, nSrcWidth, nSrcHeight); + + PreDraw(); + // TODO offscreen case + aTexture = OpenGLTexture( nSrcX, GetHeight() - nSrcY - nSrcHeight, + nSrcWidth, nSrcHeight ); + DrawTexture( aTexture, aPosAry ); + PostDraw(); +} + +// CopyBits and DrawBitmap --> RasterOp and ClipRegion +// CopyBits() --> pSrcGraphics == NULL, then CopyBits on same Graphics +void OpenGLSalGraphicsImpl::DoCopyBits( const SalTwoRect& rPosAry, OpenGLSalGraphicsImpl& rImpl ) +{ + VCL_GL_INFO( "::copyBits" ); + + rImpl.FlushDeferredDrawing(); + + if( !rImpl.maOffscreenTex ) + { + VCL_GL_INFO( "::copyBits - skipping copy of un-initialized framebuffer contents of size " + << rImpl.GetWidth() << "x" << rImpl.GetHeight() ); + return; + } + + if( &rImpl == this && + (rPosAry.mnSrcWidth == rPosAry.mnDestWidth) && + (rPosAry.mnSrcHeight == rPosAry.mnDestHeight)) + { + // short circuit if there is nothing to do + if( (rPosAry.mnSrcX == rPosAry.mnDestX) && + (rPosAry.mnSrcY == rPosAry.mnDestY)) + return; + // use copyArea() if source and destination context are identical + copyArea( rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY, + rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, false/*bWindowInvalidate*/ ); + return; + } + + PreDraw(); + DrawTexture( rImpl.maOffscreenTex, rPosAry ); + PostDraw(); +} + +void OpenGLSalGraphicsImpl::drawBitmap( const SalTwoRect& rPosAry, const SalBitmap& rSalBitmap ) +{ + // check that carefully only in the debug mode + assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBitmap)); + + OpenGLZone aZone; + + const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSalBitmap); + OpenGLTexture& rTexture = rBitmap.GetTexture(); + + VCL_GL_INFO( "::drawBitmap" ); + PreDraw(); + if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || + rPosAry.mnSrcHeight != rPosAry.mnDestHeight) + { + basegfx::B2DPoint aNull(rPosAry.mnDestX,rPosAry.mnDestY); + basegfx::B2DPoint aX(rPosAry.mnDestX + rPosAry.mnDestWidth, rPosAry.mnDestY); + basegfx::B2DPoint aY(rPosAry.mnDestX, rPosAry.mnDestY + rPosAry.mnDestHeight); + OpenGLTexture mask; // no mask set + DrawTransformedTexture(rTexture, mask, aNull, aX, aY); + } + else + { + DrawTexture( rTexture, rPosAry ); + } + PostDraw(); +} + +void OpenGLSalGraphicsImpl::drawBitmap( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rMaskBitmap ) +{ + VCL_GL_INFO("::drawBitmap with MASK -> redirect to ::drawAlphaBitmap"); + drawAlphaBitmap(rPosAry, rSalBitmap, rMaskBitmap); +} + +void OpenGLSalGraphicsImpl::drawMask( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + Color nMaskColor ) +{ + VCL_GL_INFO("::drawMask"); + + assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBitmap)); + const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSalBitmap); + mpRenderList->addDrawTextureWithMaskColor(rBitmap.GetTexture(), nMaskColor, rPosAry); + PostBatchDraw(); +} + +std::shared_ptr<SalBitmap> OpenGLSalGraphicsImpl::getBitmap( long nX, long nY, long nWidth, long nHeight ) +{ + FlushDeferredDrawing(); + + OpenGLZone aZone; + + std::shared_ptr<OpenGLSalBitmap> pBitmap(std::make_shared<OpenGLSalBitmap>()); + VCL_GL_INFO( "::getBitmap " << nX << "," << nY << + " " << nWidth << "x" << nHeight ); + //TODO really needed? + PreDraw(); + pBitmap->Create( maOffscreenTex, nX, nY, nWidth, nHeight ); + PostDraw(); + return pBitmap; +} + +Color OpenGLSalGraphicsImpl::getPixel( long nX, long nY ) +{ + FlushDeferredDrawing(); + + char pixel[3] = { 0, 0, 0 }; + + PreDraw( XOROption::IMPLEMENT_XOR ); + nY = GetHeight() - nY - 1; + glReadPixels( nX, nY, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixel); + CHECK_GL_ERROR(); + PostDraw(); + + return Color( pixel[0], pixel[1], pixel[2] ); +} + +// invert --> ClipRegion (only Windows or VirDevs) +void OpenGLSalGraphicsImpl::invert( + long nX, long nY, + long nWidth, long nHeight, + SalInvert nFlags) +{ + PreDraw(); + + if( UseInvert( nFlags ) ) + { + if( nFlags & SalInvert::TrackFrame ) + { // FIXME: could be more efficient. + DrawRect( nX, nY, nWidth, 1 ); + DrawRect( nX, nY + nHeight, nWidth, 1 ); + DrawRect( nX, nY, 1, nHeight ); + DrawRect( nX + nWidth, nY, 1, nHeight ); + } + else + DrawRect( nX, nY, nWidth, nHeight ); + } + + PostDraw(); +} + +void OpenGLSalGraphicsImpl::invert( sal_uInt32 nPoints, const SalPoint* pPtAry, SalInvert nFlags ) +{ + PreDraw(); + + if( UseInvert( nFlags ) ) + { + if (nFlags & SalInvert::TrackFrame) + { + // Track frame means the invert50FragmentShader must remain active + // (to draw what looks like a dashed line), so DrawLineSegment() + // can't be used. Draw the edge of the polygon as polygons instead. + for (size_t nPoint = 0; nPoint < nPoints; ++nPoint) + { + const SalPoint& rFrom = pPtAry[nPoint]; + const SalPoint& rTo = pPtAry[(nPoint + 1) % nPoints]; + if (rFrom.mnX == rTo.mnX) + { + // Extend to the right, comments assuming "to" is above + // "from": + const SalPoint aPoints[] = { { rFrom.mnX + 1, rFrom.mnY }, // bottom right + { rFrom.mnX, rFrom.mnY }, // bottom left + { rTo.mnX, rTo.mnY }, // top left + { rTo.mnX + 1, rTo.mnY } }; // top right + DrawConvexPolygon(4, aPoints, true); + } + else + { + // Otherwise can extend downwards, comments assuming "to" + // is above and on the right of "from": + const SalPoint aPoints[] = { { rFrom.mnX, rFrom.mnY + 1 }, // bottom left + { rFrom.mnX, rFrom.mnY }, // top left + { rTo.mnX, rTo.mnY }, // top right + { rTo.mnX, rTo.mnY + 1 } }; // bottom right + DrawConvexPolygon(4, aPoints, true); + } + } + } + else + DrawPolygon(nPoints, pPtAry); + } + + PostDraw(); +} + +bool OpenGLSalGraphicsImpl::drawEPS( + long /*nX*/, long /*nY*/, + long /*nWidth*/, long /*nHeight*/, + void* /*pPtr*/, + sal_uInt32 /*nSize*/ ) +{ + return false; +} + +bool OpenGLSalGraphicsImpl::blendBitmap( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap ) +{ + assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBitmap)); + + OpenGLZone aZone; + + const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSalBitmap); + OpenGLTexture& rTexture( rBitmap.GetTexture() ); + + VCL_GL_INFO( "::blendBitmap" ); + PreDraw(); + + if (!UseProgram("combinedTextureVertexShader", "combinedTextureFragmentShader")) + return true; + + mpProgram->SetShaderType(TextureShaderType::Normal); + mpProgram->SetIdentityTransform("transform"); + mpProgram->SetTexture("texture", rTexture); + + GLfloat aTexCoord[8]; + rTexture.GetCoord(aTexCoord, rPosAry); + mpProgram->SetTextureCoord(aTexCoord); + mpProgram->SetMaskCoord(aTexCoord); + mpProgram->SetAlphaCoord(aTexCoord); + + mpProgram->SetBlendMode(GL_ZERO, GL_SRC_COLOR); + DrawTextureRect(rPosAry); + mpProgram->Clean(); + + PostDraw(); + return true; +} + +bool OpenGLSalGraphicsImpl::blendAlphaBitmap( + const SalTwoRect& rPosAry, + const SalBitmap& rSalSrcBitmap, + const SalBitmap& rSalMaskBitmap, + const SalBitmap& rSalAlphaBitmap ) +{ + assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalSrcBitmap)); + assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalMaskBitmap)); + assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalAlphaBitmap)); + + OpenGLZone aZone; + + const OpenGLSalBitmap& rSrcBitmap = static_cast<const OpenGLSalBitmap&>(rSalSrcBitmap); + const OpenGLSalBitmap& rMaskBitmap = static_cast<const OpenGLSalBitmap&>(rSalMaskBitmap); + const OpenGLSalBitmap& rAlphaBitmap = static_cast<const OpenGLSalBitmap&>(rSalAlphaBitmap); + OpenGLTexture& rTexture( rSrcBitmap.GetTexture() ); + OpenGLTexture& rMask( rMaskBitmap.GetTexture() ); + OpenGLTexture& rAlpha( rAlphaBitmap.GetTexture() ); + + VCL_GL_INFO( "::blendAlphaBitmap" ); + PreDraw(); + DrawBlendedTexture( rTexture, rMask, rAlpha, rPosAry ); + PostDraw(); + return true; +} + +/** Render bitmap with alpha channel + + @param rSourceBitmap + Source bitmap to blit + + @param rAlphaBitmap + Alpha channel to use for blitting + + @return true, if the operation succeeded, and false + otherwise. In this case, clients should try to emulate alpha + compositing themselves + */ +bool OpenGLSalGraphicsImpl::drawAlphaBitmap( + const SalTwoRect& rPosAry, + const SalBitmap& rSalBitmap, + const SalBitmap& rAlphaBitmap ) +{ + assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBitmap)); + assert(dynamic_cast<const OpenGLSalBitmap*>(&rAlphaBitmap)); + + OpenGLZone aZone; + + const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSalBitmap); + const OpenGLSalBitmap& rAlpha = static_cast<const OpenGLSalBitmap&>(rAlphaBitmap); + OpenGLTexture& rTexture(rBitmap.GetTexture()); + OpenGLTexture& rAlphaTexture(rAlpha.GetTexture()); + + VCL_GL_INFO( "::drawAlphaBitmap" ); + PreDraw(); + + if (rPosAry.mnSrcWidth != rPosAry.mnDestWidth || + rPosAry.mnSrcHeight != rPosAry.mnDestHeight) + { + basegfx::B2DPoint aNull(rPosAry.mnDestX,rPosAry.mnDestY); + basegfx::B2DPoint aX(rPosAry.mnDestX + rPosAry.mnDestWidth, rPosAry.mnDestY); + basegfx::B2DPoint aY(rPosAry.mnDestX, rPosAry.mnDestY + rPosAry.mnDestHeight); + DrawTransformedTexture(rTexture, rAlphaTexture, aNull, aX, aY); + } + else + { + DrawTextureWithMask( rTexture, rAlphaTexture, rPosAry ); + } + + PostDraw(); + return true; +} + +/** draw transformed bitmap (maybe with alpha) where Null, X, Y define the coordinate system */ +bool OpenGLSalGraphicsImpl::drawTransformedBitmap( + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY, + const SalBitmap& rSrcBitmap, + const SalBitmap* pAlphaBitmap) +{ + assert(dynamic_cast<const OpenGLSalBitmap*>(&rSrcBitmap)); + assert(!pAlphaBitmap || dynamic_cast<const OpenGLSalBitmap*>(pAlphaBitmap)); + + OpenGLZone aZone; + + const OpenGLSalBitmap& rBitmap = static_cast<const OpenGLSalBitmap&>(rSrcBitmap); + const OpenGLSalBitmap* pMaskBitmap = static_cast<const OpenGLSalBitmap*>(pAlphaBitmap); + OpenGLTexture& rTexture( rBitmap.GetTexture() ); + OpenGLTexture aMask; // no texture + + if( pMaskBitmap != nullptr ) + aMask = pMaskBitmap->GetTexture(); + + VCL_GL_INFO( "::drawTransformedBitmap" ); + PreDraw(); + DrawTransformedTexture( rTexture, aMask, rNull, rX, rY ); + PostDraw(); + + return true; +} + +/** Render solid rectangle with given transparency + + @param nTransparency + Transparency value (0-255) to use. 0 blits and opaque, 255 a + fully transparent rectangle + */ +bool OpenGLSalGraphicsImpl::drawAlphaRect( + long nX, long nY, + long nWidth, long nHeight, + sal_uInt8 nTransparency ) +{ + VCL_GL_INFO("::drawAlphaRect (" << nX << ", " << nY << ") [" << nWidth << ", " << nHeight << "]"); + mpRenderList->addDrawRectangle(nX, nY, nWidth, nHeight, nTransparency / 100.0, mnLineColor, mnFillColor); + PostBatchDraw(); + return true; +} + +bool OpenGLSalGraphicsImpl::drawGradient(const tools::PolyPolygon& rPolyPoly, + const Gradient& rGradient) +{ + tools::Rectangle aBoundRect( rPolyPoly.GetBoundRect() ); + + VCL_GL_INFO("::drawGradient " << rPolyPoly.GetBoundRect()); + + if (aBoundRect.IsEmpty()) + { + VCL_GL_INFO("::drawGradient nothing to draw"); + return true; + } + + if (rGradient.GetStyle() != GradientStyle::Linear && + rGradient.GetStyle() != GradientStyle::Axial && + rGradient.GetStyle() != GradientStyle::Radial ) + { + VCL_GL_INFO("::drawGradient unsupported gradient type"); + return false; + } + + aBoundRect.AdjustLeft( -1 ); + aBoundRect.AdjustTop( -1 ); + aBoundRect.AdjustRight( 1 ); + aBoundRect.AdjustBottom( 1 ); + + PreDraw( XOROption::IMPLEMENT_XOR ); + +#define FIXME_BROKEN_STENCIL_FOR_GRADIENTS 0 +#if FIXME_BROKEN_STENCIL_FOR_GRADIENTS + ImplSetClipBit( vcl::Region( rPolyPoly ), 0x02 ); + if( mbUseStencil ) + { + mpContext->state().stencil().enable(); + CHECK_GL_ERROR(); + glStencilFunc( GL_EQUAL, 3, 0xFF ); + CHECK_GL_ERROR(); + } + else + { + mpContext->state().stencil().enable(); + CHECK_GL_ERROR(); + glStencilFunc( GL_EQUAL, 2, 0xFF ); + CHECK_GL_ERROR(); + } +#endif + + // if border >= 100%, draw solid rectangle with start color + if (rGradient.GetBorder() >= 100.0) + { + VCL_GL_INFO("::drawGradient -> DrawRect (no gradient)"); + + Color aColor = rGradient.GetStartColor(); + long nIntensity = rGradient.GetStartIntensity(); + if (UseSolid(Color(aColor.GetRed() * nIntensity / 100.0, + aColor.GetGreen()* nIntensity / 100.0, + aColor.GetBlue() * nIntensity / 100.0))) + { + DrawRect(aBoundRect); + } + } + else if (rGradient.GetStyle() == GradientStyle::Linear) + { + VCL_GL_INFO("::drawGradient -> DrawLinearGradient"); + DrawLinearGradient(rGradient, aBoundRect); + } + else if (rGradient.GetStyle() == GradientStyle::Axial) + { + VCL_GL_INFO("::drawGradient -> DrawAxialGradient"); + DrawAxialGradient(rGradient, aBoundRect); + } + else if (rGradient.GetStyle() == GradientStyle::Radial) + { + VCL_GL_INFO("::drawGradient -> DrawRadialGradient"); + DrawRadialGradient(rGradient, aBoundRect); + } + +#if FIXME_BROKEN_STENCIL_FOR_GRADIENTS + if( !mbUseStencil ) + { + mpContext->state().stencil().disable(); + CHECK_GL_ERROR(); + } +#endif + PostDraw(); + + return true; +} + +void OpenGLSalGraphicsImpl::flush() +{ + FlushDeferredDrawing(); + + if( IsOffscreen() ) + return; + + if( !Application::IsInExecute() ) + { + // otherwise nothing would trigger idle rendering + doFlush(); + } + else if( !mpFlush->IsActive() ) + mpFlush->Start(); +} + +void OpenGLSalGraphicsImpl::doFlush() +{ + FlushDeferredDrawing(); + + if (OpenGLContext::hasCurrent()) + { + mpContext->state().scissor().disable(); + mpContext->state().stencil().disable(); + } + + if( IsOffscreen() ) + return; + + if( !maOffscreenTex ) + { + VCL_GL_INFO( "doFlush - odd no texture !" ); + return; + } + + if( mnDrawCountAtFlush == mnDrawCount ) + { + VCL_GL_INFO( "eliding redundant doFlush, no drawing since last!" ); + return; + } + + mnDrawCountAtFlush = mnDrawCount; + + OpenGLZone aZone; + + VCL_GL_INFO( "doFlush" ); + + if( !mpWindowContext.is() ) + { + // ensure everything is released from the old context. + OpenGLContext::clearCurrent(); + mpWindowContext = CreateWinContext(); + VCL_GL_INFO( "late creation of window context" ); + } + + assert( mpWindowContext.is() ); + + if( !mpWindowContext.is() ) + { + // failed to create a GL context for this window: + // eg. mis-matching pixel formats, underlying window + // resource lifecycle, etc. + VCL_GL_INFO( "Failed to create window context" ); + return; + } + + // Interesting ! -> this destroys a context [ somehow ] ... + mpWindowContext->makeCurrent(); + CHECK_GL_ERROR(); + + VCL_GL_INFO( "doFlush - acquire default framebuffer" ); + + mpWindowContext->AcquireDefaultFramebuffer(); + + CHECK_GL_ERROR(); + + mpWindowContext->state().sync(); + mpWindowContext->state().viewport(tools::Rectangle(Point(0, 0), Size(GetWidth(), GetHeight()))); + mpWindowContext->state().scissor().disable(); + mpWindowContext->state().stencil().disable(); + +#if OSL_DEBUG_LEVEL > 0 // random background glClear + glClearColor(static_cast<float>(double(rand())/RAND_MAX), + static_cast<float>(double(rand())/RAND_MAX), + static_cast<float>(double(rand())/RAND_MAX), 1.0); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); + CHECK_GL_ERROR(); +#endif + + VCL_GL_INFO( "Texture height " << maOffscreenTex.GetHeight() << " vs. window height " << GetHeight() ); + + OpenGLFramebuffer* pFrameBuffer = mpWindowContext->AcquireFramebuffer(maOffscreenTex); + CHECK_GL_ERROR(); + if (pFrameBuffer) + { + OpenGLFramebuffer::Unbind(GL_DRAW_FRAMEBUFFER); + pFrameBuffer->Bind(GL_READ_FRAMEBUFFER); + + glBlitFramebuffer(0, 0, GetWidth(), GetHeight(), + 0, 0, GetWidth(), GetHeight(), GL_COLOR_BUFFER_BIT, GL_NEAREST); + CHECK_GL_ERROR(); + + pFrameBuffer->Bind(); + } + + static bool bNoSwap = getenv("SAL_GL_NO_SWAP"); + if (!bNoSwap) + mpWindowContext->swapBuffers(); + + VCL_GL_INFO( "doFlush - end." ); +} + +bool OpenGLSalGraphicsImpl::supportsOperation(OutDevSupportType eType) const +{ + switch (eType) + { + case OutDevSupportType::B2DDraw: + case OutDevSupportType::TransparentRect: + return true; + default: + return false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/opengl_blacklist_windows.xml b/vcl/opengl/opengl_blacklist_windows.xml new file mode 100644 index 000000000..71e562fa9 --- /dev/null +++ b/vcl/opengl/opengl_blacklist_windows.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +* 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/. +--> + +<!-- + entry attributes: + os - "all", "7", "8", "8_1", "10" + vendor - "all", "intel", "amd", "nvidia", "microsoft" + compare - "less", "less_equal", "greater", "greater_equal", "equal", "not_equal", "between_exclusive", "between_inclusive", "between_inclusive_start" + version + minVersion + maxVersion +--> + +<root> + <whitelist> + </whitelist> + <blacklist> + <entry os="all" vendor="intel" compare="less" version="10.18.14.4264"> + <device id="all"/> + </entry> + <!-- tdf#99919 --> + <entry os="all" vendor="intel" compare="equal" version="20.19.15.4352"> + <device id="0x1927"/> + </entry> + <!-- tdf#100243 --> + <!-- tdf#117477 --> + <entry os="all" vendor="intel" compare="equal" version="21.20.16.4664"> + <device id="0x591b"/> + <device id="0x5916"/> + </entry> + <!-- tdf#115092 --> + <entry os="all" vendor="intel" compare="equal" version="22.20.16.4735"> + <device id="0x1912"/> + </entry> + <entry os="7" vendor="intel"> + <device id="all"/> + </entry> + <entry os="8" vendor="intel" compare="equal" version="10.18.10.3308"><!-- Intel(R) HD Graphics 4000 --> + <device id="0x0166"/> + </entry> + <entry os="10" vendor="intel" compare="between_inclusive_start" minVersion="26.20.100.6861" maxVersion="26.20.100.7584"><!-- tdf#125516 --> + <device id="all"/> + </entry> + <!-- tdf#131221 --> + <entry os="10" vendor="intel"> + <device id="0x5917"/> + </entry> + <entry os="all" vendor="amd" compare="less" version="15.200.1062.1004"> <!-- 150.200 --> + <device id="all"/> + </entry> + <entry os="all" vendor="nvidia" compare="less" version="10.18.13.5362"> <!-- 353.62 --> + <device id="all"/> + </entry> + <entry os="10" vendor="nvidia"> <!-- tdf#128441 --> + <device id="0x2182"/> + </entry> + <entry os="all" vendor="microsoft" compare="less" version="6.2.0.0"> <!-- 6.2.0.0 --> + <device id="all"/> + </entry> + </blacklist> +</root> diff --git a/vcl/opengl/program.cxx b/vcl/opengl/program.cxx new file mode 100644 index 000000000..6557eccf8 --- /dev/null +++ b/vcl/opengl/program.cxx @@ -0,0 +1,390 @@ +/* -*- 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 <opengl/program.hxx> +#include <opengl/RenderState.hxx> + +#include <vcl/opengl/OpenGLHelper.hxx> +#include <vcl/opengl/OpenGLContext.hxx> + +#include <glm/glm.hpp> +#include <glm/gtc/type_ptr.hpp> +#include <glm/gtc/matrix_transform.hpp> + +OpenGLProgram::OpenGLProgram() : + mnId( 0 ), + mnEnabledAttribs( 0 ), + mnPositionAttrib( SAL_MAX_UINT32 ), + mnTexCoordAttrib( SAL_MAX_UINT32 ), + mnAlphaCoordAttrib( SAL_MAX_UINT32 ), + mnMaskCoordAttrib( SAL_MAX_UINT32 ), + mnExtrusionVectorsAttrib( SAL_MAX_UINT32 ), + mnVertexColorsAttrib( SAL_MAX_UINT32 ), + mbBlending(false), + mfLastWidth(0.0), + mfLastHeight(0.0), + mfLastPixelOffset(0.0) +{ +} + +OpenGLProgram::~OpenGLProgram() +{ + maUniformLocations.clear(); + if( mnId != 0 ) + { + glDeleteProgram( mnId ); + CHECK_GL_ERROR(); + } +} + +bool OpenGLProgram::Load( const OUString& rVertexShader, + const OUString& rFragmentShader, + const OString& preamble, + const OString& rDigest ) +{ + mnId = OpenGLHelper::LoadShaders( rVertexShader, rFragmentShader, preamble, rDigest ); + return ( mnId != 0 ); +} + +void OpenGLProgram::Reuse() +{ + mbBlending = false; +} + +void OpenGLProgram::Use() +{ + if (!mnId) + return; + + glUseProgram(mnId); + CHECK_GL_ERROR(); + Reuse(); +} + +void OpenGLProgram::Clean() +{ + // unbind all textures + for (OpenGLTexture& rTexture : maTextures) + { + rTexture.Unbind(); + } + maTextures.clear(); + + // disable any enabled vertex attrib array + if( mnEnabledAttribs ) + { + for( int i = 0; i < 32; i++ ) + { + if( mnEnabledAttribs & ( 1 << i ) ) + { + glDisableVertexAttribArray( i ); + CHECK_GL_ERROR(); + } + } + mnEnabledAttribs = 0; + } +} + +bool OpenGLProgram::EnableVertexAttrib(GLuint& rAttrib, const OString& rName) +{ + if( rAttrib == SAL_MAX_UINT32 ) + { + GLint aLocation = glGetAttribLocation(mnId, rName.getStr()); + CHECK_GL_ERROR(); + if (aLocation < 0) + return false; + rAttrib = GLuint(aLocation); + } + if( (mnEnabledAttribs & ( 1 << rAttrib )) == 0 ) + { + glEnableVertexAttribArray( rAttrib ); + CHECK_GL_ERROR(); + mnEnabledAttribs |= ( 1 << rAttrib ); + } + return true; +} + +void OpenGLProgram::SetVertexAttrib(GLuint& rAttrib, const OString& rName, GLint nSize, + GLenum eType, GLboolean bNormalized, GLsizei aStride, + const GLvoid* pPointer) +{ + if (EnableVertexAttrib(rAttrib, rName)) + { + glVertexAttribPointer(rAttrib, nSize, eType, bNormalized, aStride, pPointer); + CHECK_GL_ERROR(); + } + else + { + VCL_GL_INFO("Vertex attribute '" << rName << "' doesn't exist in this program (" << mnId << ")"); + } +} + +void OpenGLProgram::SetVertices( const GLvoid* pData ) +{ + SetVertexAttrib(mnPositionAttrib, "position", 2, GL_FLOAT, GL_FALSE, 0, pData); +} + +void OpenGLProgram::SetTextureCoord( const GLvoid* pData ) +{ + SetVertexAttrib(mnTexCoordAttrib, "tex_coord_in", 2, GL_FLOAT, GL_FALSE, 0, pData); +} + +void OpenGLProgram::SetAlphaCoord( const GLvoid* pData ) +{ + SetVertexAttrib(mnAlphaCoordAttrib, "alpha_coord_in", 2, GL_FLOAT, GL_FALSE, 0, pData); +} + +void OpenGLProgram::SetMaskCoord(const GLvoid* pData) +{ + SetVertexAttrib(mnMaskCoordAttrib, "mask_coord_in", 2, GL_FLOAT, GL_FALSE, 0, pData); +} + +void OpenGLProgram::SetExtrusionVectors(const GLvoid* pData) +{ + SetVertexAttrib(mnExtrusionVectorsAttrib, "extrusion_vectors", 3, GL_FLOAT, GL_FALSE, 0, pData); +} + +void OpenGLProgram::SetVertexColors(std::vector<GLubyte>& rColorVector) +{ + SetVertexAttrib(mnVertexColorsAttrib, "vertex_color_in", 4, GL_UNSIGNED_BYTE, GL_FALSE, 0, rColorVector.data()); +} + +void OpenGLProgram::SetShaderType(TextureShaderType eTextureShaderType) +{ + SetUniform1i("type", GLint(eTextureShaderType)); +} + +void OpenGLProgram::SetShaderType(DrawShaderType eDrawShaderType) +{ + SetUniform1i("type", GLint(eDrawShaderType)); +} + +GLuint OpenGLProgram::GetUniformLocation( const OString& rName ) +{ + auto it = maUniformLocations.find( rName ); + if( it == maUniformLocations.end() ) + { + GLuint nLocation = glGetUniformLocation( mnId, rName.getStr() ); + CHECK_GL_ERROR(); + maUniformLocations[rName] = nLocation; + return nLocation; + } + + return it->second; +} + +void OpenGLProgram::DrawArrays(GLenum aMode, std::vector<GLfloat>& aVertices) +{ + if (!mbBlending) + OpenGLContext::getVCLContext()->state().blend().disable(); + + SetVertices(aVertices.data()); + glDrawArrays(aMode, 0, aVertices.size() / 2); +} + +void OpenGLProgram::DrawElements(GLenum aMode, GLuint nNumberOfVertices) +{ + if (!mbBlending) + OpenGLContext::getVCLContext()->state().blend().disable(); + + glDrawElements(aMode, nNumberOfVertices, GL_UNSIGNED_INT, nullptr); +} + +void OpenGLProgram::SetUniform1f( const OString& rName, GLfloat v1 ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform1f( nUniform, v1 ); + CHECK_GL_ERROR(); +} + +void OpenGLProgram::SetUniform2f( const OString& rName, GLfloat v1, GLfloat v2 ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform2f( nUniform, v1, v2 ); + CHECK_GL_ERROR(); +} + +void OpenGLProgram::SetUniform1fv( const OString& rName, GLsizei nCount, GLfloat const * aValues ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform1fv( nUniform, nCount, aValues ); + CHECK_GL_ERROR(); +} + +void OpenGLProgram::SetUniform2fv( const OString& rName, GLsizei nCount, GLfloat const * aValues ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform2fv( nUniform, nCount, aValues ); + CHECK_GL_ERROR(); +} + +void OpenGLProgram::SetUniform1i( const OString& rName, GLint v1 ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform1i( nUniform, v1 ); + CHECK_GL_ERROR(); +} + +void OpenGLProgram::SetColor( const OString& rName, Color nColor, sal_uInt8 nTransparency ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform4f( nUniform, + nColor.GetRed() / 255.0f, + nColor.GetGreen() / 255.0f, + nColor.GetBlue() / 255.0f, + (100 - nTransparency) * (1.0 / 100) ); + CHECK_GL_ERROR(); + + if( nTransparency > 0 ) + SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); +} + +void OpenGLProgram::SetColorf( const OString& rName, Color nColor, double fTransparency ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform4f( nUniform, + nColor.GetRed() / 255.0f, + nColor.GetGreen() / 255.0f, + nColor.GetBlue() / 255.0f, + (1.0f - fTransparency) ); + CHECK_GL_ERROR(); + + if( fTransparency > 0.0 ) + SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); +} + +void OpenGLProgram::SetColor( const OString& rName, const Color& rColor ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform4f( nUniform, + static_cast<float>(rColor.GetRed()) / 255, + static_cast<float>(rColor.GetGreen()) / 255, + static_cast<float>(rColor.GetBlue()) / 255, + 1.0f - static_cast<float>(rColor.GetTransparency()) / 255 ); + CHECK_GL_ERROR(); + + if( rColor.GetTransparency() > 0 ) + SetBlendMode( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); +} + +void OpenGLProgram::SetColorWithIntensity( const OString& rName, const Color& rColor, long nFactor ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform4f( nUniform, + static_cast<float>(rColor.GetRed()) * nFactor / 25500.0, + static_cast<float>(rColor.GetGreen()) * nFactor / 25500.0, + static_cast<float>(rColor.GetBlue()) * nFactor / 25500.0, + 1.0f ); + CHECK_GL_ERROR(); +} + +void OpenGLProgram::SetTexture( const OString& rName, OpenGLTexture& rTexture ) +{ + GLuint nUniform = GetUniformLocation( rName ); + int nIndex = maTextures.size(); + + glUniform1i( nUniform, nIndex ); + CHECK_GL_ERROR(); + + OpenGLContext::getVCLContext()->state().texture().active(nIndex); + + rTexture.Bind(); + maTextures.push_back(rTexture); +} + +void OpenGLProgram::SetTransform( + const OString& rName, + const OpenGLTexture& rTexture, + const basegfx::B2DPoint& rNull, + const basegfx::B2DPoint& rX, + const basegfx::B2DPoint& rY ) +{ + auto nTexWidth = rTexture.GetWidth(); + auto nTexHeight = rTexture.GetHeight(); + if (nTexWidth == 0 || nTexHeight == 0) + return; + + GLuint nUniform = GetUniformLocation( rName ); + const basegfx::B2DVector aXRel = rX - rNull; + const basegfx::B2DVector aYRel = rY - rNull; + const float aValues[] = { + static_cast<float>(aXRel.getX())/nTexWidth, static_cast<float>(aXRel.getY())/nTexWidth, 0, 0, + static_cast<float>(aYRel.getX())/nTexHeight, static_cast<float>(aYRel.getY())/nTexHeight, 0, 0, + 0, 0, 1, 0, + static_cast<float>(rNull.getX()), static_cast<float>(rNull.getY()), 0, 1 }; + glm::mat4 aMatrix = glm::make_mat4( aValues ); + glUniformMatrix4fv( nUniform, 1, GL_FALSE, glm::value_ptr( aMatrix ) ); + CHECK_GL_ERROR(); +} + +void OpenGLProgram::SetIdentityTransform(const OString& rName) +{ + GLuint nUniform = GetUniformLocation(rName); + glm::mat4 aMatrix {}; + glUniformMatrix4fv(nUniform, 1, GL_FALSE, glm::value_ptr( aMatrix ) ); + CHECK_GL_ERROR(); +} + +void OpenGLProgram::ApplyMatrix(float fWidth, float fHeight, float fPixelOffset) +{ + + if (mfLastWidth == fWidth && mfLastHeight == fHeight && mfLastPixelOffset == fPixelOffset) + return; + + mfLastWidth = fWidth; + mfLastHeight = fHeight; + mfLastPixelOffset = fPixelOffset; + + GLuint nUniform = GetUniformLocation("mvp"); + + glm::mat4 aMVP = glm::ortho(0.0f, fWidth, fHeight, 0.0f, 0.0f, 1.0f); + + if (fPixelOffset != 0.0f) + aMVP = glm::translate(aMVP, glm::vec3(fPixelOffset, fPixelOffset, 0.0f)); + + glUniformMatrix4fv(nUniform, 1, GL_FALSE, glm::value_ptr(aMVP)); + CHECK_GL_ERROR(); +} + +void OpenGLProgram::SetBlendMode(GLenum nSFactor, GLenum nDFactor) +{ + OpenGLContext::getVCLContext()->state().blend().enable(); + OpenGLContext::getVCLContext()->state().blend().func(nSFactor, nDFactor); + mbBlending = true; +} + +void OpenGLProgram::DrawTexture( const OpenGLTexture& rTexture ) +{ + if (!rTexture) + return; + + float fWidth = rTexture.GetWidth(); + float fHeight = rTexture.GetHeight(); + + float fMinX = 0.0f; + float fMaxX = fWidth; + float fMinY = 0.0f; + float fMaxY = fHeight; + + std::vector<GLfloat> aPosition { + fMinX, fMaxY, + fMinX, fMinY, + fMaxX, fMinY, + fMaxX, fMaxY + }; + GLfloat aTexCoord[8]; + + rTexture.GetWholeCoord( aTexCoord ); + SetTextureCoord( aTexCoord ); + ApplyMatrix(fWidth, fHeight); + DrawArrays(GL_TRIANGLE_FAN, aPosition); + CHECK_GL_ERROR(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/salbmp.cxx b/vcl/opengl/salbmp.cxx new file mode 100644 index 000000000..e9b1bca73 --- /dev/null +++ b/vcl/opengl/salbmp.cxx @@ -0,0 +1,783 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <sal/config.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/debug.hxx> + +#include <vcl/opengl/OpenGLHelper.hxx> + +#include <vcl/bitmap.hxx> +#include <vcl/checksum.hxx> +#include <vcl/outdev.hxx> +#include <svdata.hxx> +#include <salgdi.hxx> +#include <vcleventlisteners.hxx> +#include <vcl/lazydelete.hxx> +#include <scanlinewriter.hxx> + +#include <o3tl/make_shared.hxx> + +#include <opengl/zone.hxx> +#include <opengl/program.hxx> +#include <opengl/salbmp.hxx> +#include <opengl/RenderState.hxx> +#include <opengl/FixedTextureAtlas.hxx> + +#if OSL_DEBUG_LEVEL > 0 +# define CANARY "tex-canary" +#endif + +namespace +{ + +bool determineTextureFormat(sal_uInt16 nBits, GLenum& nFormat, GLenum& nType) +{ + switch(nBits) + { + case 8: + nFormat = GL_LUMINANCE; + nType = GL_UNSIGNED_BYTE; + return true; + case 24: + nFormat = GL_RGB; + nType = GL_UNSIGNED_BYTE; + return true; + case 32: + nFormat = GL_RGBA; + nType = GL_UNSIGNED_BYTE; + return true; + default: + break; + } + SAL_WARN("vcl.opengl", "Could not determine the appropriate texture format for input bits '" << nBits << "'"); + return false; +} + +bool isValidBitCount( sal_uInt16 nBitCount ) +{ + return (nBitCount == 1) || (nBitCount == 4) || (nBitCount == 8) || (nBitCount == 24) || (nBitCount == 32); +} + +sal_uInt32 lclBytesPerRow(sal_uInt16 nBits, int nWidth) +{ + switch(nBits) + { + case 1: return (nWidth + 7) >> 3; + case 4: return (nWidth + 1) >> 1; + case 8: return nWidth; + case 24: return nWidth * 3; + case 32: return nWidth * 4; + default: + OSL_FAIL("vcl::OpenGLSalBitmap::AllocateUserData(), illegal bitcount!"); + } + return 0; +} +} + +OpenGLSalBitmap::OpenGLSalBitmap() +: mbDirtyTexture(true) +, mnBits(0) +, mnBytesPerRow(0) +, mnWidth(0) +, mnHeight(0) +{ +} + +OpenGLSalBitmap::~OpenGLSalBitmap() +{ + Destroy(); + VCL_GL_INFO( "~OpenGLSalBitmap" ); +} + +void OpenGLSalBitmap::Create( const OpenGLTexture& rTex, long nX, long nY, long nWidth, long nHeight ) +{ + DBG_TESTSOLARMUTEX(); + static const BitmapPalette aEmptyPalette; + OpenGLVCLContextZone aContextZone; + + Destroy(); + VCL_GL_INFO( "OpenGLSalBitmap::Create from FBO: [" + << nX << ", " << nY << "] " << nWidth << "x" << nHeight ); + + GLint nMaxTextureSize; + glGetIntegerv( GL_MAX_TEXTURE_SIZE, &nMaxTextureSize ); + if ( nWidth > nMaxTextureSize ) + { + nWidth = nMaxTextureSize; + VCL_GL_INFO( "Width limited to " << nMaxTextureSize ); + } + + if ( nHeight > nMaxTextureSize ) + { + nHeight = nMaxTextureSize; + VCL_GL_INFO( "Height limited to " << nMaxTextureSize ); + } + + mnWidth = nWidth; + mnHeight = nHeight; + + // TODO Check the framebuffer configuration + mnBits = 32; + maPalette = aEmptyPalette; + + if( rTex ) + maTexture = OpenGLTexture( rTex, nX, nY, nWidth, nHeight ); + else + maTexture = OpenGLTexture( nX, nY, nWidth, nHeight ); + mbDirtyTexture = false; + VCL_GL_INFO( "Created texture " << maTexture.Id() ); + + assert(mnWidth == maTexture.GetWidth() && + mnHeight == maTexture.GetHeight()); +} + +bool OpenGLSalBitmap::Create( const Size& rSize, sal_uInt16 nBits, const BitmapPalette& rBitmapPalette ) +{ + DBG_TESTSOLARMUTEX(); + OpenGLVCLContextZone aContextZone; + + Destroy(); + VCL_GL_INFO( "OpenGLSalBitmap::Create with size: " << rSize ); + + if( !isValidBitCount( nBits ) ) + return false; + maPalette = rBitmapPalette; + mnBits = nBits; + mnWidth = rSize.Width(); + mnHeight = rSize.Height(); + + // Limit size to what GL allows, so later glTexImage2D() won't fail. + GLint nMaxTextureSize; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &nMaxTextureSize); + if (mnWidth > nMaxTextureSize) + mnWidth = nMaxTextureSize; + if (mnHeight > nMaxTextureSize) + mnHeight = nMaxTextureSize; + + return false; +} + +bool OpenGLSalBitmap::Create( const SalBitmap& rSalBmp ) +{ + DBG_TESTSOLARMUTEX(); + return Create( rSalBmp, rSalBmp.GetBitCount() ); +} + +bool OpenGLSalBitmap::Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics ) +{ + DBG_TESTSOLARMUTEX(); + return Create( rSalBmp, pGraphics ? pGraphics->GetBitCount() : rSalBmp.GetBitCount() ); +} + +bool OpenGLSalBitmap::Create( const SalBitmap& rSalBmp, sal_uInt16 nNewBitCount ) +{ + DBG_TESTSOLARMUTEX(); + OpenGLZone aZone; + + // check that carefully only in the debug mode + assert(dynamic_cast<const OpenGLSalBitmap*>(&rSalBmp)); + + const OpenGLSalBitmap& rSourceBitmap = static_cast<const OpenGLSalBitmap&>(rSalBmp); + + VCL_GL_INFO("OpenGLSalBitmap::Create from BMP: " + << rSourceBitmap.mnWidth << "x" << rSourceBitmap.mnHeight + << " Bits old: " << mnBits << " new:" << nNewBitCount ); + + if( isValidBitCount( nNewBitCount ) ) + { + // TODO: lfrb: What about the pending operations?! + mnBits = nNewBitCount; + mnBytesPerRow = rSourceBitmap.mnBytesPerRow; + mnWidth = rSourceBitmap.mnWidth; + mnHeight = rSourceBitmap.mnHeight; + maPalette = rSourceBitmap.maPalette; + // execute any pending operations on the source bitmap + maTexture = rSourceBitmap.GetTexture(); + mbDirtyTexture = false; + + // be careful here, we are share & reference-count the + // mpUserBuffer, BUT this Create() is called from + // Bitmap::ImplMakeUnique(). + // Consequently, there might be cases when this needs to be made + // unique later (when we don't do that right away here), like when + // using the BitmapWriteAccess. + mpUserBuffer = rSourceBitmap.mpUserBuffer; + + return true; + } + return false; +} + +bool OpenGLSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/, Size& /*rSize*/, bool /*bMask*/ ) +{ + DBG_TESTSOLARMUTEX(); + // TODO Is this method needed? + return false; +} + +OpenGLTexture& OpenGLSalBitmap::GetTexture() const +{ + OpenGLSalBitmap* pThis = const_cast<OpenGLSalBitmap*>(this); + if( !maTexture || mbDirtyTexture ) + pThis->CreateTexture(); + VCL_GL_INFO( "Got texture " << maTexture.Id() ); + return pThis->maTexture; +} + +void OpenGLSalBitmap::Destroy() +{ + OpenGLZone aZone; + + VCL_GL_INFO("Destroy OpenGLSalBitmap texture:" << maTexture.Id()); + maTexture = OpenGLTexture(); + DeallocateUserData(); +} + +bool OpenGLSalBitmap::AllocateUserData() +{ + VCL_GL_INFO( "OpenGLSalBitmap::AllocateUserData" ); + + if( mnWidth && mnHeight ) + { + mnBytesPerRow = lclBytesPerRow(mnBits, mnWidth); + } + + bool alloc = false; + if (mnBytesPerRow != 0 && mnHeight && + mnBytesPerRow <= std::numeric_limits<sal_uInt32>::max() / mnHeight) + { + try + { + size_t nToAllocate = mnBytesPerRow * mnHeight; +#if OSL_DEBUG_LEVEL > 0 + nToAllocate += sizeof(CANARY); +#endif + mpUserBuffer = o3tl::make_shared_array<sal_uInt8>(nToAllocate); +#if OSL_DEBUG_LEVEL > 0 + memcpy(mpUserBuffer.get() + nToAllocate - sizeof(CANARY), + CANARY, sizeof(CANARY)); +#endif + alloc = true; + } + catch (const std::bad_alloc &) {} + } + if (!alloc) + { + SAL_WARN("vcl.opengl", "bad alloc " << mnBytesPerRow << "x" << mnHeight); + DeallocateUserData(); + } +#ifdef DBG_UTIL + else + { + for (size_t i = 0; i < size_t(mnBytesPerRow * mnHeight); i++) + mpUserBuffer.get()[i] = (i & 0xFF); + } +#endif + + return mpUserBuffer != nullptr; +} + +void OpenGLSalBitmap::DeallocateUserData() +{ + mpUserBuffer.reset(); + mnBytesPerRow = 0; +} + +namespace { + +void lclInstantiateTexture(OpenGLTexture& rTexture, const int nWidth, const int nHeight, + const GLenum nFormat, const GLenum nType, sal_uInt8 const * pData) +{ + if (nWidth == nHeight) + { + typedef std::vector<std::unique_ptr<FixedTextureAtlasManager>> TextureAtlasVector; + static vcl::DeleteOnDeinit<TextureAtlasVector> aTextureAtlases([]() { + TextureAtlasVector* p = new TextureAtlasVector; + p->reserve(5); + p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 16)); + p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 24)); + p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 32)); + p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 48)); + p->push_back(std::make_unique<FixedTextureAtlasManager>(8, 8, 64)); + return p; + }()); + for (std::unique_ptr<FixedTextureAtlasManager>& pTextureAtlas : *aTextureAtlases.get()) + { + if (nWidth == pTextureAtlas->GetSubtextureSize()) + { + rTexture = pTextureAtlas->InsertBuffer(nWidth, nHeight, nFormat, nType, pData); + return; + } + } + } + rTexture = OpenGLTexture (nWidth, nHeight, nFormat, nType, pData); +} + +} // end anonymous namespace + +Size OpenGLSalBitmap::GetSize() const +{ + return Size(mnWidth, mnHeight); +} + +GLuint OpenGLSalBitmap::CreateTexture() +{ + VCL_GL_INFO( "::CreateTexture bits: " << mnBits); + GLenum nFormat = GL_RGBA; + GLenum nType = GL_UNSIGNED_BYTE; + sal_uInt8* pData( nullptr ); + bool bAllocated( false ); + + if (mpUserBuffer != nullptr) + { + if( mnBits == 24 || mnBits == 32 ) + { + // no conversion needed for truecolor + pData = mpUserBuffer.get(); + + determineTextureFormat(mnBits, nFormat, nType); + } + else if( mnBits == 8 && maPalette.IsGreyPalette8Bit() ) + { + // no conversion needed for 8bit grayscale + pData = mpUserBuffer.get(); + nFormat = GL_LUMINANCE; + nType = GL_UNSIGNED_BYTE; + } + else + { + VCL_GL_INFO( "::CreateTexture - convert from " << mnBits << " to 24 bits" ); + // convert to 24 bits RGB using palette + determineTextureFormat(24, nFormat, nType); + pData = convertDataBitCount( mpUserBuffer.get(), mnWidth, mnHeight, + mnBits, mnBytesPerRow, maPalette, + nFormat == GL_BGR ? BitConvert::BGR : BitConvert::RGB ).release(); + bAllocated = true; + } + } + + OpenGLVCLContextZone aContextZone; + + lclInstantiateTexture(maTexture, mnWidth, mnHeight, nFormat, nType, pData); + + VCL_GL_INFO("Created texture " << maTexture.Id() << " bits: " << mnBits); + + if( bAllocated ) + delete[] pData; + + mbDirtyTexture = false; + + CHECK_GL_ERROR(); + return maTexture.Id(); +} + +bool OpenGLSalBitmap::ReadTexture() +{ + sal_uInt8* pData = mpUserBuffer.get(); + + GLenum nFormat = GL_RGBA; + GLenum nType = GL_UNSIGNED_BYTE; + + VCL_GL_INFO( "::ReadTexture " << mnWidth << "x" << mnHeight << " bits: " << mnBits); + + if( pData == nullptr ) + return false; + + OpenGLVCLContextZone aContextZone; + + rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext(); + xContext->state().scissor().disable(); + xContext->state().stencil().disable(); + + if ((mnBits == 8 && maPalette.IsGreyPalette8Bit()) || mnBits == 24 || mnBits == 32) + { + determineTextureFormat(mnBits, nFormat, nType); + +#if OSL_DEBUG_LEVEL > 0 + // help valgrind & drmemory rescue us - touch last and first bits. + pData[0] = 0; + pData[mnBits/8*mnWidth*mnHeight-1] = 0; + // if this fails we can read too much into pData + assert(mnWidth == maTexture.GetWidth() && + mnHeight == maTexture.GetHeight()); +#endif + + maTexture.Read(nFormat, nType, pData); + +#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG + // If we read over the end of pData we have a real hidden memory + // corruption problem ! + size_t nCanary = mnBytesPerRow * mnHeight; + assert(!memcmp(pData + nCanary, CANARY, sizeof (CANARY))); +#endif + return true; + } + else if (mnBits == 1 || mnBits == 4 || mnBits == 8) + { // convert buffers from 24-bit RGB to 1,4 or 8-bit buffer + std::vector<sal_uInt8> aBuffer(mnWidth * mnHeight * 3); + + sal_uInt8* pBuffer = aBuffer.data(); + determineTextureFormat(24, nFormat, nType); + maTexture.Read(nFormat, nType, pBuffer); + sal_uInt32 nSourceBytesPerRow = lclBytesPerRow(24, mnWidth); + + std::unique_ptr<vcl::ScanlineWriter> pWriter = vcl::ScanlineWriter::Create(mnBits, maPalette); + for (int y = 0; y < mnHeight; ++y) + { + sal_uInt8* pSource = &pBuffer[y * nSourceBytesPerRow]; + sal_uInt8* pDestination = &pData[y * mnBytesPerRow]; + + pWriter->nextLine(pDestination); + + for (int x = 0; x < mnWidth; ++x) + { + // read source + sal_uInt8 nR = *pSource++; + sal_uInt8 nG = *pSource++; + sal_uInt8 nB = *pSource++; + + pWriter->writeRGB(nR, nG, nB); + } + } + return true; + } + + SAL_WARN("vcl.opengl", "::ReadTexture - tx:" << maTexture.Id() << " @ " + << mnWidth << "x" << mnHeight << "- unimplemented bit depth: " + << mnBits); + return false; +} + +sal_uInt16 OpenGLSalBitmap::GetBitCount() const +{ + return mnBits; +} + +bool OpenGLSalBitmap::calcChecksumGL(OpenGLTexture& rInputTexture, BitmapChecksum& rChecksum) const +{ + OUString FragShader("areaHashCRC64TFragmentShader"); + + rtl::Reference< OpenGLContext > xContext = OpenGLContext::getVCLContext(); + xContext->state().scissor().disable(); + xContext->state().stencil().disable(); + + static vcl::DeleteOnDeinit<OpenGLTexture> gCRCTableTexture( + new OpenGLTexture(512, 1, GL_RGBA, GL_UNSIGNED_BYTE, + vcl_get_crc64_table())); + OpenGLTexture &rCRCTableTexture = *gCRCTableTexture.get(); + + // First Pass + + int nWidth = rInputTexture.GetWidth(); + int nHeight = rInputTexture.GetHeight(); + + OpenGLProgram* pProgram = xContext->UseProgram("textureVertexShader", FragShader); + if (pProgram == nullptr) + return false; + + int nNewWidth = ceil( nWidth / 4.0 ); + int nNewHeight = ceil( nHeight / 4.0 ); + + OpenGLTexture aFirstPassTexture(nNewWidth, nNewHeight); + OpenGLFramebuffer* pFramebuffer = xContext->AcquireFramebuffer(aFirstPassTexture); + + pProgram->SetUniform1f( "xstep", 1.0 / mnWidth ); + pProgram->SetUniform1f( "ystep", 1.0 / mnHeight ); + + pProgram->SetTexture("crc_table", rCRCTableTexture); + pProgram->SetTexture("sampler", rInputTexture); + pProgram->DrawTexture(rInputTexture); + pProgram->Clean(); + + OpenGLContext::ReleaseFramebuffer(pFramebuffer); + + CHECK_GL_ERROR(); + + // Second Pass + + nWidth = aFirstPassTexture.GetWidth(); + nHeight = aFirstPassTexture.GetHeight(); + + pProgram = xContext->UseProgram("textureVertexShader", FragShader); + if (pProgram == nullptr) + return false; + + nNewWidth = ceil( nWidth / 4.0 ); + nNewHeight = ceil( nHeight / 4.0 ); + + OpenGLTexture aSecondPassTexture(nNewWidth, nNewHeight); + pFramebuffer = xContext->AcquireFramebuffer(aSecondPassTexture); + + pProgram->SetUniform1f( "xstep", 1.0 / mnWidth ); + pProgram->SetUniform1f( "ystep", 1.0 / mnHeight ); + + pProgram->SetTexture("crc_table", rCRCTableTexture); + pProgram->SetTexture("sampler", aFirstPassTexture); + pProgram->DrawTexture(aFirstPassTexture); + pProgram->Clean(); + + OpenGLContext::ReleaseFramebuffer(pFramebuffer); + + CHECK_GL_ERROR(); + + // Final CRC on CPU + OpenGLTexture& aFinalTexture = aSecondPassTexture; + std::vector<sal_uInt8> aBuf( aFinalTexture.GetWidth() * aFinalTexture.GetHeight() * 4 ); + aFinalTexture.Read(GL_RGBA, GL_UNSIGNED_BYTE, aBuf.data()); + + BitmapChecksum nCrc = vcl_get_checksum(0, aBuf.data(), aBuf.size()); + + rChecksum = nCrc; + return true; +} + +void OpenGLSalBitmap::updateChecksum() const +{ + if (mbChecksumValid) + return; + + if( (mnWidth * mnHeight) < (1024*768) || mnWidth < 128 || mnHeight < 128 ) + { + SalBitmap::updateChecksum(); + return; + } + + OpenGLSalBitmap* pThis = const_cast<OpenGLSalBitmap*>(this); + + OpenGLVCLContextZone aContextZone; + OpenGLTexture& rInputTexture = GetTexture(); + pThis->mbChecksumValid = calcChecksumGL(rInputTexture, pThis->mnChecksum); + if (!pThis->mbChecksumValid) + SalBitmap::updateChecksum(); +} + +BitmapBuffer* OpenGLSalBitmap::AcquireBuffer( BitmapAccessMode nMode ) +{ + OpenGLVCLContextZone aContextZone; + + if( nMode != BitmapAccessMode::Info ) + { + if (!mpUserBuffer) + { + if( !AllocateUserData() ) + return nullptr; + if( maTexture && !ReadTexture() ) + { + DeallocateUserData(); + return nullptr; + } + } + } + + // mpUserBuffer must be unique when we are doing the write access + if (nMode == BitmapAccessMode::Write && mpUserBuffer && mpUserBuffer.use_count() > 1) + { + std::shared_ptr<sal_uInt8> aBuffer(mpUserBuffer); + + mpUserBuffer.reset(); + AllocateUserData(); + memcpy(mpUserBuffer.get(), aBuffer.get(), mnBytesPerRow * mnHeight); + } + + BitmapBuffer* pBuffer = new BitmapBuffer; + pBuffer->mnWidth = mnWidth; + pBuffer->mnHeight = mnHeight; + pBuffer->maPalette = maPalette; + pBuffer->mnScanlineSize = mnBytesPerRow; + pBuffer->mpBits = mpUserBuffer.get(); + pBuffer->mnBitCount = mnBits; + + switch (mnBits) + { + case 1: + pBuffer->mnFormat = ScanlineFormat::N1BitMsbPal; + break; + case 4: + pBuffer->mnFormat = ScanlineFormat::N4BitMsnPal; + break; + case 8: + pBuffer->mnFormat = ScanlineFormat::N8BitPal; + break; + case 24: + { + pBuffer->mnFormat = ScanlineFormat::N24BitTcRgb; + break; + } + case 32: + { + pBuffer->mnFormat = ScanlineFormat::N32BitTcRgba; + ColorMaskElement aRedMask(0xff000000); + aRedMask.CalcMaskShift(); + ColorMaskElement aGreenMask(0x00ff0000); + aGreenMask.CalcMaskShift(); + ColorMaskElement aBlueMask(0x0000ff00); + aBlueMask.CalcMaskShift(); + pBuffer->maColorMask = ColorMask(aRedMask, aGreenMask, aBlueMask); + break; + } + default: assert(false); + } + + return pBuffer; +} + +void OpenGLSalBitmap::ReleaseBuffer( BitmapBuffer* pBuffer, BitmapAccessMode nMode ) +{ + OpenGLVCLContextZone aContextZone; + + if( nMode == BitmapAccessMode::Write ) + { + maTexture = OpenGLTexture(); + mbDirtyTexture = true; + mbChecksumValid = false; + } + // The palette is modified on read during the BitmapWriteAccess, + // but of course, often it is not modified; interesting. + maPalette = pBuffer->maPalette; + + // Are there any more ground movements underneath us ? + assert( pBuffer->mnWidth == mnWidth ); + assert( pBuffer->mnHeight == mnHeight ); + assert( pBuffer->mnBitCount == mnBits ); + + delete pBuffer; +} + +bool OpenGLSalBitmap::GetSystemData( BitmapSystemData& /*rData*/ ) +{ + SAL_WARN( "vcl.opengl", "*** NOT IMPLEMENTED *** GetSystemData" ); +#if 0 + // TODO Implement for ANDROID/OSX/IOS/WIN32 + X11SalBitmap rBitmap; + BitmapBuffer* pBuffer; + + rBitmap.Create( GetSize(), mnBits, maPalette ); + pBuffer = rBitmap.AcquireBuffer( false ); + if( pBuffer == NULL ) + return false; + + if (!mpUserBuffer.get()) + { + if( !AllocateUserData() || !ReadTexture() ) + { + rBitmap.ReleaseBuffer( pBuffer, false ); + DeallocateUserData(); + return false; + } + } + + // TODO Might be more efficient to add a static method to SalBitmap + // to get system data from a buffer + memcpy( pBuffer->mpBits, mpUserBuffer.get(), mnBytesPerRow * mnHeight ); + + rBitmap.ReleaseBuffer( pBuffer, false ); + return rBitmap.GetSystemData( rData ); +#else + return false; +#endif +} + +bool OpenGLSalBitmap::Replace( const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol ) +{ + VCL_GL_INFO("::Replace"); + + OpenGLZone aZone; + rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext(); + xContext->state().scissor().disable(); + xContext->state().stencil().disable(); + + OpenGLFramebuffer* pFramebuffer; + OpenGLProgram* pProgram; + + GetTexture(); + pProgram = xContext->UseProgram( "textureVertexShader", + "replaceColorFragmentShader" ); + if( !pProgram ) + return false; + + OpenGLTexture aNewTex( mnWidth, mnHeight ); + pFramebuffer = xContext->AcquireFramebuffer( aNewTex ); + + pProgram->SetTexture( "sampler", maTexture ); + pProgram->SetColor( "search_color", rSearchColor ); + pProgram->SetColor( "replace_color", rReplaceColor ); + pProgram->SetUniform1f( "epsilon", nTol / 255.0f ); + pProgram->DrawTexture( maTexture ); + pProgram->Clean(); + + OpenGLContext::ReleaseFramebuffer( pFramebuffer ); + maTexture = aNewTex; + + CHECK_GL_ERROR(); + return true; +} + +// Convert texture to greyscale and adjust bitmap metadata +bool OpenGLSalBitmap::ConvertToGreyscale() +{ + VCL_GL_INFO("::ConvertToGreyscale"); + + // avoid re-converting to 8bits. + if ( mnBits == 8 && maPalette.IsGreyPalette8Bit()) + return true; + + OpenGLZone aZone; + rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext(); + xContext->state().scissor().disable(); + xContext->state().stencil().disable(); + + OpenGLFramebuffer* pFramebuffer; + OpenGLProgram* pProgram; + + GetTexture(); + pProgram = xContext->UseProgram("textureVertexShader", "greyscaleFragmentShader"); + + if (!pProgram) + return false; + + OpenGLTexture aNewTex(mnWidth, mnHeight); + pFramebuffer = xContext->AcquireFramebuffer(aNewTex); + pProgram->SetTexture("sampler", maTexture); + pProgram->DrawTexture(maTexture); + pProgram->Clean(); + + OpenGLContext::ReleaseFramebuffer( pFramebuffer ); + maTexture = aNewTex; + mnBits = 8; + maPalette = Bitmap::GetGreyPalette(256); + + // AllocateUserData will handle the rest. + DeallocateUserData(); + mbDirtyTexture = false; + + CHECK_GL_ERROR(); + return true; +} + +// This is needed to just make the bitmap usable as an alpha channel. +// Converting to 8bit grey will do. +bool OpenGLSalBitmap::InterpretAs8Bit() +{ + return ConvertToGreyscale(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/scale.cxx b/vcl/opengl/scale.cxx new file mode 100644 index 000000000..98f0f5ea7 --- /dev/null +++ b/vcl/opengl/scale.cxx @@ -0,0 +1,423 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <cmath> + +#include <vcl/opengl/OpenGLHelper.hxx> + +#include <vcl/bitmap.hxx> + +#include <opengl/zone.hxx> +#include <opengl/salbmp.hxx> +#include <opengl/program.hxx> +#include <opengl/texture.hxx> +#include <opengl/RenderState.hxx> + +#include <ResampleKernel.hxx> + +using vcl::Kernel; +using vcl::Lanczos3Kernel; + +bool OpenGLSalBitmap::ImplScaleFilter( + const rtl::Reference< OpenGLContext > &xContext, + const double& rScaleX, + const double& rScaleY, + GLenum nFilter ) +{ + OpenGLFramebuffer* pFramebuffer; + OpenGLProgram* pProgram; + GLenum nOldFilter; + int nNewWidth( mnWidth * rScaleX ); + int nNewHeight( mnHeight * rScaleY ); + + pProgram = xContext->UseProgram( "textureVertexShader", + "textureFragmentShader" ); + if( !pProgram ) + return false; + + OpenGLTexture aNewTex(nNewWidth, nNewHeight); + pFramebuffer = xContext->AcquireFramebuffer( aNewTex ); + + pProgram->SetTexture( "sampler", maTexture ); + nOldFilter = maTexture.GetFilter(); + maTexture.SetFilter( nFilter ); + pProgram->ApplyMatrix(mnWidth, mnHeight); + pProgram->DrawTexture( maTexture ); + maTexture.SetFilter( nOldFilter ); + pProgram->Clean(); + + OpenGLContext::ReleaseFramebuffer( pFramebuffer ); + + mnWidth = nNewWidth; + mnHeight = nNewHeight; + maTexture = aNewTex; + + CHECK_GL_ERROR(); + return true; +} + +void OpenGLSalBitmap::ImplCreateKernel( + const double& fScale, + const Kernel& rKernel, + GLfloat*& pWeights, + sal_uInt32& aKernelSize ) +{ + const double fSamplingRadius(rKernel.GetWidth()); + const double fScaledRadius((fScale < 1.0) ? fSamplingRadius / fScale : fSamplingRadius); + const double fFilterFactor(std::min(fScale, 1.0)); + int aNumberOfContributions; + double aSum( 0 ); + + aNumberOfContributions = (static_cast< sal_uInt32 >(fabs(ceil(fScaledRadius))) * 2) + 1 - 6; + aKernelSize = aNumberOfContributions / 2 + 1; + + // avoid a crash for now; re-think me. + if (aKernelSize > 16) + aKernelSize = 16; + + pWeights = new GLfloat[16]; + memset( pWeights, 0, 16 * sizeof( GLfloat ) ); + + for( sal_uInt32 i(0); i < aKernelSize; i++ ) + { + const GLfloat aWeight( rKernel.Calculate( fFilterFactor * i ) ); + if( fabs( aWeight ) >= 0.0001 ) + { + pWeights[i] = aWeight; + aSum += i > 0 ? aWeight * 2 : aWeight; + } + } + + for( sal_uInt32 i(0); i < aKernelSize; i++ ) + { + pWeights[i] /= aSum; + } +} + +bool OpenGLSalBitmap::ImplScaleConvolution( + const rtl::Reference< OpenGLContext > &xContext, + const double& rScaleX, + const double& rScaleY, + const Kernel& aKernel ) +{ + OpenGLFramebuffer* pFramebuffer; + OpenGLProgram* pProgram; + GLfloat* pWeights( nullptr ); + sal_uInt32 nKernelSize; + GLfloat aOffsets[32]; + int nNewWidth( mnWidth * rScaleX ); + int nNewHeight( mnHeight * rScaleY ); + + // TODO Make sure the framebuffer is alright + + pProgram = xContext->UseProgram( "textureVertexShader", + "convolutionFragmentShader" ); + if( pProgram == nullptr ) + return false; + + // horizontal scaling in scratch texture + if( mnWidth != nNewWidth ) + { + OpenGLTexture aScratchTex(nNewWidth, nNewHeight); + + pFramebuffer = xContext->AcquireFramebuffer( aScratchTex ); + + for( sal_uInt32 i = 0; i < 16; i++ ) + { + aOffsets[i * 2] = i / static_cast<double>(mnWidth); + aOffsets[i * 2 + 1] = 0; + } + ImplCreateKernel( rScaleX, aKernel, pWeights, nKernelSize ); + pProgram->SetUniform1fv( "kernel", 16, pWeights ); + pProgram->SetUniform2fv( "offsets", 16, aOffsets ); + pProgram->SetTexture( "sampler", maTexture ); + pProgram->DrawTexture( maTexture ); + pProgram->Clean(); + + maTexture = aScratchTex; + OpenGLContext::ReleaseFramebuffer( pFramebuffer ); + } + + // vertical scaling in final texture + if( mnHeight != nNewHeight ) + { + OpenGLTexture aScratchTex(nNewWidth, nNewHeight); + + pFramebuffer = xContext->AcquireFramebuffer( aScratchTex ); + + for( sal_uInt32 i = 0; i < 16; i++ ) + { + aOffsets[i * 2] = 0; + aOffsets[i * 2 + 1] = i / static_cast<double>(mnHeight); + } + ImplCreateKernel( rScaleY, aKernel, pWeights, nKernelSize ); + pProgram->SetUniform1fv( "kernel", 16, pWeights ); + pProgram->SetUniform2fv( "offsets", 16, aOffsets ); + pProgram->SetTexture( "sampler", maTexture ); + pProgram->DrawTexture( maTexture ); + pProgram->Clean(); + + maTexture = aScratchTex; + OpenGLContext::ReleaseFramebuffer( pFramebuffer ); + } + + mnWidth = nNewWidth; + mnHeight = nNewHeight; + + CHECK_GL_ERROR(); + return true; +} + +/* + "Area" scaling algorithm, which seems to give better results for downscaling + than other algorithms. The principle (taken from opencv, see resize.cl) + is that each resulting pixel is the average of all the source pixel values + it represents. Which is trivial in the case of exact multiples for downscaling, + the generic case needs to also consider that some source pixels contribute + only partially to their resulting pixels (because of non-integer multiples). +*/ +bool OpenGLSalBitmap::ImplScaleArea( const rtl::Reference< OpenGLContext > &xContext, + double rScaleX, double rScaleY ) +{ + int nNewWidth( mnWidth * rScaleX ); + int nNewHeight( mnHeight * rScaleY ); + + if( nNewWidth == mnWidth && nNewHeight == mnHeight ) + return true; + + double ixscale = 1 / rScaleX; + double iyscale = 1 / rScaleY; + bool fast = ( ixscale == std::trunc( ixscale ) && iyscale == std::trunc( iyscale ) + && int( nNewWidth * ixscale ) == mnWidth && int( nNewHeight * iyscale ) == mnHeight ); + + bool bTwoPasses = false; + + // The generic case has arrays only up to 100 ratio downscaling, which is hopefully enough + // in practice, but protect against buffer overflows in case such an extreme case happens + // (and in such case the precision of the generic algorithm probably doesn't matter anyway). + if( ixscale > 100 || iyscale > 100 ) + { + fast = true; + } + else + { + if (ixscale > 16 || iyscale > 16) + { + ixscale = std::floor(std::sqrt(ixscale)); + iyscale = std::floor(std::sqrt(iyscale)); + nNewWidth = int(mnWidth / ixscale); + rScaleX *= ixscale; // second pass x-scale factor + nNewHeight = int(mnHeight / iyscale); + rScaleY *= iyscale; // second pass y-scale factor + bTwoPasses = true; + } + } + + // TODO Make sure the framebuffer is alright + + OString sUseReducedRegisterVariantDefine; + if (xContext->getOpenGLCapabilitySwitch().mbLimitedShaderRegisters) + sUseReducedRegisterVariantDefine = OString("#define USE_REDUCED_REGISTER_VARIANT\n"); + + OpenGLProgram* pProgram = xContext->UseProgram( "textureVertexShader", + fast ? OUString( "areaScaleFastFragmentShader" ) : OUString( "areaScaleFragmentShader" ), + sUseReducedRegisterVariantDefine); + + if( pProgram == nullptr ) + return false; + + OpenGLTexture aScratchTex(nNewWidth, nNewHeight); + + OpenGLFramebuffer* pFramebuffer = xContext->AcquireFramebuffer( aScratchTex ); + + // NOTE: This setup is also done in OpenGLSalGraphicsImpl::DrawTransformedTexture(). + if( fast ) + { + pProgram->SetUniform1i( "xscale", ixscale ); + pProgram->SetUniform1i( "yscale", iyscale ); + // The shader operates on pixels in the surrounding area, so it's necessary + // to know the step in texture coordinates to get to the next pixel. + // With a texture atlas the "texture" is just a subtexture of a larger texture, + // so while with a normal texture we'd map between <0.0,1.0> and <0,mnWidth>, + // with a subtexture the texture coordinates range is smaller. + GLfloat srcCoords[ 8 ]; + maTexture.GetWholeCoord( srcCoords ); + pProgram->SetUniform1f( "xstep", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / mnWidth ); + pProgram->SetUniform1f( "ystep", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / mnHeight ); + pProgram->SetUniform1f( "ratio", 1.0 / ( ixscale * iyscale )); + } + else + { + pProgram->SetUniform1f( "xscale", ixscale ); + pProgram->SetUniform1f( "yscale", iyscale ); + pProgram->SetUniform1i( "swidth", mnWidth ); + pProgram->SetUniform1i( "sheight", mnHeight ); + // The shader internally actually operates on pixel coordinates, + // so it needs to know how to convert to those from the texture coordinates. + // With a simple texture that would mean converting e.g. between + // <0,mnWidth-1> and <0.0,1.0> coordinates. + // However with a texture atlas the "texture" is just a subtexture + // of a larger texture, so the texture coordinates need offset and ratio + // conversion too. + GLfloat srcCoords[ 8 ]; + maTexture.GetWholeCoord( srcCoords ); + pProgram->SetUniform1f( "xoffset", srcCoords[ 0 ] ); + pProgram->SetUniform1f( "yoffset", srcCoords[ 1 ] ); + pProgram->SetUniform1f( "xtopixelratio", nNewWidth / ( srcCoords[ 4 ] - srcCoords[ 0 ] )); + pProgram->SetUniform1f( "ytopixelratio", nNewHeight / ( srcCoords[ 5 ] - srcCoords[ 1 ] )); + pProgram->SetUniform1f( "xfrompixelratio", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / mnWidth ); + pProgram->SetUniform1f( "yfrompixelratio", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / mnHeight ); + } + + pProgram->SetTexture( "sampler", maTexture ); + pProgram->DrawTexture( maTexture ); + pProgram->Clean(); + + OpenGLContext::ReleaseFramebuffer(pFramebuffer); + + CHECK_GL_ERROR(); + + if (bTwoPasses) + { + mnWidth = nNewWidth; + mnHeight = nNewHeight; + + nNewWidth = round(mnWidth * rScaleX); + nNewHeight = round(mnHeight * rScaleY); + + ixscale = 1 / rScaleX; + iyscale = 1 / rScaleY; + + pProgram = xContext->UseProgram("textureVertexShader", "areaScaleFragmentShader", sUseReducedRegisterVariantDefine); + if (pProgram == nullptr) + return false; + + OpenGLTexture aScratchTex2(nNewWidth, nNewHeight); + + pFramebuffer = xContext->AcquireFramebuffer(aScratchTex2); + + pProgram->SetUniform1f("xscale", ixscale); + pProgram->SetUniform1f("yscale", iyscale); + pProgram->SetUniform1i("swidth", mnWidth); + pProgram->SetUniform1i("sheight", mnHeight); + + GLfloat srcCoords[ 8 ]; + aScratchTex.GetWholeCoord( srcCoords ); + pProgram->SetUniform1f( "xoffset", srcCoords[ 0 ] ); + pProgram->SetUniform1f( "yoffset", srcCoords[ 1 ] ); + pProgram->SetUniform1f( "xtopixelratio", nNewWidth / ( srcCoords[ 4 ] - srcCoords[ 0 ] )); + pProgram->SetUniform1f( "ytopixelratio", nNewHeight / ( srcCoords[ 5 ] - srcCoords[ 1 ] )); + pProgram->SetUniform1f( "xfrompixelratio", ( srcCoords[ 4 ] - srcCoords[ 0 ] ) / mnWidth ); + pProgram->SetUniform1f( "yfrompixelratio", ( srcCoords[ 5 ] - srcCoords[ 1 ] ) / mnHeight ); + + pProgram->SetTexture("sampler", aScratchTex); + pProgram->DrawTexture(aScratchTex); + pProgram->Clean(); + + OpenGLContext::ReleaseFramebuffer(pFramebuffer); + + CHECK_GL_ERROR(); + + maTexture = aScratchTex2; + mnWidth = nNewWidth; + mnHeight = nNewHeight; + } + else + { + maTexture = aScratchTex; + mnWidth = nNewWidth; + mnHeight = nNewHeight; + } + + return true; +} + +void OpenGLSalBitmap::ImplScale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) +{ + VCL_GL_INFO( "::ImplScale" ); + + mpUserBuffer.reset(); + OpenGLVCLContextZone aContextZone; + rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext(); + xContext->state().scissor().disable(); + xContext->state().stencil().disable(); + + if (rScaleX <= 1 && rScaleY <= 1) + { + nScaleFlag = BmpScaleFlag::BestQuality; + } + + if( nScaleFlag == BmpScaleFlag::Fast ) + { + ImplScaleFilter( xContext, rScaleX, rScaleY, GL_NEAREST ); + } + else if( nScaleFlag == BmpScaleFlag::BiLinear ) + { + ImplScaleFilter( xContext, rScaleX, rScaleY, GL_LINEAR ); + } + else if( nScaleFlag == BmpScaleFlag::Default ) + { + const Lanczos3Kernel aKernel; + + ImplScaleConvolution( xContext, rScaleX, rScaleY, aKernel ); + } + else if( nScaleFlag == BmpScaleFlag::BestQuality && rScaleX <= 1 && rScaleY <= 1 ) + { // Use area scaling for best quality, but only if downscaling. + ImplScaleArea( xContext, rScaleX, rScaleY ); + } + else if( nScaleFlag == BmpScaleFlag::Lanczos || nScaleFlag == BmpScaleFlag::BestQuality ) + { + const Lanczos3Kernel aKernel; + + ImplScaleConvolution( xContext, rScaleX, rScaleY, aKernel ); + } + else + SAL_WARN( "vcl.opengl", "Invalid flag for scaling operation" ); +} + +bool OpenGLSalBitmap::ScalingSupported() const +{ + return true; +} + +bool OpenGLSalBitmap::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag ) +{ + OpenGLVCLContextZone aContextZone; + + VCL_GL_INFO("::Scale " << int(nScaleFlag) + << " from " << mnWidth << "x" << mnHeight + << " to " << (mnWidth * rScaleX) << "x" << (mnHeight * rScaleY) ); + + if( nScaleFlag == BmpScaleFlag::Fast || + nScaleFlag == BmpScaleFlag::BiLinear || + nScaleFlag == BmpScaleFlag::Lanczos || + nScaleFlag == BmpScaleFlag::Default || + nScaleFlag == BmpScaleFlag::BestQuality ) + { + ImplScale( rScaleX, rScaleY, nScaleFlag ); + return true; + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/areaHashCRC64TFragmentShader.glsl b/vcl/opengl/shaders/areaHashCRC64TFragmentShader.glsl new file mode 100644 index 000000000..901b481d8 --- /dev/null +++ b/vcl/opengl/shaders/areaHashCRC64TFragmentShader.glsl @@ -0,0 +1,87 @@ +/* -*- 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/. + */ + +/* TODO Use textureOffset for newest version of GLSL */ + + +#version 130 + +uniform sampler2D crc_table; +uniform sampler2D sampler; +uniform float xstep; +uniform float ystep; + +varying vec2 tex_coord; + +const int scale = 4; +const float ratio = 16.0; + + +ivec2 crc64( ivec2 hval, int color ) +{ + int dx = 2 * ((hval[0] ^ color) & 0xff); + float s = dx / 255.0; + vec4 table_value_lo = round(texture2D( crc_table, vec2( s, 0.0 ) ) * 255.0); + s = (dx+1) / 255.0; + vec4 table_value_hi = round(texture2D( crc_table, vec2( s, 0.0 ) ) * 255.0); + + int tvalue_lo = int(table_value_lo[0]) | (int(table_value_lo[1]) << 8) | (int(table_value_lo[2]) << 16) | (int(table_value_lo[3]) << 24); + int tvalue_hi = int(table_value_hi[0]) | (int(table_value_hi[1]) << 8) | (int(table_value_hi[2]) << 16) | (int(table_value_hi[3]) << 24); + + hval[1] = tvalue_hi ^ (hval[1] >> 8); + hval[0] = tvalue_lo ^ ( (hval[1] << 24) | (hval[0] >> 8) ); + + return hval; +} + + +void main(void) +{ + ivec2 Crc = ivec2( 0xffffffff, 0xffffffff ); + vec2 offset = vec2( 0.0, 0.0 ); + vec2 next_coord = tex_coord.st; + for( int y = 0; y < scale && next_coord.y <= 1.0; ++y ) + { + for( int x = 0; x < scale && next_coord.x <= 1.0; ++x ) + { + vec4 pixel = round(texture2D( sampler, next_coord ) * 255.0); + + int r = int(pixel.r); // 0..255 + int g = int(pixel.g); // 0..255 + int b = int(pixel.b); // 0..255 + int a = int(pixel.a); // 0..255 + + Crc = crc64( Crc, r ); + Crc = crc64( Crc, g ); + Crc = crc64( Crc, b ); + Crc = crc64( Crc, a ); + + offset.x += xstep; + next_coord = tex_coord.st + offset; + } + offset.y += ystep; + offset.x = 0.0; + next_coord = tex_coord.st + offset; + } + + Crc[0] = ~Crc[0]; + Crc[1] = ~Crc[1]; + + int Hash = Crc[0] ^ Crc[1]; + + float fr = ( Hash & 0xff) / 255.0; + float fg = ((Hash >> 8) & 0xff) / 255.0; + float fb = ((Hash >> 16) & 0xff) / 255.0; + float fa = ((Hash >> 24) & 0xff) / 255.0; + + + gl_FragColor = vec4(fr, fg, fb, fa); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/areaScaleFastFragmentShader.glsl b/vcl/opengl/shaders/areaScaleFastFragmentShader.glsl new file mode 100644 index 000000000..57ad8fa97 --- /dev/null +++ b/vcl/opengl/shaders/areaScaleFastFragmentShader.glsl @@ -0,0 +1,59 @@ +/* -*- 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/. + */ + +/* TODO Use textureOffset for newest version of GLSL */ + +#version 130 + +uniform sampler2D sampler; +uniform int xscale; +uniform int yscale; +uniform float xstep; +uniform float ystep; +uniform float ratio; // = 1.0/(xscale*yscale) + +varying vec2 tex_coord; + +// This mode makes the scaling work like maskedTextureFragmentShader.glsl +// (instead of like plain textureVertexShader.glsl). +#ifdef MASKED +varying vec2 mask_coord; +uniform sampler2D mask; +#endif + +/* + Just make the resulting color the average of all the source pixels + (which is an area (xscale)x(yscale) ). +*/ +void main(void) +{ + vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 ); + vec2 offset = vec2( 0.0, 0.0 ); + for( int y = 0; y < yscale; ++y ) + { + for( int x = 0; x < xscale; ++x ) + { +#ifndef MASKED + sum += texture2D( sampler, tex_coord.st + offset ); +#else + vec4 texel; + texel = texture2D( sampler, tex_coord.st + offset ); + texel.a = 1.0 - texture2D( mask, mask_coord.st + offset ).r; + sum += texel; +#endif + offset.x += xstep; + } + offset.y += ystep; + offset.x = 0.0; + } + sum *= ratio; + gl_FragColor = sum; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/areaScaleFragmentShader.glsl b/vcl/opengl/shaders/areaScaleFragmentShader.glsl new file mode 100644 index 000000000..5dab5ba01 --- /dev/null +++ b/vcl/opengl/shaders/areaScaleFragmentShader.glsl @@ -0,0 +1,239 @@ +/* -*- 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/. + */ + +#version 130 + +uniform sampler2D sampler; +uniform int swidth; +uniform int sheight; +uniform float xscale; +uniform float yscale; +uniform float xoffset; +uniform float yoffset; +uniform float xfrompixelratio; +uniform float yfrompixelratio; +uniform float xtopixelratio; +uniform float ytopixelratio; + +varying vec2 tex_coord; + +// This mode makes the scaling work like maskedTextureFragmentShader.glsl +// (instead of like plain textureVertexShader.glsl). +#ifdef MASKED +varying vec2 mask_coord; +uniform sampler2D mask; +#endif + +#ifdef USE_REDUCED_REGISTER_VARIANT + +vec4 getTexel(int x, int y) +{ + vec2 pos = vec2( x * xfrompixelratio + xoffset, y * yfrompixelratio + yoffset ); + vec4 texel = texture2D(sampler, pos); +#ifdef MASKED + texel.a = 1.0 - texture2D(mask, pos - tex_coord.st + mask_coord.st).r; +#endif + return texel; +} + +void main(void) +{ + // Convert to pixel coordinates again. + int dx = int(( tex_coord.s - xoffset ) * xtopixelratio ); + int dy = int(( tex_coord.t - yoffset ) * ytopixelratio ); + + // Compute the range of source pixels which will make up this destination pixel. + float fsx1 = min(dx * xscale, float(swidth - 1)); + float fsx2 = min(fsx1 + xscale, float(swidth - 1)); + + float fsy1 = min(dy * yscale, float(sheight - 1)); + float fsy2 = min(fsy1 + yscale, float(sheight - 1)); + + // To whole pixel coordinates. + int xstart = int(floor(fsx1)); + int xend = int(floor(fsx2)); + + int ystart = int(floor(fsy1)); + int yend = int(floor(fsy2)); + + float xlength = fsx2 - fsx1; + float ylength = fsy2 - fsy1; + + float xContribution[3]; + xContribution[0] = (1.0 - max(0.0, fsx1 - xstart)) / xlength; + xContribution[1] = 1.0 / xlength; + xContribution[2] = (1.0 - max(0.0, (xend + 1) - fsx2)) / xlength; + + float yContribution[3]; + yContribution[0] = (1.0 - max(0.0, fsy1 - ystart)) / ylength; + yContribution[1] = 1.0 / ylength; + yContribution[2] = (1.0 - max(0.0, (yend + 1) - fsy2)) / ylength; + + vec4 sumAll = vec4(0.0, 0.0, 0.0, 0.0); + vec4 texel; + // First Y pass + { + vec4 sumX = vec4(0.0, 0.0, 0.0, 0.0); + + sumX += getTexel(xstart, ystart) * xContribution[0]; + for (int x = xstart + 1; x < xend; ++x) + { + sumX += getTexel(x, ystart) * xContribution[1]; + } + sumX += getTexel(xend, ystart) * xContribution[2]; + + sumAll += sumX * yContribution[0]; + } + + // Middle Y Passes + for (int y = ystart + 1; y < yend; ++y) + { + vec4 sumX = vec4(0.0, 0.0, 0.0, 0.0); + + sumX += getTexel(xstart, y) * xContribution[0]; + for (int x = xstart + 1; x < xend; ++x) + { + sumX += getTexel(x, y) * xContribution[1]; + } + sumX += getTexel(xend, y) * xContribution[2]; + + sumAll += sumX * yContribution[1]; + } + + // Last Y pass + { + vec4 sumX = vec4(0.0, 0.0, 0.0, 0.0); + + sumX += getTexel(xstart, yend) * xContribution[0]; + for (int x = xstart + 1; x < xend; ++x) + { + sumX += getTexel(x, yend) * xContribution[1]; + } + sumX += getTexel(xend, yend) * xContribution[2]; + + sumAll += sumX * yContribution[2]; + } + + gl_FragColor = sumAll; +} +#else +void main(void) +{ + // Convert to pixel coordinates again. + int dx = int(( tex_coord.s - xoffset ) * xtopixelratio ); + int dy = int(( tex_coord.t - yoffset ) * ytopixelratio ); + + // How much each column/row will contribute to the resulting pixel. + // Note: These values are always the same for the same X (or Y), + // so they could be precalculated in C++ and passed to the shader, + // but GLSL has limits on the size of uniforms passed to it, + // so it'd need something like texture buffer objects from newer + // GLSL versions, and it seems the hassle is not really worth it. + float xratio[ 16 + 2 ]; + float yratio[ 16 + 2 ]; + + // For finding the first and last source pixel. + int xpixel[ 16 + 2 ]; + int ypixel[ 16 + 2 ]; + + int xpos = 0; + int ypos = 0; + + // Compute the range of source pixels which will make up this destination pixel. + float fsx1 = dx * xscale; + float fsx2 = fsx1 + xscale; + // To whole pixel coordinates. + int sx1 = int( ceil( fsx1 ) ); + int sx2 = int( floor( fsx2 ) ); + // Range checking. + sx2 = min( sx2, swidth - 1 ); + sx1 = min( sx1, sx2 ); + + // How much one full column contributes to the resulting pixel. + float width = min( xscale, swidth - fsx1 ); + + if( sx1 - fsx1 > 0.001 ) + { // The first column contributes only partially. + xpixel[ xpos ] = sx1 - 1; + xratio[ xpos ] = ( sx1 - fsx1 ) / width; + ++xpos; + } + for( int sx = sx1; sx < sx2; ++sx ) + { // Columns that fully contribute to the resulting pixel. + xpixel[ xpos ] = sx; + xratio[ xpos ] = 1.0 / width; + ++xpos; + } + if( fsx2 - sx2 > 0.001 ) + { // The last column contributes only partially. + xpixel[ xpos ] = sx2; + xratio[ xpos ] = min( min( fsx2 - sx2, 1.0 ) / width, 1.0 ); + ++xpos; + } + + // The same for Y. + float fsy1 = dy * yscale; + float fsy2 = fsy1 + yscale; + int sy1 = int( ceil( fsy1 ) ); + int sy2 = int( floor( fsy2 ) ); + sy2 = min( sy2, sheight - 1 ); + sy1 = min( sy1, sy2 ); + + float height = min( yscale, sheight - fsy1 ); + + if( sy1 - fsy1 > 0.001 ) + { + ypixel[ ypos ] = sy1 - 1; + yratio[ ypos ] = ( sy1 - fsy1 ) / height; + ++ypos; + } + for( int sy = sy1; sy < sy2; ++sy ) + { + ypixel[ ypos ] = sy; + yratio[ ypos ] = 1.0 / height; + ++ypos; + } + if( fsy2 - sy2 > 0.001 ) + { + ypixel[ ypos ] = sy2; + yratio[ ypos ] = min( min( fsy2 - sy2, 1.0 ) / height, 1.0 ); + ++ypos; + } + + int xstart = xpixel[ 0 ]; + int xend = xpixel[ xpos - 1 ]; + int ystart = ypixel[ 0 ]; + int yend = ypixel[ ypos - 1 ]; + + vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 ); + + ypos = 0; + for( int y = ystart; y <= yend; ++y, ++ypos ) + { + vec4 tmp = vec4( 0.0, 0.0, 0.0, 0.0 ); + xpos = 0; + for( int x = xstart; x <= xend; ++x, ++xpos ) + { + vec2 pos = vec2( x * xfrompixelratio + xoffset, y * yfrompixelratio + yoffset ); +#ifndef MASKED + tmp += texture2D( sampler, pos ) * xratio[ xpos ]; +#else + vec4 texel; + texel = texture2D( sampler, pos ); + texel.a = 1.0 - texture2D( mask, pos - tex_coord.st + mask_coord.st ).r; + tmp += texel * xratio[ xpos ]; +#endif + } + sum += tmp * yratio[ ypos ]; + } + + gl_FragColor = sum; +} +#endif +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/blendedTextureFragmentShader.glsl b/vcl/opengl/shaders/blendedTextureFragmentShader.glsl new file mode 100644 index 000000000..15dfcf7e7 --- /dev/null +++ b/vcl/opengl/shaders/blendedTextureFragmentShader.glsl @@ -0,0 +1,33 @@ +/* -*- 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/. + */ + +#version 130 + +varying vec2 tex_coord; +varying vec2 alpha_coord; +varying vec2 mask_coord; + +uniform sampler2D sampler; +uniform sampler2D mask; +uniform sampler2D alpha; + +void main() +{ + vec4 texel0, texel1, texel2; + + texel0 = texture2D(sampler, tex_coord); + texel1 = texture2D(mask, mask_coord); + texel2 = texture2D(alpha, alpha_coord); + gl_FragColor = texel0; + + /* Only blend if the alpha texture wasn't fully transparent */ + gl_FragColor.a = 1.0 - (1.0 - floor(texel2.r)) * texel1.r; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/blendedTextureVertexShader.glsl b/vcl/opengl/shaders/blendedTextureVertexShader.glsl new file mode 100644 index 000000000..3e60d0e22 --- /dev/null +++ b/vcl/opengl/shaders/blendedTextureVertexShader.glsl @@ -0,0 +1,28 @@ +/* -*- 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/. + */ + +#version 130 + +attribute vec4 position; +attribute vec2 tex_coord_in; +attribute vec2 alpha_coord_in; +attribute vec2 mask_coord_in; +varying vec2 tex_coord; +varying vec2 alpha_coord; +varying vec2 mask_coord; +uniform mat4 mvp; + +void main() { + gl_Position = mvp * position; + tex_coord = tex_coord_in; + alpha_coord = alpha_coord_in; + mask_coord = mask_coord_in; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/combinedFragmentShader.glsl b/vcl/opengl/shaders/combinedFragmentShader.glsl new file mode 100644 index 000000000..2515b174f --- /dev/null +++ b/vcl/opengl/shaders/combinedFragmentShader.glsl @@ -0,0 +1,44 @@ +/* -*- 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/. + */ + +#version 130 + +varying float fade_factor; // 0->1 fade factor used for AA +varying float multiply; + +#ifdef USE_VERTEX_COLORS +varying vec4 vertex_color; +#endif + +uniform vec4 color; + +#define TYPE_NORMAL 0 +#define TYPE_LINE 1 + +uniform int type; + +void main() +{ +#ifdef USE_VERTEX_COLORS + vec4 result = vertex_color; +#else + vec4 result = color; +#endif + + if (type == TYPE_LINE) + { + float dist = (1.0 - abs(fade_factor)) * multiply; + float alpha = clamp(dist, 0.0, 1.0); + result.a = result.a * alpha; + } + + gl_FragColor = result; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/combinedTextureFragmentShader.glsl b/vcl/opengl/shaders/combinedTextureFragmentShader.glsl new file mode 100644 index 000000000..2990de8c4 --- /dev/null +++ b/vcl/opengl/shaders/combinedTextureFragmentShader.glsl @@ -0,0 +1,73 @@ +/* -*- 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/. + */ + +#version 130 + +varying vec2 tex_coord; +varying vec2 alpha_coord; +varying vec2 mask_coord; +#ifdef USE_VERTEX_COLORS +varying vec4 vertex_color; +#endif + +uniform sampler2D texture; +uniform sampler2D mask; +uniform sampler2D alpha; + +uniform vec4 color; + +uniform int type; + +#define TYPE_NORMAL 0 +#define TYPE_BLEND 1 +#define TYPE_MASKED 2 +#define TYPE_DIFF 3 +#define TYPE_MASKED_COLOR 4 + +void main() +{ + vec4 texelTexture = texture2D(texture, tex_coord); + + if (type == TYPE_NORMAL) + { + gl_FragColor = texelTexture; + } + else if (type == TYPE_BLEND) + { + vec4 texelMask = texture2D(mask, mask_coord); + vec4 texelAlpha = texture2D(alpha, alpha_coord); + gl_FragColor = texelTexture; + gl_FragColor.a = 1.0 - (1.0 - floor(texelAlpha.r)) * texelMask.r; + } + else if (type == TYPE_MASKED) + { + vec4 texelMask = texture2D(mask, mask_coord); + gl_FragColor = texelTexture; + gl_FragColor.a = 1.0 - texelMask.r; + } + else if (type == TYPE_DIFF) + { + vec4 texelMask = texture2D(mask, mask_coord); + float alpha = 1.0 - abs(texelTexture.r - texelMask.r); + if (alpha > 0.0) + gl_FragColor = texelMask / alpha; + gl_FragColor.a = alpha; + } + else if (type == TYPE_MASKED_COLOR) + { +#ifdef USE_VERTEX_COLORS + gl_FragColor = vertex_color; +#else + gl_FragColor = color; +#endif + gl_FragColor.a = 1.0 - texelTexture.r; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/combinedTextureVertexShader.glsl b/vcl/opengl/shaders/combinedTextureVertexShader.glsl new file mode 100644 index 000000000..52d44d553 --- /dev/null +++ b/vcl/opengl/shaders/combinedTextureVertexShader.glsl @@ -0,0 +1,43 @@ +/* -*- 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/. + */ + +#version 130 + +attribute vec4 position; +attribute vec2 tex_coord_in; +attribute vec2 mask_coord_in; +attribute vec2 alpha_coord_in; +#ifdef USE_VERTEX_COLORS +attribute vec4 vertex_color_in; +#endif + +varying vec2 tex_coord; +varying vec2 mask_coord; +varying vec2 alpha_coord; +#ifdef USE_VERTEX_COLORS +varying vec4 vertex_color; +#endif + +uniform mat4 mvp; +uniform mat4 transform; + +uniform int type; + +void main() +{ + gl_Position = mvp * transform * position; + tex_coord = tex_coord_in; + mask_coord = mask_coord_in; + alpha_coord = alpha_coord_in; +#ifdef USE_VERTEX_COLORS + vertex_color = vertex_color_in / 255.0; +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/combinedVertexShader.glsl b/vcl/opengl/shaders/combinedVertexShader.glsl new file mode 100644 index 000000000..16fc4a942 --- /dev/null +++ b/vcl/opengl/shaders/combinedVertexShader.glsl @@ -0,0 +1,76 @@ +/* -*- 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/. + */ + +#version 130 + +attribute vec2 position; +attribute vec4 extrusion_vectors; +#ifdef USE_VERTEX_COLORS +attribute vec4 vertex_color_in; +#endif + +varying float fade_factor; // fade factor for anti-aliasing +varying float multiply; + +#ifdef USE_VERTEX_COLORS +varying vec4 vertex_color; +#endif + +uniform float line_width; +uniform float feather; // width where we fade the line + +uniform mat4 mvp; + +#define TYPE_NORMAL 0 +#define TYPE_LINE 1 + +uniform int type; + +void main() +{ + vec2 extrusion_vector = extrusion_vectors.xy; + + float render_thickness = 0.0; + + if (type == TYPE_LINE) + { + // miter factor to additionally lengthen the distance of vertex (needed for miter) + // if 1.0 - miter_factor has no effect + float miter_factor = 1.0 / abs(extrusion_vectors.z); + // fade factor is always -1.0 or 1.0 -> we transport that info together with length + fade_factor = sign(extrusion_vectors.z); +#ifdef USE_VERTEX_COLORS + float the_feather = (1.0 + sign(extrusion_vectors.w)) / 4.0; + float the_line_width = abs(extrusion_vectors.w); +#else + float the_feather = feather; + float the_line_width = line_width; +#endif + render_thickness = (the_line_width * miter_factor + the_feather * 2.0 * miter_factor); + + // Calculate the multiplier so we can transform the 0->1 fade factor + // to take feather and line width into account. + + float start = mix(0.0, (the_line_width / 2.0) - the_feather, the_feather * 2.0); + float end = mix(1.0, (the_line_width / 2.0) + the_feather, the_feather * 2.0); + + multiply = 1.0 / (1.0 - (start / end)); + } + + // lengthen the vertex in direction of the extrusion vector by line width. + vec4 final_position = vec4(position + (extrusion_vector * (render_thickness / 2.0) ), 0.0, 1.0); + + gl_Position = mvp * final_position; + +#ifdef USE_VERTEX_COLORS + vertex_color = vertex_color_in / 255.0; +#endif +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/convolutionFragmentShader.glsl b/vcl/opengl/shaders/convolutionFragmentShader.glsl new file mode 100644 index 000000000..4b2f316e0 --- /dev/null +++ b/vcl/opengl/shaders/convolutionFragmentShader.glsl @@ -0,0 +1,30 @@ +/* -*- 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/. + */ + +/* TODO Use textureOffset for newest version of GLSL */ + +#version 130 + +uniform sampler2D sampler; +uniform vec2 offsets[16]; +uniform float kernel[16]; + +varying vec2 tex_coord; + +void main(void) +{ + vec4 sum = texture2D(sampler, tex_coord.st) * kernel[0]; + for (int i = 1; i < 16; i++) { + sum += texture2D(sampler, tex_coord.st - offsets[i]) * kernel[i]; + sum += texture2D(sampler, tex_coord.st + offsets[i]) * kernel[i]; + } + gl_FragColor = sum; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/diffTextureFragmentShader.glsl b/vcl/opengl/shaders/diffTextureFragmentShader.glsl new file mode 100644 index 000000000..8c50ddf98 --- /dev/null +++ b/vcl/opengl/shaders/diffTextureFragmentShader.glsl @@ -0,0 +1,30 @@ +/* -*- 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/. + */ + +#version 130 + +/*precision mediump float;*/ +varying vec2 tex_coord; +varying vec2 mask_coord; +uniform sampler2D texture; /* white background */ +uniform sampler2D mask; /* black background */ + +void main() +{ + float alpha; + vec4 texel0, texel1; + texel0 = texture2D(texture, tex_coord); + texel1 = texture2D(mask, mask_coord); + alpha = 1.0 - abs(texel0.r - texel1.r); + if(alpha > 0.0) + gl_FragColor = texel1 / alpha; + gl_FragColor.a = alpha; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/dumbVertexShader.glsl b/vcl/opengl/shaders/dumbVertexShader.glsl new file mode 100644 index 000000000..80341b614 --- /dev/null +++ b/vcl/opengl/shaders/dumbVertexShader.glsl @@ -0,0 +1,20 @@ +/* -*- 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/. + */ + +#version 130 + +attribute vec4 position; +uniform mat4 mvp; + +void main() { + gl_Position = mvp * position; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/greyscaleFragmentShader.glsl b/vcl/opengl/shaders/greyscaleFragmentShader.glsl new file mode 100644 index 000000000..c37f0d5df --- /dev/null +++ b/vcl/opengl/shaders/greyscaleFragmentShader.glsl @@ -0,0 +1,20 @@ +/* -*- 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/. + */ + +#version 130 + +varying vec2 tex_coord; +uniform sampler2D sampler; + +void main() { + vec4 texel = texture2D(sampler, tex_coord); + gl_FragColor = vec4(vec3(dot(texel.rgb, vec3(0.301, 0.591, 0.108))), 1.0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/invert50FragmentShader.glsl b/vcl/opengl/shaders/invert50FragmentShader.glsl new file mode 100644 index 000000000..9222888f0 --- /dev/null +++ b/vcl/opengl/shaders/invert50FragmentShader.glsl @@ -0,0 +1,25 @@ +/* -*- 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/. + */ + +#version 130 + +/*precision mediump float;*/ + +void main() { + vec2 tex_mod = mod(gl_FragCoord, 2).xy; + bool bLeft = (tex_mod.x > 0.0) && (tex_mod.x < 1.0); + bool bTop = (tex_mod.y > 0.0) && (tex_mod.y < 1.0); + // horrors - where is the XOR operator ? + if ((bTop && bLeft) || (!bTop && !bLeft)) + gl_FragColor = vec4(255,255,255,0); + else + gl_FragColor = vec4(0,0,0,0); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/lineFragmentShader.glsl b/vcl/opengl/shaders/lineFragmentShader.glsl new file mode 100644 index 000000000..c49570be3 --- /dev/null +++ b/vcl/opengl/shaders/lineFragmentShader.glsl @@ -0,0 +1,38 @@ +/* -*- 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/. + */ + +#version 130 + +varying float fade_factor; // 0->1 fade factor used for AA +uniform vec4 color; + +uniform float line_width; +uniform float feather; + +void main() +{ + float start = (line_width / 2.0) - feather; // where we start to apply alpha + float end = (line_width / 2.0) + feather; // where we end to apply alpha + + // Calculate the multiplier so we can transform the 0->1 fade factor + // to take feather and line width into account. + float multiplied = 1.0 / (1.0 - (start / end)); + + float dist = (1.0 - abs(fade_factor)) * multiplied; + + float alpha = clamp(dist, 0.0, 1.0); + + // modify the alpha channel only + vec4 result_color = color; + result_color.a = result_color.a * alpha; + + gl_FragColor = result_color; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/lineVertexShader.glsl b/vcl/opengl/shaders/lineVertexShader.glsl new file mode 100644 index 000000000..e26be78d0 --- /dev/null +++ b/vcl/opengl/shaders/lineVertexShader.glsl @@ -0,0 +1,37 @@ +/* -*- 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/. + */ + +attribute vec2 position; +attribute vec4 extrusion_vectors; + +varying float fade_factor; // fade factor for anti-aliasing + +uniform float line_width; +uniform float feather; // width where we fade the line + +uniform mat4 mvp; + +void main() +{ + vec2 extrusion_vector = extrusion_vectors.xy; + // miter factor to additionally lengthen the distance of vertex (needed for miter) + // if 1.0 - miter_factor has no effect + float miter_factor = 1.0f / abs(extrusion_vectors.z); + // fade factor is always -1.0 or 1.0 -> we transport that info together with length + fade_factor = sign(extrusion_vectors.z); + + float rendered_thickness = (line_width + feather * 2.0) * miter_factor; + + // lengthen the vertex in direction of the extrusion vector by line width. + vec4 position = vec4(position + (extrusion_vector * (rendered_thickness / 2.0) ), 0.0, 1.0); + + gl_Position = mvp * position; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/linearGradientFragmentShader.glsl b/vcl/opengl/shaders/linearGradientFragmentShader.glsl new file mode 100644 index 000000000..bd1137c16 --- /dev/null +++ b/vcl/opengl/shaders/linearGradientFragmentShader.glsl @@ -0,0 +1,23 @@ +/* -*- 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/. + */ + +#version 120 + +uniform vec4 start_color; +uniform vec4 end_color; +uniform mat3x3 transform; +varying vec2 tex_coord; + +void main(void) +{ + gl_FragColor = mix(end_color, start_color, + clamp(tex_coord.t, 0.0, 1.0)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/maskFragmentShader.glsl b/vcl/opengl/shaders/maskFragmentShader.glsl new file mode 100644 index 000000000..864869c89 --- /dev/null +++ b/vcl/opengl/shaders/maskFragmentShader.glsl @@ -0,0 +1,23 @@ +/* -*- 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/. + */ + +#version 130 + +varying vec2 tex_coord; +uniform sampler2D sampler; +uniform vec4 color; + +void main() { + vec4 texel0; + texel0 = texture2D(sampler, tex_coord); + gl_FragColor = color; + gl_FragColor.a = 1.0 - texel0.r; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/maskedTextureFragmentShader.glsl b/vcl/opengl/shaders/maskedTextureFragmentShader.glsl new file mode 100644 index 000000000..31c793897 --- /dev/null +++ b/vcl/opengl/shaders/maskedTextureFragmentShader.glsl @@ -0,0 +1,27 @@ +/* -*- 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/. + */ + +#version 130 + +/*precision mediump float;*/ +varying vec2 tex_coord; +varying vec2 mask_coord; +uniform sampler2D sampler; +uniform sampler2D mask; + +void main() +{ + vec4 texel0, texel1; + texel0 = texture2D(sampler, tex_coord); + texel1 = texture2D(mask, mask_coord); + gl_FragColor = texel0; + gl_FragColor.a = 1.0 - texel1.r; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/maskedTextureVertexShader.glsl b/vcl/opengl/shaders/maskedTextureVertexShader.glsl new file mode 100644 index 000000000..6b5f327da --- /dev/null +++ b/vcl/opengl/shaders/maskedTextureVertexShader.glsl @@ -0,0 +1,26 @@ +/* -*- 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/. + */ + +#version 130 + +attribute vec4 position; +attribute vec2 tex_coord_in; +attribute vec2 mask_coord_in; +varying vec2 tex_coord; +varying vec2 mask_coord; +uniform mat4 mvp; + +void main() +{ + gl_Position = mvp * position; + tex_coord = tex_coord_in; + mask_coord = mask_coord_in; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/radialGradientFragmentShader.glsl b/vcl/opengl/shaders/radialGradientFragmentShader.glsl new file mode 100644 index 000000000..94a86eb95 --- /dev/null +++ b/vcl/opengl/shaders/radialGradientFragmentShader.glsl @@ -0,0 +1,23 @@ +/* -*- 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/. + */ + +#version 120 + +uniform vec4 start_color; +uniform vec4 end_color; +uniform vec2 center; +varying vec2 tex_coord; + +void main(void) +{ + gl_FragColor = mix(end_color, start_color, + clamp(distance(tex_coord, center), 0.0, 1.0)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/replaceColorFragmentShader.glsl b/vcl/opengl/shaders/replaceColorFragmentShader.glsl new file mode 100644 index 000000000..24f6008e2 --- /dev/null +++ b/vcl/opengl/shaders/replaceColorFragmentShader.glsl @@ -0,0 +1,25 @@ +/* -*- 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/. + */ + +#version 130 + +varying vec2 tex_coord; +uniform sampler2D sampler; +uniform vec4 search_color; +uniform vec4 replace_color; +uniform float epsilon; + +void main() { + vec4 texel = texture2D(sampler, tex_coord); + vec4 diff = clamp(abs(texel - search_color) - epsilon, 0.0, 1.0); + float bump = max(0.0, 1.0 - ceil(diff.x + diff.y + diff.z)); + gl_FragColor = texel + bump * (replace_color - search_color); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/solidFragmentShader.glsl b/vcl/opengl/shaders/solidFragmentShader.glsl new file mode 100644 index 000000000..b77e2578d --- /dev/null +++ b/vcl/opengl/shaders/solidFragmentShader.glsl @@ -0,0 +1,19 @@ +/* -*- 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/. + */ + +#version 130 + +/*precision mediump float;*/ + +uniform vec4 color; +void main() { + gl_FragColor = color; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/textureFragmentShader.glsl b/vcl/opengl/shaders/textureFragmentShader.glsl new file mode 100644 index 000000000..b1fedcba5 --- /dev/null +++ b/vcl/opengl/shaders/textureFragmentShader.glsl @@ -0,0 +1,20 @@ +/* -*- 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/. + */ + +#version 130 + +/* precision mediump float; */ +varying vec2 tex_coord; +uniform sampler2D sampler; + +void main() { + gl_FragColor = texture2D(sampler, tex_coord); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/textureVertexShader.glsl b/vcl/opengl/shaders/textureVertexShader.glsl new file mode 100644 index 000000000..7fbdcf1eb --- /dev/null +++ b/vcl/opengl/shaders/textureVertexShader.glsl @@ -0,0 +1,22 @@ +/* -*- 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/. + */ + +#version 130 + +attribute vec4 position; +attribute vec2 tex_coord_in; +varying vec2 tex_coord; +uniform mat4 mvp; + +void main() { + gl_Position = mvp * position; + tex_coord = tex_coord_in; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/shaders/transformedTextureVertexShader.glsl b/vcl/opengl/shaders/transformedTextureVertexShader.glsl new file mode 100644 index 000000000..3d67f78e0 --- /dev/null +++ b/vcl/opengl/shaders/transformedTextureVertexShader.glsl @@ -0,0 +1,28 @@ +/* -*- 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/. + */ + +#version 130 + +attribute vec4 position; +attribute vec2 tex_coord_in; +attribute vec2 mask_coord_in; +uniform vec2 viewport; +uniform mat4 transform; +uniform mat4 mvp; +varying vec2 tex_coord; +varying vec2 mask_coord; + +void main() { + vec4 pos = mvp * transform * position; + gl_Position = pos; + tex_coord = tex_coord_in; + mask_coord = mask_coord_in; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/texture.cxx b/vcl/opengl/texture.cxx new file mode 100644 index 000000000..9f4acc0fc --- /dev/null +++ b/vcl/opengl/texture.cxx @@ -0,0 +1,606 @@ +/* -*- 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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <vcl/opengl/OpenGLContext.hxx> +#include <vcl/opengl/OpenGLHelper.hxx> + +#include <svdata.hxx> + +#include <vcl/pngwrite.hxx> + +#include <opengl/framebuffer.hxx> +#include <opengl/texture.hxx> +#include <opengl/zone.hxx> +#include <opengl/RenderState.hxx> + +namespace +{ + +constexpr GLenum constInternalFormat = GL_RGBA8; + +} // end anonymous namespace + +// texture with allocated size +ImplOpenGLTexture::ImplOpenGLTexture( int nWidth, int nHeight, bool bAllocate ) : + mnTexture( 0 ), + mnWidth( nWidth ), + mnHeight( nHeight ), + mnFilter( GL_NEAREST ), + mnOptStencil( 0 ) +{ + OpenGLVCLContextZone aContextZone; + + auto& rState = OpenGLContext::getVCLContext()->state(); + TextureState::generate(mnTexture); + rState.texture().active(0); + rState.texture().bind(mnTexture); + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + CHECK_GL_ERROR(); + if( bAllocate ) + { +#ifdef DBG_UTIL + std::vector< sal_uInt8 > buffer; + buffer.resize( nWidth * nHeight * 4 ); + for( int i = 0; i < nWidth * nHeight; ++i ) + { // pre-fill the texture with deterministic garbage + bool odd = (i & 0x01); + buffer[ i * 4 ] = odd ? 0x40 : 0xBF; + buffer[ i * 4 + 1 ] = 0x80; + buffer[ i * 4 + 2 ] = odd ? 0xBF : 0x40; + buffer[ i * 4 + 3 ] = 0xFF; + } + glTexImage2D( GL_TEXTURE_2D, 0, constInternalFormat, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer.data()); +#else + glTexImage2D( GL_TEXTURE_2D, 0, constInternalFormat, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr ); +#endif + CHECK_GL_ERROR(); + } + + VCL_GL_INFO( "OpenGLTexture " << mnTexture << " " << nWidth << "x" << nHeight << " allocate" ); +} + +// texture with content retrieved from FBO +ImplOpenGLTexture::ImplOpenGLTexture( int nX, int nY, int nWidth, int nHeight ) : + mnTexture( 0 ), + mnWidth( nWidth ), + mnHeight( nHeight ), + mnFilter( GL_NEAREST ), + mnOptStencil( 0 ) +{ + OpenGLVCLContextZone aContextZone; + + // FIXME We need the window height here + // nY = GetHeight() - nHeight - nY; + + auto& rState = OpenGLContext::getVCLContext()->state(); + TextureState::generate(mnTexture); + rState.texture().active(0); + rState.texture().bind(mnTexture); + + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + CHECK_GL_ERROR(); + glCopyTexImage2D( GL_TEXTURE_2D, 0, constInternalFormat, nX, nY, nWidth, nHeight, 0 ); + CHECK_GL_ERROR(); + + VCL_GL_INFO( "OpenGLTexture " << mnTexture << " " << nWidth << "x" << nHeight << " from x" << nX << ", y" << nY ); +} + +// texture from buffer data +ImplOpenGLTexture::ImplOpenGLTexture( int nWidth, int nHeight, int nFormat, int nType, void const * pData ) : + mnTexture( 0 ), + mnWidth( nWidth ), + mnHeight( nHeight ), + mnFilter( GL_NEAREST ), + mnOptStencil( 0 ) +{ + OpenGLVCLContextZone aContextZone; + + auto& rState = OpenGLContext::getVCLContext()->state(); + TextureState::generate(mnTexture); + rState.texture().active(0); + rState.texture().bind(mnTexture); + + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + CHECK_GL_ERROR(); + glTexImage2D( GL_TEXTURE_2D, 0, constInternalFormat, mnWidth, mnHeight, 0, nFormat, nType, pData ); + CHECK_GL_ERROR(); + + VCL_GL_INFO( "OpenGLTexture " << mnTexture << " " << nWidth << "x" << nHeight << " from data" ); +} + +GLuint ImplOpenGLTexture::AddStencil() +{ + assert( mnOptStencil == 0 ); + + glGenRenderbuffers( 1, &mnOptStencil ); + CHECK_GL_ERROR(); + glBindRenderbuffer( GL_RENDERBUFFER, mnOptStencil ); + CHECK_GL_ERROR(); + VCL_GL_INFO( "Allocate stencil " << mnWidth << " x " << mnHeight ); + glRenderbufferStorage( GL_RENDERBUFFER, GL_STENCIL_INDEX, + mnWidth, mnHeight ); + CHECK_GL_ERROR(); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + CHECK_GL_ERROR(); + + return mnOptStencil; +} + +ImplOpenGLTexture::~ImplOpenGLTexture() +{ + VCL_GL_INFO( "~OpenGLTexture " << mnTexture ); + if( mnTexture != 0 ) + { + // During shutdown GL is already de-initialized, so we should not try to create a new context. + OpenGLZone aZone; + rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext(false); + if( xContext.is() ) + { + // FIXME: this is really not optimal performance-wise. + + // Check we have been correctly un-bound from all framebuffers. + ImplSVData* pSVData = ImplGetSVData(); + rtl::Reference<OpenGLContext> pContext = pSVData->maGDIData.mpLastContext; + + if( pContext.is() ) + { + pContext->makeCurrent(); + pContext->UnbindTextureFromFramebuffers( mnTexture ); + } + + if( mnOptStencil != 0 ) + { + glDeleteRenderbuffers( 1, &mnOptStencil ); + mnOptStencil = 0; + } + auto& rState = pContext->state(); + rState.texture().unbindAndDelete(mnTexture); + mnTexture = 0; + } + else + { + mnOptStencil = 0; + mnTexture = 0; + } + } +} + +bool ImplOpenGLTexture::InsertBuffer(int nX, int nY, int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData) +{ + if (!pData || mnTexture == 0) + return false; + + rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext(); + xContext->state().texture().active(0); + xContext->state().texture().bind(mnTexture); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + CHECK_GL_ERROR(); + glTexSubImage2D(GL_TEXTURE_2D, 0, nX, mnHeight - nY - nHeight, nWidth, nHeight, nFormat, nType, pData); + CHECK_GL_ERROR(); + + VCL_GL_INFO( "OpenGLTexture " << mnTexture << " Insert buff. to " << nX << " " << nY + << " size " << nWidth << "x" << nHeight << " from data" ); + + return true; +} + +void ImplOpenGLTexture::InitializeSlotMechanism(int nInitialSlotSize) +{ + if (mpSlotReferences) + return; + + mpSlotReferences.reset(new std::vector<int>(nInitialSlotSize, 0)); +} + +void ImplOpenGLTexture::IncreaseRefCount(int nSlotNumber) +{ + if (mpSlotReferences && nSlotNumber >= 0) + { + if (nSlotNumber >= int(mpSlotReferences->size())) + mpSlotReferences->resize(nSlotNumber + 1, 0); + + mpSlotReferences->at(nSlotNumber)++; + } +} + +void ImplOpenGLTexture::DecreaseRefCount(int nSlotNumber) +{ + if (mpSlotReferences && nSlotNumber >= 0) + { + if (nSlotNumber >= int(mpSlotReferences->size())) + mpSlotReferences->resize(nSlotNumber, 0); + + mpSlotReferences->at(nSlotNumber)--; + + if (mpSlotReferences->at(nSlotNumber) == 0 && mFunctSlotDeallocateCallback) + { + mFunctSlotDeallocateCallback(nSlotNumber); + } + } +} + +OpenGLTexture::OpenGLTexture() : + maRect( 0, 0, 0, 0 ), + mpImpl(), + mnSlotNumber(-1) +{ +} + +OpenGLTexture::OpenGLTexture(const std::shared_ptr<ImplOpenGLTexture>& rpImpl, tools::Rectangle aRectangle, int nSlotNumber) + : maRect(aRectangle) + , mpImpl(rpImpl) + , mnSlotNumber(nSlotNumber) +{ + if (mpImpl) + mpImpl->IncreaseRefCount(nSlotNumber); +} + +OpenGLTexture::OpenGLTexture( int nWidth, int nHeight, bool bAllocate ) + : maRect( Point( 0, 0 ), Size( nWidth, nHeight ) ) + , mpImpl(std::make_shared<ImplOpenGLTexture>(nWidth, nHeight, bAllocate)) + , mnSlotNumber(-1) +{ +} + +OpenGLTexture::OpenGLTexture( int nX, int nY, int nWidth, int nHeight ) + : maRect( Point( 0, 0 ), Size( nWidth, nHeight ) ) + , mpImpl(std::make_shared<ImplOpenGLTexture>(nX, nY, nWidth, nHeight)) + , mnSlotNumber(-1) +{ +} + +OpenGLTexture::OpenGLTexture( int nWidth, int nHeight, int nFormat, int nType, void const * pData ) + : maRect( Point( 0, 0 ), Size( nWidth, nHeight ) ) + , mpImpl(std::make_shared<ImplOpenGLTexture>(nWidth, nHeight, nFormat, nType, pData)) + , mnSlotNumber(-1) +{ + +} + +OpenGLTexture::OpenGLTexture(const OpenGLTexture& rTexture) + : maRect(rTexture.maRect) + , mpImpl(rTexture.mpImpl) + , mnSlotNumber(rTexture.mnSlotNumber) +{ + if (mpImpl) + mpImpl->IncreaseRefCount(mnSlotNumber); +} + +OpenGLTexture::OpenGLTexture(OpenGLTexture&& rTexture) noexcept + : maRect(rTexture.maRect) + , mpImpl(std::move(rTexture.mpImpl)) + , mnSlotNumber(rTexture.mnSlotNumber) +{ +} + +OpenGLTexture::OpenGLTexture( const OpenGLTexture& rTexture, + int nX, int nY, int nWidth, int nHeight ) +{ + maRect = tools::Rectangle( Point( rTexture.maRect.Left() + nX, rTexture.maRect.Top() + nY ), + Size( nWidth, nHeight ) ); + mpImpl = rTexture.mpImpl; + mnSlotNumber = rTexture.mnSlotNumber; + if (mpImpl) + mpImpl->IncreaseRefCount(mnSlotNumber); + VCL_GL_INFO( "Copying texture " << Id() << " [" << maRect.Left() << "," << maRect.Top() << "] " << GetWidth() << "x" << GetHeight() ); +} + +OpenGLTexture::~OpenGLTexture() +{ + if (mpImpl) + mpImpl->DecreaseRefCount(mnSlotNumber); +} + +bool OpenGLTexture::IsUnique() const +{ + return !mpImpl || (mpImpl.use_count() == 1); +} + +GLuint OpenGLTexture::Id() const +{ + if (mpImpl) + return mpImpl->mnTexture; + return 0; +} + +int OpenGLTexture::GetWidth() const +{ + return maRect.GetWidth(); +} + +int OpenGLTexture::GetHeight() const +{ + return maRect.GetHeight(); +} + +GLuint OpenGLTexture::StencilId() const +{ + return mpImpl ? mpImpl->mnOptStencil : 0; +} + +GLuint OpenGLTexture::AddStencil() +{ + if (mpImpl) + return mpImpl->AddStencil(); + else + return 0; +} + +void OpenGLTexture::GetCoord( GLfloat* pCoord, const SalTwoRect& rPosAry, bool bInverted ) const +{ + VCL_GL_INFO( "Getting coord " << Id() << " [" << maRect.Left() << "," << maRect.Top() << "] " << GetWidth() << "x" << GetHeight() ); + + if (!IsValid()) + { + pCoord[0] = pCoord[1] = pCoord[2] = pCoord[3] = 0.0f; + pCoord[4] = pCoord[5] = pCoord[6] = pCoord[7] = 0.0f; + return; + } + + pCoord[0] = pCoord[2] = (maRect.Left() + rPosAry.mnSrcX) / static_cast<double>(mpImpl->mnWidth); + pCoord[4] = pCoord[6] = (maRect.Left() + rPosAry.mnSrcX + rPosAry.mnSrcWidth) / static_cast<double>(mpImpl->mnWidth); + + if( !bInverted ) + { + pCoord[3] = pCoord[5] = 1.0f - (maRect.Top() + rPosAry.mnSrcY) / static_cast<double>(mpImpl->mnHeight); + pCoord[1] = pCoord[7] = 1.0f - (maRect.Top() + rPosAry.mnSrcY + rPosAry.mnSrcHeight) / static_cast<double>(mpImpl->mnHeight); + } + else + { + pCoord[1] = pCoord[7] = 1.0f - (maRect.Top() + rPosAry.mnSrcY) / static_cast<double>(mpImpl->mnHeight); + pCoord[3] = pCoord[5] = 1.0f - (maRect.Top() + rPosAry.mnSrcY + rPosAry.mnSrcHeight) / static_cast<double>(mpImpl->mnHeight); + } +} + +void OpenGLTexture::GetTextureRect(const SalTwoRect& rPosAry, GLfloat& x1, GLfloat& x2, GLfloat& y1, GLfloat& y2) const +{ + if (IsValid()) + { + double fTextureWidth(mpImpl->mnWidth); + double fTextureHeight(mpImpl->mnHeight); + + x1 = (maRect.Left() + rPosAry.mnSrcX) / fTextureWidth; + x2 = (maRect.Left() + rPosAry.mnSrcX + rPosAry.mnSrcWidth) / fTextureWidth; + + y1 = 1.0f - (maRect.Top() + rPosAry.mnSrcY) / fTextureHeight; + y2 = 1.0f - (maRect.Top() + rPosAry.mnSrcY + rPosAry.mnSrcHeight) / fTextureHeight; + } +} + +template <> +void OpenGLTexture::FillCoords<GL_TRIANGLE_FAN>(std::vector<GLfloat>& rCoords, const SalTwoRect& rPosAry) const +{ + GLfloat x1 = 0.0f; + GLfloat x2 = 0.0f; + GLfloat y1 = 0.0f; + GLfloat y2 = 0.0f; + + GetTextureRect(rPosAry, x1, x2, y1, y2); + + rCoords.insert(rCoords.end(), { + x1, y2, x1, y1, + x2, y1, x2, y2 + }); +} + +template <> +void OpenGLTexture::FillCoords<GL_TRIANGLES>(std::vector<GLfloat>& rCoords, const SalTwoRect& rPosAry) const +{ + GLfloat x1 = 0.0f; + GLfloat x2 = 0.0f; + GLfloat y1 = 0.0f; + GLfloat y2 = 0.0f; + + GetTextureRect(rPosAry, x1, x2, y1, y2); + + rCoords.insert(rCoords.end(), { + x1, y1, x2, y1, x1, y2, + x1, y2, x2, y1, x2, y2 + }); +} + +void OpenGLTexture::GetWholeCoord( GLfloat* pCoord ) const +{ + if( GetWidth() != mpImpl->mnWidth || GetHeight() != mpImpl->mnHeight ) + { + pCoord[0] = pCoord[2] = maRect.Left() / static_cast<double>(mpImpl->mnWidth); + pCoord[4] = pCoord[6] = maRect.Right() / static_cast<double>(mpImpl->mnWidth); + pCoord[3] = pCoord[5] = 1.0f - maRect.Top() / static_cast<double>(mpImpl->mnHeight); + pCoord[1] = pCoord[7] = 1.0f - maRect.Bottom() / static_cast<double>(mpImpl->mnHeight); + } + else + { + pCoord[0] = pCoord[2] = 0; + pCoord[4] = pCoord[6] = 1; + pCoord[1] = pCoord[7] = 0; + pCoord[3] = pCoord[5] = 1; + } +} + +GLenum OpenGLTexture::GetFilter() const +{ + if( mpImpl ) + return mpImpl->mnFilter; + return GL_NEAREST; +} + +bool OpenGLTexture::CopyData(int nWidth, int nHeight, int nFormat, int nType, sal_uInt8 const * pData) +{ + if (!pData || !IsValid()) + return false; + + int nX = maRect.Left(); + int nY = maRect.Top(); + + return mpImpl->InsertBuffer(nX, nY, nWidth, nHeight, nFormat, nType, pData); +} + +void OpenGLTexture::SetFilter( GLenum nFilter ) +{ + if( mpImpl ) + { + mpImpl->mnFilter = nFilter; + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, nFilter ); + CHECK_GL_ERROR(); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, nFilter ); + CHECK_GL_ERROR(); + } +} + +void OpenGLTexture::Bind() +{ + if (IsValid()) + { + OpenGLContext::getVCLContext()->state().texture().bind(mpImpl->mnTexture); + } + else + VCL_GL_INFO( "OpenGLTexture::Binding invalid texture" ); + + CHECK_GL_ERROR(); +} + +void OpenGLTexture::Unbind() +{ + if (IsValid()) + { + OpenGLContext::getVCLContext()->state().texture().unbind(mpImpl->mnTexture); + } +} + +void OpenGLTexture::SaveToFile(const OUString& rFileName) +{ + std::vector<sal_uInt8> aBuffer(GetWidth() * GetHeight() * 4); + Read(OpenGLHelper::OptimalBufferFormat(), GL_UNSIGNED_BYTE, aBuffer.data()); + BitmapEx aBitmap = OpenGLHelper::ConvertBufferToBitmapEx(aBuffer.data(), GetWidth(), GetHeight()); + try + { + vcl::PNGWriter aWriter(aBitmap); + SvFileStream sOutput(rFileName, StreamMode::WRITE); + aWriter.Write(sOutput); + sOutput.Close(); + } + catch (...) + { + SAL_WARN("vcl.opengl", "Error writing png to " << rFileName); + } +} + +void OpenGLTexture::Read( GLenum nFormat, GLenum nType, sal_uInt8* pData ) +{ + if (!IsValid()) + { + SAL_WARN( "vcl.opengl", "Can't read invalid texture" ); + return; + } + + OpenGLVCLContextZone aContextZone; + + VCL_GL_INFO( "Reading texture " << Id() << " " << GetWidth() << "x" << GetHeight() ); + + if( GetWidth() == mpImpl->mnWidth && GetHeight() == mpImpl->mnHeight ) + { + Bind(); + glPixelStorei( GL_PACK_ALIGNMENT, 1 ); + CHECK_GL_ERROR(); + // XXX: Call not available with GLES 2.0 + glGetTexImage( GL_TEXTURE_2D, 0, nFormat, nType, pData ); + CHECK_GL_ERROR(); + Unbind(); + } + else + { + long nWidth = maRect.GetWidth(); + long nHeight = maRect.GetHeight(); + long nX = maRect.Left(); + long nY = mpImpl->mnHeight - maRect.Top() - nHeight; + + // Retrieve current context + ImplSVData* pSVData = ImplGetSVData(); + rtl::Reference<OpenGLContext> pContext = pSVData->maGDIData.mpLastContext; + OpenGLFramebuffer* pFramebuffer = pContext->AcquireFramebuffer(*this); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + CHECK_GL_ERROR(); + glReadPixels(nX, nY, nWidth, nHeight, nFormat, nType, pData); + CHECK_GL_ERROR(); + OpenGLContext::ReleaseFramebuffer(pFramebuffer); + } +} + +OpenGLTexture::operator bool() const +{ + return IsValid(); +} + +OpenGLTexture& OpenGLTexture::operator=(const OpenGLTexture& rTexture) +{ + OpenGLTexture aTemp(rTexture); + *this = std::move(aTemp); + return *this; +} + +OpenGLTexture& OpenGLTexture::operator=(OpenGLTexture&& rTexture) +{ + if (mpImpl) + mpImpl->DecreaseRefCount(mnSlotNumber); + + maRect = rTexture.maRect; + mpImpl = std::move(rTexture.mpImpl); + mnSlotNumber = rTexture.mnSlotNumber; + + return *this; +} + +bool OpenGLTexture::operator==( const OpenGLTexture& rTexture ) const +{ + return (mpImpl == rTexture.mpImpl + && maRect == rTexture.maRect + && mnSlotNumber == rTexture.mnSlotNumber); +} + +bool OpenGLTexture::operator!=( const OpenGLTexture& rTexture ) const +{ + return !( *this == rTexture ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/win/WinDeviceInfo.cxx b/vcl/opengl/win/WinDeviceInfo.cxx new file mode 100644 index 000000000..301c8e74d --- /dev/null +++ b/vcl/opengl/win/WinDeviceInfo.cxx @@ -0,0 +1,494 @@ +/* -*- 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 <opengl/win/WinDeviceInfo.hxx> + +#include <driverblocklist.hxx> +#include <config_folders.h> + +#if !defined WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> +#include <objbase.h> +#include <setupapi.h> +#include <algorithm> +#include <cstdint> +#include <memory> + +#include <osl/file.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <o3tl/char16_t2wchar_t.hxx> + +#include <desktop/crashreport.hxx> + +namespace { + +bool GetKeyValue(const WCHAR* keyLocation, const WCHAR* keyName, OUString& destString, int type) +{ + HKEY key; + DWORD dwcbData; + DWORD dValue; + DWORD resultType; + LONG result; + bool retval = true; + + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, keyLocation, 0, KEY_QUERY_VALUE, &key); + if (result != ERROR_SUCCESS) + { + return false; + } + + switch (type) + { + case REG_DWORD: + { + // We only use this for vram size + dwcbData = sizeof(dValue); + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + reinterpret_cast<LPBYTE>(&dValue), &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_DWORD) + { + dValue = dValue / 1024 / 1024; + destString += OUString::number(int32_t(dValue)); + } + else + { + retval = false; + } + break; + } + case REG_MULTI_SZ: + { + // A chain of null-separated strings; we convert the nulls to spaces + WCHAR wCharValue[1024]; + dwcbData = sizeof(wCharValue); + + result = RegQueryValueExW(key, keyName, nullptr, &resultType, + reinterpret_cast<LPBYTE>(wCharValue), &dwcbData); + if (result == ERROR_SUCCESS && resultType == REG_MULTI_SZ) + { + // This bit here could probably be cleaner. + bool isValid = false; + + DWORD strLen = dwcbData/sizeof(wCharValue[0]); + for (DWORD i = 0; i < strLen; i++) + { + if (wCharValue[i] == '\0') + { + if (i < strLen - 1 && wCharValue[i + 1] == '\0') + { + isValid = true; + break; + } + else + { + wCharValue[i] = ' '; + } + } + } + + // ensure wCharValue is null terminated + wCharValue[strLen-1] = '\0'; + + if (isValid) + destString = OUString(o3tl::toU(wCharValue)); + + } + else + { + retval = false; + } + + break; + } + } + RegCloseKey(key); + + return retval; +} + +// The device ID is a string like PCI\VEN_15AD&DEV_0405&SUBSYS_040515AD +// this function is used to extract the id's out of it +uint32_t ParseIDFromDeviceID(const OUString &key, const char *prefix, int length) +{ + OUString id = key.toAsciiUpperCase(); + OUString aPrefix = OUString::fromUtf8(prefix); + int32_t start = id.indexOf(aPrefix); + if (start != -1) + { + id = id.copy(start + aPrefix.getLength(), length); + } + return id.toUInt32(16); +} + +/* Other interesting places for info: + * IDXGIAdapter::GetDesc() + * IDirectDraw7::GetAvailableVidMem() + * e->GetAvailableTextureMem() + * */ + +template<typename T> void appendIntegerWithPadding(OUString& rString, T value, sal_uInt32 nChars) +{ + rString += "0x"; + OUString aValue = OUString::number(value, 16); + sal_Int32 nLength = aValue.getLength(); + sal_uInt32 nPadLength = nChars - nLength; + assert(nPadLength >= 0); + OUStringBuffer aBuffer; + for (sal_uInt32 i = 0; i < nPadLength; ++i) + { + aBuffer.append("0"); + } + rString += aBuffer.makeStringAndClear() + aValue; +} + +#define DEVICE_KEY_PREFIX L"\\Registry\\Machine\\" +} + +WinOpenGLDeviceInfo::WinOpenGLDeviceInfo(): + mbHasDualGPU(false), + mbRDP(false) +{ + GetData(); +} + +WinOpenGLDeviceInfo::~WinOpenGLDeviceInfo() +{ +} + +static OUString getBlacklistFile() +{ + OUString url("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER); + rtl::Bootstrap::expandMacros(url); + + return url + "/opengl/opengl_blacklist_windows.xml"; +} + +bool WinOpenGLDeviceInfo::FindBlocklistedDeviceInList() +{ + return DriverBlocklist::IsDeviceBlocked( getBlacklistFile(), DriverBlocklist::VersionType::OpenGL, + maDriverVersion, maAdapterVendorID, maAdapterDeviceID); +} + +namespace { + +OUString getCacheFolder() +{ + OUString url("${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/"); + rtl::Bootstrap::expandMacros(url); + + osl::Directory::create(url); + + return url; +} + +void writeToLog(SvStream& rStrm, const char* pKey, const OUString & rVal) +{ + rStrm.WriteCharPtr(pKey); + rStrm.WriteCharPtr(": "); + rStrm.WriteOString(OUStringToOString(rVal, RTL_TEXTENCODING_UTF8)); + rStrm.WriteChar('\n'); +} + +} + +bool WinOpenGLDeviceInfo::isDeviceBlocked() +{ + CrashReporter::addKeyValue("OpenGLVendor", maAdapterVendorID, CrashReporter::AddItem); + CrashReporter::addKeyValue("OpenGLDevice", maAdapterDeviceID, CrashReporter::AddItem); + CrashReporter::addKeyValue("OpenGLDriver", maDriverVersion, CrashReporter::Write); + + SAL_INFO("vcl.opengl", maDriverVersion); + SAL_INFO("vcl.opengl", maDriverDate); + SAL_INFO("vcl.opengl", maDeviceID); + SAL_INFO("vcl.opengl", maAdapterVendorID); + SAL_INFO("vcl.opengl", maAdapterDeviceID); + SAL_INFO("vcl.opengl", maAdapterSubsysID); + SAL_INFO("vcl.opengl", maDeviceKey); + SAL_INFO("vcl.opengl", maDeviceString); + + OUString aCacheFolder = getCacheFolder(); + + OUString aCacheFile(aCacheFolder + "/opengl_device.log"); + SvFileStream aOpenGLLogFile(aCacheFile, StreamMode::WRITE|StreamMode::TRUNC); + + writeToLog(aOpenGLLogFile, "DriverVersion", maDriverVersion); + writeToLog(aOpenGLLogFile, "DriverDate", maDriverDate); + writeToLog(aOpenGLLogFile, "DeviceID", maDeviceID); + writeToLog(aOpenGLLogFile, "AdapterVendorID", maAdapterVendorID); + writeToLog(aOpenGLLogFile, "AdapterDeviceID", maAdapterDeviceID); + writeToLog(aOpenGLLogFile, "AdapterSubsysID", maAdapterSubsysID); + writeToLog(aOpenGLLogFile, "DeviceKey", maDeviceKey); + writeToLog(aOpenGLLogFile, "DeviceString", maDeviceString); + + // Check if the device is blocked from the downloaded blocklist. If not, check + // the static list after that. This order is used so that we can later escape + // out of static blocks (i.e. if we were wrong or something was patched, we + // can back out our static block without doing a release). + if (mbRDP) + { + SAL_WARN("vcl.opengl", "all OpenGL blocked for RDP sessions"); + return true; + } + + return FindBlocklistedDeviceInList(); +} + +void WinOpenGLDeviceInfo::GetData() +{ + DISPLAY_DEVICEW displayDevice; + displayDevice.cb = sizeof(displayDevice); + + int deviceIndex = 0; + + while (EnumDisplayDevicesW(nullptr, deviceIndex, &displayDevice, 0)) + { + if (displayDevice.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) + { + break; + } + deviceIndex++; + } + + // make sure the string is null terminated + // (using the term "null" here to mean a zero UTF-16 unit) + if (wcsnlen(displayDevice.DeviceKey, SAL_N_ELEMENTS(displayDevice.DeviceKey)) + == SAL_N_ELEMENTS(displayDevice.DeviceKey)) + { + // we did not find a null + SAL_WARN("vcl.opengl", "string not null terminated"); + return; + } + + /* DeviceKey is "reserved" according to MSDN so we'll be careful with it */ + /* check that DeviceKey begins with DEVICE_KEY_PREFIX */ + /* some systems have a DeviceKey starting with \REGISTRY\Machine\ so we need to compare case insensitively */ + if (_wcsnicmp(displayDevice.DeviceKey, DEVICE_KEY_PREFIX, SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1) != 0) + { + SAL_WARN("vcl.opengl", "incorrect DeviceKey"); + return; + } + + // chop off DEVICE_KEY_PREFIX + maDeviceKey = o3tl::toU(displayDevice.DeviceKey) + SAL_N_ELEMENTS(DEVICE_KEY_PREFIX)-1; + + maDeviceID = o3tl::toU(displayDevice.DeviceID); + maDeviceString = o3tl::toU(displayDevice.DeviceString); + + if (maDeviceID.isEmpty() && + (maDeviceString == "RDPDD Chained DD" || + (maDeviceString == "RDPUDD Chained DD"))) + { + // we need to block RDP as it does not provide OpenGL 2.1+ + mbRDP = true; + SAL_WARN("vcl.opengl", "RDP => blocked"); + return; + } + + /* create a device information set composed of the current display device */ + HDEVINFO devinfo = SetupDiGetClassDevsW(nullptr, o3tl::toW(maDeviceID.getStr()), nullptr, + DIGCF_PRESENT | DIGCF_PROFILE | DIGCF_ALLCLASSES); + + if (devinfo != INVALID_HANDLE_VALUE) + { + HKEY key; + LONG result; + WCHAR value[255]; + DWORD dwcbData; + SP_DEVINFO_DATA devinfoData; + DWORD memberIndex = 0; + + devinfoData.cbSize = sizeof(devinfoData); + /* enumerate device information elements in the device information set */ + while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) + { + /* get a string that identifies the device's driver key */ + if (SetupDiGetDeviceRegistryPropertyW(devinfo, + &devinfoData, + SPDRP_DRIVER, + nullptr, + reinterpret_cast<PBYTE>(value), + sizeof(value), + nullptr)) + { + OUString driverKey(OUStringLiteral("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value)); + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey.getStr()), 0, KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) + { + /* we've found the driver we're looking for */ + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result == ERROR_SUCCESS) + { + maDriverVersion = OUString(o3tl::toU(value)); + } + else + { + // If the entry wasn't found, assume the worst (0.0.0.0). + maDriverVersion = OUString("0.0.0.0"); + } + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result == ERROR_SUCCESS) + { + maDriverDate = o3tl::toU(value); + } + else + { + // Again, assume the worst + maDriverDate = OUString("01-01-1970"); + } + RegCloseKey(key); + break; + } + } + } + + SetupDiDestroyDeviceInfoList(devinfo); + } + else + { + SAL_WARN("vcl.opengl", "invalid handle value"); + } + + appendIntegerWithPadding(maAdapterVendorID, ParseIDFromDeviceID(maDeviceID, "VEN_", 4), 4); + appendIntegerWithPadding(maAdapterDeviceID, ParseIDFromDeviceID(maDeviceID, "&DEV_", 4), 4); + appendIntegerWithPadding(maAdapterSubsysID, ParseIDFromDeviceID(maDeviceID, "&SUBSYS_", 8), 8); + + // We now check for second display adapter. + + // Device interface class for display adapters. + CLSID GUID_DISPLAY_DEVICE_ARRIVAL; + HRESULT hresult = CLSIDFromString(L"{1CA05180-A699-450A-9A0C-DE4FBE3DDD89}", + &GUID_DISPLAY_DEVICE_ARRIVAL); + if (hresult == NOERROR) + { + devinfo = SetupDiGetClassDevsW(&GUID_DISPLAY_DEVICE_ARRIVAL, + nullptr, nullptr, + DIGCF_PRESENT | DIGCF_INTERFACEDEVICE); + + if (devinfo != INVALID_HANDLE_VALUE) + { + HKEY key; + LONG result; + WCHAR value[255]; + DWORD dwcbData; + SP_DEVINFO_DATA devinfoData; + DWORD memberIndex = 0; + devinfoData.cbSize = sizeof(devinfoData); + + OUString aAdapterDriver2; + OUString aDeviceID2; + OUString aDriverVersion2; + OUString aDriverDate2; + uint32_t adapterVendorID2; + uint32_t adapterDeviceID2; + + /* enumerate device information elements in the device information set */ + while (SetupDiEnumDeviceInfo(devinfo, memberIndex++, &devinfoData)) + { + /* get a string that identifies the device's driver key */ + if (SetupDiGetDeviceRegistryPropertyW(devinfo, + &devinfoData, + SPDRP_DRIVER, + nullptr, + reinterpret_cast<PBYTE>(value), + sizeof(value), + nullptr)) + { + OUString driverKey2(OUStringLiteral("System\\CurrentControlSet\\Control\\Class\\") + o3tl::toU(value)); + result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, o3tl::toW(driverKey2.getStr()), 0, KEY_QUERY_VALUE, &key); + if (result == ERROR_SUCCESS) + { + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"MatchingDeviceId", nullptr, + nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result != ERROR_SUCCESS) + { + continue; + } + aDeviceID2 = o3tl::toU(value); + OUString aAdapterVendorID2String; + OUString aAdapterDeviceID2String; + adapterVendorID2 = ParseIDFromDeviceID(aDeviceID2, "VEN_", 4); + appendIntegerWithPadding(aAdapterVendorID2String, adapterVendorID2, 4); + adapterDeviceID2 = ParseIDFromDeviceID(aDeviceID2, "&DEV_", 4); + appendIntegerWithPadding(aAdapterDeviceID2String, adapterDeviceID2, 4); + if (maAdapterVendorID == aAdapterVendorID2String && + maAdapterDeviceID == aAdapterDeviceID2String) + { + RegCloseKey(key); + continue; + } + + // If this device is missing driver information, it is unlikely to + // be a real display adapter. + if (!GetKeyValue(o3tl::toW(driverKey2.getStr()), L"InstalledDisplayDrivers", + aAdapterDriver2, REG_MULTI_SZ)) + { + RegCloseKey(key); + continue; + } + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverVersion", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result != ERROR_SUCCESS) + { + RegCloseKey(key); + continue; + } + aDriverVersion2 = o3tl::toU(value); + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDate", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result != ERROR_SUCCESS) + { + RegCloseKey(key); + continue; + } + aDriverDate2 = o3tl::toU(value); + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"Device Description", nullptr, + nullptr, reinterpret_cast<LPBYTE>(value), &dwcbData); + if (result != ERROR_SUCCESS) + { + dwcbData = sizeof(value); + result = RegQueryValueExW(key, L"DriverDesc", nullptr, nullptr, + reinterpret_cast<LPBYTE>(value), &dwcbData); + } + RegCloseKey(key); + if (result == ERROR_SUCCESS) + { + mbHasDualGPU = true; + maDeviceString2 = o3tl::toU(value); + maDeviceID2 = aDeviceID2; + maDeviceKey2 = driverKey2; + maDriverVersion2 = aDriverVersion2; + maDriverDate2 = aDriverDate2; + appendIntegerWithPadding(maAdapterVendorID2, adapterVendorID2, 4); + appendIntegerWithPadding(maAdapterDeviceID2, adapterDeviceID2, 4); + appendIntegerWithPadding(maAdapterSubsysID2, ParseIDFromDeviceID(maDeviceID2, "&SUBSYS_", 8), 8); + break; + } + } + } + } + + SetupDiDestroyDeviceInfoList(devinfo); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/win/gdiimpl.cxx b/vcl/opengl/win/gdiimpl.cxx new file mode 100644 index 000000000..eabfe8a09 --- /dev/null +++ b/vcl/opengl/win/gdiimpl.cxx @@ -0,0 +1,898 @@ +/* -*- 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 <memory> +#include <thread> +#include <opengl/win/gdiimpl.hxx> +#include <vcl/opengl/OpenGLHelper.hxx> + +#include <sal/log.hxx> +#include <comphelper/windowserrorstring.hxx> +#include <opengl/zone.hxx> +#include <win/wincomp.hxx> +#include <win/saldata.hxx> +#include <win/salframe.h> +#include <win/salinst.h> +#include <epoxy/wgl.h> +#include <ControlCacheKey.hxx> + +static std::vector<HGLRC> g_vShareList; +static bool g_bAnyCurrent; + +namespace { + +class GLWinWindow : public GLWindow +{ +public: + HWND hWnd; + HDC hDC; + HGLRC hRC; + GLWinWindow(); +}; + +} + +GLWinWindow::GLWinWindow() + : hWnd(nullptr) + , hDC(nullptr) + , hRC(nullptr) +{ +} + +namespace { + +class WinOpenGLContext : public OpenGLContext +{ +public: + bool init( HDC hDC, HWND hWnd ); + virtual void initWindow() override; +private: + GLWinWindow m_aGLWin; + virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; } + virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; } + virtual bool ImplInit() override; + virtual void makeCurrent() override; + virtual void destroyCurrentContext() override; + virtual bool isCurrent() override; + virtual bool isAnyCurrent() override; + virtual void resetCurrent() override; + virtual void swapBuffers() override; +}; + +} + +void WinOpenGLContext::swapBuffers() +{ + OpenGLZone aZone; + + SwapBuffers(m_aGLWin.hDC); + + BuffersSwapped(); +} + +void WinOpenGLContext::resetCurrent() +{ + clearCurrent(); + + OpenGLZone aZone; + + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; +} + +static void ensureDispatchTable() +{ + thread_local bool bEpoxyDispatchMakeCurrentCalled = false; + if (!bEpoxyDispatchMakeCurrentCalled) + { + epoxy_handle_external_wglMakeCurrent(); + bEpoxyDispatchMakeCurrentCalled = true; + } +} + +bool WinOpenGLContext::isCurrent() +{ + OpenGLZone aZone; + if (!g_bAnyCurrent || !m_aGLWin.hRC) + return false; + ensureDispatchTable(); + return wglGetCurrentContext() == m_aGLWin.hRC && wglGetCurrentDC() == m_aGLWin.hDC; +} + +bool WinOpenGLContext::isAnyCurrent() +{ + return g_bAnyCurrent && wglGetCurrentContext() != nullptr; +} + +void WinOpenGLContext::makeCurrent() +{ + if (isCurrent()) + return; + + OpenGLZone aZone; + + clearCurrent(); + + ensureDispatchTable(); + + if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC)) + { + g_bAnyCurrent = false; + DWORD nLastError = GetLastError(); + if (nLastError != ERROR_SUCCESS) + SAL_WARN("vcl.opengl", "wglMakeCurrent failed: " << WindowsErrorString(nLastError)); + return; + } + + g_bAnyCurrent = true; + + registerAsCurrent(); +} + +bool WinOpenGLContext::init(HDC hDC, HWND hWnd) +{ + if (isInitialized()) + return true; + + m_aGLWin.hDC = hDC; + m_aGLWin.hWnd = hWnd; + return ImplInit(); +} + +void WinOpenGLContext::initWindow() +{ + if( !m_pChildWindow ) + { + SystemWindowData winData = generateWinData(mpWindow, false); + m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false); + } + + if (m_pChildWindow) + { + InitChildWindow(m_pChildWindow.get()); + const SystemEnvData* sysData(m_pChildWindow->GetSystemData()); + m_aGLWin.hWnd = sysData->hWnd; + } + + m_aGLWin.hDC = GetDC(m_aGLWin.hWnd); +} + +void WinOpenGLContext::destroyCurrentContext() +{ + if (m_aGLWin.hRC) + { + std::vector<HGLRC>::iterator itr = std::remove(g_vShareList.begin(), g_vShareList.end(), m_aGLWin.hRC); + if (itr != g_vShareList.end()) + g_vShareList.erase(itr); + + if (wglGetCurrentContext() != nullptr) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + } + wglDeleteContext( m_aGLWin.hRC ); + ReleaseDC( m_aGLWin.hWnd, m_aGLWin.hDC ); + m_aGLWin.hRC = nullptr; + } +} + +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_CREATE: + return 0; + case WM_CLOSE: + PostQuitMessage(0); + return 0; + case WM_DESTROY: + return 0; + default: + return DefWindowProcW(hwnd, message, wParam, lParam); + } +} + +static bool InitTempWindow(HWND& hwnd, int width, int height, const PIXELFORMATDESCRIPTOR& inPfd, GLWinWindow& glWin) +{ + OpenGLZone aZone; + + PIXELFORMATDESCRIPTOR pfd = inPfd; + int ret; + WNDCLASSW wc; + wc.style = 0; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = wc.cbWndExtra = 0; + wc.hInstance = nullptr; + wc.hIcon = nullptr; + wc.hCursor = nullptr; + wc.hbrBackground = nullptr; + wc.lpszMenuName = nullptr; + wc.lpszClassName = L"GLRenderer"; + RegisterClassW(&wc); + hwnd = CreateWindowW(wc.lpszClassName, nullptr, WS_DISABLED, 0, 0, width, height, nullptr, nullptr, wc.hInstance, nullptr); + glWin.hDC = GetDC(hwnd); + + int nPixelFormat = ChoosePixelFormat(glWin.hDC, &pfd); + if (!nPixelFormat) + { + ReleaseDC(hwnd, glWin.hDC); + DestroyWindow(hwnd); + return false; + } + ret = SetPixelFormat(glWin.hDC, nPixelFormat, &pfd); + if(!ret) + { + ReleaseDC(hwnd, glWin.hDC); + DestroyWindow(hwnd); + return false; + } + glWin.hRC = wglCreateContext(glWin.hDC); + if(!(glWin.hRC)) + { + ReleaseDC(hwnd, glWin.hDC); + DestroyWindow(hwnd); + return false; + } + ret = wglMakeCurrent(glWin.hDC, glWin.hRC); + if(!ret) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hwnd, glWin.hDC); + DestroyWindow(hwnd); + return false; + } + g_bAnyCurrent = false; + + return true; +} + +static bool WGLisExtensionSupported(const char *extension) +{ + OpenGLZone aZone; + + const size_t extlen = strlen(extension); + const char *supported = nullptr; + + // Try to use wglGetExtensionStringARB on current DC, if possible + PROC wglGetExtString = wglGetProcAddress("wglGetExtensionsStringARB"); + + if (wglGetExtString) + supported = reinterpret_cast<char*(__stdcall*)(HDC)>(wglGetExtString)(wglGetCurrentDC()); + // If that failed, try standard OpenGL extensions string + if (supported == nullptr) + supported = reinterpret_cast<char const *>(glGetString(GL_EXTENSIONS)); + // If that failed too, must be no extensions supported + if (supported == nullptr) + return false; + + // Begin examination at start of string, increment by 1 on false match + for (const char* p = supported; ; p++) + { + // Advance p up to the next possible match + p = strstr(p, extension); + + if (p == nullptr) + return false; // No Match + + // Make sure that match is at the start of the string or that + // the previous char is a space, or else we could accidentally + // match "wglFunkywglExtension" with "wglExtension" + + // Also, make sure that the following character is space or null + // or else "wglExtensionTwo" might match "wglExtension" + if ((p==supported || p[-1]==' ') && (p[extlen]=='\0' || p[extlen]==' ')) + return true; // Match + } +} + +static bool InitMultisample(const PIXELFORMATDESCRIPTOR& pfd, int& rPixelFormat, + bool bUseDoubleBufferedRendering, bool bRequestVirtualDevice) +{ + OpenGLZone aZone; + + HWND hWnd = nullptr; + GLWinWindow glWin; + // Create a temp window to check whether support multi-sample, if support, get the format + if (!InitTempWindow(hWnd, 32, 32, pfd, glWin)) + { + SAL_WARN("vcl.opengl", "Can't create temp window to test"); + return false; + } + + // See if the string exists in WGL + if (!WGLisExtensionSupported("WGL_ARB_multisample")) + { + SAL_WARN("vcl.opengl", "Device doesn't support multisample"); + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + return false; + } + // Get our pixel format + PFNWGLCHOOSEPIXELFORMATARBPROC fn_wglChoosePixelFormatARB = reinterpret_cast<PFNWGLCHOOSEPIXELFORMATARBPROC>(wglGetProcAddress("wglChoosePixelFormatARB")); + if (!fn_wglChoosePixelFormatARB) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + return false; + } + // Get our current device context + HDC hDC = GetDC(hWnd); + + int pixelFormat; + int valid; + UINT numFormats; + float fAttributes[] = {0,0}; + // These attributes are the bits we want to test for in our sample. + // Everything is pretty standard, the only one we want to + // really focus on is the WGL_SAMPLE_BUFFERS_ARB and WGL_SAMPLES_ARB. + // These two are going to do the main testing for whether or not + // we support multisampling on this hardware. + int iAttributes[] = + { + WGL_DOUBLE_BUFFER_ARB,GL_TRUE, + WGL_DRAW_TO_WINDOW_ARB,GL_TRUE, + WGL_SUPPORT_OPENGL_ARB,GL_TRUE, + WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB, + WGL_COLOR_BITS_ARB,24, + WGL_ALPHA_BITS_ARB,8, + WGL_DEPTH_BITS_ARB,24, + WGL_STENCIL_BITS_ARB,0, + WGL_SAMPLE_BUFFERS_ARB,GL_TRUE, + WGL_SAMPLES_ARB,8, + 0,0 + }; + + if (!bUseDoubleBufferedRendering) + { + // Use asserts to make sure the iAttributes array is not changed without changing these ugly + // hardcode indexes into it. + assert(iAttributes[0] == WGL_DOUBLE_BUFFER_ARB); + iAttributes[1] = GL_FALSE; + } + + if (bRequestVirtualDevice) + { + assert(iAttributes[2] == WGL_DRAW_TO_WINDOW_ARB); + iAttributes[2] = WGL_DRAW_TO_BITMAP_ARB; + } + + bool bArbMultisampleSupported = false; + + // First we check to see if we can get a pixel format for 8 samples + valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats); + // If we returned true, and our format count is greater than 1 + if (valid && numFormats >= 1) + { + bArbMultisampleSupported = true; + rPixelFormat = pixelFormat; + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + return bArbMultisampleSupported; + } + // Our pixel format with 8 samples failed, test for 2 samples + assert(iAttributes[18] == WGL_SAMPLES_ARB); + iAttributes[19] = 2; + valid = fn_wglChoosePixelFormatARB(hDC, iAttributes, fAttributes, 1, &pixelFormat, &numFormats); + if (valid && numFormats >= 1) + { + bArbMultisampleSupported = true; + rPixelFormat = pixelFormat; + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + return bArbMultisampleSupported; + } + // Return the valid format + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(glWin.hRC); + ReleaseDC(hWnd, glWin.hDC); + DestroyWindow(hWnd); + + return bArbMultisampleSupported; +} + +namespace +{ + +bool tryShaders(const OUString& rVertexShader, const OUString& rFragmentShader, const OUString& rGeometryShader = "", const OString& rPreamble = "") +{ + GLint nId; + + // Somewhat mysteriously, the OpenGLHelper::LoadShaders() API saves a compiled binary of the + // shader only if you give it the digest of the shaders. We have API to calculate the digest + // only of the combination of vertex and fragment (but not geometry) shader. So if we have a + // geometry shader, we should not save the binary. + if (rGeometryShader.isEmpty()) + { + nId = OpenGLHelper::LoadShaders(rVertexShader, rFragmentShader, rPreamble, OpenGLHelper::GetDigest( rVertexShader, rFragmentShader, rPreamble)); + } + else + { + assert(rPreamble.isEmpty()); + nId = OpenGLHelper::LoadShaders(rVertexShader, rFragmentShader, rGeometryShader); + } + if (!nId) + return false; + + // We're interested in the error returned by glDeleteProgram(). + glGetError(); + + glDeleteProgram(nId); + return glGetError() == GL_NO_ERROR; +} + +bool compiledShaderBinariesWork() +{ + static bool bBeenHere = false; + static bool bResult; + + if (bBeenHere) + return bResult; + + bBeenHere = true; + + bResult = + ( +#if 0 // Only look at shaders used by vcl for now + // canvas + tryShaders("dummyVertexShader", "linearMultiColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "linearTwoColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "radialMultiColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "radialTwoColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "rectangularMultiColorGradientFragmentShader") && + tryShaders("dummyVertexShader", "rectangularTwoColorGradientFragmentShader") && + // chart2 + (GLEW_VERSION_3_3 ? + (tryShaders("shape3DVertexShader", "shape3DFragmentShader") && + tryShaders("shape3DVertexShaderBatchScroll", "shape3DFragmentShaderBatchScroll") && + tryShaders("shape3DVertexShaderBatch", "shape3DFragmentShaderBatch") && + tryShaders("textVertexShaderBatch", "textFragmentShaderBatch")) : + (tryShaders("shape3DVertexShaderV300", "shape3DFragmentShaderV300"))) && + tryShaders("textVertexShader", "textFragmentShader") && + tryShaders("screenTextVertexShader", "screenTextFragmentShader") && + tryShaders("commonVertexShader", "commonFragmentShader") && + tryShaders("pickingVertexShader", "pickingFragmentShader") && + tryShaders("backgroundVertexShader", "backgroundFragmentShader") && + tryShaders("symbolVertexShader", "symbolFragmentShader") && + tryShaders("symbolVertexShader", "symbolFragmentShader") && + // slideshow + tryShaders("reflectionVertexShader", "reflectionFragmentShader") && + tryShaders("basicVertexShader", "basicFragmentShader") && + tryShaders("vortexVertexShader", "vortexFragmentShader", "vortexGeometryShader") && + tryShaders("basicVertexShader", "rippleFragmentShader") && + tryShaders("glitterVertexShader", "glitterFragmentShader") && + tryShaders("honeycombVertexShader", "honeycombFragmentShader", "honeycombGeometryShader") && +#endif + // vcl + tryShaders("combinedVertexShader", "combinedFragmentShader") && + tryShaders("dumbVertexShader", "invert50FragmentShader") && + tryShaders("textureVertexShader", "areaScaleFragmentShader") && + tryShaders("transformedTextureVertexShader", "maskedTextureFragmentShader") && + tryShaders("transformedTextureVertexShader", "areaScaleFastFragmentShader") && + tryShaders("transformedTextureVertexShader", "areaScaleFastFragmentShader", "", "#define MASKED") && + tryShaders("transformedTextureVertexShader", "areaScaleFragmentShader") && + tryShaders("transformedTextureVertexShader", "areaScaleFragmentShader", "", "#define MASKED") && + tryShaders("transformedTextureVertexShader", "textureFragmentShader") && + tryShaders("combinedTextureVertexShader", "combinedTextureFragmentShader") && + tryShaders("combinedTextureVertexShader", "combinedTextureFragmentShader", "", "// flush shader\n") && + tryShaders("textureVertexShader", "linearGradientFragmentShader") && + tryShaders("textureVertexShader", "radialGradientFragmentShader") && + tryShaders("textureVertexShader", "areaHashCRC64TFragmentShader") && + tryShaders("textureVertexShader", "replaceColorFragmentShader") && + tryShaders("textureVertexShader", "greyscaleFragmentShader") && + tryShaders("textureVertexShader", "textureFragmentShader") && + tryShaders("textureVertexShader", "convolutionFragmentShader") && + tryShaders("textureVertexShader", "areaScaleFastFragmentShader")); + + return bResult; +} + +} // unnamed namespace + +bool WinOpenGLContext::ImplInit() +{ + static bool bFirstCall = true; + + OpenGLZone aZone; + + VCL_GL_INFO("OpenGLContext::ImplInit----start"); + // PixelFormat tells Windows how we want things to be + PIXELFORMATDESCRIPTOR PixelFormatFront = + { + sizeof(PIXELFORMATDESCRIPTOR), + 1, // Version Number + PFD_SUPPORT_OPENGL, + PFD_TYPE_RGBA, // Request An RGBA Format + BYTE(32), // Select Our Color Depth + 0, 0, 0, 0, 0, 0, // Color Bits Ignored + 0, // No Alpha Buffer + 0, // Shift Bit Ignored + 0, // No Accumulation Buffer + 0, 0, 0, 0, // Accumulation Bits Ignored + 24, // 24 bit z-buffer + 8, // stencil buffer + 0, // No Auxiliary Buffer + 0, // now ignored + 0, // Reserved + 0, 0, 0 // Layer Masks Ignored + }; + + PixelFormatFront.dwFlags |= PFD_DOUBLEBUFFER; + PixelFormatFront.dwFlags |= PFD_DRAW_TO_WINDOW; + + // we must check whether can set the MSAA + int WindowPix = 0; + bool bMultiSampleSupport = false; + + if (!mbVCLOnly) + bMultiSampleSupport = InitMultisample(PixelFormatFront, WindowPix, /*bUseDoubleBufferedRendering*/true, false); + else + VCL_GL_INFO("Skipping multisample detection for VCL."); + + if (bMultiSampleSupport && WindowPix != 0) + { + m_aGLWin.bMultiSampleSupported = true; + } + else + { + WindowPix = ChoosePixelFormat(m_aGLWin.hDC, &PixelFormatFront); +#if OSL_DEBUG_LEVEL > 0 + PIXELFORMATDESCRIPTOR pfd; + DescribePixelFormat(m_aGLWin.hDC, WindowPix, sizeof(PIXELFORMATDESCRIPTOR), &pfd); + SAL_WARN("vcl.opengl", "Render Target: Window: " << static_cast<int>((pfd.dwFlags & PFD_DRAW_TO_WINDOW) != 0) << ", Bitmap: " << static_cast<int>((pfd.dwFlags & PFD_DRAW_TO_BITMAP) != 0)); + SAL_WARN("vcl.opengl", "Supports OpenGL: " << static_cast<int>((pfd.dwFlags & PFD_SUPPORT_OPENGL) != 0)); +#endif + } + + if (WindowPix == 0) + { + SAL_WARN("vcl.opengl", "Invalid pixelformat"); + return false; + } + + if (!SetPixelFormat(m_aGLWin.hDC, WindowPix, &PixelFormatFront)) + { + SAL_WARN("vcl.opengl", "SetPixelFormat failed: " << WindowsErrorString(GetLastError())); + return false; + } + + HGLRC hTempRC = wglCreateContext(m_aGLWin.hDC); + if (hTempRC == nullptr) + { + SAL_WARN("vcl.opengl", "wglCreateContext failed: "<< WindowsErrorString(GetLastError())); + return false; + } + + if (!wglMakeCurrent(m_aGLWin.hDC, hTempRC)) + { + g_bAnyCurrent = false; + SAL_WARN("vcl.opengl", "wglMakeCurrent failed: "<< WindowsErrorString(GetLastError())); + return false; + } + + g_bAnyCurrent = true; + + if (!InitGL()) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + return false; + } + + HGLRC hSharedCtx = nullptr; + if (!g_vShareList.empty()) + hSharedCtx = g_vShareList.front(); + + if (!wglCreateContextAttribsARB) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + return false; + } + + // now setup the shared context; this needs a temporary context already + // set up in order to work + int const attribs [] = + { +#ifdef DBG_UTIL + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, +#endif + 0 + }; + m_aGLWin.hRC = wglCreateContextAttribsARB(m_aGLWin.hDC, hSharedCtx, attribs); + if (m_aGLWin.hRC == nullptr) + { + SAL_WARN("vcl.opengl", "wglCreateContextAttribsARB failed: "<< WindowsErrorString(GetLastError())); + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + return false; + } + + if (!compiledShaderBinariesWork()) + { + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + return false; + } + + wglMakeCurrent(nullptr, nullptr); + g_bAnyCurrent = false; + wglDeleteContext(hTempRC); + + if (!wglMakeCurrent(m_aGLWin.hDC, m_aGLWin.hRC)) + { + g_bAnyCurrent = false; + SAL_WARN("vcl.opengl", "wglMakeCurrent failed: " << WindowsErrorString(GetLastError())); + return false; + } + + g_bAnyCurrent = true; + + if (bFirstCall) + { + // Checking texture size + GLint nMaxTextureSize; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &nMaxTextureSize); + if (nMaxTextureSize <= 4096) + SAL_WARN("vcl.opengl", "Max texture size is " << nMaxTextureSize + << ". This may not be enough for normal operation."); + else + VCL_GL_INFO("Max texture size: " << nMaxTextureSize); + + // Trying to make a texture and check its size + for (GLint nWidthHeight = 1023; nWidthHeight < nMaxTextureSize; nWidthHeight += (nWidthHeight + 1)) + { + glTexImage2D(GL_PROXY_TEXTURE_2D, 0, 4, nWidthHeight, nWidthHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, nullptr); + CHECK_GL_ERROR(); + if (glGetError() == GL_NO_ERROR) + { + GLint nWidth = 0; + GLint nHeight = 0; + glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &nWidth); + glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &nHeight); + VCL_GL_INFO("Created texture " << nWidthHeight << "," << nWidthHeight << " reports size: " << nWidth << ", " << nHeight); + } + else + { + SAL_WARN("vcl.opengl", "Error when creating a " << nWidthHeight << ", " << nWidthHeight << " test texture."); + } + } + } + + InitGLDebugging(); + + g_vShareList.push_back(m_aGLWin.hRC); + + RECT clientRect; + GetClientRect(WindowFromDC(m_aGLWin.hDC), &clientRect); + m_aGLWin.Width = clientRect.right - clientRect.left; + m_aGLWin.Height = clientRect.bottom - clientRect.top; + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + registerAsCurrent(); + + bFirstCall = false; + + static OString aVendor(reinterpret_cast<const char*>(glGetString(GL_VENDOR))); + + if (aVendor.equalsIgnoreAsciiCase("intel")) + maOpenGLCapabilitySwitch.mbLimitedShaderRegisters = true; + + return true; +} + +OpenGLContext* WinSalInstance::CreateOpenGLContext() +{ + return new WinOpenGLContext; +} + +WinOpenGLSalGraphicsImpl::WinOpenGLSalGraphicsImpl(WinSalGraphics& rGraphics, + SalGeometryProvider *mpProvider): + OpenGLSalGraphicsImpl(rGraphics,mpProvider), + mrWinParent(rGraphics) +{ +} + +void WinOpenGLSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) +{ + OpenGLSalGraphicsImpl *pImpl = pSrcGraphics ? static_cast< OpenGLSalGraphicsImpl* >(pSrcGraphics->GetImpl()) : static_cast< OpenGLSalGraphicsImpl *>(mrParent.GetImpl()); + OpenGLSalGraphicsImpl::DoCopyBits( rPosAry, *pImpl ); +} + +rtl::Reference<OpenGLContext> WinOpenGLSalGraphicsImpl::CreateWinContext() +{ + rtl::Reference<WinOpenGLContext> xContext(new WinOpenGLContext); + xContext->setVCLOnly(); + if (!xContext->init(mrWinParent.mhLocalDC, mrWinParent.mhWnd)) + { + SAL_WARN("vcl.opengl", "Context could not be created."); + return rtl::Reference<OpenGLContext>(); + } + return rtl::Reference<OpenGLContext>(xContext.get()); +} + +void WinOpenGLSalGraphicsImpl::Init() +{ + if (!IsOffscreen() && mpContext.is() && mpContext->isInitialized()) + { + const GLWinWindow& rGLWindow = static_cast<const GLWinWindow&>(mpContext->getOpenGLWindow()); + if (rGLWindow.hWnd != mrWinParent.mhWnd || rGLWindow.hDC == mrWinParent.mhLocalDC) + { + // This can legitimately happen, SalFrame keeps 2x + // SalGraphics which share the same hWnd and hDC. + // The shape 'Area' dialog does reparenting to trigger this. + SAL_WARN("vcl.opengl", "Unusual: Windows handle / DC changed without DeInit"); + DeInit(); + } + } + + OpenGLSalGraphicsImpl::Init(); +} + +OpenGLControlsCache::OpenGLControlsCache(): cache(200) {} + +OpenGLControlCacheType & OpenGLControlsCache::get() { + SalData * data = GetSalData(); + if (!data->m_pOpenGLControlsCache) { + data->m_pOpenGLControlsCache.reset(new OpenGLControlsCache); + } + return data->m_pOpenGLControlsCache->cache; +} + +OpenGLCompatibleDC::OpenGLCompatibleDC(SalGraphics &rGraphics, int x, int y, int width, int height) +: CompatibleDC( rGraphics, x, y, width, height, false ) +{ +} + +OpenGLTexture* OpenGLCompatibleDC::getOpenGLTexture() const +{ + if (!mpImpl) + return nullptr; + + // turn what's in the mpData into a texture + return new OpenGLTexture(maRects.mnSrcWidth, maRects.mnSrcHeight, GL_BGRA, GL_UNSIGNED_BYTE, mpData); +} + +std::unique_ptr<CompatibleDC::Texture> OpenGLCompatibleDC::getAsMaskTexture() const +{ + auto ret = std::make_unique<OpenGLCompatibleDC::Texture>(); + ret->texture = OpenGLTexture(maRects.mnSrcWidth, maRects.mnSrcHeight, GL_BGRA, GL_UNSIGNED_BYTE, mpData); + return ret; +} + +bool OpenGLCompatibleDC::copyToTexture(CompatibleDC::Texture& aTexture) const +{ + if (!mpImpl) + return false; + + assert(dynamic_cast<OpenGLCompatibleDC::Texture*>(&aTexture)); + return static_cast<OpenGLCompatibleDC::Texture&>(aTexture).texture.CopyData( + maRects.mnSrcWidth, maRects.mnSrcHeight, GL_BGRA, GL_UNSIGNED_BYTE, reinterpret_cast<sal_uInt8*>(mpData)); +} + +bool WinOpenGLSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey const & rControlCacheKey, int nX, int nY) +{ + static bool gbCacheEnabled = !getenv("SAL_WITHOUT_WIDGET_CACHE"); + + if (!gbCacheEnabled) + return false; + + auto & gTextureCache = OpenGLControlsCache::get(); + OpenGLControlCacheType::const_iterator iterator = gTextureCache.find(rControlCacheKey); + + if (iterator == gTextureCache.end()) + return false; + + const std::unique_ptr<TextureCombo>& pCombo = iterator->second; + + bool bRet = false; + + PreDraw(); + + bRet = RenderTextureCombo(*pCombo, nX, nY); + + PostDraw(); + + return bRet; +} + +bool WinOpenGLSalGraphicsImpl::RenderTextureCombo(TextureCombo const & rCombo, int nX, int nY) +{ + OpenGLTexture& rTexture = *rCombo.mpTexture; + + SalTwoRect aPosAry(0, 0, rTexture.GetWidth(), rTexture.GetHeight(), + nX, nY, rTexture.GetWidth(), rTexture.GetHeight()); + + DrawTextureDiff(rTexture, *rCombo.mpMask, aPosAry, false); + + return true; +} + +bool WinOpenGLSalGraphicsImpl::RenderCompatibleDC(OpenGLCompatibleDC& rWhite, OpenGLCompatibleDC& rBlack, + int nX, int nY, TextureCombo& rCombo) +{ + bool bRet = false; + + PreDraw(); + + rCombo.mpTexture.reset(rWhite.getOpenGLTexture()); + rCombo.mpMask.reset(rBlack.getOpenGLTexture()); + + bRet = RenderTextureCombo(rCombo, nX, nY); + + PostDraw(); + return bRet; +} + +bool WinOpenGLSalGraphicsImpl::RenderAndCacheNativeControl(CompatibleDC& rWhite, CompatibleDC& rBlack, + int nX, int nY , ControlCacheKey& aControlCacheKey) +{ + assert(dynamic_cast<OpenGLCompatibleDC*>(&rWhite)); + assert(dynamic_cast<OpenGLCompatibleDC*>(&rBlack)); + + std::unique_ptr<TextureCombo> pCombo(new TextureCombo); + + bool bResult = RenderCompatibleDC(static_cast<OpenGLCompatibleDC&>(rWhite), + static_cast<OpenGLCompatibleDC&>(rBlack), nX, nY, *pCombo); + if (!bResult) + return false; + + if (!aControlCacheKey.canCacheControl()) + return true; + + OpenGLControlCachePair pair(aControlCacheKey, std::move(pCombo)); + OpenGLControlsCache::get().insert(std::move(pair)); + + return bResult; +} + +void WinOpenGLSalGraphicsImpl::PreDrawText() +{ + PreDraw(); +} + +void WinOpenGLSalGraphicsImpl::PostDrawText() +{ + PostDraw(); +} + +void WinOpenGLSalGraphicsImpl::DeferredTextDraw(const CompatibleDC::Texture* pTexture, Color aMaskColor, const SalTwoRect& rPosAry) +{ + assert(dynamic_cast<const OpenGLCompatibleDC::Texture*>(pTexture)); + mpRenderList->addDrawTextureWithMaskColor( + static_cast<const OpenGLCompatibleDC::Texture*>(pTexture)->texture, aMaskColor, rPosAry); + PostBatchDraw(); +} + +void WinOpenGLSalGraphicsImpl::DrawTextMask( CompatibleDC::Texture* pTexture, Color nMaskColor, const SalTwoRect& rPosAry ) +{ + assert(dynamic_cast<OpenGLCompatibleDC::Texture*>(pTexture)); + DrawMask( static_cast<OpenGLCompatibleDC::Texture*>(pTexture)->texture, nMaskColor, rPosAry ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/win/winlayout.cxx b/vcl/opengl/win/winlayout.cxx new file mode 100644 index 000000000..59bf12c25 --- /dev/null +++ b/vcl/opengl/win/winlayout.cxx @@ -0,0 +1,60 @@ +/* -*- 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 <opengl/win/winlayout.hxx> + +#include <opengl/win/gdiimpl.hxx> + +bool OpenGLGlobalWinGlyphCache::AllocateTexture(WinGlyphDrawElement& rElement, CompatibleDC* dc) +{ + assert(rElement.maTexture.get() == nullptr); + assert(dynamic_cast<OpenGLCompatibleDC*>(dc)); + OpenGLCompatibleDC* odc = static_cast<OpenGLCompatibleDC*>(dc); + OpenGLCompatibleDC::Texture* texture = new OpenGLCompatibleDC::Texture; + rElement.maTexture.reset(texture); + texture->texture = maPackedTextureAtlas.Reserve(dc->getBitmapWidth(), dc->getBitmapHeight()); + if (!texture->texture) + return false; + if (!odc->copyToTexture(*rElement.maTexture)) + return false; + return true; +} + +void OpenGLGlobalWinGlyphCache::Prune() +{ + std::vector<GLuint> aTextureIDs = maPackedTextureAtlas.ReduceTextureNumber(8); + if (!aTextureIDs.empty()) + { + for (auto& pWinGlyphCache : maWinGlyphCaches) + static_cast<OpenGLWinGlyphCache*>(pWinGlyphCache)->RemoveTextures(aTextureIDs); + } +} + +void OpenGLWinGlyphCache::RemoveTextures(std::vector<GLuint>& rTextureIDs) +{ + auto it = maWinTextureCache.begin(); + + while (it != maWinTextureCache.end()) + { + assert(dynamic_cast<OpenGLCompatibleDC::Texture*>(it->second.maTexture.get())); + GLuint nTextureID + = static_cast<OpenGLCompatibleDC::Texture*>(it->second.maTexture.get())->texture.Id(); + + if (std::find(rTextureIDs.begin(), rTextureIDs.end(), nTextureID) != rTextureIDs.end()) + { + it = maWinTextureCache.erase(it); + } + else + { + ++it; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/x11/X11DeviceInfo.cxx b/vcl/opengl/x11/X11DeviceInfo.cxx new file mode 100644 index 000000000..7f671952f --- /dev/null +++ b/vcl/opengl/x11/X11DeviceInfo.cxx @@ -0,0 +1,363 @@ +/* -*- 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 <opengl/x11/X11DeviceInfo.hxx> +#include <opengl/x11/glxtest.hxx> + +#include <config_features.h> + +#include <rtl/ustring.hxx> +#include <sal/log.hxx> + +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/utsname.h> + +#include <desktop/crashreport.hxx> + +namespace glx { + +static int glxtest_pipe = 0; + +static pid_t glxtest_pid = 0; + +} + +pid_t* getGlxPid() +{ + return &glx::glxtest_pid; +} + +int* getGlxPipe() +{ + return &glx::glxtest_pipe; +} + +namespace { + +const char* +strspnp_wrapper(const char* aDelims, const char* aStr) +{ + const char* d; + do + { + for (d = aDelims; *d != '\0'; ++d) + { + if (*aStr == *d) + { + ++aStr; + break; + } + } + } while (*d); + + return aStr; +} + +char* strtok_wrapper(const char* aDelims, char** aStr) +{ + if (!*aStr) + { + return nullptr; + } + + char* ret = const_cast<char*>(strspnp_wrapper(aDelims, *aStr)); + + if (!*ret) + { + *aStr = ret; + return nullptr; + } + + char* i = ret; + do + { + for (const char* d = aDelims; *d != '\0'; ++d) + { + if (*i == *d) { + *i = '\0'; + *aStr = ++i; + return ret; + } + } + ++i; + } while (*i); + + *aStr = nullptr; + return ret; +} + +uint64_t version(uint32_t major, uint32_t minor, uint32_t revision = 0) +{ + return (uint64_t(major) << 32) + (uint64_t(minor) << 16) + uint64_t(revision); +} + +} + +X11OpenGLDeviceInfo::X11OpenGLDeviceInfo(): + mbIsMesa(false), + mbIsNVIDIA(false), + mbIsFGLRX(false), + mbIsNouveau(false), + mbIsIntel(false), + mbIsOldSwrast(false), + mbIsLlvmpipe(false), + mnGLMajorVersion(0), + mnMajorVersion(0), + mnMinorVersion(0), + mnRevisionVersion(0) +{ + GetData(); +} + +void X11OpenGLDeviceInfo::GetData() +{ + if (!glx::glxtest_pipe) + return; + + // to understand this function, see bug moz#639842. We retrieve the OpenGL driver information in a + // separate process to protect against bad drivers. + enum { buf_size = 1024 }; + char buf[buf_size]; + ssize_t bytesread = read(glx::glxtest_pipe, + &buf, + buf_size-1); // -1 because we'll append a zero + close(glx::glxtest_pipe); + glx::glxtest_pipe = 0; + + // bytesread < 0 would mean that the above read() call failed. + // This should never happen. If it did, the outcome would be to blacklist anyway. + if (bytesread < 0) + bytesread = 0; + + // let buf be a zero-terminated string + buf[bytesread] = 0; + + // Wait for the glxtest process to finish. This serves 2 purposes: + // * avoid having a zombie glxtest process laying around + // * get the glxtest process status info. + int glxtest_status = 0; + bool wait_for_glxtest_process = true; + bool waiting_for_glxtest_process_failed = false; + int waitpid_errno = 0; + while(wait_for_glxtest_process) + { + wait_for_glxtest_process = false; + if (waitpid(glx::glxtest_pid, &glxtest_status, 0) == -1) + { + waitpid_errno = errno; + if (waitpid_errno == EINTR) + { + wait_for_glxtest_process = true; + } + else + { + // Bug moz#718629 + // ECHILD happens when the glxtest process got reaped got reaped after a PR_CreateProcess + // as per bug moz#227246. This shouldn't matter, as we still seem to get the data + // from the pipe, and if we didn't, the outcome would be to blacklist anyway. + waiting_for_glxtest_process_failed = (waitpid_errno != ECHILD); + } + } + } + + bool exited_with_error_code = !waiting_for_glxtest_process_failed && + WIFEXITED(glxtest_status) && + WEXITSTATUS(glxtest_status) != EXIT_SUCCESS; + bool received_signal = !waiting_for_glxtest_process_failed && + WIFSIGNALED(glxtest_status); + + bool error = waiting_for_glxtest_process_failed || exited_with_error_code || received_signal; + + OString textureFromPixmap; + OString *stringToFill = nullptr; + char *bufptr = buf; + if (!error) + { + while(true) + { + char *line = strtok_wrapper("\n", &bufptr); + if (!line) + break; + if (stringToFill) { + *stringToFill = OString(line); + stringToFill = nullptr; + } + else if(!strcmp(line, "VENDOR")) + stringToFill = &maVendor; + else if(!strcmp(line, "RENDERER")) + stringToFill = &maRenderer; + else if(!strcmp(line, "VERSION")) + stringToFill = &maVersion; + else if(!strcmp(line, "TFP")) + stringToFill = &textureFromPixmap; + } + } + + // only useful for Linux kernel version check for FGLRX driver. + // assumes X client == X server, which is sad. + struct utsname unameobj; + if (!uname(&unameobj)) + { + maOS = OString(unameobj.sysname); + maOSRelease = OString(unameobj.release); + } + + // determine the major OpenGL version. That's the first integer in the version string. + mnGLMajorVersion = strtol(maVersion.getStr(), nullptr, 10); + + // determine driver type (vendor) and where in the version string + // the actual driver version numbers should be expected to be found (whereToReadVersionNumbers) + const char *whereToReadVersionNumbers = nullptr; + const char *Mesa_in_version_string = strstr(maVersion.getStr(), "Mesa"); + if (Mesa_in_version_string) + { + mbIsMesa = true; + // with Mesa, the version string contains "Mesa major.minor" and that's all the version information we get: + // there is no actual driver version info. + whereToReadVersionNumbers = Mesa_in_version_string + strlen("Mesa"); + if (strcasestr(maVendor.getStr(), "nouveau")) + mbIsNouveau = true; + if (strcasestr(maRenderer.getStr(), "intel")) // yes, intel is in the renderer string + mbIsIntel = true; + if (strcasestr(maRenderer.getStr(), "llvmpipe")) + mbIsLlvmpipe = true; + if (strcasestr(maRenderer.getStr(), "software rasterizer")) + mbIsOldSwrast = true; + } + else if (strstr(maVendor.getStr(), "NVIDIA Corporation")) + { + mbIsNVIDIA = true; + // with the NVIDIA driver, the version string contains "NVIDIA major.minor" + // note that here the vendor and version strings behave differently, that's why we don't put this above + // alongside Mesa_in_version_string. + const char *NVIDIA_in_version_string = strstr(maVersion.getStr(), "NVIDIA"); + if (NVIDIA_in_version_string) + whereToReadVersionNumbers = NVIDIA_in_version_string + strlen("NVIDIA"); + } + else if (strstr(maVendor.getStr(), "ATI Technologies Inc")) + { + mbIsFGLRX = true; + // with the FGLRX driver, the version string only gives an OpenGL version: so let's return that. + // that can at least give a rough idea of how old the driver is. + whereToReadVersionNumbers = maVersion.getStr(); + } + + // read major.minor version numbers of the driver (not to be confused with the OpenGL version) + if (whereToReadVersionNumbers) + { + // copy into writable buffer, for tokenization + strncpy(buf, whereToReadVersionNumbers, buf_size-1); + buf[buf_size-1] = 0; + bufptr = buf; + + // now try to read major.minor version numbers. In case of failure, gracefully exit: these numbers have + // been initialized as 0 anyways + char *token = strtok_wrapper(".", &bufptr); + if (token) + { + mnMajorVersion = strtol(token, nullptr, 10); + token = strtok_wrapper(".", &bufptr); + if (token) + { + mnMinorVersion = strtol(token, nullptr, 10); + token = strtok_wrapper(".", &bufptr); + if (token) + mnRevisionVersion = strtol(token, nullptr, 10); + } + } + } +} + +bool X11OpenGLDeviceInfo::isDeviceBlocked() +{ + // don't even try to use OpenGL 1.x + if (mnGLMajorVersion == 1) + return true; + + CrashReporter::addKeyValue("AdapterVendorId", OStringToOUString(maVendor, RTL_TEXTENCODING_UTF8), CrashReporter::AddItem); + CrashReporter::addKeyValue("AdapterDeviceId", OStringToOUString(maRenderer, RTL_TEXTENCODING_UTF8), CrashReporter::Write); + + SAL_INFO("vcl.opengl", "Vendor: " << maVendor); + SAL_INFO("vcl.opengl", "Renderer: " << maRenderer); + SAL_INFO("vcl.opengl", "Version: " << maVersion); + SAL_INFO("vcl.opengl", "OS: " << maOS); + SAL_INFO("vcl.opengl", "OSRelease: " << maOSRelease); + + if (mbIsMesa) + { + if (mbIsNouveau && version(mnMajorVersion, mnMinorVersion) < version(8,0)) + { + SAL_WARN("vcl.opengl", "blocked driver version: old nouveau driver (requires mesa 8.0+)"); + return true; + } + else if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(7,10,3)) + { + SAL_WARN("vcl.opengl", "blocked driver version: requires at least mesa 7.10.3"); + return true; + } + else if (mbIsIntel && version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) == version(9,0,2)) + { + SAL_WARN("vcl.opengl", "blocked driver version: my broken intel driver Mesa 9.0.2"); + return true; + } + else if (mbIsOldSwrast) + { + SAL_WARN("vcl.opengl", "blocked driver version: software rasterizer"); + return true; + } + else if (mbIsLlvmpipe && version(mnMajorVersion, mnMinorVersion) < version(9, 1)) + { + // bug moz#791905, Mesa bug 57733, fixed in Mesa 9.1 according to + // https://bugs.freedesktop.org/show_bug.cgi?id=57733#c3 + SAL_WARN("vcl.opengl", "blocked driver version: fdo#57733"); + return true; + } + } + else if (mbIsNVIDIA) + { + if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(257,21)) + { + SAL_WARN("vcl.opengl", "blocked driver version: nvidia requires at least 257.21"); + return true; + } + } + else if (mbIsFGLRX) + { + // FGLRX does not report a driver version number, so we have the OpenGL version instead. + // by requiring OpenGL 3, we effectively require recent drivers. + if (version(mnMajorVersion, mnMinorVersion, mnRevisionVersion) < version(3, 0)) + { + SAL_WARN("vcl.opengl", "blocked driver version: require at least OpenGL 3 for fglrx"); + return true; + } + // Bug moz#724640: FGLRX + Linux 2.6.32 is a crashy combo + bool unknownOS = maOS.isEmpty() || maOSRelease.isEmpty(); + bool badOS = maOS.indexOf("Linux") != -1 && + maOSRelease.indexOf("2.6.32") != -1; + if (unknownOS || badOS) + { + SAL_WARN("vcl.opengl", "blocked OS version with fglrx"); + return true; + } + } + else + { + // like on windows, let's block unknown vendors. Think of virtual machines. + // Also, this case is hit whenever the GLXtest probe failed to get driver info or crashed. + SAL_WARN("vcl.opengl", "unknown vendor => blocked"); + return true; + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/x11/cairotextrender.cxx b/vcl/opengl/x11/cairotextrender.cxx new file mode 100644 index 000000000..39b5f661d --- /dev/null +++ b/vcl/opengl/x11/cairotextrender.cxx @@ -0,0 +1,84 @@ +/* -*- 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 <opengl/x11/cairotextrender.hxx> + +#include <opengl/gdiimpl.hxx> + +#include <cairo.h> + +OpenGLX11CairoTextRender::OpenGLX11CairoTextRender(X11SalGraphics& rParent) + : X11CairoTextRender(rParent) +{ +} + +cairo_t* OpenGLX11CairoTextRender::getCairoContext() +{ + cairo_surface_t* surface = nullptr; + OpenGLSalGraphicsImpl *pImpl = dynamic_cast< OpenGLSalGraphicsImpl* >(mrParent.GetImpl()); + if( pImpl ) + { + tools::Rectangle aClipRect = pImpl->getClipRegion().GetBoundRect(); + if( aClipRect.GetWidth() == 0 || aClipRect.GetHeight() == 0 ) + { + aClipRect.setWidth( GetWidth() ); + aClipRect.setHeight( GetHeight() ); + } + surface = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, aClipRect.GetWidth(), aClipRect.GetHeight() ); + } + if (!surface) + return nullptr; + cairo_t *cr = cairo_create(surface); + cairo_surface_destroy(surface); + return cr; +} + +void OpenGLX11CairoTextRender::getSurfaceOffset( double& nDX, double& nDY ) +{ + OpenGLSalGraphicsImpl *pImpl = dynamic_cast< OpenGLSalGraphicsImpl* >(mrParent.GetImpl()); + if( pImpl ) + { + tools::Rectangle aClipRect = pImpl->getClipRegion().GetBoundRect(); + nDX = -aClipRect.Left(); + nDY = -aClipRect.Top(); + } +} + +void OpenGLX11CairoTextRender::releaseCairoContext(cairo_t* cr) +{ + // XXX: lfrb: GLES 2.0 doesn't support GL_UNSIGNED_INT_8_8_8_8_REV + OpenGLSalGraphicsImpl *pImpl = dynamic_cast< OpenGLSalGraphicsImpl* >(mrParent.GetImpl()); + if(!pImpl) + { + cairo_destroy(cr); + return; + } + + cairo_surface_t* pSurface = cairo_get_target(cr); + int nWidth = cairo_image_surface_get_width( pSurface ); + int nHeight = cairo_image_surface_get_height( pSurface ); + cairo_surface_flush(pSurface); + unsigned char *pSrc = cairo_image_surface_get_data( pSurface ); + + // XXX: lfrb: GLES 2.0 doesn't support GL_UNSIGNED_INT_8_8_8_8_REV + tools::Rectangle aClipRect = pImpl->getClipRegion().GetBoundRect(); + + SalTwoRect aRect(0, 0, nWidth, nHeight, + aClipRect.Left(), aClipRect.Top(), nWidth, nHeight); + + // Cairo surface data is ARGB with premultiplied alpha and is Y-inverted + OpenGLTexture aTexture( nWidth, nHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pSrc ); + pImpl->PreDraw(); + pImpl->DrawAlphaTexture( aTexture, aRect, true, true ); + pImpl->PostDraw(); + + cairo_destroy(cr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/x11/gdiimpl.cxx b/vcl/opengl/x11/gdiimpl.cxx new file mode 100644 index 000000000..c00ff76e8 --- /dev/null +++ b/vcl/opengl/x11/gdiimpl.cxx @@ -0,0 +1,598 @@ +/* -*- 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 <memory> +#include <vcl/lazydelete.hxx> + +#include <svdata.hxx> + +#include <unx/saldisp.hxx> +#include <unx/salframe.h> +#include <unx/salgdi.h> +#include <unx/salinst.h> +#include <unx/salvd.h> +#include <unx/x11/xlimits.hxx> + +#include <opengl/texture.hxx> +#include <opengl/zone.hxx> +#include <opengl/RenderState.hxx> +#include <opengl/x11/gdiimpl.hxx> +#include <opengl/x11/salvd.hxx> + +#include <vcl/opengl/OpenGLContext.hxx> +#include <vcl/opengl/OpenGLHelper.hxx> +#include <sal/log.hxx> + +#include <o3tl/lru_map.hxx> +#include <ControlCacheKey.hxx> + +static std::vector<GLXContext> g_vShareList; +static bool g_bAnyCurrent; + +namespace { + +class X11OpenGLContext : public OpenGLContext +{ +public: + void init(Display* dpy, Window win, int screen); + virtual void initWindow() override; +private: + GLX11Window m_aGLWin; + virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; } + virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; } + virtual bool ImplInit() override; + void initGLWindow(Visual* pVisual); + virtual SystemWindowData generateWinData(vcl::Window* pParent, bool bRequestLegacyContext) override; + virtual void makeCurrent() override; + virtual void destroyCurrentContext() override; + virtual bool isCurrent() override; + virtual bool isAnyCurrent() override; + virtual void sync() override; + virtual void resetCurrent() override; + virtual void swapBuffers() override; +}; + +#ifdef DBG_UTIL + int unxErrorHandler(Display* dpy, XErrorEvent* event) + { + char err[256]; + char req[256]; + char minor[256]; + XGetErrorText(dpy, event->error_code, err, 256); + XGetErrorText(dpy, event->request_code, req, 256); + XGetErrorText(dpy, event->minor_code, minor, 256); + SAL_WARN("vcl.opengl", "Error: " << err << ", Req: " << req << ", Minor: " << minor); + return 0; + } +#endif + + typedef int (*errorHandler)(Display* /*dpy*/, XErrorEvent* /*evnt*/); + + class TempErrorHandler + { + private: + errorHandler oldErrorHandler; + Display* mdpy; + + public: + TempErrorHandler(Display* dpy, errorHandler newErrorHandler) + : oldErrorHandler(nullptr) + , mdpy(dpy) + { + if (mdpy) + { + XLockDisplay(dpy); + XSync(dpy, false); + oldErrorHandler = XSetErrorHandler(newErrorHandler); + } + } + + ~TempErrorHandler() + { + if (mdpy) + { + // sync so that we possibly get an XError + glXWaitGL(); + XSync(mdpy, false); + XSetErrorHandler(oldErrorHandler); + XUnlockDisplay(mdpy); + } + } + }; + + static bool errorTriggered; + int oglErrorHandler( Display* /*dpy*/, XErrorEvent* /*evnt*/ ) + { + errorTriggered = true; + + return 0; + } + + GLXFBConfig* getFBConfig(Display* dpy, Window win, int& nBestFBC) + { + OpenGLZone aZone; + + if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) ) + return nullptr; + + VCL_GL_INFO("window: " << win); + + XWindowAttributes xattr; + if( !XGetWindowAttributes( dpy, win, &xattr ) ) + { + SAL_WARN("vcl.opengl", "Failed to get window attributes for fbconfig " << win); + xattr.screen = nullptr; + xattr.visual = nullptr; + } + + int screen = XScreenNumberOfScreen( xattr.screen ); + + // TODO: moggi: Select colour channel depth based on visual attributes, not hardcoded */ + static int visual_attribs[] = + { + GLX_DOUBLEBUFFER, True, + GLX_X_RENDERABLE, True, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_ALPHA_SIZE, 8, + GLX_DEPTH_SIZE, 24, + GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, + None + }; + + int fbCount = 0; + GLXFBConfig* pFBC = glXChooseFBConfig( dpy, + screen, + visual_attribs, &fbCount ); + + if(!pFBC) + { + SAL_WARN("vcl.opengl", "no suitable fb format found"); + return nullptr; + } + + int best_num_samp = -1; + for(int i = 0; i < fbCount; ++i) + { + XVisualInfo* pVi = glXGetVisualFromFBConfig( dpy, pFBC[i] ); + if(pVi && (xattr.visual && pVi->visualid == xattr.visual->visualid) ) + { + // pick the one with the most samples per pixel + int nSampleBuf = 0; + int nSamples = 0; + glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLE_BUFFERS, &nSampleBuf ); + glXGetFBConfigAttrib( dpy, pFBC[i], GLX_SAMPLES , &nSamples ); + + if ( nBestFBC < 0 || (nSampleBuf && ( nSamples > best_num_samp )) ) + { + nBestFBC = i; + best_num_samp = nSamples; + } + } + XFree( pVi ); + } + + return pFBC; + } + + Visual* getVisual(Display* dpy, Window win) + { + OpenGLZone aZone; + + XWindowAttributes xattr; + if( !XGetWindowAttributes( dpy, win, &xattr ) ) + { + SAL_WARN("vcl.opengl", "Failed to get window attributes for getVisual " << win); + xattr.visual = nullptr; + } + VCL_GL_INFO("using VisualID " << xattr.visual); + return xattr.visual; + } +} + +void X11OpenGLContext::sync() +{ + OpenGLZone aZone; + glXWaitGL(); + XSync(m_aGLWin.dpy, false); +} + +void X11OpenGLContext::swapBuffers() +{ + OpenGLZone aZone; + + glXSwapBuffers(m_aGLWin.dpy, m_aGLWin.win); + + BuffersSwapped(); +} + +void X11OpenGLContext::resetCurrent() +{ + clearCurrent(); + + OpenGLZone aZone; + + if (m_aGLWin.dpy) + { + glXMakeCurrent(m_aGLWin.dpy, None, nullptr); + g_bAnyCurrent = false; + } +} + +bool X11OpenGLContext::isCurrent() +{ + OpenGLZone aZone; + return g_bAnyCurrent && m_aGLWin.ctx && glXGetCurrentContext() == m_aGLWin.ctx && + glXGetCurrentDrawable() == m_aGLWin.win; +} + +bool X11OpenGLContext::isAnyCurrent() +{ + return g_bAnyCurrent && glXGetCurrentContext() != None; +} + +SystemWindowData X11OpenGLContext::generateWinData(vcl::Window* pParent, bool /*bRequestLegacyContext*/) +{ + OpenGLZone aZone; + + SystemWindowData aWinData; + aWinData.pVisual = nullptr; + aWinData.bClipUsingNativeWidget = false; + + const SystemEnvData* sysData(pParent->GetSystemData()); + + Display *dpy = static_cast<Display*>(sysData->pDisplay); + Window win = sysData->aWindow; + + if( dpy == nullptr || !glXQueryExtension( dpy, nullptr, nullptr ) ) + return aWinData; + + int best_fbc = -1; + GLXFBConfig* pFBC = getFBConfig(dpy, win, best_fbc); + + if (!pFBC) + return aWinData; + + XVisualInfo* vi = nullptr; + if( best_fbc != -1 ) + vi = glXGetVisualFromFBConfig( dpy, pFBC[best_fbc] ); + + XFree(pFBC); + + if( vi ) + { + VCL_GL_INFO("using VisualID " << vi->visualid); + aWinData.pVisual = static_cast<void*>(vi->visual); + } + + return aWinData; +} + +bool X11OpenGLContext::ImplInit() +{ + if (!m_aGLWin.dpy) + return false; + + OpenGLZone aZone; + + GLXContext pSharedCtx( nullptr ); +#ifdef DBG_UTIL + TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler); +#endif + + VCL_GL_INFO("OpenGLContext::ImplInit----start"); + + if (!g_vShareList.empty()) + pSharedCtx = g_vShareList.front(); + + //tdf#112166 for, e.g. VirtualBox GL, claiming OpenGL 2.1 + static bool hasCreateContextAttribsARB = glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB")) != nullptr; + if (hasCreateContextAttribsARB && !mbRequestLegacyContext) + { + int best_fbc = -1; + GLXFBConfig* pFBC = getFBConfig(m_aGLWin.dpy, m_aGLWin.win, best_fbc); + + if (pFBC && best_fbc != -1) + { + int const pContextAttribs[] = + { +#if 0 // defined(DBG_UTIL) + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 2, +#endif + None + + }; + m_aGLWin.ctx = glXCreateContextAttribsARB(m_aGLWin.dpy, pFBC[best_fbc], pSharedCtx, /* direct, not via X */ GL_TRUE, pContextAttribs); + SAL_INFO_IF(m_aGLWin.ctx, "vcl.opengl", "created a 3.2 core context"); + } + else + SAL_WARN("vcl.opengl", "unable to find correct FBC"); + } + + if (!m_aGLWin.ctx) + { + if (!m_aGLWin.vi) + return false; + + SAL_WARN("vcl.opengl", "attempting to create a non-double-buffered " + "visual matching the context"); + + m_aGLWin.ctx = glXCreateContext(m_aGLWin.dpy, + m_aGLWin.vi, + pSharedCtx, + GL_TRUE /* direct, not via X server */); + } + + if( m_aGLWin.ctx ) + { + g_vShareList.push_back( m_aGLWin.ctx ); + } + else + { + SAL_WARN("vcl.opengl", "unable to create GLX context"); + return false; + } + + if( !glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx ) ) + { + g_bAnyCurrent = false; + SAL_WARN("vcl.opengl", "unable to select current GLX context"); + return false; + } + + g_bAnyCurrent = true; + + int glxMinor, glxMajor; + double nGLXVersion = 0; + if( glXQueryVersion( m_aGLWin.dpy, &glxMajor, &glxMinor ) ) + nGLXVersion = glxMajor + 0.1*glxMinor; + SAL_INFO("vcl.opengl", "available GLX version: " << nGLXVersion); + + SAL_INFO("vcl.opengl", "available GL extensions: " << glGetString(GL_EXTENSIONS)); + + XWindowAttributes aWinAttr; + if( !XGetWindowAttributes( m_aGLWin.dpy, m_aGLWin.win, &aWinAttr ) ) + { + SAL_WARN("vcl.opengl", "Failed to get window attributes on " << m_aGLWin.win); + m_aGLWin.Width = 0; + m_aGLWin.Height = 0; + } + else + { + m_aGLWin.Width = aWinAttr.width; + m_aGLWin.Height = aWinAttr.height; + } + + if( m_aGLWin.HasGLXExtension("GLX_SGI_swap_control" ) ) + { + // enable vsync + typedef GLint (*glXSwapIntervalProc)(GLint); + glXSwapIntervalProc glXSwapInterval = reinterpret_cast<glXSwapIntervalProc>(glXGetProcAddress( reinterpret_cast<const GLubyte*>("glXSwapIntervalSGI") )); + if( glXSwapInterval ) + { + TempErrorHandler aLocalErrorHandler(m_aGLWin.dpy, oglErrorHandler); + + errorTriggered = false; + + glXSwapInterval( 1 ); + + if( errorTriggered ) + SAL_WARN("vcl.opengl", "error when trying to set swap interval, NVIDIA or Mesa bug?"); + else + VCL_GL_INFO("set swap interval to 1 (enable vsync)"); + } + } + + bool bRet = InitGL(); + InitGLDebugging(); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + registerAsCurrent(); + + return bRet; +} + +void X11OpenGLContext::makeCurrent() +{ + if (isCurrent()) + return; + + OpenGLZone aZone; + + clearCurrent(); + +#ifdef DBG_UTIL + TempErrorHandler aErrorHandler(m_aGLWin.dpy, unxErrorHandler); +#endif + + if (m_aGLWin.dpy) + { + if (!glXMakeCurrent( m_aGLWin.dpy, m_aGLWin.win, m_aGLWin.ctx )) + { + g_bAnyCurrent = false; + SAL_WARN("vcl.opengl", "OpenGLContext::makeCurrent failed " + "on drawable " << m_aGLWin.win); + return; + } + g_bAnyCurrent = true; + } + + registerAsCurrent(); +} + +void X11OpenGLContext::destroyCurrentContext() +{ + if(m_aGLWin.ctx) + { + std::vector<GLXContext>::iterator itr = std::remove( g_vShareList.begin(), g_vShareList.end(), m_aGLWin.ctx ); + if (itr != g_vShareList.end()) + g_vShareList.erase(itr); + + glXMakeCurrent(m_aGLWin.dpy, None, nullptr); + g_bAnyCurrent = false; + if( glGetError() != GL_NO_ERROR ) + { + SAL_WARN("vcl.opengl", "glError: " << glGetError()); + } + glXDestroyContext(m_aGLWin.dpy, m_aGLWin.ctx); + m_aGLWin.ctx = nullptr; + } +} + +void X11OpenGLContext::init(Display* dpy, Window win, int screen) +{ + if (isInitialized()) + return; + + if (!dpy) + return; + + OpenGLZone aZone; + + m_aGLWin.dpy = dpy; + m_aGLWin.win = win; + m_aGLWin.screen = screen; + + Visual* pVisual = getVisual(dpy, win); + + initGLWindow(pVisual); + + ImplInit(); +} + +void X11OpenGLContext::initGLWindow(Visual* pVisual) +{ + OpenGLZone aZone; + + // Get visual info + { + XVisualInfo aTemplate; + aTemplate.visualid = XVisualIDFromVisual( pVisual ); + int nVisuals = 0; + XVisualInfo* pInfo = XGetVisualInfo( m_aGLWin.dpy, VisualIDMask, &aTemplate, &nVisuals ); + if( nVisuals != 1 ) + SAL_WARN( "vcl.opengl", "match count for visual id is not 1" ); + m_aGLWin.vi = pInfo; + } + + // Check multisample support + /* TODO: moggi: This is not necessarily correct in the DBG_UTIL path, as it picks + * an FBConfig instead ... */ + int nSamples = 0; + glXGetConfig(m_aGLWin.dpy, m_aGLWin.vi, GLX_SAMPLES, &nSamples); + if( nSamples > 0 ) + m_aGLWin.bMultiSampleSupported = true; + + m_aGLWin.GLXExtensions = glXQueryExtensionsString( m_aGLWin.dpy, m_aGLWin.screen ); + SAL_INFO("vcl.opengl", "available GLX extensions: " << m_aGLWin.GLXExtensions); +} + +void X11OpenGLContext::initWindow() +{ + const SystemEnvData* pChildSysData = nullptr; + SystemWindowData winData = generateWinData(mpWindow, false); + if( winData.pVisual ) + { + if( !m_pChildWindow ) + { + m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false); + } + pChildSysData = m_pChildWindow->GetSystemData(); + } + + if (!m_pChildWindow || !pChildSysData) + return; + + InitChildWindow(m_pChildWindow.get()); + + m_aGLWin.dpy = static_cast<Display*>(pChildSysData->pDisplay); + m_aGLWin.win = pChildSysData->aWindow; + m_aGLWin.screen = pChildSysData->nScreen; + + Visual* pVisual = static_cast<Visual*>(pChildSysData->pVisual); + initGLWindow(pVisual); +} + +GLX11Window::GLX11Window() + : dpy(nullptr) + , screen(0) + , win(0) + , vi(nullptr) + , ctx(nullptr) +{ +} + +bool GLX11Window::HasGLXExtension( const char* name ) const +{ + for (sal_Int32 i = 0; i != -1;) { + if (GLXExtensions.getToken(0, ' ', i) == name) { + return true; + } + } + return false; +} + +GLX11Window::~GLX11Window() +{ + XFree(vi); +} + +bool GLX11Window::Synchronize(bool bOnoff) const +{ + XSynchronize(dpy, bOnoff); + return true; +} + +OpenGLContext* X11SalInstance::CreateOpenGLContext() +{ + return new X11OpenGLContext; +} + +X11OpenGLSalGraphicsImpl::X11OpenGLSalGraphicsImpl( X11SalGraphics& rParent ): + OpenGLSalGraphicsImpl(rParent,rParent.GetGeometryProvider()), + mrX11Parent(rParent) +{ +} + +X11OpenGLSalGraphicsImpl::~X11OpenGLSalGraphicsImpl() +{ +} + +void X11OpenGLSalGraphicsImpl::Init() +{ + // The m_pFrame and m_pVDev pointers are updated late in X11 + mpProvider = mrX11Parent.GetGeometryProvider(); + OpenGLSalGraphicsImpl::Init(); +} + +rtl::Reference<OpenGLContext> X11OpenGLSalGraphicsImpl::CreateWinContext() +{ + NativeWindowHandleProvider *pProvider = dynamic_cast<NativeWindowHandleProvider*>(mrX11Parent.m_pFrame); + + if( !pProvider ) + return nullptr; + + sal_uIntPtr aWin = pProvider->GetNativeWindowHandle(); + rtl::Reference<X11OpenGLContext> xContext = new X11OpenGLContext; + xContext->setVCLOnly(); + xContext->init( mrX11Parent.GetXDisplay(), aWin, + mrX11Parent.m_nXScreen.getXScreen() ); + return rtl::Reference<OpenGLContext>(xContext.get()); +} + +void X11OpenGLSalGraphicsImpl::copyBits( const SalTwoRect& rPosAry, SalGraphics* pSrcGraphics ) +{ + OpenGLSalGraphicsImpl *pImpl = pSrcGraphics ? static_cast< OpenGLSalGraphicsImpl* >(pSrcGraphics->GetImpl()) : static_cast< OpenGLSalGraphicsImpl *>(mrX11Parent.GetImpl()); + OpenGLSalGraphicsImpl::DoCopyBits( rPosAry, *pImpl ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/x11/salvd.cxx b/vcl/opengl/x11/salvd.cxx new file mode 100644 index 000000000..a6ed5602f --- /dev/null +++ b/vcl/opengl/x11/salvd.cxx @@ -0,0 +1,90 @@ +/* -*- 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/sysdata.hxx> + +#include <unx/salunx.h> +#include <unx/saldisp.hxx> +#include <unx/salgdi.h> +#include <unx/salvd.h> + +#include <opengl/x11/salvd.hxx> + +void X11SalGraphics::Init( X11OpenGLSalVirtualDevice *pDevice ) +{ + SalDisplay *pDisplay = pDevice->GetDisplay(); + + m_nXScreen = pDevice->GetXScreenNumber(); + m_pColormap = &pDisplay->GetColormap( m_nXScreen ); + + m_pVDev = pDevice; + m_pFrame = nullptr; + + bWindow_ = pDisplay->IsDisplay(); + bVirDev_ = true; + + mxImpl->Init(); +} + +X11OpenGLSalVirtualDevice::X11OpenGLSalVirtualDevice( SalGraphics const * pGraphics, + long nDX, long nDY, + const SystemGraphicsData *pData, + std::unique_ptr<X11SalGraphics> pNewGraphics) : + mpGraphics(std::move(pNewGraphics)), + mbGraphics( false ), + mnXScreen( 0 ) +{ + assert(mpGraphics); + + // TODO Check where a VirtualDevice is created from SystemGraphicsData + assert( pData == nullptr ); (void)pData; + + mpDisplay = vcl_sal::getSalDisplay(GetGenericUnixSalData()); + mnXScreen = pGraphics ? static_cast<X11SalGraphics const *>(pGraphics)->GetScreenNumber() : + vcl_sal::getSalDisplay(GetGenericUnixSalData())->GetDefaultXScreen(); + mnWidth = nDX; + mnHeight = nDY; + mpGraphics->Init( this ); +} + +X11OpenGLSalVirtualDevice::~X11OpenGLSalVirtualDevice() +{ +} + +SalGraphics* X11OpenGLSalVirtualDevice::AcquireGraphics() +{ + if( mbGraphics ) + return nullptr; + + if( mpGraphics ) + mbGraphics = true; + + return mpGraphics.get(); +} + +void X11OpenGLSalVirtualDevice::ReleaseGraphics( SalGraphics* ) +{ + mbGraphics = false; +} + + +bool X11OpenGLSalVirtualDevice::SetSize( long nDX, long nDY ) +{ + if( !nDX ) nDX = 1; + if( !nDY ) nDY = 1; + + mnWidth = nDX; + mnHeight = nDY; + if( mpGraphics ) + mpGraphics->Init( this ); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |