libkwineffects: handle GLTexture upload failures

This is required for properly dealing with GPU resets
This commit is contained in:
Xaver Hugl 2023-05-15 13:18:41 +02:00
parent 572bc75de4
commit 16fb2848ed
14 changed files with 135 additions and 141 deletions

View file

@ -94,97 +94,6 @@ GLTexture::GLTexture(std::unique_ptr<GLTexturePrivate> &&dd)
{
}
GLTexture::GLTexture(const QImage &image, GLenum target)
: d_ptr(new GLTexturePrivate())
{
Q_D(GLTexture);
if (image.isNull()) {
return;
}
d->m_target = target;
if (d->m_target != GL_TEXTURE_RECTANGLE_ARB) {
d->m_scale.setWidth(1.0 / image.width());
d->m_scale.setHeight(1.0 / image.height());
} else {
d->m_scale.setWidth(1.0);
d->m_scale.setHeight(1.0);
}
d->m_size = image.size();
setContentTransform(TextureTransform::MirrorY);
d->m_canUseMipmaps = false;
d->m_mipLevels = 1;
d->updateMatrix();
const bool created = create();
Q_ASSERT(created);
bind();
if (!GLPlatform::instance()->isGLES()) {
QImage im;
GLenum internalFormat;
GLenum format;
GLenum type;
const QImage::Format index = image.format();
if (index < sizeof(formatTable) / sizeof(formatTable[0]) && formatTable[index].internalFormat
&& !(formatTable[index].type == GL_UNSIGNED_SHORT && !d->s_supportsTexture16Bit)) {
internalFormat = formatTable[index].internalFormat;
format = formatTable[index].format;
type = formatTable[index].type;
im = image;
} else {
im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
internalFormat = GL_RGBA8;
format = GL_BGRA;
type = GL_UNSIGNED_INT_8_8_8_8_REV;
}
d->m_internalFormat = internalFormat;
if (d->s_supportsTextureStorage) {
glTexStorage2D(d->m_target, 1, internalFormat, im.width(), im.height());
glTexSubImage2D(d->m_target, 0, 0, 0, im.width(), im.height(),
format, type, im.constBits());
d->m_immutable = true;
} else {
glTexParameteri(d->m_target, GL_TEXTURE_MAX_LEVEL, d->m_mipLevels - 1);
glTexImage2D(d->m_target, 0, internalFormat, im.width(), im.height(), 0,
format, type, im.constBits());
}
} else {
d->m_internalFormat = GL_RGBA8;
if (d->s_supportsARGB32) {
const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
glTexImage2D(d->m_target, 0, GL_BGRA_EXT, im.width(), im.height(),
0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, im.constBits());
} else {
const QImage im = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
glTexImage2D(d->m_target, 0, GL_RGBA, im.width(), im.height(),
0, GL_RGBA, GL_UNSIGNED_BYTE, im.constBits());
}
}
unbind();
setFilter(GL_LINEAR);
}
GLTexture::GLTexture(const QPixmap &pixmap, GLenum target)
: GLTexture(pixmap.toImage(), target)
{
}
GLTexture::GLTexture(const QString &fileName)
: GLTexture(QImage(fileName))
{
}
GLTexture::GLTexture(GLuint textureId, GLenum internalFormat, const QSize &size, int levels, bool isImmutable)
: d_ptr(new GLTexturePrivate())
{
@ -767,4 +676,74 @@ std::unique_ptr<GLTexture> GLTexture::allocate(GLenum internalFormat, const QSiz
return ret;
}
std::unique_ptr<GLTexture> GLTexture::upload(const QImage &image)
{
if (image.isNull()) {
return nullptr;
}
GLuint texture = 0;
glGenTextures(1, &texture);
if (texture == 0) {
qCWarning(LIBKWINGLUTILS, "generating OpenGL texture handle failed");
return nullptr;
}
glBindTexture(GL_TEXTURE_2D, texture);
GLenum internalFormat;
bool immutable = false;
if (!GLPlatform::instance()->isGLES()) {
QImage im;
GLenum format;
GLenum type;
const QImage::Format index = image.format();
if (index < sizeof(formatTable) / sizeof(formatTable[0]) && formatTable[index].internalFormat
&& !(formatTable[index].type == GL_UNSIGNED_SHORT && !GLTexturePrivate::s_supportsTexture16Bit)) {
internalFormat = formatTable[index].internalFormat;
format = formatTable[index].format;
type = formatTable[index].type;
im = image;
} else {
im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
internalFormat = GL_RGBA8;
format = GL_BGRA;
type = GL_UNSIGNED_INT_8_8_8_8_REV;
}
if (GLTexturePrivate::s_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());
immutable = true;
} 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 {
internalFormat = GL_RGBA8;
if (GLTexturePrivate::s_supportsARGB32) {
const QImage im = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
glTexImage2D(GL_TEXTURE_2D, 0, GL_BGRA_EXT, im.width(), im.height(),
0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, im.constBits());
} else {
const QImage im = image.convertToFormat(QImage::Format_RGBA8888_Premultiplied);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, im.width(), im.height(),
0, GL_RGBA, GL_UNSIGNED_BYTE, im.constBits());
}
}
glBindTexture(GL_TEXTURE_2D, 0);
auto ret = std::make_unique<GLTexture>(texture, internalFormat, image.size(), 1, immutable);
ret->setContentTransform(TextureTransform::MirrorY);
ret->d_ptr->m_foreign = false;
return ret;
}
std::unique_ptr<GLTexture> GLTexture::upload(const QPixmap &pixmap)
{
return upload(pixmap.toImage());
}
} // namespace KWin

View file

@ -49,9 +49,6 @@ class KWINGLUTILS_EXPORT GLTexture
{
public:
explicit GLTexture(GLenum target);
explicit GLTexture(const QImage &image, GLenum target = GL_TEXTURE_2D);
explicit GLTexture(const QPixmap &pixmap, GLenum target = GL_TEXTURE_2D);
explicit GLTexture(const QString &fileName);
/**
* Creates the underlying texture object. Returns @c true if the texture has been created
@ -157,6 +154,8 @@ public:
static bool supportsFormatRG();
static std::unique_ptr<GLTexture> allocate(GLenum internalFormat, const QSize &size, int levels = 1, bool needsMutability = false);
static std::unique_ptr<GLTexture> upload(const QImage &image);
static std::unique_ptr<GLTexture> upload(const QPixmap &pixmap);
protected:
const std::unique_ptr<GLTexturePrivate> d_ptr;

View file

@ -407,10 +407,7 @@ void OffscreenQuickView::hide()
GLTexture *OffscreenQuickView::bufferAsTexture()
{
if (d->m_useBlit) {
if (d->m_image.isNull()) {
return nullptr;
}
d->m_textureExport.reset(new GLTexture(d->m_image));
d->m_textureExport = GLTexture::upload(d->m_image);
} else {
if (!d->m_fbo) {
return nullptr;

View file

@ -77,7 +77,7 @@ bool BasicEGLSurfaceTextureInternal::updateFromImage(const QRegion &region)
}
if (!m_texture) {
m_texture.reset(new GLTexture(image));
m_texture = GLTexture::upload(image);
} else {
const QRegion nativeRegion = scale(region, image.devicePixelRatio());
for (const QRect &rect : nativeRegion) {
@ -85,7 +85,7 @@ bool BasicEGLSurfaceTextureInternal::updateFromImage(const QRegion &region)
}
}
return true;
return m_texture != nullptr;
}
} // namespace KWin

View file

@ -60,12 +60,10 @@ void BasicEGLSurfaceTextureWayland::update(const QRegion &region)
bool BasicEGLSurfaceTextureWayland::loadShmTexture(KWaylandServer::ShmClientBuffer *buffer)
{
const QImage &image = buffer->data();
if (Q_UNLIKELY(image.isNull())) {
m_texture = GLTexture::upload(buffer->data());
if (!m_texture) {
return false;
}
m_texture.reset(new GLTexture(image));
m_texture->setFilter(GL_LINEAR);
m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
m_texture->setContentTransform(TextureTransform::MirrorY);

View file

@ -691,7 +691,10 @@ void BlurEffect::generateNoiseTexture()
// The noise texture looks distorted when not scaled with integer
noiseImage = noiseImage.scaled(noiseImage.size() * m_scalingFactor);
m_noiseTexture.reset(new GLTexture(noiseImage));
m_noiseTexture = GLTexture::upload(noiseImage);
if (!m_noiseTexture) {
return;
}
m_noiseTexture->setFilter(GL_NEAREST);
m_noiseTexture->setWrapMode(GL_REPEAT);
}
@ -833,6 +836,9 @@ void BlurEffect::applyNoise(const ScreenData &data, const RenderTarget &renderTa
{
if (!m_noiseTexture) {
generateNoiseTexture();
if (!m_noiseTexture) {
return;
}
}
m_shader->bind(BlurShader::NoiseSampleType);

View file

@ -551,7 +551,7 @@ void ScreenCastStream::recordFrame(const QRegion &_damagedRegion)
if (cursorImage.isNull()) {
m_cursor.texture = nullptr;
} else {
m_cursor.texture = std::make_unique<GLTexture>(cursorImage.image());
m_cursor.texture = GLTexture::upload(cursorImage.image());
}
}
if (m_cursor.texture) {

View file

@ -79,6 +79,9 @@ void ScreenEdgeEffect::paintScreen(const RenderTarget &renderTarget, const Rende
}
if (effects->isOpenGLCompositing()) {
GLTexture *texture = glow->texture.get();
if (!texture) {
return;
}
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
ShaderBinder binder(ShaderTrait::MapTexture | ShaderTrait::Modulate | ShaderTrait::TransformColorspace);
@ -92,10 +95,10 @@ void ScreenEdgeEffect::paintScreen(const RenderTarget &renderTarget, const Rende
texture->render(glow->geometry.size(), scale);
glDisable(GL_BLEND);
} else if (effects->compositingType() == QPainterCompositing) {
QImage tmp(glow->image->size(), QImage::Format_ARGB32_Premultiplied);
QImage tmp(glow->image.size(), QImage::Format_ARGB32_Premultiplied);
tmp.fill(Qt::transparent);
QPainter p(&tmp);
p.drawImage(0, 0, *glow->image.get());
p.drawImage(0, 0, glow->image);
QColor color(Qt::transparent);
color.setAlphaF(opacity);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
@ -140,9 +143,9 @@ void ScreenEdgeEffect::edgeApproaching(ElectricBorder border, qreal factor, cons
effects->addRepaint(glow->geometry);
if (border == ElectricLeft || border == ElectricRight || border == ElectricTop || border == ElectricBottom) {
if (effects->isOpenGLCompositing()) {
glow->texture.reset(createEdgeGlow<GLTexture>(border, geometry.size()));
glow->texture = GLTexture::upload(createEdgeGlow(border, geometry.size()));
} else if (effects->compositingType() == QPainterCompositing) {
glow->image.reset(createEdgeGlow<QImage>(border, geometry.size()));
glow->image = createEdgeGlow(border, geometry.size());
}
}
}
@ -172,25 +175,23 @@ std::unique_ptr<Glow> ScreenEdgeEffect::createGlow(ElectricBorder border, qreal
if (effects->isOpenGLCompositing()) {
effects->makeOpenGLContextCurrent();
if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) {
glow->texture.reset(createCornerGlow<GLTexture>(border));
glow->texture = GLTexture::upload(createCornerGlow(border));
} else {
glow->texture.reset(createEdgeGlow<GLTexture>(border, geometry.size()));
}
if (glow->texture) {
glow->texture->setWrapMode(GL_CLAMP_TO_EDGE);
glow->texture = GLTexture::upload(createEdgeGlow(border, geometry.size()));
}
if (!glow->texture) {
return nullptr;
}
glow->texture->setWrapMode(GL_CLAMP_TO_EDGE);
} else if (effects->compositingType() == QPainterCompositing) {
if (border == ElectricTopLeft || border == ElectricTopRight || border == ElectricBottomRight || border == ElectricBottomLeft) {
glow->image.reset(createCornerGlow<QImage>(border));
glow->image = createCornerGlow(border);
glow->pictureSize = cornerGlowSize(border);
} else {
glow->image.reset(createEdgeGlow<QImage>(border, geometry.size()));
glow->image = createEdgeGlow(border, geometry.size());
glow->pictureSize = geometry.size();
}
if (!glow->image) {
if (glow->image.isNull()) {
return nullptr;
}
}
@ -198,22 +199,21 @@ std::unique_ptr<Glow> ScreenEdgeEffect::createGlow(ElectricBorder border, qreal
return glow;
}
template<typename T>
T *ScreenEdgeEffect::createCornerGlow(ElectricBorder border)
QImage ScreenEdgeEffect::createCornerGlow(ElectricBorder border)
{
ensureGlowSvg();
switch (border) {
case ElectricTopLeft:
return new T(m_glow->pixmap(QStringLiteral("bottomright")).toImage());
return m_glow->pixmap(QStringLiteral("bottomright")).toImage();
case ElectricTopRight:
return new T(m_glow->pixmap(QStringLiteral("bottomleft")).toImage());
return m_glow->pixmap(QStringLiteral("bottomleft")).toImage();
case ElectricBottomRight:
return new T(m_glow->pixmap(QStringLiteral("topleft")).toImage());
return m_glow->pixmap(QStringLiteral("topleft")).toImage();
case ElectricBottomLeft:
return new T(m_glow->pixmap(QStringLiteral("topright")).toImage());
return m_glow->pixmap(QStringLiteral("topright")).toImage();
default:
return nullptr;
return QImage{};
}
}
@ -235,8 +235,7 @@ QSize ScreenEdgeEffect::cornerGlowSize(ElectricBorder border)
}
}
template<typename T>
T *ScreenEdgeEffect::createEdgeGlow(ElectricBorder border, const QSize &size)
QImage ScreenEdgeEffect::createEdgeGlow(ElectricBorder border, const QSize &size)
{
ensureGlowSvg();
@ -268,7 +267,7 @@ T *ScreenEdgeEffect::createEdgeGlow(ElectricBorder border, const QSize &size)
pixmapPosition = QPoint(size.width() - c.width(), 0);
break;
default:
return nullptr;
return QImage{};
}
QPixmap image(size);
image.fill(Qt::transparent);
@ -294,7 +293,7 @@ T *ScreenEdgeEffect::createEdgeGlow(ElectricBorder border, const QSize &size)
p.drawPixmap(QPoint(pixmapPosition.x(), size.height() - r.height()), r);
}
p.end();
return new T(image.toImage());
return image.toImage();
}
bool ScreenEdgeEffect::isActive() const

View file

@ -42,10 +42,8 @@ private Q_SLOTS:
private:
void ensureGlowSvg();
std::unique_ptr<Glow> createGlow(ElectricBorder border, qreal factor, const QRect &geometry);
template<typename T>
T *createCornerGlow(ElectricBorder border);
template<typename T>
T *createEdgeGlow(ElectricBorder border, const QSize &size);
QImage createCornerGlow(ElectricBorder border);
QImage createEdgeGlow(ElectricBorder border, const QSize &size);
QSize cornerGlowSize(ElectricBorder border);
Plasma::Svg *m_glow = nullptr;
std::map<ElectricBorder, std::unique_ptr<Glow>> m_borders;
@ -56,7 +54,7 @@ class Glow
{
public:
std::unique_ptr<GLTexture> texture;
std::unique_ptr<QImage> image;
QImage image;
QSize pictureSize;
qreal strength;
QRect geometry;

View file

@ -212,6 +212,9 @@ void StartupFeedbackEffect::paintScreen(const RenderTarget &renderTarget, const
default:
return; // safety
}
if (!texture) {
return;
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
GLShader *shader = nullptr;
@ -356,14 +359,20 @@ void StartupFeedbackEffect::prepareTextures(const QPixmap &pix)
switch (m_type) {
case BouncingFeedback:
for (int i = 0; i < 5; ++i) {
m_bouncingTextures[i].reset(new GLTexture(scalePixmap(pix, BOUNCE_SIZES[i])));
m_bouncingTextures[i] = GLTexture::upload(scalePixmap(pix, BOUNCE_SIZES[i]));
if (!m_bouncingTextures[i]) {
return;
}
m_bouncingTextures[i]->setFilter(GL_LINEAR);
m_bouncingTextures[i]->setWrapMode(GL_CLAMP_TO_EDGE);
}
break;
case BlinkingFeedback:
case PassiveFeedback:
m_texture.reset(new GLTexture(pix));
m_texture = GLTexture::upload(pix);
if (!m_texture) {
return;
}
m_texture->setFilter(GL_LINEAR);
m_texture->setWrapMode(GL_CLAMP_TO_EDGE);
break;

View file

@ -236,7 +236,7 @@ void TrackMouseEffect::loadTexture()
for (int i = 0; i < 2; ++i) {
if (effects->isOpenGLCompositing()) {
QImage img(f[i]);
m_texture[i] = std::make_unique<GLTexture>(img);
m_texture[i] = GLTexture::upload(img);
m_lastRect[i].setSize(img.size());
}
if (effects->compositingType() == QPainterCompositing) {

View file

@ -155,7 +155,10 @@ GLTexture *ZoomEffect::ensureCursorTexture()
m_cursorTextureDirty = false;
const auto cursor = effects->cursorImage();
if (!cursor.image().isNull()) {
m_cursorTexture = std::make_unique<GLTexture>(cursor.image());
m_cursorTexture = GLTexture::upload(cursor.image());
if (!m_cursorTexture) {
return nullptr;
}
m_cursorTexture->setWrapMode(GL_CLAMP_TO_EDGE);
}
}

View file

@ -49,7 +49,7 @@ void ImageItemOpenGL::preprocess()
m_textureKey = m_image.cacheKey();
if (!m_texture || m_texture->size() != m_image.size()) {
m_texture = std::make_unique<GLTexture>(m_image);
m_texture = GLTexture::upload(m_image);
} else {
m_texture->update(m_image);
}

View file

@ -162,7 +162,10 @@ std::shared_ptr<GLTexture> DecorationShadowTextureCache::getTexture(ShadowTextur
}
Data d;
d.providers << provider;
d.texture = std::make_shared<GLTexture>(shadow->decorationShadowImage());
d.texture = GLTexture::upload(shadow->decorationShadowImage());
if (!d.texture) {
return nullptr;
}
d.texture->setFilter(GL_LINEAR);
d.texture->setWrapMode(GL_CLAMP_TO_EDGE);
m_cache.insert(decoShadow.get(), d);
@ -252,7 +255,10 @@ void OpenGLShadowTextureProvider::update()
}
}
m_texture = std::make_shared<GLTexture>(image);
m_texture = GLTexture::upload(image);
if (!m_texture) {
return;
}
m_texture->setFilter(GL_LINEAR);
m_texture->setWrapMode(GL_CLAMP_TO_EDGE);