Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,30 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2017 Martin Flöser <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "abstract_opengl_context_attribute_builder.h"
namespace KWin
{
QDebug AbstractOpenGLContextAttributeBuilder::operator<<(QDebug dbg) const
{
QDebugStateSaver saver(dbg);
dbg.nospace() << "\nVersion requested:\t" << isVersionRequested() << "\n";
if (isVersionRequested()) {
dbg.nospace() << "Version:\t" << majorVersion() << "." << minorVersion() << "\n";
}
dbg.nospace() << "Robust:\t" << isRobust() << "\n";
dbg.nospace() << "Reset on video memory purge:\t" << isResetOnVideoMemoryPurge() << "\n";
dbg.nospace() << "Forward compatible:\t" << isForwardCompatible() << "\n";
dbg.nospace() << "Core profile:\t" << isCoreProfile() << "\n";
dbg.nospace() << "Compatibility profile:\t" << isCompatibilityProfile() << "\n";
dbg.nospace() << "High priority:\t" << isHighPriority();
return dbg;
}
}
@@ -0,0 +1,132 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2017 Martin Flöser <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QDebug>
#include <kwin_export.h>
namespace KWin
{
class KWIN_EXPORT AbstractOpenGLContextAttributeBuilder
{
public:
virtual ~AbstractOpenGLContextAttributeBuilder()
{
}
void setVersion(int major, int minor = 0)
{
m_versionRequested = true;
m_majorVersion = major;
m_minorVersion = minor;
}
bool isVersionRequested() const
{
return m_versionRequested;
}
int majorVersion() const
{
return m_majorVersion;
}
int minorVersion() const
{
return m_minorVersion;
}
void setRobust(bool robust)
{
m_robust = robust;
}
bool isRobust() const
{
return m_robust;
}
void setForwardCompatible(bool forward)
{
m_forwardCompatible = forward;
}
bool isForwardCompatible() const
{
return m_forwardCompatible;
}
void setCoreProfile(bool core)
{
m_coreProfile = core;
if (m_coreProfile) {
setCompatibilityProfile(false);
}
}
bool isCoreProfile() const
{
return m_coreProfile;
}
void setCompatibilityProfile(bool compatibility)
{
m_compatibilityProfile = compatibility;
if (m_compatibilityProfile) {
setCoreProfile(false);
}
}
bool isCompatibilityProfile() const
{
return m_compatibilityProfile;
}
void setResetOnVideoMemoryPurge(bool reset)
{
m_resetOnVideoMemoryPurge = reset;
}
bool isResetOnVideoMemoryPurge() const
{
return m_resetOnVideoMemoryPurge;
}
void setHighPriority(bool highPriority)
{
m_highPriority = highPriority;
}
bool isHighPriority() const
{
return m_highPriority;
}
virtual std::vector<int> build() const = 0;
QDebug operator<<(QDebug dbg) const;
private:
bool m_versionRequested = false;
int m_majorVersion = 0;
int m_minorVersion = 0;
bool m_robust = false;
bool m_forwardCompatible = false;
bool m_coreProfile = false;
bool m_compatibilityProfile = false;
bool m_resetOnVideoMemoryPurge = false;
bool m_highPriority = false;
};
inline QDebug operator<<(QDebug dbg, const AbstractOpenGLContextAttributeBuilder *attribs)
{
return attribs->operator<<(dbg);
}
}
@@ -0,0 +1,188 @@
const int sRGB_EOTF = 0;
const int linear_EOTF = 1;
const int PQ_EOTF = 2;
const int gamma22_EOTF = 3;
uniform mat4 colorimetryTransform;
uniform int sourceNamedTransferFunction;
/**
* x: min luminance
* y: max luminance - min luminance
*/
uniform vec2 sourceTransferFunctionParams;
uniform int destinationNamedTransferFunction;
/**
* x: min luminance
* y: max luminance - min luminance
*/
uniform vec2 destinationTransferFunctionParams;
// in nits
uniform float sourceReferenceLuminance;
uniform float maxTonemappingLuminance;
uniform float destinationReferenceLuminance;
uniform float maxDestinationLuminance;
uniform mat4 destinationToLMS;
uniform mat4 lmsToDestination;
vec3 linearToPq(vec3 linear) {
const float c1 = 0.8359375;
const float c2 = 18.8515625;
const float c3 = 18.6875;
const float m1 = 0.1593017578125;
const float m2 = 78.84375;
vec3 powed = pow(clamp(linear, vec3(0), vec3(1)), vec3(m1));
vec3 num = vec3(c1) + c2 * powed;
vec3 denum = vec3(1.0) + c3 * powed;
return pow(num / denum, vec3(m2));
}
vec3 pqToLinear(vec3 pq) {
const float c1 = 0.8359375;
const float c2 = 18.8515625;
const float c3 = 18.6875;
const float m1_inv = 1.0 / 0.1593017578125;
const float m2_inv = 1.0 / 78.84375;
vec3 powed = pow(clamp(pq, vec3(0.0), vec3(1.0)), vec3(m2_inv));
vec3 num = max(powed - c1, vec3(0.0));
vec3 den = c2 - c3 * powed;
return pow(num / den, vec3(m1_inv));
}
float singleLinearToPq(float linear) {
const float c1 = 0.8359375;
const float c2 = 18.8515625;
const float c3 = 18.6875;
const float m1 = 0.1593017578125;
const float m2 = 78.84375;
float powed = pow(clamp(linear, 0.0, 1.0), m1);
float num = c1 + c2 * powed;
float denum = 1.0 + c3 * powed;
return pow(num / denum, m2);
}
float singlePqToLinear(float pq) {
const float c1 = 0.8359375;
const float c2 = 18.8515625;
const float c3 = 18.6875;
const float m1_inv = 1.0 / 0.1593017578125;
const float m2_inv = 1.0 / 78.84375;
float powed = pow(clamp(pq, 0.0, 1.0), m2_inv);
float num = max(powed - c1, 0.0);
float den = c2 - c3 * powed;
return pow(num / den, m1_inv);
}
vec3 srgbToLinear(vec3 color) {
bvec3 isLow = lessThanEqual(color, vec3(0.04045));
vec3 loPart = color / 12.92;
vec3 hiPart = pow((color + 0.055) / 1.055, vec3(12.0 / 5.0));
#if __VERSION__ >= 130
return mix(hiPart, loPart, isLow);
#else
return mix(hiPart, loPart, vec3(isLow.r ? 1.0 : 0.0, isLow.g ? 1.0 : 0.0, isLow.b ? 1.0 : 0.0));
#endif
}
vec3 linearToSrgb(vec3 color) {
bvec3 isLow = lessThanEqual(color, vec3(0.0031308));
vec3 loPart = color * 12.92;
vec3 hiPart = pow(color, vec3(5.0 / 12.0)) * 1.055 - 0.055;
#if __VERSION__ >= 130
return mix(hiPart, loPart, isLow);
#else
return mix(hiPart, loPart, vec3(isLow.r ? 1.0 : 0.0, isLow.g ? 1.0 : 0.0, isLow.b ? 1.0 : 0.0));
#endif
}
const mat3 toICtCp = mat3(
0.5, 1.613769531250, 4.378173828125,
0.5, -3.323486328125, -4.245605468750,
0.0, 1.709716796875, -0.132568359375
);
const mat3 fromICtCp = mat3(
1.0, 1.0, 1.0,
0.00860903703793, -0.008609037037, 0.56031335710680,
0.11102962500303, -0.111029625003, -0.32062717498732
);
vec3 doTonemapping(vec3 color) {
if (maxTonemappingLuminance < maxDestinationLuminance * 1.01) {
// clipping is enough
return clamp(color.rgb, vec3(0.0), vec3(maxDestinationLuminance));
}
// first, convert to ICtCp, to properly split luminance and color
// intensity is PQ-encoded luminance
vec3 lms = (destinationToLMS * vec4(color, 1.0)).rgb;
vec3 lms_PQ = linearToPq(lms / 10000.0);
vec3 ICtCp = toICtCp * lms_PQ;
float luminance = singlePqToLinear(ICtCp.r) * 10000.0;
float inputRange = maxTonemappingLuminance / destinationReferenceLuminance;
float outputRange = maxDestinationLuminance / destinationReferenceLuminance;
// how much dynamic range we need to decently present the content
float minDecentRange = min(inputRange, 1.5);
// if the output doesn't provide enough HDR headroom for the tone mapper to do a good job, dim the image to create some
float referenceDimming = 1.0 / clamp(minDecentRange / outputRange, 1.0, minDecentRange);
float outputReferenceLuminance = destinationReferenceLuminance * referenceDimming;
// keep it linear up to the reference luminance
float low = min(luminance * referenceDimming, outputReferenceLuminance);
// and apply a nonlinear curve above, to reduce the luminance without completely removing differences
float relativeHighlight = clamp((luminance / destinationReferenceLuminance - 1.0) / (inputRange - 1.0), 0.0, 1.0);
const float e = 2.718281828459045;
float high = log(relativeHighlight * (e - 1.0) + 1.0) * (maxDestinationLuminance - outputReferenceLuminance);
luminance = low + high;
// last, convert back to rgb
ICtCp.r = singleLinearToPq(luminance / 10000.0);
return (lmsToDestination * vec4(pqToLinear(fromICtCp * ICtCp), 1.0)).rgb * 10000.0;
}
vec4 encodingToNits(vec4 color, int sourceTransferFunction, float luminanceOffset, float luminanceScale) {
if (sourceTransferFunction == sRGB_EOTF) {
color.rgb /= max(color.a, 0.001);
color.rgb = srgbToLinear(color.rgb) * luminanceScale + vec3(luminanceOffset);
color.rgb *= color.a;
} else if (sourceTransferFunction == linear_EOTF) {
color.rgb = color.rgb * luminanceScale + vec3(luminanceOffset);
} else if (sourceTransferFunction == PQ_EOTF) {
color.rgb /= max(color.a, 0.001);
color.rgb = pqToLinear(color.rgb) * luminanceScale + vec3(luminanceOffset);
color.rgb *= color.a;
} else if (sourceTransferFunction == gamma22_EOTF) {
color.rgb /= max(color.a, 0.001);
color.rgb = pow(max(color.rgb, vec3(0.0)), vec3(2.2)) * luminanceScale + vec3(luminanceOffset);
color.rgb *= color.a;
}
return color;
}
vec4 sourceEncodingToNitsInDestinationColorspace(vec4 color) {
color = encodingToNits(color, sourceNamedTransferFunction, sourceTransferFunctionParams.x, sourceTransferFunctionParams.y);
color.rgb = (colorimetryTransform * vec4(color.rgb, 1.0)).rgb;
return vec4(doTonemapping(color.rgb), color.a);
}
vec4 nitsToEncoding(vec4 color, int destinationTransferFunction, float luminanceOffset, float luminanceScale) {
if (destinationTransferFunction == sRGB_EOTF) {
color.rgb /= max(color.a, 0.001);
color.rgb = linearToSrgb((color.rgb - vec3(luminanceOffset)) / luminanceScale);
color.rgb *= color.a;
} else if (destinationTransferFunction == linear_EOTF) {
color.rgb = (color.rgb - vec3(luminanceOffset)) / luminanceScale;
} else if (destinationTransferFunction == PQ_EOTF) {
color.rgb /= max(color.a, 0.001);
color.rgb = linearToPq((color.rgb - vec3(luminanceOffset)) / luminanceScale);
color.rgb *= color.a;
} else if (destinationTransferFunction == gamma22_EOTF) {
color.rgb /= max(color.a, 0.001);
color.rgb = pow(max((color.rgb - vec3(luminanceOffset)) / luminanceScale, vec3(0.0)), vec3(1.0 / 2.2));
color.rgb *= color.a;
}
return color;
}
vec4 nitsToDestinationEncoding(vec4 color) {
return nitsToEncoding(color, destinationNamedTransferFunction, destinationTransferFunctionParams.x, destinationTransferFunctionParams.y);
}
@@ -0,0 +1,79 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2017 Martin Flöser <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "egl_context_attribute_builder.h"
#include <epoxy/egl.h>
namespace KWin
{
std::vector<int> EglContextAttributeBuilder::build() const
{
std::vector<int> attribs;
if (isVersionRequested()) {
attribs.emplace_back(EGL_CONTEXT_MAJOR_VERSION_KHR);
attribs.emplace_back(majorVersion());
attribs.emplace_back(EGL_CONTEXT_MINOR_VERSION_KHR);
attribs.emplace_back(minorVersion());
}
int contextFlags = 0;
if (isRobust()) {
attribs.emplace_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR);
attribs.emplace_back(EGL_LOSE_CONTEXT_ON_RESET_KHR);
contextFlags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR;
if (isResetOnVideoMemoryPurge()) {
attribs.emplace_back(EGL_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV);
attribs.emplace_back(GL_TRUE);
}
}
if (isForwardCompatible()) {
contextFlags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR;
}
if (contextFlags != 0) {
attribs.emplace_back(EGL_CONTEXT_FLAGS_KHR);
attribs.emplace_back(contextFlags);
}
if (isCoreProfile() || isCompatibilityProfile()) {
attribs.emplace_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR);
if (isCoreProfile()) {
attribs.emplace_back(EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR);
} else if (isCompatibilityProfile()) {
attribs.emplace_back(EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR);
}
}
if (isHighPriority()) {
attribs.emplace_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
attribs.emplace_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
}
attribs.emplace_back(EGL_NONE);
return attribs;
}
std::vector<int> EglOpenGLESContextAttributeBuilder::build() const
{
std::vector<int> attribs;
attribs.emplace_back(EGL_CONTEXT_CLIENT_VERSION);
attribs.emplace_back(majorVersion());
if (isRobust()) {
attribs.emplace_back(EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT);
attribs.emplace_back(EGL_TRUE);
attribs.emplace_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT);
attribs.emplace_back(EGL_LOSE_CONTEXT_ON_RESET_EXT);
if (isResetOnVideoMemoryPurge()) {
attribs.emplace_back(EGL_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV);
attribs.emplace_back(GL_TRUE);
}
}
if (isHighPriority()) {
attribs.emplace_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
attribs.emplace_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
}
attribs.emplace_back(EGL_NONE);
return attribs;
}
}
@@ -0,0 +1,28 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2017 Martin Flöser <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "abstract_opengl_context_attribute_builder.h"
#include <kwin_export.h>
namespace KWin
{
class KWIN_EXPORT EglContextAttributeBuilder : public AbstractOpenGLContextAttributeBuilder
{
public:
std::vector<int> build() const override;
};
class KWIN_EXPORT EglOpenGLESContextAttributeBuilder : public AbstractOpenGLContextAttributeBuilder
{
public:
std::vector<int> build() const override;
};
}
@@ -0,0 +1,247 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "eglcontext.h"
#include "core/graphicsbuffer.h"
#include "egldisplay.h"
#include "eglimagetexture.h"
#include "glvertexbuffer_p.h"
#include "opengl/egl_context_attribute_builder.h"
#include "opengl/eglutils_p.h"
#include "opengl/glutils.h"
#include "utils/common.h"
#include "utils/drm_format_helper.h"
#include <QOpenGLContext>
#include <drm_fourcc.h>
namespace KWin
{
std::unique_ptr<EglContext> EglContext::create(EglDisplay *display, EGLConfig config, ::EGLContext sharedContext)
{
auto handle = createContext(display, config, sharedContext);
if (!handle) {
return nullptr;
}
if (!eglMakeCurrent(display->handle(), EGL_NO_SURFACE, EGL_NO_SURFACE, handle)) {
eglDestroyContext(display->handle(), handle);
return nullptr;
}
auto ret = std::make_unique<EglContext>(display, config, handle);
s_currentContext = ret.get();
if (!ret->checkSupported()) {
return nullptr;
}
return ret;
}
typedef void (*eglFuncPtr)();
static eglFuncPtr getProcAddress(const char *name)
{
return eglGetProcAddress(name);
}
EglContext::EglContext(EglDisplay *display, EGLConfig config, ::EGLContext context)
: OpenGlContext(true)
, m_display(display)
, m_handle(context)
, m_config(config)
, m_shaderManager(std::make_unique<ShaderManager>())
, m_streamingBuffer(std::make_unique<GLVertexBuffer>(GLVertexBuffer::Stream))
, m_indexBuffer(std::make_unique<IndexBuffer>())
{
glResolveFunctions(&getProcAddress);
initDebugOutput();
setShaderManager(m_shaderManager.get());
setStreamingBuffer(m_streamingBuffer.get());
setIndexBuffer(m_indexBuffer.get());
// It is not legal to not have a vertex array object bound in a core context
// to make code handling old and new OpenGL versions easier, bind a dummy vao that's used for everything
if (!isOpenGLES() && hasOpenglExtension(QByteArrayLiteral("GL_ARB_vertex_array_object"))) {
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
}
}
EglContext::~EglContext()
{
makeCurrent();
if (m_vao) {
glDeleteVertexArrays(1, &m_vao);
}
m_shaderManager.reset();
m_streamingBuffer.reset();
m_indexBuffer.reset();
doneCurrent();
eglDestroyContext(m_display->handle(), m_handle);
}
bool EglContext::makeCurrent()
{
return makeCurrent(EGL_NO_SURFACE);
}
bool EglContext::makeCurrent(EGLSurface surface)
{
if (QOpenGLContext *context = QOpenGLContext::currentContext()) {
// Workaround to tell Qt that no QOpenGLContext is current
context->doneCurrent();
}
const bool ret = eglMakeCurrent(m_display->handle(), surface, surface, m_handle) == EGL_TRUE;
if (ret) {
s_currentContext = this;
}
return ret;
}
void EglContext::doneCurrent() const
{
eglMakeCurrent(m_display->handle(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
s_currentContext = nullptr;
}
EglDisplay *EglContext::displayObject() const
{
return m_display;
}
::EGLContext EglContext::handle() const
{
return m_handle;
}
EGLConfig EglContext::config() const
{
return m_config;
}
bool EglContext::isValid() const
{
return m_display != nullptr && m_handle != EGL_NO_CONTEXT;
}
static inline bool shouldUseOpenGLES()
{
if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) {
return true;
}
return QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES;
}
::EGLContext EglContext::createContext(EglDisplay *display, EGLConfig config, ::EGLContext sharedContext)
{
const bool haveRobustness = display->hasExtension(QByteArrayLiteral("EGL_EXT_create_context_robustness"));
const bool haveCreateContext = display->hasExtension(QByteArrayLiteral("EGL_KHR_create_context"));
const bool haveContextPriority = display->hasExtension(QByteArrayLiteral("EGL_IMG_context_priority"));
const bool haveResetOnVideoMemoryPurge = display->hasExtension(QByteArrayLiteral("EGL_NV_robustness_video_memory_purge"));
std::vector<std::unique_ptr<AbstractOpenGLContextAttributeBuilder>> candidates;
if (shouldUseOpenGLES()) {
if (haveCreateContext && haveRobustness && haveContextPriority && haveResetOnVideoMemoryPurge) {
auto glesRobustPriority = std::make_unique<EglOpenGLESContextAttributeBuilder>();
glesRobustPriority->setResetOnVideoMemoryPurge(true);
glesRobustPriority->setVersion(2);
glesRobustPriority->setRobust(true);
glesRobustPriority->setHighPriority(true);
candidates.push_back(std::move(glesRobustPriority));
}
if (haveCreateContext && haveRobustness && haveContextPriority) {
auto glesRobustPriority = std::make_unique<EglOpenGLESContextAttributeBuilder>();
glesRobustPriority->setVersion(2);
glesRobustPriority->setRobust(true);
glesRobustPriority->setHighPriority(true);
candidates.push_back(std::move(glesRobustPriority));
}
if (haveCreateContext && haveRobustness) {
auto glesRobust = std::make_unique<EglOpenGLESContextAttributeBuilder>();
glesRobust->setVersion(2);
glesRobust->setRobust(true);
candidates.push_back(std::move(glesRobust));
}
if (haveContextPriority) {
auto glesPriority = std::make_unique<EglOpenGLESContextAttributeBuilder>();
glesPriority->setVersion(2);
glesPriority->setHighPriority(true);
candidates.push_back(std::move(glesPriority));
}
auto gles = std::make_unique<EglOpenGLESContextAttributeBuilder>();
gles->setVersion(2);
candidates.push_back(std::move(gles));
} else {
if (haveCreateContext) {
if (haveRobustness && haveContextPriority && haveResetOnVideoMemoryPurge) {
auto robustCorePriority = std::make_unique<EglContextAttributeBuilder>();
robustCorePriority->setResetOnVideoMemoryPurge(true);
robustCorePriority->setVersion(3, 1);
robustCorePriority->setRobust(true);
robustCorePriority->setHighPriority(true);
candidates.push_back(std::move(robustCorePriority));
}
if (haveRobustness && haveContextPriority) {
auto robustCorePriority = std::make_unique<EglContextAttributeBuilder>();
robustCorePriority->setVersion(3, 1);
robustCorePriority->setRobust(true);
robustCorePriority->setHighPriority(true);
candidates.push_back(std::move(robustCorePriority));
}
if (haveRobustness) {
auto robustCore = std::make_unique<EglContextAttributeBuilder>();
robustCore->setVersion(3, 1);
robustCore->setRobust(true);
candidates.push_back(std::move(robustCore));
}
if (haveContextPriority) {
auto corePriority = std::make_unique<EglContextAttributeBuilder>();
corePriority->setVersion(3, 1);
corePriority->setHighPriority(true);
candidates.push_back(std::move(corePriority));
}
auto core = std::make_unique<EglContextAttributeBuilder>();
core->setVersion(3, 1);
candidates.push_back(std::move(core));
}
if (haveRobustness && haveCreateContext && haveContextPriority) {
auto robustPriority = std::make_unique<EglContextAttributeBuilder>();
robustPriority->setRobust(true);
robustPriority->setHighPriority(true);
candidates.push_back(std::move(robustPriority));
}
if (haveRobustness && haveCreateContext) {
auto robust = std::make_unique<EglContextAttributeBuilder>();
robust->setRobust(true);
candidates.push_back(std::move(robust));
}
candidates.emplace_back(new EglContextAttributeBuilder);
}
for (const auto &candidate : candidates) {
const auto attribs = candidate->build();
::EGLContext ctx = eglCreateContext(display->handle(), config, sharedContext, attribs.data());
if (ctx != EGL_NO_CONTEXT) {
qCDebug(KWIN_OPENGL) << "Created EGL context with attributes:" << candidate.get();
return ctx;
}
}
qCCritical(KWIN_OPENGL) << "Create Context failed" << getEglErrorString();
return EGL_NO_CONTEXT;
}
std::shared_ptr<GLTexture> EglContext::importDmaBufAsTexture(const DmaBufAttributes &attributes) const
{
EGLImageKHR image = m_display->importDmaBufAsImage(attributes);
if (image != EGL_NO_IMAGE_KHR) {
const auto info = FormatInfo::get(attributes.format);
return EGLImageTexture::create(m_display, image, info ? info->openglFormat : GL_RGBA8, QSize(attributes.width, attributes.height), m_display->isExternalOnly(attributes.format, attributes.modifier));
} else {
qCWarning(KWIN_OPENGL) << "Error creating EGLImageKHR: " << getEglErrorString();
return nullptr;
}
}
}
@@ -0,0 +1,55 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "opengl/gltexture.h"
#include "opengl/openglcontext.h"
#include <QByteArray>
#include <QList>
#include <epoxy/egl.h>
namespace KWin
{
class EglDisplay;
class ShaderManager;
struct DmaBufAttributes;
class KWIN_EXPORT EglContext : public OpenGlContext
{
public:
EglContext(EglDisplay *display, EGLConfig config, ::EGLContext context);
~EglContext() override;
bool makeCurrent() override;
bool makeCurrent(EGLSurface surface);
void doneCurrent() const override;
std::shared_ptr<GLTexture> importDmaBufAsTexture(const DmaBufAttributes &attributes) const;
EglDisplay *displayObject() const;
::EGLContext handle() const;
EGLConfig config() const;
bool isValid() const;
static std::unique_ptr<EglContext> create(EglDisplay *display, EGLConfig config, ::EGLContext sharedContext);
private:
static ::EGLContext createContext(EglDisplay *display, EGLConfig config, ::EGLContext sharedContext);
EglDisplay *const m_display;
const ::EGLContext m_handle;
const EGLConfig m_config;
std::unique_ptr<ShaderManager> m_shaderManager;
std::unique_ptr<GLVertexBuffer> m_streamingBuffer;
std::unique_ptr<IndexBuffer> m_indexBuffer;
uint32_t m_vao = 0;
};
}
@@ -0,0 +1,367 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "egldisplay.h"
#include "core/drmdevice.h"
#include "core/graphicsbuffer.h"
#include "opengl/eglutils_p.h"
#include "opengl/glutils.h"
#include "utils/common.h"
#include <QOpenGLContext>
#include <drm_fourcc.h>
#include <utils/drm_format_helper.h>
#ifndef EGL_DRM_RENDER_NODE_FILE_EXT
#define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377
#endif
namespace KWin
{
bool EglDisplay::shouldUseOpenGLES()
{
if (qstrcmp(qgetenv("KWIN_COMPOSE"), "O2ES") == 0) {
return true;
}
return QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES;
}
std::unique_ptr<EglDisplay> EglDisplay::create(::EGLDisplay display, bool owning)
{
if (!display) {
return nullptr;
}
EGLint major, minor;
if (eglInitialize(display, &major, &minor) == EGL_FALSE) {
qCWarning(KWIN_OPENGL) << "eglInitialize failed";
EGLint error = eglGetError();
if (error != EGL_SUCCESS) {
qCWarning(KWIN_OPENGL) << "Error during eglInitialize " << error;
}
return nullptr;
}
EGLint error = eglGetError();
if (error != EGL_SUCCESS) {
qCWarning(KWIN_OPENGL) << "Error during eglInitialize " << error;
return nullptr;
}
qCDebug(KWIN_OPENGL) << "Egl Initialize succeeded";
if (eglBindAPI(shouldUseOpenGLES() ? EGL_OPENGL_ES_API : EGL_OPENGL_API) == EGL_FALSE) {
qCCritical(KWIN_OPENGL) << "bind OpenGL API failed";
return nullptr;
}
qCDebug(KWIN_OPENGL) << "EGL version: " << major << "." << minor;
const auto extensions = QByteArray(eglQueryString(display, EGL_EXTENSIONS)).split(' ');
const QByteArray requiredExtensions[] = {
QByteArrayLiteral("EGL_KHR_no_config_context"),
QByteArrayLiteral("EGL_KHR_surfaceless_context"),
};
for (const QByteArray &extensionName : requiredExtensions) {
if (!extensions.contains(extensionName)) {
qCWarning(KWIN_OPENGL) << extensionName << "extension is unsupported";
return nullptr;
}
}
return std::make_unique<EglDisplay>(display, extensions, owning);
}
static std::optional<dev_t> devIdForFileName(const QString &path)
{
auto device = DrmDevice::open(path);
if (device) {
return device->deviceId();
} else {
qCWarning(KWIN_OPENGL, "couldn't find dev node for drm device %s", qPrintable(path));
return std::nullopt;
}
}
EglDisplay::EglDisplay(::EGLDisplay display, const QList<QByteArray> &extensions, bool owning)
: m_handle(display)
, m_extensions(extensions)
, m_owning(owning)
, m_renderNode(determineRenderNode())
, m_renderDevNode(devIdForFileName(m_renderNode))
, m_supportsBufferAge(extensions.contains(QByteArrayLiteral("EGL_EXT_buffer_age")) && qgetenv("KWIN_USE_BUFFER_AGE") != "0")
, m_supportsNativeFence(extensions.contains(QByteArrayLiteral("EGL_ANDROID_native_fence_sync"))
&& extensions.contains(QByteArrayLiteral("EGL_KHR_wait_sync")))
{
m_functions.createImageKHR = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
m_functions.destroyImageKHR = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
m_functions.queryDmaBufFormatsEXT = reinterpret_cast<PFNEGLQUERYDMABUFFORMATSEXTPROC>(eglGetProcAddress("eglQueryDmaBufFormatsEXT"));
m_functions.queryDmaBufModifiersEXT = reinterpret_cast<PFNEGLQUERYDMABUFMODIFIERSEXTPROC>(eglGetProcAddress("eglQueryDmaBufModifiersEXT"));
m_importFormats = queryImportFormats();
}
EglDisplay::~EglDisplay()
{
if (m_owning) {
eglTerminate(m_handle);
}
}
QList<QByteArray> EglDisplay::extensions() const
{
return m_extensions;
}
::EGLDisplay EglDisplay::handle() const
{
return m_handle;
}
bool EglDisplay::hasExtension(const QByteArray &name) const
{
return m_extensions.contains(name);
}
static bool checkExtension(const QByteArrayView extensions, const QByteArrayView extension)
{
for (int i = 0; i < extensions.size();) {
if (extensions[i] == ' ') {
i++;
continue;
}
int next = extensions.indexOf(' ', i);
if (next == -1) {
next = extensions.size();
}
const int size = next - i;
if (extension.size() == size && extensions.sliced(i, size) == extension) {
return true;
}
i = next;
}
return false;
}
QString EglDisplay::renderNode() const
{
return m_renderNode;
}
bool EglDisplay::supportsBufferAge() const
{
return m_supportsBufferAge;
}
bool EglDisplay::supportsNativeFence() const
{
return m_supportsNativeFence;
}
EGLImageKHR EglDisplay::importDmaBufAsImage(const DmaBufAttributes &dmabuf) const
{
QList<EGLint> attribs;
attribs.reserve(6 + dmabuf.planeCount * 10 + 1);
attribs << EGL_WIDTH << dmabuf.width
<< EGL_HEIGHT << dmabuf.height
<< EGL_LINUX_DRM_FOURCC_EXT << dmabuf.format;
attribs << EGL_DMA_BUF_PLANE0_FD_EXT << dmabuf.fd[0].get()
<< EGL_DMA_BUF_PLANE0_OFFSET_EXT << dmabuf.offset[0]
<< EGL_DMA_BUF_PLANE0_PITCH_EXT << dmabuf.pitch[0];
if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
attribs << EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
<< EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
}
if (dmabuf.planeCount > 1) {
attribs << EGL_DMA_BUF_PLANE1_FD_EXT << dmabuf.fd[1].get()
<< EGL_DMA_BUF_PLANE1_OFFSET_EXT << dmabuf.offset[1]
<< EGL_DMA_BUF_PLANE1_PITCH_EXT << dmabuf.pitch[1];
if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
attribs << EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
<< EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
}
}
if (dmabuf.planeCount > 2) {
attribs << EGL_DMA_BUF_PLANE2_FD_EXT << dmabuf.fd[2].get()
<< EGL_DMA_BUF_PLANE2_OFFSET_EXT << dmabuf.offset[2]
<< EGL_DMA_BUF_PLANE2_PITCH_EXT << dmabuf.pitch[2];
if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
attribs << EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
<< EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
}
}
if (dmabuf.planeCount > 3) {
attribs << EGL_DMA_BUF_PLANE3_FD_EXT << dmabuf.fd[3].get()
<< EGL_DMA_BUF_PLANE3_OFFSET_EXT << dmabuf.offset[3]
<< EGL_DMA_BUF_PLANE3_PITCH_EXT << dmabuf.pitch[3];
if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
attribs << EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
<< EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
}
}
attribs << EGL_NONE;
return createImage(EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data());
}
EGLImageKHR EglDisplay::importDmaBufAsImage(const DmaBufAttributes &dmabuf, int plane, int format, const QSize &size) const
{
QList<EGLint> attribs;
attribs.reserve(6 + 1 * 10 + 1);
attribs << EGL_WIDTH << size.width()
<< EGL_HEIGHT << size.height()
<< EGL_LINUX_DRM_FOURCC_EXT << format;
attribs << EGL_DMA_BUF_PLANE0_FD_EXT << dmabuf.fd[plane].get()
<< EGL_DMA_BUF_PLANE0_OFFSET_EXT << dmabuf.offset[plane]
<< EGL_DMA_BUF_PLANE0_PITCH_EXT << dmabuf.pitch[plane];
if (dmabuf.modifier != DRM_FORMAT_MOD_INVALID) {
attribs << EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT << EGLint(dmabuf.modifier & 0xffffffff)
<< EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT << EGLint(dmabuf.modifier >> 32);
}
attribs << EGL_NONE;
return createImage(EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs.data());
}
QHash<uint32_t, EglDisplay::DrmFormatInfo> EglDisplay::allSupportedDrmFormats() const
{
return m_importFormats;
}
QHash<uint32_t, QList<uint64_t>> EglDisplay::nonExternalOnlySupportedDrmFormats() const
{
QHash<uint32_t, QList<uint64_t>> ret;
ret.reserve(m_importFormats.size());
for (auto it = m_importFormats.constBegin(), itEnd = m_importFormats.constEnd(); it != itEnd; ++it) {
ret[it.key()] = it->nonExternalOnlyModifiers;
}
return ret;
}
bool EglDisplay::isExternalOnly(uint32_t format, uint64_t modifier) const
{
if (const auto it = m_importFormats.find(format); it != m_importFormats.end()) {
return it->externalOnlyModifiers.contains(modifier);
} else {
return false;
}
}
QHash<uint32_t, EglDisplay::DrmFormatInfo> EglDisplay::queryImportFormats() const
{
if (!hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import")) || !hasExtension(QByteArrayLiteral("EGL_EXT_image_dma_buf_import_modifiers"))) {
return {};
}
if (m_functions.queryDmaBufFormatsEXT == nullptr) {
return {};
}
EGLint count = 0;
EGLBoolean success = m_functions.queryDmaBufFormatsEXT(m_handle, 0, nullptr, &count);
if (!success || count == 0) {
qCCritical(KWIN_OPENGL) << "eglQueryDmaBufFormatsEXT failed!" << getEglErrorString();
return {};
}
QList<uint32_t> formats(count);
if (!m_functions.queryDmaBufFormatsEXT(m_handle, count, (EGLint *)formats.data(), &count)) {
qCCritical(KWIN_OPENGL) << "eglQueryDmaBufFormatsEXT with count" << count << "failed!" << getEglErrorString();
return {};
}
QHash<uint32_t, DrmFormatInfo> ret;
for (const auto format : std::as_const(formats)) {
if (m_functions.queryDmaBufModifiersEXT != nullptr) {
EGLint count = 0;
const EGLBoolean success = m_functions.queryDmaBufModifiersEXT(m_handle, format, 0, nullptr, nullptr, &count);
if (success && count > 0) {
DrmFormatInfo drmFormatInfo;
drmFormatInfo.allModifiers.resize(count);
QList<EGLBoolean> externalOnly(count);
if (m_functions.queryDmaBufModifiersEXT(m_handle, format, count, drmFormatInfo.allModifiers.data(), externalOnly.data(), &count)) {
drmFormatInfo.externalOnlyModifiers = drmFormatInfo.allModifiers;
drmFormatInfo.nonExternalOnlyModifiers = drmFormatInfo.allModifiers;
for (int i = drmFormatInfo.allModifiers.size() - 1; i >= 0; i--) {
if (externalOnly[i]) {
drmFormatInfo.nonExternalOnlyModifiers.removeAll(drmFormatInfo.allModifiers[i]);
} else {
drmFormatInfo.externalOnlyModifiers.removeAll(drmFormatInfo.allModifiers[i]);
}
}
if (!drmFormatInfo.allModifiers.empty()) {
if (!drmFormatInfo.allModifiers.contains(DRM_FORMAT_MOD_INVALID)) {
drmFormatInfo.allModifiers.push_back(DRM_FORMAT_MOD_INVALID);
if (!drmFormatInfo.nonExternalOnlyModifiers.empty()) {
drmFormatInfo.nonExternalOnlyModifiers.push_back(DRM_FORMAT_MOD_INVALID);
} else {
drmFormatInfo.externalOnlyModifiers.push_back(DRM_FORMAT_MOD_INVALID);
}
}
ret.insert(format, drmFormatInfo);
}
continue;
}
}
}
DrmFormatInfo drmFormat;
drmFormat.allModifiers = {DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_LINEAR};
drmFormat.nonExternalOnlyModifiers = {DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_LINEAR};
ret.insert(format, drmFormat);
}
return ret;
}
QString EglDisplay::determineRenderNode() const
{
const char *clientExtensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (checkExtension(clientExtensions, "EGL_EXT_device_query")) {
EGLAttrib eglDeviceAttrib;
if (eglQueryDisplayAttribEXT(m_handle, EGL_DEVICE_EXT, &eglDeviceAttrib)) {
EGLDeviceEXT eglDevice = reinterpret_cast<EGLDeviceEXT>(eglDeviceAttrib);
const char *deviceExtensions = eglQueryDeviceStringEXT(eglDevice, EGL_EXTENSIONS);
if (checkExtension(deviceExtensions, "EGL_EXT_device_drm_render_node")) {
if (const char *node = eglQueryDeviceStringEXT(eglDevice, EGL_DRM_RENDER_NODE_FILE_EXT)) {
return QString::fromLocal8Bit(node);
}
}
if (checkExtension(deviceExtensions, "EGL_EXT_device_drm")) {
// Fallback to display device.
if (const char *node = eglQueryDeviceStringEXT(eglDevice, EGL_DRM_DEVICE_FILE_EXT)) {
return QString::fromLocal8Bit(node);
}
}
}
}
return QString();
}
std::optional<dev_t> EglDisplay::renderDevNode() const
{
return m_renderDevNode;
}
EGLImageKHR EglDisplay::createImage(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list) const
{
Q_ASSERT(m_functions.createImageKHR);
return m_functions.createImageKHR(m_handle, ctx, target, buffer, attrib_list);
}
void EglDisplay::destroyImage(EGLImageKHR image) const
{
Q_ASSERT(m_functions.destroyImageKHR);
m_functions.destroyImageKHR(m_handle, image);
}
}
@@ -0,0 +1,86 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QByteArray>
#include <QHash>
#include <QList>
#include <QSize>
#include <epoxy/egl.h>
#include <sys/types.h>
namespace KWin
{
struct DmaBufAttributes;
class GLTexture;
class KWIN_EXPORT EglDisplay
{
public:
struct DrmFormatInfo
{
QList<uint64_t> allModifiers;
QList<uint64_t> nonExternalOnlyModifiers;
QList<uint64_t> externalOnlyModifiers;
};
EglDisplay(::EGLDisplay display, const QList<QByteArray> &extensions, bool owning = true);
~EglDisplay();
QList<QByteArray> extensions() const;
::EGLDisplay handle() const;
bool hasExtension(const QByteArray &name) const;
QString renderNode() const;
std::optional<dev_t> renderDevNode() const;
bool supportsBufferAge() const;
bool supportsNativeFence() const;
QHash<uint32_t, QList<uint64_t>> nonExternalOnlySupportedDrmFormats() const;
QHash<uint32_t, DrmFormatInfo> allSupportedDrmFormats() const;
bool isExternalOnly(uint32_t format, uint64_t modifier) const;
EGLImageKHR createImage(EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list) const;
void destroyImage(EGLImageKHR image) const;
EGLImageKHR importDmaBufAsImage(const DmaBufAttributes &dmabuf) const;
EGLImageKHR importDmaBufAsImage(const DmaBufAttributes &dmabuf, int plane, int format, const QSize &size) const;
static bool shouldUseOpenGLES();
static std::unique_ptr<EglDisplay> create(::EGLDisplay display, bool owning = true);
private:
QHash<uint32_t, DrmFormatInfo> queryImportFormats() const;
QString determineRenderNode() const;
const ::EGLDisplay m_handle;
const QList<QByteArray> m_extensions;
const bool m_owning;
const QString m_renderNode;
const std::optional<dev_t> m_renderDevNode;
const bool m_supportsBufferAge;
const bool m_supportsNativeFence;
QHash<uint32_t, DrmFormatInfo> m_importFormats;
struct
{
PFNEGLCREATEIMAGEKHRPROC createImageKHR = nullptr;
PFNEGLDESTROYIMAGEKHRPROC destroyImageKHR = nullptr;
PFNEGLQUERYDMABUFFORMATSEXTPROC queryDmaBufFormatsEXT = nullptr;
PFNEGLQUERYDMABUFMODIFIERSEXTPROC queryDmaBufModifiersEXT = nullptr;
} m_functions;
};
}
@@ -0,0 +1,49 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "eglimagetexture.h"
#include "egldisplay.h"
#include "opengl/gltexture_p.h"
#include <QDebug>
#include <epoxy/egl.h>
namespace KWin
{
EGLImageTexture::EGLImageTexture(EglDisplay *display, EGLImage image, uint textureId, int internalFormat, const QSize &size, uint32_t target)
: GLTexture(target, textureId, internalFormat, size, 1, true, OutputTransform::FlipY)
, m_image(image)
, m_display(display)
{
}
EGLImageTexture::~EGLImageTexture()
{
m_display->destroyImage(m_image);
}
std::shared_ptr<EGLImageTexture> EGLImageTexture::create(EglDisplay *display, EGLImageKHR image, int internalFormat, const QSize &size, bool externalOnly)
{
if (image == EGL_NO_IMAGE) {
return nullptr;
}
GLuint texture = 0;
glGenTextures(1, &texture);
if (!texture) {
return nullptr;
}
const uint32_t target = externalOnly ? GL_TEXTURE_EXTERNAL_OES : GL_TEXTURE_2D;
glBindTexture(target, texture);
glEGLImageTargetTexture2DOES(target, image);
glBindTexture(target, 0);
return std::make_shared<EGLImageTexture>(display, image, texture, internalFormat, size, target);
}
} // namespace KWin
@@ -0,0 +1,34 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "opengl/gltexture.h"
typedef void *EGLImageKHR;
typedef void *EGLClientBuffer;
namespace KWin
{
class EglDisplay;
class KWIN_EXPORT EGLImageTexture : public GLTexture
{
public:
explicit EGLImageTexture(EglDisplay *display, EGLImageKHR image, uint textureId, int internalFormat, const QSize &size, uint32_t target);
~EGLImageTexture() override;
static std::shared_ptr<EGLImageTexture> create(EglDisplay *display, EGLImageKHR image, int internalFormat, const QSize &size, bool externalOnly);
EGLImageKHR m_image;
EglDisplay *const m_display;
};
}
@@ -0,0 +1,77 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "eglnativefence.h"
#include "egldisplay.h"
#include <unistd.h>
namespace KWin
{
#ifndef EGL_ANDROID_native_fence_sync
#define EGL_SYNC_NATIVE_FENCE_ANDROID 0x3144
#define EGL_NO_NATIVE_FENCE_FD_ANDROID -1
#endif // EGL_ANDROID_native_fence_sync
EGLNativeFence::EGLNativeFence(EglDisplay *display)
: EGLNativeFence(display, eglCreateSyncKHR(display->handle(), EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr))
{
if (m_sync != EGL_NO_SYNC_KHR) {
// The native fence will get a valid sync file fd only after a flush.
glFlush();
m_fileDescriptor = FileDescriptor(eglDupNativeFenceFDANDROID(m_display->handle(), m_sync));
}
}
EGLNativeFence::EGLNativeFence(EglDisplay *display, EGLSyncKHR sync)
: m_sync(sync)
, m_display(display)
{
}
EGLNativeFence::~EGLNativeFence()
{
m_fileDescriptor.reset();
if (m_sync != EGL_NO_SYNC_KHR) {
eglDestroySyncKHR(m_display->handle(), m_sync);
}
}
bool EGLNativeFence::isValid() const
{
return m_sync != EGL_NO_SYNC_KHR && m_fileDescriptor.isValid();
}
const FileDescriptor &EGLNativeFence::fileDescriptor() const
{
return m_fileDescriptor;
}
FileDescriptor &&EGLNativeFence::takeFileDescriptor()
{
return std::move(m_fileDescriptor);
}
bool EGLNativeFence::waitSync() const
{
return eglWaitSync(m_display->handle(), m_sync, 0) == EGL_TRUE;
}
EGLNativeFence EGLNativeFence::importFence(EglDisplay *display, FileDescriptor &&fd)
{
EGLint attributes[] = {
EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fd.get(),
EGL_NONE};
auto ret = eglCreateSyncKHR(display->handle(), EGL_SYNC_NATIVE_FENCE_ANDROID, attributes);
if (ret != EGL_NO_SYNC_KHR) {
// eglCreateSyncKHR takes ownership only on success
fd.take();
}
return EGLNativeFence(display, ret);
}
} // namespace KWin
@@ -0,0 +1,41 @@
/*
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <epoxy/egl.h>
#include "kwin_export.h"
#include "utils/filedescriptor.h"
namespace KWin
{
class EglDisplay;
class KWIN_EXPORT EGLNativeFence
{
public:
explicit EGLNativeFence(EglDisplay *display);
explicit EGLNativeFence(EglDisplay *display, EGLSyncKHR sync);
EGLNativeFence(EGLNativeFence &&) = delete;
EGLNativeFence(const EGLNativeFence &) = delete;
~EGLNativeFence();
bool isValid() const;
const FileDescriptor &fileDescriptor() const;
FileDescriptor &&takeFileDescriptor();
bool waitSync() const;
static EGLNativeFence importFence(EglDisplay *display, FileDescriptor &&fd);
private:
EGLSyncKHR m_sync = EGL_NO_SYNC_KHR;
EglDisplay *m_display = nullptr;
FileDescriptor m_fileDescriptor;
};
} // namespace KWin
@@ -0,0 +1,183 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2017 Martin Flöser <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2022 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "opengl/eglswapchain.h"
#include "core/graphicsbuffer.h"
#include "core/graphicsbufferallocator.h"
#include "opengl/eglcontext.h"
#include "opengl/glutils.h"
#include "utils/common.h"
#include <drm_fourcc.h>
#include <errno.h>
namespace KWin
{
EglSwapchainSlot::EglSwapchainSlot(GraphicsBuffer *buffer, std::unique_ptr<GLFramebuffer> &&framebuffer, const std::shared_ptr<GLTexture> &texture)
: m_buffer(buffer)
, m_framebuffer(std::move(framebuffer))
, m_texture(texture)
{
}
EglSwapchainSlot::~EglSwapchainSlot()
{
m_framebuffer.reset();
m_texture.reset();
m_buffer->drop();
}
GraphicsBuffer *EglSwapchainSlot::buffer() const
{
return m_buffer;
}
std::shared_ptr<GLTexture> EglSwapchainSlot::texture() const
{
return m_texture;
}
GLFramebuffer *EglSwapchainSlot::framebuffer() const
{
return m_framebuffer.get();
}
int EglSwapchainSlot::age() const
{
return m_age;
}
bool EglSwapchainSlot::isBusy() const
{
return m_buffer->isReferenced() || (m_releaseFd.isValid() && !m_releaseFd.isReadable());
}
std::shared_ptr<EglSwapchainSlot> EglSwapchainSlot::create(EglContext *context, GraphicsBuffer *buffer)
{
auto texture = context->importDmaBufAsTexture(*buffer->dmabufAttributes());
if (!texture) {
buffer->drop();
return nullptr;
}
auto framebuffer = std::make_unique<GLFramebuffer>(texture.get());
if (!framebuffer->valid()) {
buffer->drop();
return nullptr;
}
texture->setFilter(GL_LINEAR);
texture->setWrapMode(GL_CLAMP_TO_EDGE);
return std::make_shared<EglSwapchainSlot>(buffer, std::move(framebuffer), texture);
}
EglSwapchain::EglSwapchain(GraphicsBufferAllocator *allocator, EglContext *context, const QSize &size, uint32_t format, uint64_t modifier, const std::shared_ptr<EglSwapchainSlot> &seed)
: m_allocator(allocator)
, m_context(context)
, m_size(size)
, m_format(format)
, m_modifier(modifier)
, m_slots({seed})
{
}
EglSwapchain::~EglSwapchain()
{
}
QSize EglSwapchain::size() const
{
return m_size;
}
uint32_t EglSwapchain::format() const
{
return m_format;
}
uint64_t EglSwapchain::modifier() const
{
return m_modifier;
}
std::shared_ptr<EglSwapchainSlot> EglSwapchain::acquire()
{
const auto it = std::ranges::find_if(std::as_const(m_slots), [](const auto &slot) {
return !slot->isBusy();
});
if (it != m_slots.cend()) {
return *it;
}
GraphicsBuffer *buffer = m_allocator->allocate(GraphicsBufferOptions{
.size = m_size,
.format = m_format,
.modifiers = {m_modifier},
});
if (!buffer) {
qCWarning(KWIN_OPENGL) << "Failed to allocate an egl gbm swapchain graphics buffer";
return nullptr;
}
auto slot = EglSwapchainSlot::create(m_context, buffer);
if (!slot) {
return nullptr;
}
m_slots.append(slot);
return slot;
}
void EglSwapchain::release(std::shared_ptr<EglSwapchainSlot> slot, FileDescriptor &&releaseFence)
{
slot->m_releaseFd = std::move(releaseFence);
for (qsizetype i = 0; i < m_slots.count(); ++i) {
if (m_slots[i] == slot) {
m_slots[i]->m_age = 1;
} else if (m_slots[i]->m_age > 0) {
m_slots[i]->m_age++;
}
}
}
void EglSwapchain::resetBufferAge()
{
for (const auto &slot : std::as_const(m_slots)) {
slot->m_age = 0;
}
}
std::shared_ptr<EglSwapchain> EglSwapchain::create(GraphicsBufferAllocator *allocator, EglContext *context, const QSize &size, uint32_t format, const QList<uint64_t> &modifiers)
{
if (!context->makeCurrent()) {
return nullptr;
}
// The seed graphics buffer is used to fixate modifiers.
GraphicsBuffer *seed = allocator->allocate(GraphicsBufferOptions{
.size = size,
.format = format,
.modifiers = modifiers,
});
if (!seed) {
return nullptr;
}
const auto first = EglSwapchainSlot::create(context, seed);
if (!first) {
return nullptr;
}
return std::make_shared<EglSwapchain>(std::move(allocator),
context,
size,
format,
seed->dmabufAttributes()->modifier,
first);
}
} // namespace KWin
@@ -0,0 +1,80 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2017 Martin Flöser <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include "utils/filedescriptor.h"
#include <QList>
#include <QSize>
#include <cstdint>
#include <epoxy/egl.h>
#include <memory>
namespace KWin
{
class GraphicsBufferAllocator;
class GraphicsBuffer;
class GLFramebuffer;
class GLTexture;
class EglContext;
class KWIN_EXPORT EglSwapchainSlot
{
public:
EglSwapchainSlot(GraphicsBuffer *buffer, std::unique_ptr<GLFramebuffer> &&framebuffer, const std::shared_ptr<GLTexture> &texture);
~EglSwapchainSlot();
GraphicsBuffer *buffer() const;
std::shared_ptr<GLTexture> texture() const;
GLFramebuffer *framebuffer() const;
int age() const;
static std::shared_ptr<EglSwapchainSlot> create(EglContext *context, GraphicsBuffer *buffer);
private:
bool isBusy() const;
GraphicsBuffer *m_buffer;
std::unique_ptr<GLFramebuffer> m_framebuffer;
std::shared_ptr<GLTexture> m_texture;
int m_age = 0;
FileDescriptor m_releaseFd;
friend class EglSwapchain;
};
class KWIN_EXPORT EglSwapchain
{
public:
EglSwapchain(GraphicsBufferAllocator *allocator, EglContext *context, const QSize &size, uint32_t format, uint64_t modifier, const std::shared_ptr<EglSwapchainSlot> &seed);
~EglSwapchain();
QSize size() const;
uint32_t format() const;
uint64_t modifier() const;
std::shared_ptr<EglSwapchainSlot> acquire();
void release(std::shared_ptr<EglSwapchainSlot> slot, FileDescriptor &&releaseFence);
void resetBufferAge();
static std::shared_ptr<EglSwapchain> create(GraphicsBufferAllocator *allocator, EglContext *context, const QSize &size, uint32_t format, const QList<uint64_t> &modifiers);
private:
GraphicsBufferAllocator *m_allocator;
EglContext *m_context;
QSize m_size;
uint32_t m_format;
uint64_t m_modifier;
QList<std::shared_ptr<EglSwapchainSlot>> m_slots;
};
} // namespace KWin
@@ -0,0 +1,56 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2021 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <QOpenGLContext>
#include <QString>
#include <epoxy/egl.h>
static inline QString getEglErrorString(EGLint errorCode)
{
switch (errorCode) {
case EGL_SUCCESS:
return QStringLiteral("EGL_SUCCESS");
case EGL_NOT_INITIALIZED:
return QStringLiteral("EGL_NOT_INITIALIZED");
case EGL_BAD_ACCESS:
return QStringLiteral("EGL_BAD_ACCESS");
case EGL_BAD_ALLOC:
return QStringLiteral("EGL_BAD_ALLOC");
case EGL_BAD_ATTRIBUTE:
return QStringLiteral("EGL_BAD_ATTRIBUTE");
case EGL_BAD_CONTEXT:
return QStringLiteral("EGL_BAD_CONTEXT");
case EGL_BAD_CONFIG:
return QStringLiteral("EGL_BAD_CONFIG");
case EGL_BAD_CURRENT_SURFACE:
return QStringLiteral("EGL_BAD_CURRENT_SURFACE");
case EGL_BAD_DISPLAY:
return QStringLiteral("EGL_BAD_DISPLAY");
case EGL_BAD_SURFACE:
return QStringLiteral("EGL_BAD_SURFACE");
case EGL_BAD_MATCH:
return QStringLiteral("EGL_BAD_MATCH");
case EGL_BAD_PARAMETER:
return QStringLiteral("EGL_BAD_PARAMETER");
case EGL_BAD_NATIVE_PIXMAP:
return QStringLiteral("EGL_BAD_NATIVE_PIXMAP");
case EGL_BAD_NATIVE_WINDOW:
return QStringLiteral("EGL_BAD_NATIVE_WINDOW");
case EGL_CONTEXT_LOST:
return QStringLiteral("EGL_CONTEXT_LOST");
default:
return QString::number(errorCode, 16);
}
}
static inline QString getEglErrorString()
{
return getEglErrorString(eglGetError());
}
@@ -0,0 +1,310 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "glframebuffer.h"
#include "core/rendertarget.h"
#include "core/renderviewport.h"
#include "glplatform.h"
#include "gltexture.h"
#include "glutils.h"
#include "utils/common.h"
namespace KWin
{
GLFramebuffer *GLFramebuffer::currentFramebuffer()
{
return OpenGlContext::currentContext()->currentFramebuffer();
}
void GLFramebuffer::pushFramebuffer(GLFramebuffer *fbo)
{
OpenGlContext::currentContext()->pushFramebuffer(fbo);
}
GLFramebuffer *GLFramebuffer::popFramebuffer()
{
return OpenGlContext::currentContext()->popFramebuffer();
}
GLFramebuffer::GLFramebuffer()
: m_colorAttachment(nullptr)
{
}
static QString formatFramebufferStatus(GLenum status)
{
switch (status) {
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
// An attachment is the wrong type / is invalid / has 0 width or height
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
// There are no images attached to the framebuffer
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
case GL_FRAMEBUFFER_UNSUPPORTED:
// A format or the combination of formats of the attachments is unsupported
return QStringLiteral("GL_FRAMEBUFFER_UNSUPPORTED");
case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
// Not all attached images have the same width and height
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT");
case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
// The color attachments don't have the same format
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT");
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT:
// The attachments don't have the same number of samples
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE");
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
// The draw buffer is missing
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
// The read buffer is missing
return QStringLiteral("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
default:
return QStringLiteral("Unknown (0x") + QString::number(status, 16) + QStringLiteral(")");
}
}
GLFramebuffer::GLFramebuffer(GLTexture *colorAttachment, Attachment attachment)
: m_size(colorAttachment->size())
, m_colorAttachment(colorAttachment)
{
GLuint prevFbo = 0;
if (const GLFramebuffer *current = currentFramebuffer()) {
prevFbo = current->handle();
}
glGenFramebuffers(1, &m_handle);
glBindFramebuffer(GL_FRAMEBUFFER, m_handle);
initColorAttachment(colorAttachment);
if (attachment == Attachment::CombinedDepthStencil) {
initDepthStencilAttachment();
}
const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
glBindFramebuffer(GL_FRAMEBUFFER, prevFbo);
if (status != GL_FRAMEBUFFER_COMPLETE) {
// We have an incomplete framebuffer, consider it invalid
qCCritical(KWIN_OPENGL) << "Invalid framebuffer status: " << formatFramebufferStatus(status);
glDeleteFramebuffers(1, &m_handle);
return;
}
m_valid = true;
}
GLFramebuffer::GLFramebuffer(GLuint handle, const QSize &size)
: m_handle(handle)
, m_size(size)
, m_valid(true)
, m_foreign(true)
, m_colorAttachment(nullptr)
{
}
GLFramebuffer::~GLFramebuffer()
{
if (!OpenGlContext::currentContext()) {
qCWarning(KWIN_OPENGL, "Could not delete framebuffer because no context is current");
return;
}
if (!m_foreign && m_valid) {
glDeleteFramebuffers(1, &m_handle);
}
if (m_depthBuffer) {
glDeleteRenderbuffers(1, &m_depthBuffer);
}
if (m_stencilBuffer && m_stencilBuffer != m_depthBuffer) {
glDeleteRenderbuffers(1, &m_stencilBuffer);
}
}
bool GLFramebuffer::bind()
{
if (!valid()) {
qCCritical(KWIN_OPENGL) << "Can't enable invalid framebuffer object!";
return false;
}
glBindFramebuffer(GL_FRAMEBUFFER, handle());
glViewport(0, 0, m_size.width(), m_size.height());
return true;
}
void GLFramebuffer::initColorAttachment(GLTexture *colorAttachment)
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
colorAttachment->target(), colorAttachment->texture(), 0);
}
void GLFramebuffer::initDepthStencilAttachment()
{
GLuint buffer = 0;
const auto context = OpenGlContext::currentContext();
// Try to attach a depth/stencil combined attachment.
if (context->supportsBlits()) {
glGenRenderbuffers(1, &buffer);
glBindRenderbuffer(GL_RENDERBUFFER, buffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, m_size.width(), m_size.height());
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, buffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glDeleteRenderbuffers(1, &buffer);
} else {
m_depthBuffer = buffer;
m_stencilBuffer = buffer;
return;
}
}
// Try to attach a depth attachment separately.
GLenum depthFormat;
if (context->isOpenGLES()) {
if (context->supportsGLES24BitDepthBuffers()) {
depthFormat = GL_DEPTH_COMPONENT24;
} else {
depthFormat = GL_DEPTH_COMPONENT16;
}
} else {
depthFormat = GL_DEPTH_COMPONENT;
}
glGenRenderbuffers(1, &buffer);
glBindRenderbuffer(GL_RENDERBUFFER, buffer);
glRenderbufferStorage(GL_RENDERBUFFER, depthFormat, m_size.width(), m_size.height());
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, buffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glDeleteRenderbuffers(1, &buffer);
} else {
m_depthBuffer = buffer;
}
// Try to attach a stencil attachment separately.
GLenum stencilFormat;
if (context->isOpenGLES()) {
stencilFormat = GL_STENCIL_INDEX8;
} else {
stencilFormat = GL_STENCIL_INDEX;
}
glGenRenderbuffers(1, &buffer);
glBindRenderbuffer(GL_RENDERBUFFER, buffer);
glRenderbufferStorage(GL_RENDERBUFFER, stencilFormat, m_size.width(), m_size.height());
glFramebufferRenderbuffer(GL_RENDERBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glDeleteRenderbuffers(1, &buffer);
} else {
m_stencilBuffer = buffer;
}
}
void GLFramebuffer::blitFromFramebuffer(const QRect &source, const QRect &destination, GLenum filter, bool flipX, bool flipY)
{
if (!valid()) {
return;
}
const GLFramebuffer *top = currentFramebuffer();
if (!OpenGlContext::currentContext()->supportsBlits()) {
const auto texture = top->colorAttachment();
if (!texture) {
// can't do anything
return;
}
GLFramebuffer::pushFramebuffer(this);
QMatrix4x4 mat;
mat.ortho(QRectF(QPointF(), size()));
// GLTexture::render renders with origin (0, 0), move it to the correct place
mat.translate(destination.x(), destination.y());
ShaderBinder binder(ShaderTrait::MapTexture);
binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mat);
texture->render(source, infiniteRegion(), destination.size(), 1);
GLFramebuffer::popFramebuffer();
return;
}
GLFramebuffer::pushFramebuffer(this);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, handle());
glBindFramebuffer(GL_READ_FRAMEBUFFER, top->handle());
const QRect s = source.isNull() ? QRect(QPoint(0, 0), top->size()) : source;
const QRect d = destination.isNull() ? QRect(QPoint(0, 0), size()) : destination;
GLuint srcX0 = s.x();
GLuint srcY0 = top->size().height() - (s.y() + s.height());
GLuint srcX1 = s.x() + s.width();
GLuint srcY1 = top->size().height() - s.y();
if (flipX) {
std::swap(srcX0, srcX1);
}
if (flipY) {
std::swap(srcY0, srcY1);
}
const GLuint dstX0 = d.x();
const GLuint dstY0 = m_size.height() - (d.y() + d.height());
const GLuint dstX1 = d.x() + d.width();
const GLuint dstY1 = m_size.height() - d.y();
glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, GL_COLOR_BUFFER_BIT, filter);
GLFramebuffer::popFramebuffer();
}
bool GLFramebuffer::blitFromRenderTarget(const RenderTarget &sourceRenderTarget, const RenderViewport &sourceViewport, const QRect &source, const QRect &destination)
{
OutputTransform transform = sourceRenderTarget.texture() ? sourceRenderTarget.texture()->contentTransform() : OutputTransform();
// TODO: Also blit if rotated 180 degrees, it's equivalent to flipping both x and y axis
const bool normal = transform == OutputTransform::Normal;
const bool mirrorX = transform == OutputTransform::FlipX;
const bool mirrorY = transform == OutputTransform::FlipY;
if ((normal || mirrorX || mirrorY) && OpenGlContext::currentContext()->supportsBlits()) {
// either no transformation or flipping only
blitFromFramebuffer(sourceViewport.mapToRenderTarget(source), destination, GL_LINEAR, mirrorX, mirrorY);
return true;
} else {
const auto texture = sourceRenderTarget.texture();
if (!texture) {
// rotations aren't possible without a texture
return false;
}
GLFramebuffer::pushFramebuffer(this);
QMatrix4x4 mat;
mat.ortho(QRectF(QPointF(), size()));
// GLTexture::render renders with origin (0, 0), move it to the correct place
mat.translate(destination.x(), destination.y());
ShaderBinder binder(ShaderTrait::MapTexture);
binder.shader()->setUniform(GLShader::Mat4Uniform::ModelViewProjectionMatrix, mat);
texture->render(sourceViewport.mapToRenderTargetTexture(source), infiniteRegion(), destination.size(), 1);
GLFramebuffer::popFramebuffer();
return true;
}
}
GLTexture *GLFramebuffer::colorAttachment() const
{
return m_colorAttachment;
}
}
@@ -0,0 +1,132 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QRect>
#include <QStack>
#include <epoxy/gl.h>
namespace KWin
{
class GLTexture;
class RenderTarget;
class RenderViewport;
// Cleans up all resources hold by the GL Context
void KWIN_EXPORT cleanupGL();
/**
* @short OpenGL framebuffer object
*
* Framebuffer object enables you to render onto a texture. This texture can
* later be used to e.g. do post-processing of the scene.
*
* @author Rivo Laks <rivolaks@hot.ee>
*/
class KWIN_EXPORT GLFramebuffer
{
public:
enum Attachment {
NoAttachment,
CombinedDepthStencil,
};
/**
* Constructs a GLFramebuffer
* @since 5.13
*/
explicit GLFramebuffer();
/**
* Constructs a GLFramebuffer. Note that ensuring the color attachment outlives
* the framebuffer is the responsibility of the caller.
*
* @param colorAttachment texture where the scene will be rendered onto
*/
explicit GLFramebuffer(GLTexture *colorAttachment, Attachment attachment = NoAttachment);
/**
* Constructs a wrapper for an already created framebuffer object. The GLFramebuffer
* does not take the ownership of the framebuffer object handle.
*/
GLFramebuffer(GLuint handle, const QSize &size);
~GLFramebuffer();
/**
* Returns the framebuffer object handle to this framebuffer object.
*/
GLuint handle() const
{
return m_handle;
}
/**
* Returns the size of the color attachment to this framebuffer object.
*/
QSize size() const
{
return m_size;
}
bool valid() const
{
return m_valid;
}
/**
* Returns the last bound framebuffer, or @c null if no framebuffer is current.
*/
static GLFramebuffer *currentFramebuffer();
static void pushFramebuffer(GLFramebuffer *fbo);
static GLFramebuffer *popFramebuffer();
/**
* Blits from @a source rectangle in the current framebuffer to the @a destination rectangle in
* this framebuffer.
*
* Be aware that framebuffer blitting may not be supported on all hardware. Use blitSupported()
* to check whether it is supported.
*
* The @a source and the @a destination rectangles can have different sizes. The @a filter indicates
* what filter will be used in case scaling needs to be performed.
*
* @see blitSupported
* @since 4.8
*/
void blitFromFramebuffer(const QRect &source = QRect(), const QRect &destination = QRect(), GLenum filter = GL_LINEAR, bool flipX = false, bool flipY = false);
/**
* Blits from @a source rectangle in logical coordinates in the current framebuffer to the @a destination rectangle in texture-local coordinates
* in this framebuffer, taking into account any transformations the source render target may have
*/
bool blitFromRenderTarget(const RenderTarget &sourceRenderTarget, const RenderViewport &sourceViewport, const QRect &source, const QRect &destination);
/**
* @returns the color attachment of this fbo. May be nullptr
*/
GLTexture *colorAttachment() const;
protected:
void initColorAttachment(GLTexture *colorAttachment);
void initDepthStencilAttachment();
bool bind();
GLuint m_handle = 0;
GLuint m_depthBuffer = 0;
GLuint m_stencilBuffer = 0;
QSize m_size;
bool m_valid = false;
bool m_foreign = false;
GLTexture *const m_colorAttachment;
};
}
@@ -0,0 +1,78 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "gllut.h"
#include "openglcontext.h"
#include "utils/common.h"
#include <vector>
namespace KWin
{
GlLookUpTable::GlLookUpTable(GLuint handle, size_t size)
: m_handle(handle)
, m_size(size)
{
}
GlLookUpTable::~GlLookUpTable()
{
if (!OpenGlContext::currentContext()) {
qCWarning(KWIN_OPENGL, "Could not delete 1D LUT because no context is current");
return;
}
glDeleteTextures(1, &m_handle);
}
GLuint GlLookUpTable::handle() const
{
return m_handle;
}
size_t GlLookUpTable::size() const
{
return m_size;
}
void GlLookUpTable::bind()
{
glBindTexture(GL_TEXTURE_2D, m_handle);
}
std::unique_ptr<GlLookUpTable> GlLookUpTable::create(const std::function<QVector3D(size_t value)> &func, size_t size)
{
GLuint handle = 0;
glGenTextures(1, &handle);
if (!handle) {
return nullptr;
}
// this uses 2D textures because OpenGL ES doesn't support 1D textures
glBindTexture(GL_TEXTURE_2D, handle);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
std::vector<float> data;
data.reserve(4 * size);
for (size_t i = 0; i < size; i++) {
const auto color = func(i);
data.push_back(color.x());
data.push_back(color.y());
data.push_back(color.z());
data.push_back(1);
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, size, 1, 0, GL_RGBA, GL_FLOAT, data.data());
glBindTexture(GL_TEXTURE_2D, 0);
return std::make_unique<GlLookUpTable>(handle, size);
}
}
@@ -0,0 +1,40 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QVector3D>
#include <QVector>
#include <epoxy/gl.h>
#include <functional>
#include <memory>
namespace KWin
{
class KWIN_EXPORT GlLookUpTable
{
public:
explicit GlLookUpTable(GLuint handle, size_t size);
~GlLookUpTable();
GLuint handle() const;
size_t size() const;
void bind();
static std::unique_ptr<GlLookUpTable> create(const std::function<QVector3D(size_t value)> &func, size_t size);
private:
const GLuint m_handle;
const size_t m_size;
};
}
@@ -0,0 +1,94 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "gllut3D.h"
#include "openglcontext.h"
#include "utils/common.h"
#include <vector>
namespace KWin
{
GlLookUpTable3D::GlLookUpTable3D(GLuint handle, size_t xSize, size_t ySize, size_t zSize)
: m_handle(handle)
, m_xSize(xSize)
, m_ySize(ySize)
, m_zSize(zSize)
{
}
GlLookUpTable3D::~GlLookUpTable3D()
{
if (!OpenGlContext::currentContext()) {
qCWarning(KWIN_OPENGL, "Could not delete 3D LUT because no context is current");
return;
}
glDeleteTextures(1, &m_handle);
}
GLuint GlLookUpTable3D::handle() const
{
return m_handle;
}
size_t GlLookUpTable3D::xSize() const
{
return m_xSize;
}
size_t GlLookUpTable3D::ySize() const
{
return m_ySize;
}
size_t GlLookUpTable3D::zSize() const
{
return m_zSize;
}
void GlLookUpTable3D::bind()
{
glBindTexture(GL_TEXTURE_3D, m_handle);
}
std::unique_ptr<GlLookUpTable3D> GlLookUpTable3D::create(const std::function<QVector3D(size_t x, size_t y, size_t z)> &mapping, size_t xSize, size_t ySize, size_t zSize)
{
GLuint handle = 0;
glGenTextures(1, &handle);
if (!handle) {
return nullptr;
}
glBindTexture(GL_TEXTURE_3D, handle);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_LOD, 0);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LOD, 0);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
QVector<float> data;
data.reserve(4 * xSize * ySize * zSize);
for (size_t z = 0; z < zSize; z++) {
for (size_t y = 0; y < ySize; y++) {
for (size_t x = 0; x < xSize; x++) {
const auto color = mapping(x, y, z);
data.push_back(color.x());
data.push_back(color.y());
data.push_back(color.z());
data.push_back(1);
}
}
}
glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA16F, xSize, ySize, zSize, 0, GL_RGBA, GL_FLOAT, data.data());
glBindTexture(GL_TEXTURE_3D, 0);
return std::make_unique<GlLookUpTable3D>(handle, xSize, ySize, zSize);
}
}
@@ -0,0 +1,44 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QVector3D>
#include <QVector>
#include <epoxy/gl.h>
#include <functional>
#include <memory>
namespace KWin
{
class KWIN_EXPORT GlLookUpTable3D
{
public:
explicit GlLookUpTable3D(GLuint handle, size_t xSize, size_t ySize, size_t zSize);
~GlLookUpTable3D();
GLuint handle() const;
size_t xSize() const;
size_t ySize() const;
size_t zSize() const;
void bind();
static std::unique_ptr<GlLookUpTable3D> create(const std::function<QVector3D(size_t x, size_t y, size_t z)> &mapping, size_t xSize, size_t ySize, size_t zSize);
private:
const GLuint m_handle;
const size_t m_xSize;
const size_t m_ySize;
const size_t m_zSize;
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,342 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2010 Fredrik Höglund <fredrik@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "effect/globals.h"
#include "openglcontext.h"
#include <QByteArray>
#include <QSet>
#include <memory>
namespace KWin
{
// forward declare method
void cleanupGL();
class Version;
enum Driver {
Driver_R100, // Technically "Radeon"
Driver_R200,
Driver_R300C,
Driver_R300G,
Driver_R600C,
Driver_R600G,
Driver_Nouveau,
Driver_Intel,
Driver_NVidia,
Driver_Catalyst,
Driver_Swrast,
Driver_Softpipe,
Driver_Llvmpipe,
Driver_VirtualBox,
Driver_VMware,
Driver_Qualcomm,
Driver_RadeonSI,
Driver_Virgl,
Driver_Panfrost,
Driver_Lima,
Driver_VC4,
Driver_V3D,
Driver_Unknown,
};
// clang-format off
enum ChipClass {
// Radeon
R100 = 0, // GL1.3 DX7 2000
R200, // GL1.4 DX8.1 SM 1.4 2001
R300, // GL2.0 DX9 SM 2.0 2002
R400, // GL2.0 DX9b SM 2.0b 2004
R500, // GL2.0 DX9c SM 3.0 2005
R600, // GL3.3 DX10 SM 4.0 2006
R700, // GL3.3 DX10.1 SM 4.1 2008
Evergreen, // GL4.0 CL1.0 DX11 SM 5.0 2009
NorthernIslands, // GL4.0 CL1.1 DX11 SM 5.0 2010
SouthernIslands, // GL4.5 CL1.2 DX11.1 SM 5.1 2012
SeaIslands, // GL4.5 CL2.0 DX12 SM 6.0 2013
VolcanicIslands, // GL4.5 CL2.0 DX12 SM 6.0 2015
ArcticIslands, // GL4.5 CL2.0 DX12 SM 6.0 2016
Vega, // GL4.6 CL2.0 DX12 SM 6.0 2017
Navi, // GL4.6 CL2.0 DX12.1 SM 6.4 2019
UnknownRadeon = 999,
// NVIDIA
NV10 = 1000, // GL1.2 DX7 1999
NV20, // GL1.3 DX8 SM 1.1 2001
NV30, // GL1.5 DX9a SM 2.0 2003
NV40, // GL2.1 DX9c SM 3.0 2004
G80, // GL3.3 DX10 SM 4.0 2006
GF100, // GL4.1 CL1.1 DX11 SM 5.0 2010
UnknownNVidia = 1999,
// Intel
I8XX = 2000, // GL1.3 DX7 2001
I915, // GL1.4/1.5 DX9/DX9c SM 2.0 2004
I965, // GL2.0/2.1 DX9/DX10 SM 3.0/4.0 2006
SandyBridge, // Gen6 GL3.1 CL1.1 DX10.1 SM 4.0 2010
IvyBridge, // Gen7 GL4.0 CL1.1 DX11 SM 5.0 2012
Haswell, // Gen7 GL4.0 CL1.2 DX11.1 SM 5.0 2013
BayTrail, // Gen7 GL4.0 CL1.2 DX11.1 SM 5.0 2013
Cherryview, // Gen8 GL4.0 CL1.2 DX11.2 SM 5.0 2013
Broadwell, // Gen8 GL4.4 CL2.0 DX11.2 SM 5.0 2014
ApolloLake, // Gen9 GL4.6 CL3.0 DX12 SM 6.0 2016
Skylake, // Gen9 GL4.6 CL3.0 DX12 SM 6.0 2015
GeminiLake, // Gen9 GL4.6 CL3.0 DX12 SM 6.0 2017
KabyLake, // Gen9 GL4.6 CL3.0 DX12 SM 6.0 2017
CoffeeLake, // Gen9 GL4.6 CL3.0 DX12 SM 6.0 2018
WhiskeyLake, // Gen9 GL4.6 GL3.0 DX12 SM 6.0 2018
CometLake, // Gen9 GL4.6 GL3.0 DX12 SM 6.0 2019
CannonLake, // Gen10 GL4.6 GL3.0 DX12 SM 6.0 2018
IceLake, // Gen11 GL4.6 CL3.0 DX12.1 SM 6.0 2019
TigerLake, // Gen12 GL4.6 CL3.0 DX12.1 SM 6.0 2020
UnknownIntel = 2999,
// Qualcomm Adreno
// from https://en.wikipedia.org/wiki/Adreno
Adreno1XX = 3000, // GLES1.1
Adreno2XX, // GLES2.0 DX9c
Adreno3XX, // GLES3.0 CL1.1 DX11.1
Adreno4XX, // GLES3.1 CL1.2 DX11.2
Adreno5XX, // GLES3.1 CL2.0 DX11.2
UnknownAdreno = 3999,
// Panfrost Mali
// from https://docs.mesa3d.org/drivers/panfrost.html
MaliT7XX = 4000, // GLES2.0/GLES3.0
MaliT8XX, // GLES3.0
MaliGXX, // GLES3.0
UnknownPanfrost = 4999,
// Lima Mali
// from https://docs.mesa3d.org/drivers/lima.html
Mali400 = 5000,
Mali450,
Mali470,
UnknownLima = 5999,
// Broadcom VideoCore IV (e.g. Raspberry Pi 0 to 3), GLES 2.0/2.1 with caveats
VC4_2_1 = 6000, // Found in Raspberry Pi 3B+
UnknownVideoCore4 = 6999,
// Broadcom VideoCore 3D (e.g. Raspberry Pi 4, Raspberry Pi 400)
V3D_4_2 = 7000, // Found in Raspberry Pi 400
UnknownVideoCore3D = 7999,
UnknownChipClass = 99999,
};
// clang-format on
class KWIN_EXPORT GLPlatform
{
public:
explicit GLPlatform(OpenGLPlatformInterface platformInterface, QByteArrayView openglVersionString, QByteArrayView glslVersionString, QByteArrayView renderer, QByteArrayView vendor);
~GLPlatform();
/**
* Returns the OpenGL version.
*/
Version glVersion() const;
/**
* Returns the GLSL version if the driver supports GLSL, and 0 otherwise.
*/
Version glslVersion() const;
/**
* Returns the Mesa version if the driver is a Mesa driver, and 0 otherwise.
*/
Version mesaVersion() const;
/**
* Returns the driver version.
*
* For Mesa drivers, this is the same as the Mesa version number.
*/
Version driverVersion() const;
/**
* Returns the driver.
*/
Driver driver() const;
/**
* Returns the chip class.
*/
ChipClass chipClass() const;
/**
* Returns true if the driver is a Mesa driver, and false otherwise.
*/
bool isMesaDriver() const;
/**
* Returns true if the GPU is a Radeon GPU, and false otherwise.
*/
bool isRadeon() const;
/**
* Returns true if the GPU is an NVIDIA GPU, and false otherwise.
*/
bool isNvidia() const;
/**
* Returns true if the GPU is an Intel GPU, and false otherwise.
*/
bool isIntel() const;
/**
* @returns @c true if the "GPU" is a VirtualBox GPU, and @c false otherwise.
* @since 4.10
*/
bool isVirtualBox() const;
/**
* @returns @c true if the "GPU" is a VMWare GPU, and @c false otherwise.
* @since 4.10
*/
bool isVMware() const;
/**
* @returns @c true if the driver is known to be from a virtual machine.
* @since 4.10
*/
bool isVirtualMachine() const;
/**
* @returns @c true if the GPU is a Qualcomm Adreno GPU, and false otherwise
* @since 5.8
*/
bool isAdreno() const;
/**
* @returns @c true if the "GPU" is a virtio-gpu (Qemu/KVM)
* @since 5.18
**/
bool isVirgl() const;
/**
* @returns @c true if the "GPU" is a Panfrost Mali GPU
* @since 5.21.5
**/
bool isPanfrost() const;
/**
* @returns @c true if the GPU is a Mali GPU supported by the Lima driver (Mali 400, 450)
* @since 5.27.1
**/
bool isLima() const;
/**
* @returns @c true if the GPU is a Broadcom VideoCore IV (e.g. Raspberry Pi 0 to 3)
* @since 5.27.1
**/
bool isVideoCore4() const;
/**
* @returns @c true if the GPU is a Broadcom VideoCore 3D (e.g. Raspberry Pi 4, 400)
* @since 5.27.1
**/
bool isVideoCore3D() const;
/**
* @returns the GL_VERSION string as provided by the driver.
* @since 4.9
*/
QByteArrayView glVersionString() const;
/**
* @returns the GL_RENDERER string as provided by the driver.
* @since 4.9
*/
QByteArrayView glRendererString() const;
/**
* @returns the GL_VENDOR string as provided by the driver.
* @since 4.9
*/
QByteArrayView glVendorString() const;
/**
* @returns the GL_SHADING_LANGUAGE_VERSION string as provided by the driver.
* If the driver does not support the OpenGL Shading Language a null bytearray is returned.
* @since 4.9
*/
QByteArrayView glShadingLanguageVersionString() const;
/**
* @returns Whether the driver supports loose texture binding.
* @since 4.9
*/
bool isLooseBinding() const;
/**
* @returns The CompositingType recommended by the driver.
* @since 4.10
*/
CompositingType recommendedCompositor() const;
/**
* Returns true if glMapBufferRange() is likely to perform worse than glBufferSubData()
* when updating an unused range of a buffer object, and false otherwise.
*
* @since 4.11
*/
bool preferBufferSubData() const;
/**
* @returns The OpenGLPlatformInterface currently used
* @since 5.0
*/
OpenGLPlatformInterface platformInterface() const;
/**
* @returns a human readable form for the @p driver as a QString.
* @since 4.9
* @see driver
*/
static QString driverToString(Driver driver);
/**
* @returns a human readable form for the @p driver as a QByteArray.
* @since 5.5
* @see driver
*/
static QByteArray driverToString8(Driver driver);
/**
* @returns a human readable form for the @p chipClass as a QString.
* @since 4.9
* @see chipClass
*/
static QString chipClassToString(ChipClass chipClass);
/**
* @returns a human readable form for the @p chipClass as a QByteArray.
* @since 5.5
* @see chipClass
*/
static QByteArray chipClassToString8(ChipClass chipClass);
private:
QByteArrayView m_openglVersionString;
QByteArrayView m_glslVersionString;
QByteArrayView m_chipset = QByteArrayLiteral("Unknown");
QByteArrayView m_rendererString;
QByteArrayView m_vendorString;
Driver m_driver = Driver_Unknown;
ChipClass m_chipClass = UnknownChipClass;
CompositingType m_recommendedCompositor = OpenGLCompositing;
Version m_openglVersion;
Version m_glslVersion;
Version m_mesaVersion;
Version m_driverVersion;
bool m_looseBinding = false;
bool m_virtualMachine = false;
bool m_preferBufferSubData = false;
OpenGLPlatformInterface m_platformInterface;
};
} // namespace KWin
@@ -0,0 +1,87 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "glrendertimequery.h"
#include "opengl/glplatform.h"
#include "utils/common.h"
namespace KWin
{
GLRenderTimeQuery::GLRenderTimeQuery(const std::shared_ptr<OpenGlContext> &context)
: m_context(context)
{
if (context->supportsTimerQueries()) {
glGenQueries(1, &m_gpuProbe.query);
}
}
GLRenderTimeQuery::~GLRenderTimeQuery()
{
if (!m_gpuProbe.query) {
return;
}
const auto previousContext = OpenGlContext::currentContext();
const auto context = m_context.lock();
if (!context || !context->makeCurrent()) {
qCWarning(KWIN_OPENGL, "Could not delete render time query because no context is current");
return;
}
glDeleteQueries(1, &m_gpuProbe.query);
if (previousContext && previousContext != context.get()) {
previousContext->makeCurrent();
}
}
void GLRenderTimeQuery::begin()
{
if (m_gpuProbe.query) {
GLint64 start = 0;
glGetInteger64v(GL_TIMESTAMP, &start);
m_gpuProbe.start = std::chrono::nanoseconds(start);
}
m_cpuProbe.start = std::chrono::steady_clock::now();
}
void GLRenderTimeQuery::end()
{
m_hasResult = true;
if (m_gpuProbe.query) {
glQueryCounter(m_gpuProbe.query, GL_TIMESTAMP);
}
m_cpuProbe.end = std::chrono::steady_clock::now();
}
std::optional<RenderTimeSpan> GLRenderTimeQuery::query()
{
Q_ASSERT(m_hasResult);
if (m_gpuProbe.query) {
const auto previousContext = OpenGlContext::currentContext();
const auto context = m_context.lock();
if (!context || !context->makeCurrent()) {
return std::nullopt;
}
GLint64 end = 0;
glGetQueryObjecti64v(m_gpuProbe.query, GL_QUERY_RESULT, &end);
m_gpuProbe.end = std::chrono::nanoseconds(end);
if (previousContext && previousContext != context.get()) {
previousContext->makeCurrent();
}
}
// timings are pretty unpredictable in the sub-millisecond range; this minimum
// ensures that when CPU or GPU power states change, we don't drop any frames
const std::chrono::nanoseconds minimumTime = std::chrono::milliseconds(2);
const auto end = std::max({m_cpuProbe.start + (m_gpuProbe.end - m_gpuProbe.start), m_cpuProbe.end, m_cpuProbe.start + minimumTime});
return RenderTimeSpan{
.start = m_cpuProbe.start,
.end = end,
};
}
}
@@ -0,0 +1,54 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <chrono>
#include <epoxy/gl.h>
#include "core/renderbackend.h"
#include "kwin_export.h"
namespace KWin
{
class OpenGlContext;
class KWIN_EXPORT GLRenderTimeQuery : public RenderTimeQuery
{
public:
explicit GLRenderTimeQuery(const std::shared_ptr<OpenGlContext> &context);
~GLRenderTimeQuery();
void begin();
void end();
/**
* fetches the result of the query. If rendering is not done yet, this will block!
*/
std::optional<RenderTimeSpan> query() override;
private:
const std::weak_ptr<OpenGlContext> m_context;
bool m_hasResult = false;
struct
{
std::chrono::steady_clock::time_point start;
std::chrono::steady_clock::time_point end;
} m_cpuProbe;
struct
{
GLuint query = 0;
std::chrono::nanoseconds start{0};
std::chrono::nanoseconds end{0};
} m_gpuProbe;
};
}
@@ -0,0 +1,499 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "glshader.h"
#include "glplatform.h"
#include "glutils.h"
#include "utils/common.h"
#include <QFile>
namespace KWin
{
GLShader::GLShader(unsigned int flags)
: m_valid(false)
, m_locationsResolved(false)
, m_explicitLinking(flags & ExplicitLinking)
{
m_program = glCreateProgram();
}
GLShader::GLShader(const QString &vertexfile, const QString &fragmentfile, unsigned int flags)
: m_valid(false)
, m_locationsResolved(false)
, m_explicitLinking(flags & ExplicitLinking)
{
m_program = glCreateProgram();
loadFromFiles(vertexfile, fragmentfile);
}
GLShader::~GLShader()
{
if (!OpenGlContext::currentContext()) {
qCWarning(KWIN_OPENGL, "Could not delete shader because no context is current");
return;
}
if (m_program) {
glDeleteProgram(m_program);
}
}
bool GLShader::loadFromFiles(const QString &vertexFile, const QString &fragmentFile)
{
QFile vf(vertexFile);
if (!vf.open(QIODevice::ReadOnly)) {
qCCritical(KWIN_OPENGL) << "Couldn't open" << vertexFile << "for reading!";
return false;
}
const QByteArray vertexSource = vf.readAll();
QFile ff(fragmentFile);
if (!ff.open(QIODevice::ReadOnly)) {
qCCritical(KWIN_OPENGL) << "Couldn't open" << fragmentFile << "for reading!";
return false;
}
const QByteArray fragmentSource = ff.readAll();
return load(vertexSource, fragmentSource);
}
bool GLShader::link()
{
// Be optimistic
m_valid = true;
glLinkProgram(m_program);
// Get the program info log
int maxLength, length;
glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, &maxLength);
QByteArray log(maxLength, 0);
glGetProgramInfoLog(m_program, maxLength, &length, log.data());
// Make sure the program linked successfully
int status;
glGetProgramiv(m_program, GL_LINK_STATUS, &status);
if (status == 0) {
qCCritical(KWIN_OPENGL) << "Failed to link shader:"
<< "\n"
<< log;
m_valid = false;
} else if (length > 0) {
qCDebug(KWIN_OPENGL) << "Shader link log:" << log;
}
return m_valid;
}
const QByteArray GLShader::prepareSource(GLenum shaderType, const QByteArray &source) const
{
// Prepare the source code
QByteArray ba;
const auto context = OpenGlContext::currentContext();
if (context->isOpenGLES() && context->glslVersion() < Version(3, 0)) {
ba.append("precision highp float;\n");
}
ba.append(source);
if (context->isOpenGLES() && context->glslVersion() >= Version(3, 0)) {
ba.replace("#version 140", "#version 300 es\n\nprecision highp float;\n");
}
return ba;
}
bool GLShader::compile(GLuint program, GLenum shaderType, const QByteArray &source) const
{
GLuint shader = glCreateShader(shaderType);
QByteArray preparedSource = prepareSource(shaderType, source);
const char *src = preparedSource.constData();
glShaderSource(shader, 1, &src, nullptr);
// Compile the shader
glCompileShader(shader);
// Get the shader info log
int maxLength, length;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
QByteArray log(maxLength, 0);
glGetShaderInfoLog(shader, maxLength, &length, log.data());
// Check the status
int status;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == 0) {
const char *typeName = (shaderType == GL_VERTEX_SHADER ? "vertex" : "fragment");
qCCritical(KWIN_OPENGL) << "Failed to compile" << typeName << "shader:"
<< "\n"
<< log;
size_t line = 0;
const auto split = source.split('\n');
for (const auto &l : split) {
qCCritical(KWIN_OPENGL).nospace() << "line " << line++ << ":" << l;
}
} else if (length > 0) {
qCDebug(KWIN_OPENGL) << "Shader compile log:" << log;
}
if (status != 0) {
glAttachShader(program, shader);
}
glDeleteShader(shader);
return status != 0;
}
bool GLShader::load(const QByteArray &vertexSource, const QByteArray &fragmentSource)
{
m_valid = false;
// Compile the vertex shader
if (!vertexSource.isEmpty()) {
bool success = compile(m_program, GL_VERTEX_SHADER, vertexSource);
if (!success) {
return false;
}
}
// Compile the fragment shader
if (!fragmentSource.isEmpty()) {
bool success = compile(m_program, GL_FRAGMENT_SHADER, fragmentSource);
if (!success) {
return false;
}
}
if (m_explicitLinking) {
return true;
}
// link() sets mValid
return link();
}
void GLShader::bindAttributeLocation(const char *name, int index)
{
glBindAttribLocation(m_program, index, name);
}
void GLShader::bindFragDataLocation(const char *name, int index)
{
const auto context = OpenGlContext::currentContext();
if (!context->isOpenGLES() && (context->hasVersion(Version(3, 0)) || context->hasOpenglExtension(QByteArrayLiteral("GL_EXT_gpu_shader4")))) {
glBindFragDataLocation(m_program, index, name);
}
}
void GLShader::bind()
{
glUseProgram(m_program);
}
void GLShader::unbind()
{
glUseProgram(0);
}
void GLShader::resolveLocations()
{
if (m_locationsResolved) {
return;
}
m_matrix4Locations[Mat4Uniform::TextureMatrix] = uniformLocation("textureMatrix");
m_matrix4Locations[Mat4Uniform::ProjectionMatrix] = uniformLocation("projection");
m_matrix4Locations[Mat4Uniform::ModelViewMatrix] = uniformLocation("modelview");
m_matrix4Locations[Mat4Uniform::ModelViewProjectionMatrix] = uniformLocation("modelViewProjectionMatrix");
m_matrix4Locations[Mat4Uniform::WindowTransformation] = uniformLocation("windowTransformation");
m_matrix4Locations[Mat4Uniform::ScreenTransformation] = uniformLocation("screenTransformation");
m_matrix4Locations[Mat4Uniform::ColorimetryTransformation] = uniformLocation("colorimetryTransform");
m_matrix4Locations[Mat4Uniform::DestinationToLMS] = uniformLocation("destinationToLMS");
m_matrix4Locations[Mat4Uniform::LMSToDestination] = uniformLocation("lmsToDestination");
m_vec2Locations[Vec2Uniform::Offset] = uniformLocation("offset");
m_vec2Locations[Vec2Uniform::SourceTransferFunctionParams] = uniformLocation("sourceTransferFunctionParams");
m_vec2Locations[Vec2Uniform::DestinationTransferFunctionParams] = uniformLocation("destinationTransferFunctionParams");
m_vec3Locations[Vec3Uniform::PrimaryBrightness] = uniformLocation("primaryBrightness");
m_vec4Locations[Vec4Uniform::ModulationConstant] = uniformLocation("modulation");
m_floatLocations[FloatUniform::Saturation] = uniformLocation("saturation");
m_floatLocations[FloatUniform::MaxDestinationLuminance] = uniformLocation("maxDestinationLuminance");
m_floatLocations[FloatUniform::SourceReferenceLuminance] = uniformLocation("sourceReferenceLuminance");
m_floatLocations[FloatUniform::DestinationReferenceLuminance] = uniformLocation("destinationReferenceLuminance");
m_floatLocations[FloatUniform::MaxTonemappingLuminance] = uniformLocation("maxTonemappingLuminance");
m_colorLocations[ColorUniform::Color] = uniformLocation("geometryColor");
m_intLocations[IntUniform::TextureWidth] = uniformLocation("textureWidth");
m_intLocations[IntUniform::TextureHeight] = uniformLocation("textureHeight");
m_intLocations[IntUniform::Sampler] = uniformLocation("sampler");
m_intLocations[IntUniform::Sampler1] = uniformLocation("sampler1");
m_intLocations[IntUniform::SourceNamedTransferFunction] = uniformLocation("sourceNamedTransferFunction");
m_intLocations[IntUniform::DestinationNamedTransferFunction] = uniformLocation("destinationNamedTransferFunction");
m_locationsResolved = true;
}
int GLShader::uniformLocation(const char *name)
{
const int location = glGetUniformLocation(m_program, name);
return location;
}
bool GLShader::setUniform(Mat3Uniform uniform, const QMatrix3x3 &value)
{
resolveLocations();
return setUniform(m_matrix3Locations[uniform], value);
}
bool GLShader::setUniform(Mat4Uniform uniform, const QMatrix4x4 &matrix)
{
resolveLocations();
return setUniform(m_matrix4Locations[uniform], matrix);
}
bool GLShader::setUniform(Vec2Uniform uniform, const QVector2D &value)
{
resolveLocations();
return setUniform(m_vec2Locations[uniform], value);
}
bool GLShader::setUniform(Vec3Uniform uniform, const QVector3D &value)
{
resolveLocations();
return setUniform(m_vec3Locations[uniform], value);
}
bool GLShader::setUniform(Vec4Uniform uniform, const QVector4D &value)
{
resolveLocations();
return setUniform(m_vec4Locations[uniform], value);
}
bool GLShader::setUniform(FloatUniform uniform, float value)
{
resolveLocations();
return setUniform(m_floatLocations[uniform], value);
}
bool GLShader::setUniform(IntUniform uniform, int value)
{
resolveLocations();
return setUniform(m_intLocations[uniform], value);
}
bool GLShader::setUniform(ColorUniform uniform, const QVector4D &value)
{
resolveLocations();
return setUniform(m_colorLocations[uniform], value);
}
bool GLShader::setUniform(ColorUniform uniform, const QColor &value)
{
resolveLocations();
return setUniform(m_colorLocations[uniform], value);
}
bool GLShader::setUniform(const char *name, float value)
{
const int location = uniformLocation(name);
return setUniform(location, value);
}
bool GLShader::setUniform(const char *name, double value)
{
const int location = uniformLocation(name);
return setUniform(location, value);
}
bool GLShader::setUniform(const char *name, int value)
{
const int location = uniformLocation(name);
return setUniform(location, value);
}
bool GLShader::setUniform(const char *name, const QVector2D &value)
{
const int location = uniformLocation(name);
return setUniform(location, value);
}
bool GLShader::setUniform(const char *name, const QVector3D &value)
{
const int location = uniformLocation(name);
return setUniform(location, value);
}
bool GLShader::setUniform(const char *name, const QVector4D &value)
{
const int location = uniformLocation(name);
return setUniform(location, value);
}
bool GLShader::setUniform(const char *name, const QMatrix3x3 &value)
{
const int location = uniformLocation(name);
return setUniform(location, value);
}
bool GLShader::setUniform(const char *name, const QMatrix4x4 &value)
{
const int location = uniformLocation(name);
return setUniform(location, value);
}
bool GLShader::setUniform(const char *name, const QColor &color)
{
const int location = uniformLocation(name);
return setUniform(location, color);
}
bool GLShader::setUniform(int location, float value)
{
if (location >= 0) {
glUniform1f(location, value);
}
return (location >= 0);
}
bool GLShader::setUniform(int location, double value)
{
if (location >= 0) {
glUniform1f(location, value);
}
return (location >= 0);
}
bool GLShader::setUniform(int location, int value)
{
if (location >= 0) {
glUniform1i(location, value);
}
return (location >= 0);
}
bool GLShader::setUniform(int location, int xValue, int yValue, int zValue)
{
if (location >= 0) {
glUniform3i(location, xValue, yValue, zValue);
}
return location >= 0;
}
bool GLShader::setUniform(int location, const QVector2D &value)
{
if (location >= 0) {
glUniform2fv(location, 1, (const GLfloat *)&value);
}
return (location >= 0);
}
bool GLShader::setUniform(int location, const QVector3D &value)
{
if (location >= 0) {
glUniform3fv(location, 1, (const GLfloat *)&value);
}
return (location >= 0);
}
bool GLShader::setUniform(int location, const QVector4D &value)
{
if (location >= 0) {
glUniform4fv(location, 1, (const GLfloat *)&value);
}
return (location >= 0);
}
bool GLShader::setUniform(int location, const QMatrix3x3 &value)
{
if (location >= 0) {
glUniformMatrix3fv(location, 1, GL_FALSE, value.constData());
}
return location >= 0;
}
bool GLShader::setUniform(int location, const QMatrix4x4 &value)
{
if (location >= 0) {
glUniformMatrix4fv(location, 1, GL_FALSE, value.constData());
}
return (location >= 0);
}
bool GLShader::setUniform(int location, const QColor &color)
{
if (location >= 0) {
glUniform4f(location, color.redF(), color.greenF(), color.blueF(), color.alphaF());
}
return (location >= 0);
}
int GLShader::attributeLocation(const char *name)
{
int location = glGetAttribLocation(m_program, name);
return location;
}
bool GLShader::setAttribute(const char *name, float value)
{
int location = attributeLocation(name);
if (location >= 0) {
glVertexAttrib1f(location, value);
}
return (location >= 0);
}
QMatrix4x4 GLShader::getUniformMatrix4x4(const char *name)
{
int location = uniformLocation(name);
if (location >= 0) {
GLfloat m[16];
OpenGlContext::currentContext()->glGetnUniformfv(m_program, location, sizeof(m), m);
QMatrix4x4 matrix(m[0], m[4], m[8], m[12],
m[1], m[5], m[9], m[13],
m[2], m[6], m[10], m[14],
m[3], m[7], m[11], m[15]);
matrix.optimize();
return matrix;
} else {
return QMatrix4x4();
}
}
static bool s_disableTonemapping = qEnvironmentVariableIntValue("KWIN_DISABLE_TONEMAPPING") == 1;
void GLShader::setColorspaceUniforms(const ColorDescription &src, const ColorDescription &dst, RenderingIntent intent)
{
setUniform(Mat4Uniform::ColorimetryTransformation, src.toOther(dst, intent));
setUniform(IntUniform::SourceNamedTransferFunction, src.transferFunction().type);
setUniform(Vec2Uniform::SourceTransferFunctionParams, QVector2D(src.transferFunction().minLuminance, src.transferFunction().maxLuminance - src.transferFunction().minLuminance));
setUniform(FloatUniform::SourceReferenceLuminance, src.referenceLuminance());
setUniform(IntUniform::DestinationNamedTransferFunction, dst.transferFunction().type);
setUniform(Vec2Uniform::DestinationTransferFunctionParams, QVector2D(dst.transferFunction().minLuminance, dst.transferFunction().maxLuminance - dst.transferFunction().minLuminance));
setUniform(FloatUniform::DestinationReferenceLuminance, dst.referenceLuminance());
setUniform(FloatUniform::MaxDestinationLuminance, dst.maxHdrLuminance().value_or(10'000));
if (!s_disableTonemapping && intent == RenderingIntent::Perceptual) {
setUniform(FloatUniform::MaxTonemappingLuminance, src.maxHdrLuminance().value_or(src.referenceLuminance()) * dst.referenceLuminance() / src.referenceLuminance());
} else {
setUniform(FloatUniform::MaxTonemappingLuminance, dst.referenceLuminance());
}
setUniform(Mat4Uniform::DestinationToLMS, dst.containerColorimetry().toLMS());
setUniform(Mat4Uniform::LMSToDestination, dst.containerColorimetry().fromLMS());
}
}
@@ -0,0 +1,173 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/colorspace.h"
#include <QColor>
#include <QMatrix3x3>
#include <QMatrix4x4>
#include <QString>
#include <QVector2D>
#include <QVector3D>
#include <epoxy/gl.h>
namespace KWin
{
class KWIN_EXPORT GLShader
{
public:
enum Flags {
NoFlags = 0,
ExplicitLinking = (1 << 0)
};
GLShader(const QString &vertexfile, const QString &fragmentfile, unsigned int flags = NoFlags);
~GLShader();
bool isValid() const
{
return m_valid;
}
void bindAttributeLocation(const char *name, int index);
void bindFragDataLocation(const char *name, int index);
bool link();
int uniformLocation(const char *name);
bool setUniform(const char *name, float value);
bool setUniform(const char *name, double value);
bool setUniform(const char *name, int value);
bool setUniform(const char *name, const QVector2D &value);
bool setUniform(const char *name, const QVector3D &value);
bool setUniform(const char *name, const QVector4D &value);
bool setUniform(const char *name, const QMatrix3x3 &value);
bool setUniform(const char *name, const QMatrix4x4 &value);
bool setUniform(const char *name, const QColor &color);
bool setUniform(int location, float value);
bool setUniform(int location, double value);
bool setUniform(int location, int value);
bool setUniform(int location, int xValue, int yValue, int zValue);
bool setUniform(int location, const QVector2D &value);
bool setUniform(int location, const QVector3D &value);
bool setUniform(int location, const QVector4D &value);
bool setUniform(int location, const QMatrix3x3 &value);
bool setUniform(int location, const QMatrix4x4 &value);
bool setUniform(int location, const QColor &value);
int attributeLocation(const char *name);
bool setAttribute(const char *name, float value);
/**
* @return The value of the uniform as a matrix
* @since 4.7
*/
QMatrix4x4 getUniformMatrix4x4(const char *name);
enum class Mat3Uniform {
};
enum class Mat4Uniform {
TextureMatrix = 0,
ProjectionMatrix,
ModelViewMatrix,
ModelViewProjectionMatrix,
WindowTransformation,
ScreenTransformation,
ColorimetryTransformation,
DestinationToLMS,
LMSToDestination,
MatrixCount
};
enum class Vec2Uniform {
Offset,
SourceTransferFunctionParams,
DestinationTransferFunctionParams,
Vec2UniformCount
};
enum class Vec3Uniform {
PrimaryBrightness = 0
};
enum class Vec4Uniform {
ModulationConstant,
Vec4UniformCount
};
enum class FloatUniform {
Saturation,
MaxDestinationLuminance,
SourceReferenceLuminance,
DestinationReferenceLuminance,
MaxTonemappingLuminance,
FloatUniformCount
};
enum class IntUniform {
AlphaToOne, ///< @deprecated no longer used
TextureWidth,
TextureHeight,
SourceNamedTransferFunction,
DestinationNamedTransferFunction,
Sampler,
Sampler1,
IntUniformCount
};
enum class ColorUniform {
Color,
ColorUniformCount
};
bool setUniform(Mat3Uniform uniform, const QMatrix3x3 &value);
bool setUniform(Mat4Uniform uniform, const QMatrix4x4 &matrix);
bool setUniform(Vec2Uniform uniform, const QVector2D &value);
bool setUniform(Vec3Uniform uniform, const QVector3D &value);
bool setUniform(Vec4Uniform uniform, const QVector4D &value);
bool setUniform(FloatUniform uniform, float value);
bool setUniform(IntUniform uniform, int value);
bool setUniform(ColorUniform uniform, const QVector4D &value);
bool setUniform(ColorUniform uniform, const QColor &value);
void setColorspaceUniforms(const ColorDescription &src, const ColorDescription &dst, RenderingIntent intent);
protected:
GLShader(unsigned int flags = NoFlags);
bool loadFromFiles(const QString &vertexfile, const QString &fragmentfile);
bool load(const QByteArray &vertexSource, const QByteArray &fragmentSource);
const QByteArray prepareSource(GLenum shaderType, const QByteArray &sourceCode) const;
bool compile(GLuint program, GLenum shaderType, const QByteArray &sourceCode) const;
void bind();
void unbind();
void resolveLocations();
private:
unsigned int m_program;
bool m_valid : 1;
bool m_locationsResolved : 1;
bool m_explicitLinking : 1;
QHash<Mat3Uniform, int> m_matrix3Locations;
QHash<Mat4Uniform, int> m_matrix4Locations;
QHash<Vec2Uniform, int> m_vec2Locations;
QHash<Vec3Uniform, int> m_vec3Locations;
QHash<Vec4Uniform, int> m_vec4Locations;
QHash<FloatUniform, int> m_floatLocations;
QHash<IntUniform, int> m_intLocations;
QHash<ColorUniform, int> m_colorLocations;
friend class ShaderManager;
};
}
@@ -0,0 +1,380 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "glshadermanager.h"
#include "glplatform.h"
#include "glshader.h"
#include "glvertexbuffer.h"
#include "utils/common.h"
#include <QFile>
#include <QTextStream>
namespace KWin
{
ShaderManager *ShaderManager::instance()
{
return OpenGlContext::currentContext()->shaderManager();
}
ShaderManager::ShaderManager()
{
}
ShaderManager::~ShaderManager()
{
while (!m_boundShaders.isEmpty()) {
popShader();
}
}
QByteArray ShaderManager::generateVertexSource(ShaderTraits traits) const
{
QByteArray source;
QTextStream stream(&source);
const auto context = OpenGlContext::currentContext();
QByteArray attribute, varying;
if (!context->isOpenGLES()) {
const bool glsl_140 = context->glslVersion() >= Version(1, 40);
attribute = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute");
varying = glsl_140 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying");
if (glsl_140) {
stream << "#version 140\n\n";
}
} else {
const bool glsl_es_300 = context->glslVersion() >= Version(3, 0);
attribute = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("attribute");
varying = glsl_es_300 ? QByteArrayLiteral("out") : QByteArrayLiteral("varying");
if (glsl_es_300) {
stream << "#version 300 es\n\n";
}
}
stream << attribute << " vec4 position;\n";
if (traits & (ShaderTrait::MapTexture | ShaderTrait::MapExternalTexture)) {
stream << attribute << " vec4 texcoord;\n\n";
stream << varying << " vec2 texcoord0;\n\n";
} else {
stream << "\n";
}
stream << "uniform mat4 modelViewProjectionMatrix;\n\n";
stream << "void main()\n{\n";
if (traits & (ShaderTrait::MapTexture | ShaderTrait::MapExternalTexture)) {
stream << " texcoord0 = texcoord.st;\n";
}
stream << " gl_Position = modelViewProjectionMatrix * position;\n";
stream << "}\n";
stream.flush();
return source;
}
QByteArray ShaderManager::generateFragmentSource(ShaderTraits traits) const
{
QByteArray source;
QTextStream stream(&source);
const auto context = OpenGlContext::currentContext();
QByteArray varying, output, textureLookup;
if (!context->isOpenGLES()) {
const bool glsl_140 = context->glslVersion() >= Version(1, 40);
if (glsl_140) {
stream << "#version 140\n\n";
}
varying = glsl_140 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying");
textureLookup = glsl_140 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D");
output = glsl_140 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor");
} else {
const bool glsl_es_300 = context->glslVersion() >= Version(3, 0);
if (glsl_es_300) {
stream << "#version 300 es\n\n";
}
// From the GLSL ES specification:
//
// "The fragment language has no default precision qualifier for floating point types."
stream << "precision highp float;\n\n";
varying = glsl_es_300 ? QByteArrayLiteral("in") : QByteArrayLiteral("varying");
textureLookup = glsl_es_300 ? QByteArrayLiteral("texture") : QByteArrayLiteral("texture2D");
output = glsl_es_300 ? QByteArrayLiteral("fragColor") : QByteArrayLiteral("gl_FragColor");
}
if (traits & ShaderTrait::MapTexture) {
stream << "uniform sampler2D sampler;\n";
stream << "uniform sampler2D sampler1;\n";
stream << "uniform int converter;\n";
stream << varying << " vec2 texcoord0;\n";
} else if (traits & ShaderTrait::MapExternalTexture) {
stream << "#extension GL_OES_EGL_image_external : require\n\n";
stream << "uniform samplerExternalOES sampler;\n";
stream << varying << " vec2 texcoord0;\n";
} else if (traits & ShaderTrait::UniformColor) {
stream << "uniform vec4 geometryColor;\n";
}
if (traits & ShaderTrait::Modulate) {
stream << "uniform vec4 modulation;\n";
}
if (traits & ShaderTrait::AdjustSaturation) {
stream << "#include \"saturation.glsl\"\n";
}
if (traits & ShaderTrait::TransformColorspace) {
stream << "#include \"colormanagement.glsl\"\n";
}
if (output != QByteArrayLiteral("gl_FragColor")) {
stream << "\nout vec4 " << output << ";\n";
}
if (traits & ShaderTrait::MapTexture) {
// limited range BT601 in -> full range BT709 out
stream << "vec4 transformY_UV(sampler2D tex0, sampler2D tex1, vec2 texcoord0) {\n";
stream << " float y = 1.16438356 * (" << textureLookup << "(tex0, texcoord0).x - 0.0625);\n";
stream << " float u = " << textureLookup << "(tex1, texcoord0).r - 0.5;\n";
stream << " float v = " << textureLookup << "(tex1, texcoord0).g - 0.5;\n";
stream << " return vec4(y + 1.59602678 * v"
" , y - 0.39176229 * u - 0.81296764 * v"
" , y + 2.01723214 * u"
" , 1);\n";
stream << "}\n";
stream << "\n";
}
stream << "\nvoid main(void)\n{\n";
stream << " vec4 result;\n";
if (traits & ShaderTrait::MapTexture) {
stream << " if (converter == 0) {\n";
stream << " result = " << textureLookup << "(sampler, texcoord0);\n";
stream << " } else {\n";
stream << " result = transformY_UV(sampler, sampler1, texcoord0);\n";
stream << " }\n";
} else if (traits & ShaderTrait::MapExternalTexture) {
// external textures require texture2D for sampling
stream << " result = texture2D(sampler, texcoord0);\n";
} else if (traits & ShaderTrait::UniformColor) {
stream << " result = geometryColor;\n";
}
if (traits & ShaderTrait::TransformColorspace) {
stream << " result = sourceEncodingToNitsInDestinationColorspace(result);\n";
}
if (traits & ShaderTrait::AdjustSaturation) {
stream << " result = adjustSaturation(result);\n";
}
if (traits & ShaderTrait::Modulate) {
stream << " result *= modulation;\n";
}
if (traits & ShaderTrait::TransformColorspace) {
stream << " result = nitsToDestinationEncoding(result);\n";
}
stream << " " << output << " = result;\n";
stream << "}";
stream.flush();
return source;
}
std::unique_ptr<GLShader> ShaderManager::generateShader(ShaderTraits traits)
{
return generateCustomShader(traits);
}
std::optional<QByteArray> ShaderManager::preprocess(const QByteArray &src, int recursionDepth) const
{
recursionDepth++;
if (recursionDepth > 10) {
qCWarning(KWIN_OPENGL, "shader has too many recursive includes!");
return std::nullopt;
}
QByteArray ret;
ret.reserve(src.size());
const auto split = src.split('\n');
for (auto it = split.begin(); it != split.end(); it++) {
const auto &line = *it;
if (line.startsWith("#include \"") && line.endsWith("\"")) {
static constexpr ssize_t includeLength = QByteArrayView("#include \"").size();
const QByteArray path = ":/opengl/" + line.mid(includeLength, line.size() - includeLength - 1);
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
qCWarning(KWIN_OPENGL, "failed to read include line %s", qPrintable(line));
return std::nullopt;
}
const auto processed = preprocess(file.readAll(), recursionDepth);
if (!processed) {
return std::nullopt;
}
ret.append(*processed);
} else {
ret.append(line);
ret.append('\n');
}
}
return ret;
}
std::unique_ptr<GLShader> ShaderManager::generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource, const QByteArray &fragmentSource)
{
const auto vertex = preprocess(vertexSource.isEmpty() ? generateVertexSource(traits) : vertexSource);
const auto fragment = preprocess(fragmentSource.isEmpty() ? generateFragmentSource(traits) : fragmentSource);
if (!vertex || !fragment) {
return nullptr;
}
std::unique_ptr<GLShader> shader{new GLShader(GLShader::ExplicitLinking)};
shader->load(*vertex, *fragment);
shader->bindAttributeLocation("position", VA_Position);
shader->bindAttributeLocation("texcoord", VA_TexCoord);
shader->bindFragDataLocation("fragColor", 0);
shader->link();
return shader;
}
static QString resolveShaderFilePath(const QString &filePath)
{
QString suffix;
QString extension;
const auto context = OpenGlContext::currentContext();
const Version coreVersionNumber = context->isOpenGLES() ? Version(3, 0) : Version(1, 40);
if (context->glslVersion() >= coreVersionNumber) {
suffix = QStringLiteral("_core");
}
if (filePath.endsWith(QStringLiteral(".frag"))) {
extension = QStringLiteral(".frag");
} else if (filePath.endsWith(QStringLiteral(".vert"))) {
extension = QStringLiteral(".vert");
} else {
qCWarning(KWIN_OPENGL) << filePath << "must end either with .vert or .frag";
return QString();
}
const QString prefix = filePath.chopped(extension.size());
return prefix + suffix + extension;
}
std::unique_ptr<GLShader> ShaderManager::generateShaderFromFile(ShaderTraits traits, const QString &vertexFile, const QString &fragmentFile)
{
auto loadShaderFile = [](const QString &filePath) {
QFile file(filePath);
if (file.open(QIODevice::ReadOnly)) {
return file.readAll();
}
qCCritical(KWIN_OPENGL) << "Failed to read shader " << filePath;
return QByteArray();
};
QByteArray vertexSource;
QByteArray fragmentSource;
if (!vertexFile.isEmpty()) {
vertexSource = loadShaderFile(resolveShaderFilePath(vertexFile));
if (vertexSource.isEmpty()) {
return std::unique_ptr<GLShader>(new GLShader());
}
}
if (!fragmentFile.isEmpty()) {
fragmentSource = loadShaderFile(resolveShaderFilePath(fragmentFile));
if (fragmentSource.isEmpty()) {
return std::unique_ptr<GLShader>(new GLShader());
}
}
return generateCustomShader(traits, vertexSource, fragmentSource);
}
GLShader *ShaderManager::shader(ShaderTraits traits)
{
std::unique_ptr<GLShader> &shader = m_shaderHash[traits];
if (!shader) {
shader = generateShader(traits);
}
return shader.get();
}
GLShader *ShaderManager::getBoundShader() const
{
if (m_boundShaders.isEmpty()) {
return nullptr;
} else {
return m_boundShaders.top();
}
}
bool ShaderManager::isShaderBound() const
{
return !m_boundShaders.isEmpty();
}
GLShader *ShaderManager::pushShader(ShaderTraits traits)
{
GLShader *shader = this->shader(traits);
pushShader(shader);
return shader;
}
void ShaderManager::pushShader(GLShader *shader)
{
// only bind shader if it is not already bound
if (shader != getBoundShader()) {
shader->bind();
}
m_boundShaders.push(shader);
}
void ShaderManager::popShader()
{
if (m_boundShaders.isEmpty()) {
return;
}
GLShader *shader = m_boundShaders.pop();
if (m_boundShaders.isEmpty()) {
// no more shader bound - unbind
shader->unbind();
} else if (shader != m_boundShaders.top()) {
// only rebind if a different shader is on top of stack
m_boundShaders.top()->bind();
}
}
void ShaderManager::bindFragDataLocations(GLShader *shader)
{
shader->bindFragDataLocation("fragColor", 0);
}
void ShaderManager::bindAttributeLocations(GLShader *shader) const
{
shader->bindAttributeLocation("vertex", VA_Position);
shader->bindAttributeLocation("texCoord", VA_TexCoord);
}
std::unique_ptr<GLShader> ShaderManager::loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource)
{
std::unique_ptr<GLShader> shader{new GLShader(GLShader::ExplicitLinking)};
shader->load(vertexSource, fragmentSource);
bindAttributeLocations(shader.get());
bindFragDataLocations(shader.get());
shader->link();
return shader;
}
}
@@ -0,0 +1,228 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QByteArray>
#include <QFlags>
#include <QStack>
#include <map>
#include <memory>
namespace KWin
{
class GLShader;
enum class ShaderTrait {
MapTexture = (1 << 0),
UniformColor = (1 << 1),
Modulate = (1 << 2),
AdjustSaturation = (1 << 3),
TransformColorspace = (1 << 4),
MapExternalTexture = (1 << 5),
};
Q_DECLARE_FLAGS(ShaderTraits, ShaderTrait)
/**
* @short Manager for Shaders.
*
* This class provides some built-in shaders to be used by both compositing scene and effects.
* The ShaderManager provides methods to bind a built-in or a custom shader and keeps track of
* the shaders which have been bound. When a shader is unbound the previously bound shader
* will be rebound.
*
* @author Martin Gräßlin <mgraesslin@kde.org>
* @since 4.7
*/
class KWIN_EXPORT ShaderManager
{
public:
explicit ShaderManager();
~ShaderManager();
/**
* Returns a shader with the given traits, creating it if necessary.
*/
GLShader *shader(ShaderTraits traits);
/**
* @return The currently bound shader or @c null if no shader is bound.
*/
GLShader *getBoundShader() const;
/**
* @return @c true if a shader is bound, @c false otherwise
*/
bool isShaderBound() const;
/**
* Pushes the current shader onto the stack and binds a shader
* with the given traits.
*/
GLShader *pushShader(ShaderTraits traits);
/**
* Binds the @p shader.
* To unbind the shader use popShader. A previous bound shader will be rebound.
* To bind a built-in shader use the more specific method.
* @param shader The shader to be bound
* @see popShader
*/
void pushShader(GLShader *shader);
/**
* Unbinds the currently bound shader and rebinds a previous stored shader.
* If there is no previous shader, no shader will be rebound.
* It is not safe to call this method if there is no bound shader.
* @see pushShader
* @see getBoundShader
*/
void popShader();
/**
* Creates a GLShader with the specified sources.
* The difference to GLShader is that it does not need to be loaded from files.
* @param vertexSource The source code of the vertex shader
* @param fragmentSource The source code of the fragment shader.
* @return The created shader
*/
std::unique_ptr<GLShader> loadShaderFromCode(const QByteArray &vertexSource, const QByteArray &fragmentSource);
/**
* Creates a custom shader with the given @p traits and custom @p vertexSource and or @p fragmentSource.
* If the @p vertexSource is empty a vertex shader with the given @p traits is generated.
* If it is not empty the @p vertexSource is used as the source for the vertex shader.
*
* The same applies for argument @p fragmentSource just for the fragment shader.
*
* So if both @p vertesSource and @p fragmentSource are provided the @p traits are ignored.
* If neither are provided a new shader following the @p traits is generated.
*
* @param traits The shader traits for generating the shader
* @param vertexSource optional vertex shader source code to be used instead of shader traits
* @param fragmentSource optional fragment shader source code to be used instead of shader traits
* @return new generated shader
* @since 5.6
*/
std::unique_ptr<GLShader> generateCustomShader(ShaderTraits traits, const QByteArray &vertexSource = QByteArray(), const QByteArray &fragmentSource = QByteArray());
/**
* Creates a custom shader with the given @p traits and custom @p vertexFile and or @p fragmentFile.
*
* If the @p vertexFile is empty a vertex shader with the given @p traits is generated.
* If it is not empty the @p vertexFile is used as the source for the vertex shader.
*
* The same applies for argument @p fragmentFile just for the fragment shader.
*
* So if both @p vertexFile and @p fragmentFile are provided the @p traits are ignored.
* If neither are provided a new shader following the @p traits is generated.
*
* If a custom shader stage is provided and core profile is used, the final file path will
* be resolved by appending "_core" to the basename.
*
* @param traits The shader traits for generating the shader
* @param vertexFile optional vertex shader source code to be used instead of shader traits
* @param fragmentFile optional fragment shader source code to be used instead of shader traits
* @return new generated shader
* @see generateCustomShader
*/
std::unique_ptr<GLShader> generateShaderFromFile(ShaderTraits traits, const QString &vertexFile = QString(), const QString &fragmentFile = QString());
/**
* @return a pointer to the ShaderManager instance
*/
static ShaderManager *instance();
private:
void bindFragDataLocations(GLShader *shader);
void bindAttributeLocations(GLShader *shader) const;
std::optional<QByteArray> preprocess(const QByteArray &src, int recursionDepth = 0) const;
QByteArray generateVertexSource(ShaderTraits traits) const;
QByteArray generateFragmentSource(ShaderTraits traits) const;
std::unique_ptr<GLShader> generateShader(ShaderTraits traits);
QStack<GLShader *> m_boundShaders;
std::map<ShaderTraits, std::unique_ptr<GLShader>> m_shaderHash;
};
/**
* An helper class to push a Shader on to ShaderManager's stack and ensuring that the Shader
* gets popped again from the stack automatically once the object goes out of life.
*
* How to use:
* @code
* {
* GLShader *myCustomShaderIWantToPush;
* ShaderBinder binder(myCustomShaderIWantToPush);
* // do stuff with the shader being pushed on the stack
* }
* // here the Shader is automatically popped as helper does no longer exist.
* @endcode
*
* @since 4.10
*/
class KWIN_EXPORT ShaderBinder
{
public:
/**
* @brief Pushes the given @p shader to the ShaderManager's stack.
*
* @param shader The Shader to push on the stack
* @see ShaderManager::pushShader
*/
explicit ShaderBinder(GLShader *shader);
/**
* @brief Pushes the Shader with the given @p traits to the ShaderManager's stack.
*
* @param traits The traits describing the shader
* @see ShaderManager::pushShader
* @since 5.6
*/
explicit ShaderBinder(ShaderTraits traits);
~ShaderBinder();
/**
* @return The Shader pushed to the Stack.
*/
GLShader *shader();
private:
GLShader *m_shader;
};
inline ShaderBinder::ShaderBinder(GLShader *shader)
: m_shader(shader)
{
ShaderManager::instance()->pushShader(shader);
}
inline ShaderBinder::ShaderBinder(ShaderTraits traits)
: m_shader(nullptr)
{
m_shader = ShaderManager::instance()->pushShader(traits);
}
inline ShaderBinder::~ShaderBinder()
{
ShaderManager::instance()->popShader();
}
inline GLShader *ShaderBinder::shader()
{
return m_shader;
}
}
Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::ShaderTraits)
@@ -0,0 +1,604 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2012 Philipp Knechtges <philipp-dev@knechtges.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "gltexture_p.h"
#include "opengl/glframebuffer.h"
#include "opengl/glplatform.h"
#include "opengl/glutils.h"
#include "utils/common.h"
#include <QImage>
#include <QPixmap>
#include <QVector2D>
#include <QVector3D>
#include <QVector4D>
namespace KWin
{
// Table of GL formats/types associated with different values of QImage::Format.
// Zero values indicate a direct upload is not feasible.
//
// Note: Blending is set up to expect premultiplied data, so the non-premultiplied
// Format_ARGB32 must be converted to Format_ARGB32_Premultiplied ahead of time.
struct
{
GLenum internalFormat;
GLenum format;
GLenum type;
} static const formatTable[] = {
{0, 0, 0}, // QImage::Format_Invalid
{0, 0, 0}, // QImage::Format_Mono
{0, 0, 0}, // QImage::Format_MonoLSB
{0, 0, 0}, // QImage::Format_Indexed8
{GL_RGB8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV}, // QImage::Format_RGB32
{0, 0, 0}, // QImage::Format_ARGB32
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV}, // QImage::Format_ARGB32_Premultiplied
{GL_RGB8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // QImage::Format_RGB16
{0, 0, 0}, // QImage::Format_ARGB8565_Premultiplied
{0, 0, 0}, // QImage::Format_RGB666
{0, 0, 0}, // QImage::Format_ARGB6666_Premultiplied
{GL_RGB5, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // QImage::Format_RGB555
{0, 0, 0}, // QImage::Format_ARGB8555_Premultiplied
{GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE}, // QImage::Format_RGB888
{GL_RGB4, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // QImage::Format_RGB444
{GL_RGBA4, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // QImage::Format_ARGB4444_Premultiplied
{GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE}, // QImage::Format_RGBX8888
{0, 0, 0}, // QImage::Format_RGBA8888
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // QImage::Format_RGBA8888_Premultiplied
{GL_RGB10, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // QImage::Format_BGR30
{GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // QImage::Format_A2BGR30_Premultiplied
{GL_RGB10, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV}, // QImage::Format_RGB30
{GL_RGB10_A2, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV}, // QImage::Format_A2RGB30_Premultiplied
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // QImage::Format_Alpha8
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // QImage::Format_Grayscale8
{GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT}, // QImage::Format_RGBX64
{0, 0, 0}, // QImage::Format_RGBA64
{GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT}, // QImage::Format_RGBA64_Premultiplied
{GL_R16, GL_RED, GL_UNSIGNED_SHORT}, // QImage::Format_Grayscale16
{0, 0, 0}, // QImage::Format_BGR888
};
GLTexture::GLTexture(GLenum target)
: d(std::make_unique<GLTexturePrivate>())
{
d->m_target = target;
}
GLTexture::GLTexture(GLenum target, GLuint textureId, GLenum internalFormat, const QSize &size, int levels, bool owning, OutputTransform transform)
: GLTexture(target)
{
d->m_owning = owning;
d->m_texture = textureId;
d->m_scale.setWidth(1.0 / size.width());
d->m_scale.setHeight(1.0 / size.height());
d->m_size = size;
d->m_canUseMipmaps = levels > 1;
d->m_mipLevels = levels;
d->m_filter = levels > 1 ? GL_NEAREST_MIPMAP_LINEAR : GL_NEAREST;
d->m_internalFormat = internalFormat;
d->m_textureToBufferTransform = transform;
d->updateMatrix();
}
GLTexture::~GLTexture()
{
}
bool GLTexture::create()
{
if (!isNull()) {
return true;
}
glGenTextures(1, &d->m_texture);
return d->m_texture != GL_NONE;
}
GLTexturePrivate::GLTexturePrivate()
: m_texture(0)
, m_target(0)
, m_internalFormat(0)
, m_filter(GL_NEAREST)
, m_wrapMode(GL_REPEAT)
, m_canUseMipmaps(false)
, m_markedDirty(false)
, m_filterChanged(true)
, m_wrapModeChanged(false)
, m_owning(true)
, m_mipLevels(1)
, m_unnormalizeActive(0)
, m_normalizeActive(0)
{
}
GLTexturePrivate::~GLTexturePrivate()
{
if (!OpenGlContext::currentContext()) {
qCWarning(KWIN_OPENGL, "Could not delete texture because no context is current");
return;
}
if (m_texture != 0 && m_owning) {
glDeleteTextures(1, &m_texture);
}
}
bool GLTexture::isNull() const
{
return GL_NONE == d->m_texture;
}
QSize GLTexture::size() const
{
return d->m_size;
}
void GLTexture::setSize(const QSize &size)
{
if (!isNull()) {
return;
}
d->m_size = size;
d->updateMatrix();
}
void GLTexture::update(const QImage &image, const QRegion &region, const QPoint &offset)
{
if (image.isNull() || isNull()) {
return;
}
Q_ASSERT(d->m_owning);
const auto context = OpenGlContext::currentContext();
GLenum glFormat;
GLenum type;
QImage::Format uploadFormat;
if (!context->isOpenGLES()) {
const QImage::Format index = image.format();
if (index < sizeof(formatTable) / sizeof(formatTable[0]) && formatTable[index].internalFormat) {
glFormat = formatTable[index].format;
type = formatTable[index].type;
uploadFormat = index;
} else {
glFormat = GL_BGRA;
type = GL_UNSIGNED_INT_8_8_8_8_REV;
uploadFormat = QImage::Format_ARGB32_Premultiplied;
}
} else {
if (context->supportsARGB32Textures()) {
glFormat = GL_BGRA_EXT;
type = GL_UNSIGNED_BYTE;
uploadFormat = QImage::Format_ARGB32_Premultiplied;
} else {
glFormat = GL_RGBA;
type = GL_UNSIGNED_BYTE;
uploadFormat = QImage::Format_RGBA8888_Premultiplied;
}
}
QImage im = image;
if (im.format() != uploadFormat) {
im.convertTo(uploadFormat);
}
bind();
for (const QRect &rect : region) {
Q_ASSERT(im.depth() % 8 == 0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, im.bytesPerLine() / (im.depth() / 8));
glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect.x());
glPixelStorei(GL_UNPACK_SKIP_ROWS, rect.y());
glTexSubImage2D(d->m_target, 0, offset.x() + rect.x(), offset.y() + rect.y(), rect.width(), rect.height(), glFormat, type, im.constBits());
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
unbind();
}
void GLTexture::bind()
{
Q_ASSERT(d->m_texture);
glBindTexture(d->m_target, d->m_texture);
if (d->m_markedDirty) {
onDamage();
}
if (d->m_filterChanged) {
GLenum minFilter = GL_NEAREST;
GLenum magFilter = GL_NEAREST;
switch (d->m_filter) {
case GL_NEAREST:
minFilter = magFilter = GL_NEAREST;
break;
case GL_LINEAR:
minFilter = magFilter = GL_LINEAR;
break;
case GL_NEAREST_MIPMAP_NEAREST:
case GL_NEAREST_MIPMAP_LINEAR:
magFilter = GL_NEAREST;
minFilter = d->m_canUseMipmaps ? d->m_filter : GL_NEAREST;
break;
case GL_LINEAR_MIPMAP_NEAREST:
case GL_LINEAR_MIPMAP_LINEAR:
magFilter = GL_LINEAR;
minFilter = d->m_canUseMipmaps ? d->m_filter : GL_LINEAR;
break;
}
glTexParameteri(d->m_target, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(d->m_target, GL_TEXTURE_MAG_FILTER, magFilter);
d->m_filterChanged = false;
}
if (d->m_wrapModeChanged) {
glTexParameteri(d->m_target, GL_TEXTURE_WRAP_S, d->m_wrapMode);
glTexParameteri(d->m_target, GL_TEXTURE_WRAP_T, d->m_wrapMode);
d->m_wrapModeChanged = false;
}
}
void GLTexture::generateMipmaps()
{
if (d->m_canUseMipmaps) {
glGenerateMipmap(d->m_target);
}
}
void GLTexture::unbind()
{
glBindTexture(d->m_target, 0);
}
void GLTexture::render(const QSizeF &size)
{
render(infiniteRegion(), size, false);
}
void GLTexture::render(const QRegion &region, const QSizeF &targetSize, bool hardwareClipping)
{
const auto rotatedSize = d->m_textureToBufferTransform.map(size());
render(QRectF(QPoint(), rotatedSize), region, targetSize, hardwareClipping);
}
void GLTexture::render(const QRectF &source, const QRegion &region, const QSizeF &targetSize, bool hardwareClipping)
{
if (targetSize.isEmpty()) {
return; // nothing to paint and m_vbo is likely nullptr and d->m_cachedSize empty as well, #337090
}
const QSize destinationSize = targetSize.toSize(); // TODO: toSize is not enough to snap to the pixel grid, fix render() users and drop this toSize
if (destinationSize != d->m_cachedSize || d->m_cachedSource != source || d->m_cachedContentTransform != d->m_textureToBufferTransform) {
d->m_cachedSize = destinationSize;
d->m_cachedSource = source;
d->m_cachedContentTransform = d->m_textureToBufferTransform;
const float texWidth = (target() == GL_TEXTURE_RECTANGLE_ARB) ? width() : 1.0f;
const float texHeight = (target() == GL_TEXTURE_RECTANGLE_ARB) ? height() : 1.0f;
const QSize rotatedSize = d->m_textureToBufferTransform.map(size());
QMatrix4x4 textureMat;
textureMat.translate(texWidth / 2, texHeight / 2);
// our Y axis is flipped vs OpenGL
textureMat.scale(1, -1);
textureMat *= d->m_textureToBufferTransform.toMatrix();
textureMat.translate(-texWidth / 2, -texHeight / 2);
textureMat.scale(texWidth / rotatedSize.width(), texHeight / rotatedSize.height());
const QPointF p1 = textureMat.map(QPointF(source.x(), source.y()));
const QPointF p2 = textureMat.map(QPointF(source.x(), source.y() + source.height()));
const QPointF p3 = textureMat.map(QPointF(source.x() + source.width(), source.y()));
const QPointF p4 = textureMat.map(QPointF(source.x() + source.width(), source.y() + source.height()));
if (!d->m_vbo) {
d->m_vbo = std::make_unique<GLVertexBuffer>(KWin::GLVertexBuffer::Static);
}
const std::array<GLVertex2D, 4> data{
GLVertex2D{
.position = QVector2D(0, 0),
.texcoord = QVector2D(p1),
},
GLVertex2D{
.position = QVector2D(0, destinationSize.height()),
.texcoord = QVector2D(p2),
},
GLVertex2D{
.position = QVector2D(destinationSize.width(), 0),
.texcoord = QVector2D(p3),
},
GLVertex2D{
.position = QVector2D(destinationSize.width(), destinationSize.height()),
.texcoord = QVector2D(p4),
},
};
d->m_vbo->setVertices(data);
}
bind();
d->m_vbo->render(region, GL_TRIANGLE_STRIP, hardwareClipping);
unbind();
}
GLuint GLTexture::texture() const
{
return d->m_texture;
}
GLenum GLTexture::target() const
{
return d->m_target;
}
GLenum GLTexture::filter() const
{
return d->m_filter;
}
GLenum GLTexture::internalFormat() const
{
return d->m_internalFormat;
}
bool GLTexture::isDirty() const
{
return d->m_markedDirty;
}
void GLTexture::setFilter(GLenum filter)
{
if (filter != d->m_filter) {
d->m_filter = filter;
d->m_filterChanged = true;
}
}
void GLTexture::setWrapMode(GLenum mode)
{
if (mode != d->m_wrapMode) {
d->m_wrapMode = mode;
d->m_wrapModeChanged = true;
}
}
void GLTexture::setDirty()
{
d->m_markedDirty = true;
}
void GLTexture::onDamage()
{
}
void GLTexturePrivate::updateMatrix()
{
const QMatrix4x4 textureToBufferMatrix = m_textureToBufferTransform.toMatrix();
m_matrix[NormalizedCoordinates].setToIdentity();
m_matrix[UnnormalizedCoordinates].setToIdentity();
if (m_target == GL_TEXTURE_RECTANGLE_ARB) {
m_matrix[NormalizedCoordinates].scale(m_size.width(), m_size.height());
} else {
m_matrix[UnnormalizedCoordinates].scale(1.0 / m_size.width(), 1.0 / m_size.height());
}
m_matrix[NormalizedCoordinates].translate(0.5, 0.5);
// our Y axis is flipped vs OpenGL
m_matrix[NormalizedCoordinates].scale(1, -1);
m_matrix[NormalizedCoordinates] *= textureToBufferMatrix;
m_matrix[NormalizedCoordinates].translate(-0.5, -0.5);
m_matrix[UnnormalizedCoordinates].translate(m_size.width() / 2, m_size.height() / 2);
m_matrix[UnnormalizedCoordinates].scale(1, -1);
m_matrix[UnnormalizedCoordinates] *= textureToBufferMatrix;
m_matrix[UnnormalizedCoordinates].translate(-m_size.width() / 2, -m_size.height() / 2);
}
void GLTexture::setContentTransform(OutputTransform transform)
{
if (d->m_textureToBufferTransform != transform) {
d->m_textureToBufferTransform = transform;
d->updateMatrix();
}
}
OutputTransform GLTexture::contentTransform() const
{
return d->m_textureToBufferTransform;
}
void GLTexture::setSwizzle(GLenum red, GLenum green, GLenum blue, GLenum alpha)
{
if (!OpenGlContext::currentContext()->isOpenGLES()) {
const GLuint swizzle[] = {red, green, blue, alpha};
glTexParameteriv(d->m_target, GL_TEXTURE_SWIZZLE_RGBA, (const GLint *)swizzle);
} else {
glTexParameteri(d->m_target, GL_TEXTURE_SWIZZLE_R, red);
glTexParameteri(d->m_target, GL_TEXTURE_SWIZZLE_G, green);
glTexParameteri(d->m_target, GL_TEXTURE_SWIZZLE_B, blue);
glTexParameteri(d->m_target, GL_TEXTURE_SWIZZLE_A, alpha);
}
}
int GLTexture::width() const
{
return d->m_size.width();
}
int GLTexture::height() const
{
return d->m_size.height();
}
QMatrix4x4 GLTexture::matrix(TextureCoordinateType type) const
{
return d->m_matrix[type];
}
bool GLTexture::supportsSwizzle()
{
return OpenGlContext::currentContext()->supportsTextureSwizzle();
}
bool GLTexture::supportsFormatRG()
{
return OpenGlContext::currentContext()->supportsRGTextures();
}
QImage GLTexture::toImage()
{
if (target() != GL_TEXTURE_2D) {
return QImage();
}
QImage ret(size(), QImage::Format_RGBA8888_Premultiplied);
if (OpenGlContext::currentContext()->isOpenGLES()) {
GLFramebuffer fbo(this);
GLFramebuffer::pushFramebuffer(&fbo);
glReadPixels(0, 0, width(), height(), GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ret.bits());
GLFramebuffer::popFramebuffer();
} else {
GLint currentTextureBinding;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &currentTextureBinding);
if (GLuint(currentTextureBinding) != texture()) {
glBindTexture(GL_TEXTURE_2D, texture());
}
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ret.bits());
if (GLuint(currentTextureBinding) != texture()) {
glBindTexture(GL_TEXTURE_2D, currentTextureBinding);
}
}
return ret;
}
std::unique_ptr<GLTexture> GLTexture::createNonOwningWrapper(GLuint textureId, GLenum internalFormat, const QSize &size)
{
return std::unique_ptr<GLTexture>(new GLTexture(GL_TEXTURE_2D, textureId, internalFormat, size, 1, false, OutputTransform{}));
}
std::unique_ptr<GLTexture> GLTexture::allocate(GLenum internalFormat, const QSize &size, int levels)
{
GLuint texture = 0;
glGenTextures(1, &texture);
if (texture == 0) {
qCWarning(KWIN_OPENGL, "generating OpenGL texture handle failed");
return nullptr;
}
glBindTexture(GL_TEXTURE_2D, texture);
const auto context = OpenGlContext::currentContext();
if (!context->isOpenGLES()) {
if (context->supportsTextureStorage()) {
glTexStorage2D(GL_TEXTURE_2D, levels, internalFormat, size.width(), size.height());
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels - 1);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, size.width(), size.height(), 0,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
}
} else {
// The format parameter in glTexSubImage() must match the internal format
// of the texture, so it's important that we allocate the texture with
// the format that will be used in update() and clear().
const GLenum format = context->supportsARGB32Textures() ? GL_BGRA_EXT : GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, format, size.width(), size.height(), 0,
format, GL_UNSIGNED_BYTE, nullptr);
// The internalFormat is technically not correct, but it means that code that calls
// internalFormat() won't need to be specialized for GLES2.
}
glBindTexture(GL_TEXTURE_2D, 0);
return std::unique_ptr<GLTexture>(new GLTexture(GL_TEXTURE_2D, texture, internalFormat, size, levels, true, OutputTransform{}));
}
std::unique_ptr<GLTexture> GLTexture::upload(const QImage &image)
{
if (image.isNull()) {
return nullptr;
}
GLuint texture = 0;
glGenTextures(1, &texture);
if (texture == 0) {
qCWarning(KWIN_OPENGL, "generating OpenGL texture handle failed");
return nullptr;
}
const auto context = OpenGlContext::currentContext();
GLenum internalFormat;
GLenum format;
GLenum type;
QImage::Format uploadFormat;
if (!context->isOpenGLES()) {
const QImage::Format index = image.format();
if (index < sizeof(formatTable) / sizeof(formatTable[0]) && formatTable[index].internalFormat) {
internalFormat = formatTable[index].internalFormat;
format = formatTable[index].format;
type = formatTable[index].type;
uploadFormat = index;
} else {
internalFormat = GL_RGBA8;
format = GL_BGRA;
type = GL_UNSIGNED_INT_8_8_8_8_REV;
uploadFormat = QImage::Format_ARGB32_Premultiplied;
}
} else {
if (context->supportsARGB32Textures()) {
internalFormat = GL_BGRA_EXT;
format = GL_BGRA_EXT;
type = GL_UNSIGNED_BYTE;
uploadFormat = QImage::Format_ARGB32_Premultiplied;
} else {
internalFormat = GL_RGBA;
format = GL_RGBA;
type = GL_UNSIGNED_BYTE;
uploadFormat = QImage::Format_RGBA8888_Premultiplied;
}
}
QImage im = image;
if (im.format() != uploadFormat) {
im.convertTo(uploadFormat);
}
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ROW_LENGTH, im.bytesPerLine() / (im.depth() / 8));
if (!context->isOpenGLES()) {
if (context->supportsTextureStorage()) {
glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, im.width(), im.height());
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, im.width(), im.height(), format, type, im.constBits());
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, im.width(), im.height(), 0, format, type, im.constBits());
}
} else {
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, im.width(), im.height(), 0, format, type, im.constBits());
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindTexture(GL_TEXTURE_2D, 0);
return std::unique_ptr<GLTexture>(new GLTexture(GL_TEXTURE_2D, texture, internalFormat, image.size(), 1, true, OutputTransform::FlipY));
}
std::unique_ptr<GLTexture> GLTexture::upload(const QPixmap &pixmap)
{
return upload(pixmap.toImage());
}
} // namespace KWin
@@ -0,0 +1,144 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/output.h"
#include <QExplicitlySharedDataPointer>
#include <QMatrix4x4>
#include <QRegion>
#include <QSize>
#include <epoxy/gl.h>
class QImage;
class QPixmap;
/** @addtogroup kwineffects */
/** @{ */
namespace KWin
{
class GLVertexBuffer;
class GLTexturePrivate;
enum TextureCoordinateType {
NormalizedCoordinates = 0,
UnnormalizedCoordinates,
};
class KWIN_EXPORT GLTexture
{
public:
explicit GLTexture(GLenum target);
/**
* Creates the underlying texture object. Returns @c true if the texture has been created
* successfully; otherwise returns @c false. Note that this does not allocate any storage
* for the texture.
*/
bool create();
virtual ~GLTexture();
bool isNull() const;
QSize size() const;
void setSize(const QSize &size);
int width() const;
int height() const;
/**
* sets the transform between the content and the buffer
*/
void setContentTransform(OutputTransform transform);
/**
* @returns the transform between the content and the buffer
*/
OutputTransform contentTransform() const;
/**
* Specifies which component of a texel is placed in each respective
* component of the vector returned to the shader.
*
* Valid values are GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_ONE and GL_ZERO.
*
* @see swizzleSupported()
* @since 5.2
*/
void setSwizzle(GLenum red, GLenum green, GLenum blue, GLenum alpha);
/**
* Returns a matrix that transforms texture coordinates of the given type,
* taking the texture target and the y-inversion flag into account.
*
* @since 4.11
*/
QMatrix4x4 matrix(TextureCoordinateType type) const;
void update(const QImage &image, const QRegion &region, const QPoint &offset = QPoint());
void bind();
void unbind();
void render(const QSizeF &size);
void render(const QRegion &region, const QSizeF &size, bool hardwareClipping = false);
void render(const QRectF &source, const QRegion &region, const QSizeF &targetSize, bool hardwareClipping = false);
GLuint texture() const;
GLenum target() const;
GLenum filter() const;
GLenum internalFormat() const;
QImage toImage();
/**
* @deprecated track modifications to the texture yourself
*/
void setDirty();
bool isDirty() const;
void setFilter(GLenum filter);
void setWrapMode(GLenum mode);
void generateMipmaps();
/**
* Returns true if texture swizzle is supported, and false otherwise
*
* Texture swizzle requires OpenGL 3.3, GL_ARB_texture_swizzle, or OpenGL ES 3.0.
*
* @since 5.2
*/
static bool supportsSwizzle();
/**
* Returns @c true if texture formats R* are supported, and @c false otherwise.
*
* This requires OpenGL 3.0, GL_ARB_texture_rg or OpenGL ES 3.0 or GL_EXT_texture_rg.
*
* @since 5.2.1
*/
static bool supportsFormatRG();
static std::unique_ptr<GLTexture> createNonOwningWrapper(GLuint textureId, GLenum internalFormat, const QSize &size);
static std::unique_ptr<GLTexture> allocate(GLenum internalFormat, const QSize &size, int levels = 1);
static std::unique_ptr<GLTexture> upload(const QImage &image);
static std::unique_ptr<GLTexture> upload(const QPixmap &pixmap);
protected:
explicit GLTexture(GLenum target, GLuint textureId, GLenum internalFormat, const QSize &size, int levels, bool owning, OutputTransform transform);
const std::unique_ptr<GLTexturePrivate> d;
virtual void onDamage();
};
} // namespace
/** @} */
@@ -0,0 +1,62 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2011 Philipp Knechtges <philipp-dev@knechtges.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "opengl/glutils.h"
#include <QImage>
#include <QMatrix4x4>
#include <QSharedData>
#include <QSize>
#include <epoxy/gl.h>
namespace KWin
{
// forward declarations
class GLVertexBuffer;
class KWIN_EXPORT GLTexturePrivate
: public QSharedData
{
public:
GLTexturePrivate();
virtual ~GLTexturePrivate();
void updateMatrix();
GLuint m_texture;
GLenum m_target;
GLenum m_internalFormat;
GLenum m_filter;
GLenum m_wrapMode;
QSize m_size;
QSizeF m_scale; // to un-normalize GL_TEXTURE_2D
QMatrix4x4 m_matrix[2];
OutputTransform m_textureToBufferTransform;
bool m_canUseMipmaps;
bool m_markedDirty;
bool m_filterChanged;
bool m_wrapModeChanged;
bool m_owning;
int m_mipLevels;
int m_unnormalizeActive; // 0 - no, otherwise refcount
int m_normalizeActive; // 0 - no, otherwise refcount
std::unique_ptr<GLVertexBuffer> m_vbo;
QSizeF m_cachedSize;
QRectF m_cachedSource;
OutputTransform m_cachedContentTransform;
Q_DISABLE_COPY(GLTexturePrivate)
};
} // namespace
@@ -0,0 +1,61 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "opengl/glutils.h"
#include "glplatform.h"
#include "gltexture_p.h"
#include "utils/common.h"
namespace KWin
{
static QString formatGLError(GLenum err)
{
switch (err) {
case GL_NO_ERROR:
return QStringLiteral("GL_NO_ERROR");
case GL_INVALID_ENUM:
return QStringLiteral("GL_INVALID_ENUM");
case GL_INVALID_VALUE:
return QStringLiteral("GL_INVALID_VALUE");
case GL_INVALID_OPERATION:
return QStringLiteral("GL_INVALID_OPERATION");
case GL_STACK_OVERFLOW:
return QStringLiteral("GL_STACK_OVERFLOW");
case GL_STACK_UNDERFLOW:
return QStringLiteral("GL_STACK_UNDERFLOW");
case GL_OUT_OF_MEMORY:
return QStringLiteral("GL_OUT_OF_MEMORY");
default:
return QLatin1String("0x") + QString::number(err, 16);
}
}
bool checkGLError(const char *txt)
{
GLenum err = glGetError();
if (err == GL_CONTEXT_LOST) {
qCWarning(KWIN_OPENGL) << "GL error: context lost";
return true;
}
bool hasError = false;
while (err != GL_NO_ERROR) {
qCWarning(KWIN_OPENGL) << "GL error (" << txt << "): " << formatGLError(err);
hasError = true;
err = glGetError();
if (err == GL_CONTEXT_LOST) {
qCWarning(KWIN_OPENGL) << "GL error: context lost";
break;
}
}
return hasError;
}
} // namespace
@@ -0,0 +1,32 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/colorspace.h"
#include "opengl/glframebuffer.h"
#include "opengl/glshader.h"
#include "opengl/glshadermanager.h"
#include "opengl/gltexture.h"
#include "opengl/glvertexbuffer.h"
#include <QByteArray>
#include <QList>
#include <functional>
namespace KWin
{
// detect OpenGL error (add to various places in code to pinpoint the place)
bool KWIN_EXPORT checkGLError(const char *txt);
} // namespace
/** @} */
@@ -0,0 +1,591 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "glvertexbuffer.h"
#include "glframebuffer.h"
#include "glplatform.h"
#include "glshader.h"
#include "glshadermanager.h"
#include "glutils.h"
#include "glvertexbuffer_p.h"
#include "utils/common.h"
#include <QVector4D>
#include <bitset>
#include <deque>
namespace KWin
{
// Certain GPUs, especially mobile, require the data copied to the GPU to be aligned to a
// certain amount of bytes. For example, the Mali GPU requires data to be aligned to 8 bytes.
// This function helps ensure that the data is aligned.
template<typename T>
T align(T value, int bytes)
{
return (value + bytes - 1) & ~T(bytes - 1);
}
IndexBuffer::IndexBuffer()
{
// The maximum number of quads we can render with 16 bit indices is 16,384.
// But we start with 512 and grow the buffer as needed.
glGenBuffers(1, &m_buffer);
accommodate(512);
}
IndexBuffer::~IndexBuffer()
{
if (!OpenGlContext::currentContext()) {
qCWarning(KWIN_OPENGL, "Could not delete index buffer because no context is current");
return;
}
glDeleteBuffers(1, &m_buffer);
}
void IndexBuffer::accommodate(size_t count)
{
// Check if we need to grow the buffer.
if (count <= m_count) {
return;
}
Q_ASSERT(m_count * 2 < std::numeric_limits<uint16_t>::max() / 4);
const size_t oldCount = m_count;
m_count *= 2;
m_data.reserve(m_count * 6);
for (size_t i = oldCount; i < m_count; i++) {
const uint16_t offset = i * 4;
m_data[i * 6 + 0] = offset + 1;
m_data[i * 6 + 1] = offset + 0;
m_data[i * 6 + 2] = offset + 3;
m_data[i * 6 + 3] = offset + 3;
m_data[i * 6 + 4] = offset + 2;
m_data[i * 6 + 5] = offset + 1;
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_count * sizeof(uint16_t), m_data.data(), GL_STATIC_DRAW);
}
void IndexBuffer::bind()
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_buffer);
}
// ------------------------------------------------------------------
struct VertexAttrib
{
int size;
GLenum type;
int offset;
};
// ------------------------------------------------------------------
struct BufferFence
{
GLsync sync;
intptr_t nextEnd;
bool signaled() const
{
GLint value;
glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &value);
return value == GL_SIGNALED;
}
};
static void deleteAll(std::deque<BufferFence> &fences)
{
for (const BufferFence &fence : fences) {
glDeleteSync(fence.sync);
}
fences.clear();
}
// ------------------------------------------------------------------
template<size_t Count>
struct FrameSizesArray
{
public:
FrameSizesArray()
{
m_array.fill(0);
}
void push(size_t size)
{
m_array[m_index] = size;
m_index = (m_index + 1) % Count;
}
size_t average() const
{
size_t sum = 0;
for (size_t size : m_array) {
sum += size;
}
return sum / Count;
}
private:
std::array<size_t, Count> m_array;
int m_index = 0;
};
//*********************************
// GLVertexBufferPrivate
//*********************************
class GLVertexBufferPrivate
{
public:
GLVertexBufferPrivate(GLVertexBuffer::UsageHint usageHint)
: vertexCount(0)
, persistent(false)
, bufferSize(0)
, bufferEnd(0)
, mappedSize(0)
, frameSize(0)
, nextOffset(0)
, baseAddress(0)
, map(nullptr)
{
glGenBuffers(1, &buffer);
switch (usageHint) {
case GLVertexBuffer::Dynamic:
usage = GL_DYNAMIC_DRAW;
break;
case GLVertexBuffer::Static:
usage = GL_STATIC_DRAW;
break;
default:
usage = GL_STREAM_DRAW;
break;
}
}
~GLVertexBufferPrivate()
{
if (!OpenGlContext::currentContext()) {
qCWarning(KWIN_OPENGL, "Could not delete vertex buffer because no context is current");
return;
}
deleteAll(fences);
if (buffer != 0) {
glDeleteBuffers(1, &buffer);
map = nullptr;
}
}
void bindArrays();
void unbindArrays();
void reallocateBuffer(size_t size);
GLvoid *mapNextFreeRange(size_t size);
void reallocatePersistentBuffer(size_t size);
bool awaitFence(intptr_t offset);
GLvoid *getIdleRange(size_t size);
GLuint buffer;
GLenum usage;
int vertexCount;
QByteArray dataStore;
bool persistent;
size_t bufferSize;
intptr_t bufferEnd;
size_t mappedSize;
size_t frameSize;
intptr_t nextOffset;
intptr_t baseAddress;
uint8_t *map;
std::deque<BufferFence> fences;
FrameSizesArray<4> frameSizes;
std::array<VertexAttrib, VertexAttributeCount> attrib;
size_t attribStride = 0;
std::bitset<32> enabledArrays;
};
void GLVertexBufferPrivate::bindArrays()
{
glBindBuffer(GL_ARRAY_BUFFER, buffer);
for (size_t i = 0; i < enabledArrays.size(); i++) {
if (enabledArrays[i]) {
glVertexAttribPointer(i, attrib[i].size, attrib[i].type, GL_FALSE, attribStride,
(const GLvoid *)(baseAddress + attrib[i].offset));
glEnableVertexAttribArray(i);
}
}
}
void GLVertexBufferPrivate::unbindArrays()
{
for (size_t i = 0; i < enabledArrays.size(); i++) {
if (enabledArrays[i]) {
glDisableVertexAttribArray(i);
}
}
}
void GLVertexBufferPrivate::reallocatePersistentBuffer(size_t size)
{
if (buffer != 0) {
// This also unmaps and unbinds the buffer
glDeleteBuffers(1, &buffer);
buffer = 0;
deleteAll(fences);
}
if (buffer == 0) {
glGenBuffers(1, &buffer);
}
// Round the size up to 64 kb
size_t minSize = std::max<size_t>(frameSizes.average() * 3, 128 * 1024);
bufferSize = std::max(size, minSize);
const GLbitfield storage = GL_DYNAMIC_STORAGE_BIT;
const GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferStorage(GL_ARRAY_BUFFER, bufferSize, nullptr, storage | access);
map = (uint8_t *)glMapBufferRange(GL_ARRAY_BUFFER, 0, bufferSize, access);
nextOffset = 0;
bufferEnd = bufferSize;
}
bool GLVertexBufferPrivate::awaitFence(intptr_t end)
{
// Skip fences until we reach the end offset
while (!fences.empty() && fences.front().nextEnd < end) {
glDeleteSync(fences.front().sync);
fences.pop_front();
}
Q_ASSERT(!fences.empty());
// Wait on the next fence
const BufferFence &fence = fences.front();
if (!fence.signaled()) {
qCDebug(KWIN_OPENGL) << "Stalling on VBO fence";
const GLenum ret = glClientWaitSync(fence.sync, GL_SYNC_FLUSH_COMMANDS_BIT, 1000000000);
if (ret == GL_TIMEOUT_EXPIRED || ret == GL_WAIT_FAILED) {
qCCritical(KWIN_OPENGL) << "Wait failed";
return false;
}
}
glDeleteSync(fence.sync);
// Update the end pointer
bufferEnd = fence.nextEnd;
fences.pop_front();
return true;
}
GLvoid *GLVertexBufferPrivate::getIdleRange(size_t size)
{
if (size > bufferSize) {
reallocatePersistentBuffer(size * 2);
}
// Handle wrap-around
if ((nextOffset + size > bufferSize)) {
nextOffset = 0;
bufferEnd -= bufferSize;
for (BufferFence &fence : fences) {
fence.nextEnd -= bufferSize;
}
// Emit a fence now
if (auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)) {
fences.push_back(BufferFence{
.sync = sync,
.nextEnd = intptr_t(bufferSize)});
}
}
if (nextOffset + intptr_t(size) > bufferEnd) {
if (!awaitFence(nextOffset + size)) {
return nullptr;
}
}
return map + nextOffset;
}
void GLVertexBufferPrivate::reallocateBuffer(size_t size)
{
// Round the size up to 4 Kb for streaming/dynamic buffers.
const size_t minSize = 32768; // Minimum size for streaming buffers
const size_t alloc = usage != GL_STATIC_DRAW ? std::max(size, minSize) : size;
glBufferData(GL_ARRAY_BUFFER, alloc, nullptr, usage);
bufferSize = alloc;
}
GLvoid *GLVertexBufferPrivate::mapNextFreeRange(size_t size)
{
GLbitfield access = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
if ((nextOffset + size) > bufferSize) {
// Reallocate the data store if it's too small.
if (size > bufferSize) {
reallocateBuffer(size);
} else {
access |= GL_MAP_INVALIDATE_BUFFER_BIT;
access ^= GL_MAP_UNSYNCHRONIZED_BIT;
}
nextOffset = 0;
}
return glMapBufferRange(GL_ARRAY_BUFFER, nextOffset, size, access);
}
GLVertexBuffer::GLVertexBuffer(UsageHint hint)
: d(std::make_unique<GLVertexBufferPrivate>(hint))
{
}
GLVertexBuffer::~GLVertexBuffer() = default;
void GLVertexBuffer::setData(const void *data, size_t size)
{
GLvoid *ptr = map(size);
if (!ptr) {
return;
}
memcpy(ptr, data, size);
unmap();
}
GLvoid *GLVertexBuffer::map(size_t size)
{
d->mappedSize = size;
d->frameSize += size;
if (d->persistent) {
return d->getIdleRange(size);
}
glBindBuffer(GL_ARRAY_BUFFER, d->buffer);
const auto context = OpenGlContext::currentContext();
const bool preferBufferSubData = context->glPlatform()->preferBufferSubData();
if (context->hasMapBufferRange() && !preferBufferSubData) {
return (GLvoid *)d->mapNextFreeRange(size);
}
// If we can't map the buffer we allocate local memory to hold the
// buffer data and return a pointer to it. The data will be submitted
// to the actual buffer object when the user calls unmap().
if (size_t(d->dataStore.size()) < size) {
d->dataStore.resize(size);
}
return (GLvoid *)d->dataStore.data();
}
void GLVertexBuffer::unmap()
{
if (d->persistent) {
d->baseAddress = d->nextOffset;
d->nextOffset += align(d->mappedSize, 8);
d->mappedSize = 0;
return;
}
const auto context = OpenGlContext::currentContext();
const bool preferBufferSubData = context->glPlatform()->preferBufferSubData();
if (context->hasMapBufferRange() && !preferBufferSubData) {
glUnmapBuffer(GL_ARRAY_BUFFER);
d->baseAddress = d->nextOffset;
d->nextOffset += align(d->mappedSize, 8);
} else {
// Upload the data from local memory to the buffer object
if (preferBufferSubData) {
if ((d->nextOffset + d->mappedSize) > d->bufferSize) {
d->reallocateBuffer(d->mappedSize);
d->nextOffset = 0;
}
glBufferSubData(GL_ARRAY_BUFFER, d->nextOffset, d->mappedSize, d->dataStore.constData());
d->baseAddress = d->nextOffset;
d->nextOffset += align(d->mappedSize, 8);
} else {
glBufferData(GL_ARRAY_BUFFER, d->mappedSize, d->dataStore.data(), d->usage);
d->baseAddress = 0;
}
// Free the local memory buffer if it's unlikely to be used again
if (d->usage == GL_STATIC_DRAW) {
d->dataStore = QByteArray();
}
}
d->mappedSize = 0;
}
void GLVertexBuffer::setVertexCount(int count)
{
d->vertexCount = count;
}
void GLVertexBuffer::setAttribLayout(std::span<const GLVertexAttrib> attribs, size_t stride)
{
d->enabledArrays.reset();
for (const auto &attrib : attribs) {
Q_ASSERT(attrib.attributeIndex < d->attrib.size());
d->attrib[attrib.attributeIndex].size = attrib.componentCount;
d->attrib[attrib.attributeIndex].type = attrib.type;
d->attrib[attrib.attributeIndex].offset = attrib.relativeOffset;
d->enabledArrays[attrib.attributeIndex] = true;
}
d->attribStride = stride;
}
void GLVertexBuffer::render(GLenum primitiveMode)
{
render(infiniteRegion(), primitiveMode, false);
}
void GLVertexBuffer::render(const QRegion &region, GLenum primitiveMode, bool hardwareClipping)
{
d->bindArrays();
draw(region, primitiveMode, 0, d->vertexCount, hardwareClipping);
d->unbindArrays();
}
void GLVertexBuffer::bindArrays()
{
d->bindArrays();
}
void GLVertexBuffer::unbindArrays()
{
d->unbindArrays();
}
void GLVertexBuffer::draw(GLenum primitiveMode, int first, int count)
{
draw(infiniteRegion(), primitiveMode, first, count, false);
}
void GLVertexBuffer::draw(const QRegion &region, GLenum primitiveMode, int first, int count, bool hardwareClipping)
{
if (primitiveMode == GL_QUADS) {
OpenGlContext::currentContext()->indexBuffer()->bind();
OpenGlContext::currentContext()->indexBuffer()->accommodate(count / 4);
count = count * 6 / 4;
if (!hardwareClipping) {
glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first);
} else {
// Clip using scissoring
const GLFramebuffer *current = GLFramebuffer::currentFramebuffer();
for (const QRect &r : region) {
glScissor(r.x(), current->size().height() - (r.y() + r.height()), r.width(), r.height());
glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, nullptr, first);
}
}
return;
}
if (!hardwareClipping) {
glDrawArrays(primitiveMode, first, count);
} else {
// Clip using scissoring
const GLFramebuffer *current = GLFramebuffer::currentFramebuffer();
for (const QRect &r : region) {
glScissor(r.x(), current->size().height() - (r.y() + r.height()), r.width(), r.height());
glDrawArrays(primitiveMode, first, count);
}
}
}
void GLVertexBuffer::reset()
{
d->vertexCount = 0;
}
void GLVertexBuffer::endOfFrame()
{
if (!d->persistent) {
return;
}
// Emit a fence if we have uploaded data
if (d->frameSize > 0) {
d->frameSizes.push(d->frameSize);
d->frameSize = 0;
// Force the buffer to be reallocated at the beginning of the next frame
// if the average frame size is greater than half the size of the buffer
if (d->frameSizes.average() > d->bufferSize / 2) {
deleteAll(d->fences);
glDeleteBuffers(1, &d->buffer);
d->buffer = 0;
d->bufferSize = 0;
d->nextOffset = 0;
d->map = nullptr;
} else {
if (auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)) {
d->fences.push_back(BufferFence{
.sync = sync,
.nextEnd = intptr_t(d->nextOffset + d->bufferSize)});
}
}
}
}
void GLVertexBuffer::beginFrame()
{
if (!d->persistent) {
return;
}
// Remove finished fences from the list and update the bufferEnd offset
while (d->fences.size() > 1 && d->fences.front().signaled()) {
const BufferFence &fence = d->fences.front();
glDeleteSync(fence.sync);
d->bufferEnd = fence.nextEnd;
d->fences.pop_front();
}
}
GLVertexBuffer *GLVertexBuffer::streamingBuffer()
{
return OpenGlContext::currentContext()->streamingVbo();
}
void GLVertexBuffer::setPersistent()
{
d->persistent = true;
}
}
@@ -0,0 +1,287 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QColor>
#include <QRegion>
#include <QVector2D>
#include <epoxy/gl.h>
#include <optional>
#include <ranges>
#include <span>
namespace KWin
{
enum VertexAttributeType {
VA_Position = 0,
VA_TexCoord = 1,
VertexAttributeCount = 2,
};
struct GLVertex2D
{
QVector2D position;
QVector2D texcoord;
};
struct GLVertex3D
{
QVector3D position;
QVector2D texcoord;
};
/**
* Describes the format of a vertex attribute stored in a buffer object.
*
* The attribute format consists of the attribute index, the number of
* vector components, the data type, and the offset of the first element
* relative to the start of the vertex data.
*/
struct GLVertexAttrib
{
size_t attributeIndex;
size_t componentCount;
GLenum type; /** The type (e.g. GL_FLOAT) */
size_t relativeOffset; /** The relative offset of the attribute */
};
class GLVertexBufferPrivate;
/**
* @short Vertex Buffer Object
*
* This is a short helper class to use vertex buffer objects (VBO). A VBO can be used to buffer
* vertex data and to store them on graphics memory.
*
* @author Martin Gräßlin <mgraesslin@kde.org>
* @since 4.6
*/
class KWIN_EXPORT GLVertexBuffer
{
public:
/**
* Enum to define how often the vertex data in the buffer object changes.
*/
enum UsageHint {
Dynamic, ///< frequent changes, but used several times for rendering
Static, ///< No changes to data
Stream ///< Data only used once for rendering, updated very frequently
};
explicit GLVertexBuffer(UsageHint hint);
~GLVertexBuffer();
/**
* Specifies how interleaved vertex attributes are laid out in
* the buffer object.
*
* Note that the attributes and the stride should be 32 bit aligned
* or a performance penalty may be incurred.
*
* For some hardware the optimal stride is a multiple of 32 bytes.
*
* Example:
*
* struct Vertex {
* QVector3D position;
* QVector2D texcoord;
* };
*
* const std::array attribs = {
* GLVertexAttrib{ VA_Position, 3, GL_FLOAT, offsetof(Vertex, position) },
* GLVertexAttrib{ VA_TexCoord, 2, GL_FLOAT, offsetof(Vertex, texcoord) },
* };
*
* Vertex vertices[6];
* vbo->setAttribLayout(std::span(attribs), sizeof(Vertex));
* vbo->setData(vertices, sizeof(vertices));
*/
void setAttribLayout(std::span<const GLVertexAttrib> attribs, size_t stride);
/**
* Uploads data into the buffer object's data store.
*/
void setData(const void *data, size_t sizeInBytes);
/**
* Sets the number of vertices that will be drawn by the render() method.
*/
void setVertexCount(int count);
// clang-format off
template<std::ranges::contiguous_range T>
requires std::is_same<std::ranges::range_value_t<T>, GLVertex2D>::value
void setVertices(const T &range)
{
setData(range.data(), range.size() * sizeof(GLVertex2D));
setVertexCount(range.size());
setAttribLayout(std::span(GLVertex2DLayout), sizeof(GLVertex2D));
}
template<std::ranges::contiguous_range T>
requires std::is_same<std::ranges::range_value_t<T>, GLVertex3D>::value
void setVertices(const T &range)
{
setData(range.data(), range.size() * sizeof(GLVertex3D));
setVertexCount(range.size());
setAttribLayout(std::span(GLVertex3DLayout), sizeof(GLVertex3D));
}
template<std::ranges::contiguous_range T>
requires std::is_same<std::ranges::range_value_t<T>, QVector2D>::value
void setVertices(const T &range)
{
setData(range.data(), range.size() * sizeof(QVector2D));
setVertexCount(range.size());
static constexpr GLVertexAttrib layout{
.attributeIndex = VA_Position,
.componentCount = 2,
.type = GL_FLOAT,
.relativeOffset = 0,
};
setAttribLayout(std::span(&layout, 1), sizeof(QVector2D));
}
// clang-format on
/**
* Maps an unused range of the data store into the client's address space.
*
* The data store will be reallocated if it is smaller than the given size.
*
* The buffer object is mapped for writing, not reading. Attempts to read from
* the mapped buffer range may result in system errors, including program
* termination. The data in the mapped region is undefined until it has been
* written to. If subsequent GL calls access unwritten memory, the results are
* undefined and system errors, including program termination, may occur.
*
* No GL calls that access the buffer object must be made while the buffer
* object is mapped. The returned pointer must not be passed as a parameter
* value to any GL function.
*
* It is assumed that the GL_ARRAY_BUFFER_BINDING will not be changed while
* the buffer object is mapped.
*/
template<typename T>
std::optional<std::span<T>> map(size_t count)
{
if (const auto m = map(sizeof(T) * count)) {
return std::span(reinterpret_cast<T *>(m), count);
} else {
return std::nullopt;
}
}
/**
* Flushes the mapped buffer range and unmaps the buffer.
*/
void unmap();
/**
* Binds the vertex arrays to the context.
*/
void bindArrays();
/**
* Disables the vertex arrays.
*/
void unbindArrays();
/**
* Draws count vertices beginning with first.
*/
void draw(GLenum primitiveMode, int first, int count);
/**
* Draws count vertices beginning with first.
*/
void draw(const QRegion &region, GLenum primitiveMode, int first, int count, bool hardwareClipping = false);
/**
* Renders the vertex data in given @a primitiveMode.
* Please refer to OpenGL documentation of glDrawArrays or glDrawElements for allowed
* values for @a primitiveMode. Best is to use GL_TRIANGLES or similar to be future
* compatible.
*/
void render(GLenum primitiveMode);
/**
* Same as above restricting painting to @a region if @a hardwareClipping is true.
* It's within the caller's responsibility to enable GL_SCISSOR_TEST.
*/
void render(const QRegion &region, GLenum primitiveMode, bool hardwareClipping = false);
/**
* Resets the instance to default values.
* Useful for shared buffers.
* @since 4.7
*/
void reset();
/**
* Notifies the vertex buffer that we are done painting the frame.
*
* @internal
*/
void endOfFrame();
/**
* Notifies the vertex buffer that we are about to paint a frame.
*
* @internal
*/
void beginFrame();
void setPersistent();
/**
* @return A shared VBO for streaming data
* @since 4.7
*/
static GLVertexBuffer *streamingBuffer();
static constexpr std::array GLVertex2DLayout{
GLVertexAttrib{
.attributeIndex = VA_Position,
.componentCount = 2,
.type = GL_FLOAT,
.relativeOffset = offsetof(GLVertex2D, position),
},
GLVertexAttrib{
.attributeIndex = VA_TexCoord,
.componentCount = 2,
.type = GL_FLOAT,
.relativeOffset = offsetof(GLVertex2D, texcoord),
},
};
static constexpr std::array GLVertex3DLayout{
GLVertexAttrib{
.attributeIndex = VA_Position,
.componentCount = 3,
.type = GL_FLOAT,
.relativeOffset = offsetof(GLVertex3D, position),
},
GLVertexAttrib{
.attributeIndex = VA_TexCoord,
.componentCount = 2,
.type = GL_FLOAT,
.relativeOffset = offsetof(GLVertex3D, texcoord),
},
};
private:
GLvoid *map(size_t size);
const std::unique_ptr<GLVertexBufferPrivate> d;
};
}
@@ -0,0 +1,35 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2006-2007 Rivo Laks <rivolaks@hot.ee>
SPDX-FileCopyrightText: 2010, 2011 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <epoxy/gl.h>
#include <vector>
namespace KWin
{
class KWIN_EXPORT IndexBuffer
{
public:
explicit IndexBuffer();
~IndexBuffer();
void accommodate(size_t count);
void bind();
private:
GLuint m_buffer;
size_t m_count = 0;
std::vector<uint16_t> m_data;
};
}
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "colormanagement.glsl"
precision highp float;
precision highp sampler2D;
precision highp sampler3D;
in vec2 texcoord0;
uniform sampler2D src;
uniform mat4 toXYZD50;
uniform int Bsize;
uniform sampler2D Bsampler;
uniform mat4 matrix2;
uniform int Msize;
uniform sampler2D Msamplrt;
uniform ivec3 Csize;
uniform sampler3D Csampler;
uniform int Asize;
uniform sampler2D Asampler;
vec3 sample1DLut(vec3 input, sampler2D lut, int lutSize) {
float lutOffset = 0.5 / float(lutSize);
float lutScale = 1.0 - lutOffset * 2.0;
float lutR = texture2D(lut, vec2(lutOffset + input.r * lutScale, 0.5)).r;
float lutG = texture2D(lut, vec2(lutOffset + input.g * lutScale, 0.5)).g;
float lutB = texture2D(lut, vec2(lutOffset + input.b * lutScale, 0.5)).b;
return vec3(lutR, lutG, lutB);
}
void main()
{
vec4 tex = texture2D(src, texcoord0);
tex = encodingToNits(tex, sourceNamedTransferFunction, sourceTransferFunctionParams.x, sourceTransferFunctionParams.y);
tex.rgb /= max(tex.a, 0.001);
tex.rgb /= maxDestinationLuminance;
tex.rgb = (toXYZD50 * vec4(tex.rgb, 1.0)).rgb;
if (Bsize > 0) {
tex.rgb = sample1DLut(tex.rgb, Bsampler, Bsize);
}
tex.rgb = (matrix2 * vec4(tex.rgb, 1.0)).rgb;
if (Msize > 0) {
tex.rgb = sample1DLut(tex.rgb, Msampler, Msize);
}
if (Csize > 0) {
vec3 lutOffset = vec3(0.5) / vec3(Csize);
vec3 lutScale = vec3(1.0) - lutOffset * 2.0;
tex.rgb = texture3D(Csampler, lutOffset + tex.rgb * lutScale).rgb;
}
if (Asize > 0) {
tex.rgb = sample1DLut(tex.rgb, Asampler, Asize);
}
tex.rgb *= tex.a;
gl_FragColor = tex;
}
@@ -0,0 +1,65 @@
#version 140
// SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "colormanagement.glsl"
precision highp float;
precision highp sampler2D;
precision highp sampler3D;
in vec2 texcoord0;
out vec4 fragColor;
uniform sampler2D src;
uniform mat4 toXYZD50;
uniform int Bsize;
uniform sampler2D Bsampler;
uniform mat4 matrix2;
uniform int Msize;
uniform sampler2D Msampler;
uniform ivec3 Csize;
uniform sampler3D Csampler;
uniform int Asize;
uniform sampler2D Asampler;
vec3 sample1DLut(in vec3 srcColor, in sampler2D lut, in int lutSize) {
float lutOffset = 0.5 / float(lutSize);
float lutScale = 1.0 - lutOffset * 2.0;
float lutR = texture(lut, vec2(lutOffset + srcColor.r * lutScale, 0.5)).r;
float lutG = texture(lut, vec2(lutOffset + srcColor.g * lutScale, 0.5)).g;
float lutB = texture(lut, vec2(lutOffset + srcColor.b * lutScale, 0.5)).b;
return vec3(lutR, lutG, lutB);
}
void main()
{
vec4 tex = texture(src, texcoord0);
tex = encodingToNits(tex, sourceNamedTransferFunction, sourceTransferFunctionParams.x, sourceTransferFunctionParams.y);
tex.rgb /= max(tex.a, 0.001);
tex.rgb /= maxDestinationLuminance;
tex.rgb = (toXYZD50 * vec4(tex.rgb, 1.0)).rgb;
if (Bsize > 0) {
tex.rgb = sample1DLut(tex.rgb, Bsampler, Bsize);
}
tex.rgb = (matrix2 * vec4(tex.rgb, 1.0)).rgb;
if (Msize > 0) {
tex.rgb = sample1DLut(tex.rgb, Msampler, Msize);
}
if (Csize.x > 0) {
vec3 lutOffset = vec3(0.5) / vec3(Csize);
vec3 lutScale = vec3(1) - lutOffset * 2.0;
tex.rgb = texture(Csampler, lutOffset + tex.rgb * lutScale).rgb;
}
if (Asize > 0) {
tex.rgb = sample1DLut(tex.rgb, Asampler, Asize);
}
tex.rgb *= tex.a;
fragColor = tex;
}
@@ -0,0 +1,254 @@
/*
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "icc_shader.h"
#include "core/colorlut3d.h"
#include "core/colortransformation.h"
#include "core/iccprofile.h"
#include "opengl/gllut.h"
#include "opengl/gllut3D.h"
#include "opengl/glshader.h"
#include "opengl/glshadermanager.h"
#include "utils/common.h"
namespace KWin
{
static constexpr size_t lutSize = 1 << 12;
IccShader::IccShader()
: m_shader(ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, QString(), QStringLiteral(":/opengl/icc.frag")))
{
m_locations = {
.src = m_shader->uniformLocation("src"),
.toXYZD50 = m_shader->uniformLocation("toXYZD50"),
.bsize = m_shader->uniformLocation("Bsize"),
.bsampler = m_shader->uniformLocation("Bsampler"),
.matrix2 = m_shader->uniformLocation("matrix2"),
.msize = m_shader->uniformLocation("Msize"),
.msampler = m_shader->uniformLocation("Msampler"),
.csize = m_shader->uniformLocation("Csize"),
.csampler = m_shader->uniformLocation("Csampler"),
.asize = m_shader->uniformLocation("Asize"),
.asampler = m_shader->uniformLocation("Asampler"),
};
}
IccShader::~IccShader()
{
}
bool IccShader::setProfile(const std::shared_ptr<IccProfile> &profile, const ColorDescription &inputColor, RenderingIntent intent)
{
if (!profile) {
m_toXYZD50.setToIdentity();
m_B.reset();
m_matrix2.setToIdentity();
m_M.reset();
m_C.reset();
m_A.reset();
return false;
}
if (m_profile != profile || m_inputColor != inputColor || m_intent != intent) {
const auto vcgt = profile->vcgt();
QMatrix4x4 toXYZD50;
std::unique_ptr<GlLookUpTable> B;
QMatrix4x4 matrix2;
std::unique_ptr<GlLookUpTable> M;
std::unique_ptr<GlLookUpTable3D> C;
std::unique_ptr<GlLookUpTable> A;
const ColorDescription linearizedInput(inputColor.containerColorimetry(), TransferFunction(TransferFunction::linear, 0, 1), 1, 0, 1, 1);
const ColorDescription linearizedProfile(profile->colorimetry(), TransferFunction(TransferFunction::linear, 0, 1), 1, 0, 1, 1);
if (const auto tag = profile->BToATag(intent)) {
if (intent == RenderingIntent::AbsoluteColorimetric) {
// There's no BToA tag for absolute colorimetric, we have to piece it together ourselves with
// input white point -(absolute colorimetric)-> display white point
// -(relative colorimetric)-> XYZ D50 -(BToA1, also relative colorimetric)-> display white point
// First, transform from the input color to the display color space in absolute colorimetric mode
QMatrix4x4 toLinearDisplay = linearizedInput.toOther(linearizedProfile, RenderingIntent::AbsoluteColorimetric);
// Compensate for one of the channels potentially going beyond 1.0
// applyNightLight in drm_output.cpp is supposed to apply this, but it gets the brightness wrong for this code path (fixed in 6.4)
const float brightness = 1.0 / std::max({toLinearDisplay(0, 0), toLinearDisplay(1, 1), toLinearDisplay(2, 2), 1.0f});
toLinearDisplay.scale(brightness, brightness, brightness);
// Now transform that display color space to XYZ D50 in relative colorimetric mode.
// the BToA1 tag goes from XYZ D50 to the native white point of the display,
// so this matrix gets reverted by it
const QMatrix4x4 toXYZ = linearizedProfile.toOther(IccProfile::s_connectionSpace, RenderingIntent::RelativeColorimetric);
toXYZD50 = toXYZ * toLinearDisplay;
} else {
toXYZD50 = linearizedInput.toOther(IccProfile::s_connectionSpace, intent);
}
// while the above converts to XYZ D50, the encoding the ICC profile tag
// wants is CIEXYZ -> add the (absolute colorimetric) transform to that
toXYZD50 = IccProfile::s_connectionSpace.containerColorimetry().toXYZ() * toXYZD50;
auto it = tag->ops.begin();
if (it != tag->ops.end() && std::holds_alternative<std::shared_ptr<ColorTransformation>>(it->operation)) {
const auto sample = [&op = std::get<std::shared_ptr<ColorTransformation>>(it->operation)](size_t x) {
const float relativeX = x / double(lutSize - 1);
return op->transform(QVector3D(relativeX, relativeX, relativeX));
};
B = GlLookUpTable::create(sample, lutSize);
if (!B) {
return false;
}
it++;
}
if (it != tag->ops.end() && std::holds_alternative<ColorMatrix>(it->operation)) {
matrix2 = std::get<ColorMatrix>(it->operation).mat;
it++;
}
if (it != tag->ops.end() && std::holds_alternative<std::shared_ptr<ColorTransformation>>(it->operation)) {
const auto sample = [&op = std::get<std::shared_ptr<ColorTransformation>>(it->operation)](size_t x) {
const float relativeX = x / double(lutSize - 1);
return op->transform(QVector3D(relativeX, relativeX, relativeX));
};
M = GlLookUpTable::create(sample, lutSize);
if (!M) {
return false;
}
it++;
}
if (it != tag->ops.end() && std::holds_alternative<std::shared_ptr<ColorLUT3D>>(it->operation)) {
const auto &op = std::get<std::shared_ptr<ColorLUT3D>>(it->operation);
const auto sample = [op](size_t x, size_t y, size_t z) {
return op->sample(x, y, z);
};
C = GlLookUpTable3D::create(sample, op->xSize(), op->ySize(), op->zSize());
if (!C) {
return false;
}
it++;
}
if (it != tag->ops.end() && std::holds_alternative<std::shared_ptr<ColorTransformation>>(it->operation)) {
const auto sample = [&op = std::get<std::shared_ptr<ColorTransformation>>(it->operation), vcgt](size_t x) {
const float relativeX = x / double(lutSize - 1);
QVector3D ret = op->transform(QVector3D(relativeX, relativeX, relativeX));
if (vcgt) {
ret = vcgt->transform(ret);
}
return ret;
};
A = GlLookUpTable::create(sample, lutSize);
if (!A) {
return false;
}
it++;
} else if (vcgt) {
const auto sample = [&vcgt](size_t x) {
const float relativeX = x / double(lutSize - 1);
return vcgt->transform(QVector3D(relativeX, relativeX, relativeX));
};
A = GlLookUpTable::create(sample, lutSize);
}
if (it != tag->ops.end()) {
qCCritical(KWIN_OPENGL, "Couldn't represent ICC profile in the ICC shader!");
return false;
}
} else {
toXYZD50 = linearizedInput.toOther(linearizedProfile, intent);
// Compensate for one of the channels potentially going beyond 1.0
// applyNightLight in drm_output.cpp is supposed to apply this, but it gets the brightness wrong for this code path (fixed in 6.4)
const float brightness = 1.0 / std::max({toXYZD50(0, 0), toXYZD50(1, 1), toXYZD50(2, 2), 1.0f});
toXYZD50.scale(brightness, brightness, brightness);
const auto inverseEOTF = profile->inverseTransferFunction();
const auto sample = [inverseEOTF, vcgt](size_t x) {
const float relativeX = x / double(lutSize - 1);
QVector3D ret(relativeX, relativeX, relativeX);
ret = inverseEOTF->transform(ret);
if (vcgt) {
ret = vcgt->transform(ret);
}
return ret;
};
A = GlLookUpTable::create(sample, lutSize);
if (!A) {
return false;
}
}
m_toXYZD50 = toXYZD50;
m_B = std::move(B);
m_matrix2 = matrix2;
m_M = std::move(M);
m_C = std::move(C);
m_A = std::move(A);
m_profile = profile;
m_inputColor = inputColor;
m_intent = intent;
}
return true;
}
GLShader *IccShader::shader() const
{
return m_shader.get();
}
void IccShader::setUniforms(const std::shared_ptr<IccProfile> &profile, const ColorDescription &inputColor, RenderingIntent intent)
{
// this failing can be silently ignored, it should only happen with GPU resets and gets corrected later
setProfile(profile, inputColor, intent);
m_shader->setUniform(m_locations.toXYZD50, m_toXYZD50);
m_shader->setUniform(GLShader::IntUniform::SourceNamedTransferFunction, inputColor.transferFunction().type);
m_shader->setUniform(GLShader::Vec2Uniform::SourceTransferFunctionParams, QVector2D(inputColor.transferFunction().minLuminance, inputColor.transferFunction().maxLuminance - inputColor.transferFunction().minLuminance));
m_shader->setUniform(GLShader::FloatUniform::SourceReferenceLuminance, inputColor.referenceLuminance());
m_shader->setUniform(GLShader::FloatUniform::DestinationReferenceLuminance, inputColor.referenceLuminance());
m_shader->setUniform(GLShader::FloatUniform::MaxDestinationLuminance, inputColor.transferFunction().maxLuminance);
glActiveTexture(GL_TEXTURE1);
if (m_B) {
m_shader->setUniform(m_locations.bsize, int(m_B->size()));
m_shader->setUniform(m_locations.bsampler, 1);
m_B->bind();
} else {
m_shader->setUniform(m_locations.bsize, 0);
m_shader->setUniform(m_locations.bsampler, 1);
glBindTexture(GL_TEXTURE_1D, 0);
}
m_shader->setUniform(m_locations.matrix2, m_matrix2);
glActiveTexture(GL_TEXTURE2);
if (m_M) {
m_shader->setUniform(m_locations.msize, int(m_M->size()));
m_shader->setUniform(m_locations.msampler, 2);
m_M->bind();
} else {
m_shader->setUniform(m_locations.msize, 0);
m_shader->setUniform(m_locations.msampler, 1);
glBindTexture(GL_TEXTURE_1D, 0);
}
glActiveTexture(GL_TEXTURE3);
if (m_C) {
m_shader->setUniform(m_locations.csize, m_C->xSize(), m_C->ySize(), m_C->zSize());
m_shader->setUniform(m_locations.csampler, 3);
m_C->bind();
} else {
m_shader->setUniform(m_locations.csize, 0, 0, 0);
m_shader->setUniform(m_locations.csampler, 3);
glBindTexture(GL_TEXTURE_3D, 0);
}
glActiveTexture(GL_TEXTURE4);
if (m_A) {
m_shader->setUniform(m_locations.asize, int(m_A->size()));
m_shader->setUniform(m_locations.asampler, 4);
m_A->bind();
} else {
m_shader->setUniform(m_locations.asize, 0);
m_shader->setUniform(m_locations.asampler, 4);
glBindTexture(GL_TEXTURE_1D, 0);
}
glActiveTexture(GL_TEXTURE0);
m_shader->setUniform(m_locations.src, 0);
}
}
@@ -0,0 +1,62 @@
/*
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "core/colorspace.h"
#include <QMatrix4x4>
#include <QSizeF>
#include <memory>
namespace KWin
{
class IccProfile;
class GLShader;
class GlLookUpTable;
class GlLookUpTable3D;
class GLTexture;
class KWIN_EXPORT IccShader
{
public:
explicit IccShader();
~IccShader();
GLShader *shader() const;
void setUniforms(const std::shared_ptr<IccProfile> &profile, const ColorDescription &inputColor, RenderingIntent intent);
private:
bool setProfile(const std::shared_ptr<IccProfile> &profile, const ColorDescription &inputColor, RenderingIntent intent);
std::unique_ptr<GLShader> m_shader;
std::shared_ptr<IccProfile> m_profile;
RenderingIntent m_intent = RenderingIntent::RelativeColorimetric;
ColorDescription m_inputColor = ColorDescription::sRGB;
QMatrix4x4 m_toXYZD50;
std::unique_ptr<GlLookUpTable> m_B;
QMatrix4x4 m_matrix2;
std::unique_ptr<GlLookUpTable> m_M;
std::unique_ptr<GlLookUpTable3D> m_C;
std::unique_ptr<GlLookUpTable> m_A;
struct Locations
{
int src;
int toXYZD50;
int bsize;
int bsampler;
int matrix2;
int msize;
int msampler;
int csize;
int csampler;
int asize;
int asampler;
};
Locations m_locations;
};
}
@@ -0,0 +1,478 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "openglcontext.h"
#include "glframebuffer.h"
#include "glplatform.h"
#include "glshader.h"
#include "glshadermanager.h"
#include "glvertexbuffer.h"
#include "utils/common.h"
#include <QByteArray>
#include <QList>
#include <epoxy/gl.h>
namespace KWin
{
OpenGlContext *OpenGlContext::s_currentContext = nullptr;
static QSet<QByteArray> getExtensions(OpenGlContext *context)
{
QSet<QByteArray> ret;
if (!context->isOpenGLES() && context->hasVersion(Version(3, 0))) {
int count;
glGetIntegerv(GL_NUM_EXTENSIONS, &count);
for (int i = 0; i < count; i++) {
const char *name = (const char *)glGetStringi(GL_EXTENSIONS, i);
ret.insert(name);
}
} else {
const QByteArray extensions = (const char *)glGetString(GL_EXTENSIONS);
QList<QByteArray> extensionsList = extensions.split(' ');
ret = {extensionsList.constBegin(), extensionsList.constEnd()};
}
return ret;
}
static bool checkTextureSwizzleSupport(OpenGlContext *context)
{
if (context->isOpenGLES()) {
return context->hasVersion(Version(3, 0));
} else {
return context->hasVersion(Version(3, 3)) || context->hasOpenglExtension(QByteArrayLiteral("GL_ARB_texture_swizzle"));
}
}
static bool checkTextureStorageSupport(OpenGlContext *context)
{
if (context->isOpenGLES()) {
return context->hasVersion(Version(3, 0)) || context->hasOpenglExtension(QByteArrayLiteral("GL_EXT_texture_storage"));
} else {
return context->hasVersion(Version(4, 2)) || context->hasOpenglExtension(QByteArrayLiteral("GL_ARB_texture_storage"));
}
}
static bool checkIndexedQuads(OpenGlContext *context)
{
if (context->isOpenGLES()) {
const bool haveBaseVertex = context->hasOpenglExtension(QByteArrayLiteral("GL_OES_draw_elements_base_vertex"));
const bool haveCopyBuffer = context->hasVersion(Version(3, 0));
return haveBaseVertex && haveCopyBuffer && context->hasMapBufferRange();
} else {
bool haveBaseVertex = context->hasVersion(Version(3, 2)) || context->hasOpenglExtension(QByteArrayLiteral("GL_ARB_draw_elements_base_vertex"));
bool haveCopyBuffer = context->hasVersion(Version(3, 1)) || context->hasOpenglExtension(QByteArrayLiteral("GL_ARB_copy_buffer"));
return haveBaseVertex && haveCopyBuffer && context->hasMapBufferRange();
}
}
OpenGlContext::OpenGlContext(bool EGL)
: m_versionString((const char *)glGetString(GL_VERSION))
, m_version(Version::parseString(m_versionString))
, m_glslVersionString((const char *)glGetString(GL_SHADING_LANGUAGE_VERSION))
, m_glslVersion(Version::parseString(m_glslVersionString))
, m_vendor((const char *)glGetString(GL_VENDOR))
, m_renderer((const char *)glGetString(GL_RENDERER))
, m_isOpenglES(m_versionString.startsWith("OpenGL ES"))
, m_extensions(getExtensions(this))
, m_supportsTimerQueries(checkTimerQuerySupport())
, m_supportsTextureStorage(checkTextureStorageSupport(this))
, m_supportsTextureSwizzle(checkTextureSwizzleSupport(this))
, m_supportsARGB32Textures(!m_isOpenglES || hasOpenglExtension(QByteArrayLiteral("GL_EXT_texture_format_BGRA8888")))
, m_supportsRGTextures(hasVersion(Version(3, 0)) || hasOpenglExtension(QByteArrayLiteral("GL_ARB_texture_rg")) || hasOpenglExtension(QByteArrayLiteral("GL_EXT_texture_rg")))
, m_supports16BitTextures(!m_isOpenglES || hasOpenglExtension(QByteArrayLiteral("GL_EXT_texture_norm16")))
, m_supportsBlits(!m_isOpenglES || hasVersion(Version(3, 0)))
, m_supportsPackedDepthStencil(hasVersion(Version(3, 0)) || hasOpenglExtension(QByteArrayLiteral("GL_OES_packed_depth_stencil")) || hasOpenglExtension(QByteArrayLiteral("GL_ARB_framebuffer_object")) || hasOpenglExtension(QByteArrayLiteral("GL_EXT_packed_depth_stencil")))
, m_supportsGLES24BitDepthBuffers(m_isOpenglES && (hasVersion(Version(3, 0)) || hasOpenglExtension(QByteArrayLiteral("GL_OES_depth24"))))
, m_hasMapBufferRange(hasVersion(Version(3, 0)) || hasOpenglExtension(QByteArrayLiteral("GL_EXT_map_buffer_range")) || hasOpenglExtension(QByteArrayLiteral("GL_ARB_map_buffer_range")))
, m_haveBufferStorage((!m_isOpenglES && hasVersion(Version(4, 4))) || hasOpenglExtension(QByteArrayLiteral("GL_ARB_buffer_storage")) || hasOpenglExtension(QByteArrayLiteral("GL_EXT_buffer_storage")))
, m_haveSyncFences((m_isOpenglES && hasVersion(Version(3, 0))) || (!m_isOpenglES && hasVersion(Version(3, 2))) || hasOpenglExtension(QByteArrayLiteral("GL_ARB_sync")))
, m_supportsIndexedQuads(checkIndexedQuads(this))
, m_supportsPackInvert(hasOpenglExtension(QByteArrayLiteral("GL_MESA_pack_invert")))
, m_glPlatform(std::make_unique<GLPlatform>(EGL ? EglPlatformInterface : GlxPlatformInterface, m_versionString, m_glslVersionString, m_renderer, m_vendor))
{
}
OpenGlContext::~OpenGlContext()
{
if (s_currentContext == this) {
s_currentContext = nullptr;
}
}
bool OpenGlContext::checkTimerQuerySupport() const
{
if (qEnvironmentVariableIsSet("KWIN_NO_TIMER_QUERY")) {
return false;
}
if (m_isOpenglES) {
// 3.0 is required so query functions can be used without "EXT" suffix.
// Timer queries are still not part of the core OpenGL ES specification.
return openglVersion() >= Version(3, 0) && hasOpenglExtension("GL_EXT_disjoint_timer_query");
} else {
return openglVersion() >= Version(3, 3) || hasOpenglExtension("GL_ARB_timer_query");
}
}
bool OpenGlContext::hasVersion(const Version &version) const
{
return m_version >= version;
}
QByteArrayView OpenGlContext::openglVersionString() const
{
return m_versionString;
}
Version OpenGlContext::openglVersion() const
{
return m_version;
}
QByteArrayView OpenGlContext::glslVersionString() const
{
return m_glslVersionString;
}
Version OpenGlContext::glslVersion() const
{
return m_glslVersion;
}
QByteArrayView OpenGlContext::vendor() const
{
return m_vendor;
}
QByteArrayView OpenGlContext::renderer() const
{
return m_renderer;
}
bool OpenGlContext::isOpenGLES() const
{
return m_isOpenglES;
}
bool OpenGlContext::hasOpenglExtension(QByteArrayView name) const
{
return std::any_of(m_extensions.cbegin(), m_extensions.cend(), [name](const auto &string) {
return string == name;
});
}
bool OpenGlContext::isSoftwareRenderer() const
{
return m_renderer.contains("softpipe") || m_renderer.contains("Software Rasterizer") || m_renderer.contains("llvmpipe");
}
bool OpenGlContext::supportsTimerQueries() const
{
return m_supportsTimerQueries;
}
bool OpenGlContext::supportsTextureStorage() const
{
return m_supportsTextureStorage;
}
bool OpenGlContext::supportsTextureSwizzle() const
{
return m_supportsTextureSwizzle;
}
bool OpenGlContext::supportsARGB32Textures() const
{
return m_supportsARGB32Textures;
}
bool OpenGlContext::supportsRGTextures() const
{
return m_supportsRGTextures;
}
bool OpenGlContext::supports16BitTextures() const
{
return m_supports16BitTextures;
}
bool OpenGlContext::supportsBlits() const
{
return m_supportsBlits;
}
bool OpenGlContext::supportsGLES24BitDepthBuffers() const
{
return m_supportsGLES24BitDepthBuffers;
}
bool OpenGlContext::haveBufferStorage() const
{
return m_haveBufferStorage;
}
bool OpenGlContext::hasMapBufferRange() const
{
return m_hasMapBufferRange;
}
bool OpenGlContext::haveSyncFences() const
{
return m_haveSyncFences;
}
bool OpenGlContext::supportsPackInvert() const
{
return m_supportsPackInvert;
}
ShaderManager *OpenGlContext::shaderManager() const
{
return m_shaderManager;
}
GLVertexBuffer *OpenGlContext::streamingVbo() const
{
return m_streamingBuffer;
}
IndexBuffer *OpenGlContext::indexBuffer() const
{
return m_indexBuffer;
}
GLPlatform *OpenGlContext::glPlatform() const
{
return m_glPlatform.get();
}
bool OpenGlContext::checkSupported() const
{
const bool supportsGLSL = m_isOpenglES || (hasOpenglExtension("GL_ARB_shader_objects") && hasOpenglExtension("GL_ARB_fragment_shader") && hasOpenglExtension("GL_ARB_vertex_shader"));
const bool supportsNonPowerOfTwoTextures = m_isOpenglES || hasOpenglExtension("GL_ARB_texture_non_power_of_two");
const bool supports3DTextures = !m_isOpenglES || hasVersion(Version(3, 0)) || hasOpenglExtension("GL_OES_texture_3D");
const bool supportsFBOs = m_isOpenglES || hasVersion(Version(3, 0)) || hasOpenglExtension("GL_ARB_framebuffer_object") || hasOpenglExtension(QByteArrayLiteral("GL_EXT_framebuffer_object"));
const bool supportsUnpack = !m_isOpenglES || hasOpenglExtension(QByteArrayLiteral("GL_EXT_unpack_subimage"));
if (!supportsGLSL || !supportsNonPowerOfTwoTextures || !supports3DTextures || !supportsFBOs || !supportsUnpack) {
return false;
}
// some old hardware only supports very limited shaders. To prevent the shaders KWin uses later on from not working,
// test a reasonably complex one here and bail out early if it doesn't work
auto shader = m_shaderManager->shader(ShaderTrait::MapTexture | ShaderTrait::TransformColorspace | ShaderTrait::AdjustSaturation | ShaderTrait::Modulate);
return shader->isValid();
}
void OpenGlContext::setShaderManager(ShaderManager *manager)
{
m_shaderManager = manager;
}
void OpenGlContext::setStreamingBuffer(GLVertexBuffer *vbo)
{
m_streamingBuffer = vbo;
if (haveBufferStorage() && haveSyncFences()) {
if (qgetenv("KWIN_PERSISTENT_VBO") != QByteArrayLiteral("0")) {
vbo->setPersistent();
}
}
}
void OpenGlContext::setIndexBuffer(IndexBuffer *buffer)
{
m_indexBuffer = buffer;
}
QSet<QByteArray> OpenGlContext::openglExtensions() const
{
return m_extensions;
}
OpenGlContext *OpenGlContext::currentContext()
{
return s_currentContext;
}
void OpenGlContext::glResolveFunctions(const std::function<resolveFuncPtr(const char *)> &resolveFunction)
{
const bool haveArbRobustness = hasOpenglExtension(QByteArrayLiteral("GL_ARB_robustness"));
const bool haveExtRobustness = hasOpenglExtension(QByteArrayLiteral("GL_EXT_robustness"));
bool robustContext = false;
if (isOpenGLES()) {
if (haveExtRobustness) {
GLint value = 0;
glGetIntegerv(GL_CONTEXT_ROBUST_ACCESS_EXT, &value);
robustContext = (value != 0);
}
} else {
if (haveArbRobustness) {
if (hasVersion(Version(3, 0))) {
GLint value = 0;
glGetIntegerv(GL_CONTEXT_FLAGS, &value);
if (value & GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT_ARB) {
robustContext = true;
}
} else {
robustContext = true;
}
}
}
if (robustContext && haveArbRobustness) {
// See https://www.opengl.org/registry/specs/ARB/robustness.txt
m_glGetGraphicsResetStatus = (glGetGraphicsResetStatus_func)resolveFunction("glGetGraphicsResetStatusARB");
m_glReadnPixels = (glReadnPixels_func)resolveFunction("glReadnPixelsARB");
m_glGetnTexImage = (glGetnTexImage_func)resolveFunction("glGetnTexImageARB");
m_glGetnUniformfv = (glGetnUniformfv_func)resolveFunction("glGetnUniformfvARB");
} else if (robustContext && haveExtRobustness) {
// See https://www.khronos.org/registry/gles/extensions/EXT/EXT_robustness.txt
m_glGetGraphicsResetStatus = (glGetGraphicsResetStatus_func)resolveFunction("glGetGraphicsResetStatusEXT");
m_glReadnPixels = (glReadnPixels_func)resolveFunction("glReadnPixelsEXT");
m_glGetnUniformfv = (glGetnUniformfv_func)resolveFunction("glGetnUniformfvEXT");
}
}
void OpenGlContext::initDebugOutput()
{
const bool have_KHR_debug = hasOpenglExtension(QByteArrayLiteral("GL_KHR_debug"));
const bool have_ARB_debug = hasOpenglExtension(QByteArrayLiteral("GL_ARB_debug_output"));
if (!have_KHR_debug && !have_ARB_debug) {
return;
}
if (!have_ARB_debug) {
// if we don't have ARB debug, but only KHR debug we need to verify whether the context is a debug context
// it should work without as well, but empirical tests show: no it doesn't
if (isOpenGLES()) {
if (!hasVersion(Version(3, 2))) {
// empirical data shows extension doesn't work
return;
}
} else if (!hasVersion(Version(3, 0))) {
return;
}
// can only be queried with either OpenGL >= 3.0 or OpenGL ES of at least 3.1
GLint value = 0;
glGetIntegerv(GL_CONTEXT_FLAGS, &value);
if (!(value & GL_CONTEXT_FLAG_DEBUG_BIT)) {
return;
}
}
// Set the callback function
auto callback = [](GLenum source, GLenum type, GLuint id,
GLenum severity, GLsizei length,
const GLchar *message,
const GLvoid *userParam) {
while (length && std::isspace(message[length - 1])) {
--length;
}
switch (type) {
case GL_DEBUG_TYPE_ERROR:
case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR:
qCWarning(KWIN_OPENGL, "%#x: %.*s", id, length, message);
break;
case GL_DEBUG_TYPE_OTHER:
case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR:
case GL_DEBUG_TYPE_PORTABILITY:
case GL_DEBUG_TYPE_PERFORMANCE:
default:
qCDebug(KWIN_OPENGL, "%#x: %.*s", id, length, message);
break;
}
};
glDebugMessageCallback(callback, nullptr);
// This state exists only in GL_KHR_debug
if (have_KHR_debug) {
glEnable(GL_DEBUG_OUTPUT);
}
if (qEnvironmentVariableIntValue("KWIN_GL_DEBUG")) {
// Enable all debug messages
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
// Insert a test message
const QByteArray message = QByteArrayLiteral("OpenGL debug output initialized");
glDebugMessageInsert(GL_DEBUG_SOURCE_APPLICATION, GL_DEBUG_TYPE_OTHER, 0,
GL_DEBUG_SEVERITY_LOW, message.length(), message.constData());
} else {
// Only enable error messages
glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_ERROR, GL_DONT_CARE, 0, nullptr, GL_TRUE);
glDebugMessageControl(GL_DONT_CARE, GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
}
GLenum OpenGlContext::checkGraphicsResetStatus()
{
if (m_glGetGraphicsResetStatus) {
return m_glGetGraphicsResetStatus();
} else {
return GL_NO_ERROR;
}
}
void OpenGlContext::glReadnPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, GLvoid *data)
{
if (m_glReadnPixels) {
m_glReadnPixels(x, y, width, height, format, type, bufSize, data);
} else {
glReadPixels(x, y, width, height, format, type, data);
}
}
void OpenGlContext::glGetnTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels)
{
if (m_glGetnTexImage) {
m_glGetnTexImage(target, level, format, type, bufSize, pixels);
} else {
glGetTexImage(target, level, format, type, pixels);
}
}
void OpenGlContext::glGetnUniformfv(GLuint program, GLint location, GLsizei bufSize, GLfloat *params)
{
if (m_glGetnUniformfv) {
m_glGetnUniformfv(program, location, bufSize, params);
} else {
glGetUniformfv(program, location, params);
}
}
void OpenGlContext::pushFramebuffer(GLFramebuffer *fbo)
{
if (fbo != currentFramebuffer()) {
glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle());
glViewport(0, 0, fbo->size().width(), fbo->size().height());
}
m_fbos.push(fbo);
}
GLFramebuffer *OpenGlContext::popFramebuffer()
{
const auto ret = m_fbos.pop();
if (const auto fbo = currentFramebuffer(); fbo != ret) {
if (fbo) {
glBindFramebuffer(GL_FRAMEBUFFER, fbo->handle());
glViewport(0, 0, fbo->size().width(), fbo->size().height());
} else {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
}
return ret;
}
GLFramebuffer *OpenGlContext::currentFramebuffer()
{
return m_fbos.empty() ? nullptr : m_fbos.top();
}
}
@@ -0,0 +1,136 @@
/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include "utils/version.h"
#include <epoxy/gl.h>
#include <stdint.h>
#include <string_view>
#include <QByteArray>
#include <QSet>
#include <QStack>
namespace KWin
{
class ShaderManager;
class GLFramebuffer;
class GLVertexBuffer;
class IndexBuffer;
class GLPlatform;
// GL_ARB_robustness / GL_EXT_robustness
using glGetGraphicsResetStatus_func = GLenum (*)();
using glReadnPixels_func = void (*)(GLint x, GLint y, GLsizei width, GLsizei height,
GLenum format, GLenum type, GLsizei bufSize, GLvoid *data);
using glGetnTexImage_func = void (*)(GLenum target, GLint level, GLenum format, GLenum type,
GLsizei bufSize, void *pixels);
using glGetnUniformfv_func = void (*)(GLuint program, GLint location, GLsizei bufSize, GLfloat *params);
class KWIN_EXPORT OpenGlContext
{
public:
explicit OpenGlContext(bool EGL);
virtual ~OpenGlContext();
virtual bool makeCurrent() = 0;
virtual void doneCurrent() const = 0;
bool hasVersion(const Version &version) const;
QByteArrayView openglVersionString() const;
Version openglVersion() const;
QByteArrayView glslVersionString() const;
Version glslVersion() const;
QByteArrayView vendor() const;
QByteArrayView renderer() const;
bool isOpenGLES() const;
bool hasOpenglExtension(QByteArrayView name) const;
bool isSoftwareRenderer() const;
bool supportsTimerQueries() const;
bool supportsTextureSwizzle() const;
bool supportsTextureStorage() const;
bool supportsARGB32Textures() const;
bool supportsRGTextures() const;
bool supports16BitTextures() const;
bool supportsBlits() const;
bool supportsGLES24BitDepthBuffers() const;
bool hasMapBufferRange() const;
bool haveBufferStorage() const;
bool haveSyncFences() const;
bool supportsPackInvert() const;
ShaderManager *shaderManager() const;
GLVertexBuffer *streamingVbo() const;
IndexBuffer *indexBuffer() const;
GLPlatform *glPlatform() const;
QSet<QByteArray> openglExtensions() const;
/**
* checks whether or not this context supports all the features that KWin requires
*/
bool checkSupported() const;
GLenum checkGraphicsResetStatus();
void glReadnPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, GLvoid *data);
void glGetnTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels);
void glGetnUniformfv(GLuint program, GLint location, GLsizei bufSize, GLfloat *params);
void pushFramebuffer(GLFramebuffer *fbo);
GLFramebuffer *popFramebuffer();
GLFramebuffer *currentFramebuffer();
static OpenGlContext *currentContext();
protected:
bool checkTimerQuerySupport() const;
void setShaderManager(ShaderManager *manager);
void setStreamingBuffer(GLVertexBuffer *vbo);
void setIndexBuffer(IndexBuffer *buffer);
typedef void (*resolveFuncPtr)();
void glResolveFunctions(const std::function<resolveFuncPtr(const char *)> &resolveFunction);
void initDebugOutput();
static OpenGlContext *s_currentContext;
const QByteArrayView m_versionString;
const Version m_version;
const QByteArrayView m_glslVersionString;
const Version m_glslVersion;
const QByteArrayView m_vendor;
const QByteArrayView m_renderer;
const bool m_isOpenglES;
const QSet<QByteArray> m_extensions;
const bool m_supportsTimerQueries;
const bool m_supportsTextureStorage;
const bool m_supportsTextureSwizzle;
const bool m_supportsARGB32Textures;
const bool m_supportsRGTextures;
const bool m_supports16BitTextures;
const bool m_supportsBlits;
const bool m_supportsPackedDepthStencil;
const bool m_supportsGLES24BitDepthBuffers;
const bool m_hasMapBufferRange;
const bool m_haveBufferStorage;
const bool m_haveSyncFences;
const bool m_supportsIndexedQuads;
const bool m_supportsPackInvert;
const std::unique_ptr<GLPlatform> m_glPlatform;
glGetGraphicsResetStatus_func m_glGetGraphicsResetStatus = nullptr;
glReadnPixels_func m_glReadnPixels = nullptr;
glGetnTexImage_func m_glGetnTexImage = nullptr;
glGetnUniformfv_func m_glGetnUniformfv = nullptr;
ShaderManager *m_shaderManager = nullptr;
GLVertexBuffer *m_streamingBuffer = nullptr;
IndexBuffer *m_indexBuffer = nullptr;
QStack<GLFramebuffer *> m_fbos;
};
}
@@ -0,0 +1,9 @@
uniform float saturation;
uniform vec3 primaryBrightness;
vec4 adjustSaturation(vec4 color) {
// this calculates the Y component of the XYZ color representation for the color,
// which roughly corresponds to the brightness of the RGB tuple
float Y = dot(color.rgb, primaryBrightness);
return vec4(mix(vec3(Y), color.rgb, saturation), color.a);
}