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:
+30
@@ -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 ®ion, 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 ®ion, 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 ®ion, 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, ¤tTextureBinding);
|
||||
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 ®ion, const QPoint &offset = QPoint());
|
||||
void bind();
|
||||
void unbind();
|
||||
void render(const QSizeF &size);
|
||||
void render(const QRegion ®ion, const QSizeF &size, bool hardwareClipping = false);
|
||||
void render(const QRectF &source, const QRegion ®ion, 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 ®ion, 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 ®ion, 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 ®ion, 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 ®ion, 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);
|
||||
}
|
||||
Reference in New Issue
Block a user