问题描述
我使用 QT 创建了一个带有单个 Skia 画布的基本应用程序。当使用 QOpenGLWindow
作为基类时,它工作正常。但是使用 qopenglwidget
,调整窗口大小时文本会消失。主要区别在于 qopenglwidget
在每次调整大小后重新创建其帧缓冲区。
调整大小之前:
调整大小后:
使用的skia版本是Arch Linux上的chrome/m90(git tag)(用wayland和X11测试)。早期版本也会出现同样的问题。对于 chrome/m80,文字不会消失,但字母会变成黑色块。 所以我怀疑是内存损坏问题,但修复方法是什么?
#include <QSurfaceFormat>
#include <QOpenGLContext>
#include <QApplication>
#include <QOpenGLWindow>
#include <qopenglwidget>
#include <QMainWindow>
#include <gpu/gl/GrGLAssembleInterface.h>
#include <core/SkSurfaceProps.h>
#include <gpu/GrdirectContext.h>
#include <core/SkSurface.h>
#include <QOpenGLFunctions>
#include <core/SkCanvas.h>
#include <core/SkFont.h>
bool AllowEGL;
void initGL() {
QSurfaceFormat fmt;
fmt.setDepthBufferSize(0);
fmt.setRedBufferSize(8);
fmt.setGreenBufferSize(8);
fmt.setBlueBufferSize(8);
fmt.setStencilBufferSize(8);
fmt.setSamples(0);
fmt.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) {
fmt.setVersion(3,3);
fmt.setProfile(QSurfaceFormat::CoreProfile);
} else {
fmt.setVersion(3,0);
}
QSurfaceFormat::setDefaultFormat(fmt);
AllowEGL = QApplication::platformName() != "xcb";
}
sk_sp<GrdirectContext> makeContext(QOpenGLContext *ctx) {
auto interface = GrGLMakeAssembledInterface(ctx,[](auto ctx,auto name) {
return AllowEGL || strncmp(name,"egl",3) ? static_cast<QOpenGLContext *>(ctx)->getProcAddress(name) : nullptr;
});
return GrdirectContext::MakeGL(interface);
}
sk_sp<SkSurface> createSurface(GrRecordingContext *ctx,int w,int h,Grgluint fbo) {
GrGLFramebufferInfo info;
info.fFBOID = fbo;
info.fFormat = GL_RGBA8;
GrBackendrendertarget target(w,h,8,info);
const SkSurfaceProps props(0,SkPixelGeometry::kBGR_H_SkPixelGeometry);
return SkSurface::MakeFromBackendrendertarget(ctx,target,kBottomLeft_GrSurfaceOrigin,kRGBA_8888_SkColorType,nullptr,&props,[](auto r) { qDebug("release"); });
}
void draw(SkCanvas *canvas) {
canvas->clear(SK_ColorWHITE);
SkFont font;
SkPaint paint;
canvas->drawString("Hello World!",10,30,font,paint);
paint.setAntiAlias(true);
canvas->drawCircle(100,50,paint);
canvas->flush();
}
class SkiaWindow : public QOpenGLWindow {
protected:
void initializeGL() override {
ctx = makeContext(context());
f = context()->functions();
}
void resizeGL(int w,int h) override {
surface = createSurface(ctx.get(),w,defaultFramebufferObject());
f->glViewport(0,h);
}
void paintGL() override {
f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
draw(surface->getCanvas());
}
private:
sk_sp<GrdirectContext> ctx{};
sk_sp<SkSurface> surface{};
QOpenGLFunctions *f{};
};
class SkiaWidget : public qopenglwidget {
using qopenglwidget::qopenglwidget;
protected:
void initializeGL() override {
ctx = makeContext(context());
f = context()->functions();
}
void resizeGL(int w,int h) override {
f->glViewport(0,h);
surface = createSurface(ctx.get(),defaultFramebufferObject());
}
void paintGL() override {
draw(surface->getCanvas());
}
private:
sk_sp<GrdirectContext> ctx{};
sk_sp<SkSurface> surface{};
QOpenGLFunctions *f{};
};
int main(int argc,char *argv[]) {
QApplication app(argc,argv);
app.setApplicationName("qtskia");
initGL();
// Works fine
//SkiaWindow window;
// Text disappears after resizing window
QMainWindow window;
SkiaWidget widget (&window);
window.setCentralWidget(&widget);
window.show();
return QApplication::exec();
}
解决方法
要解决此问题,只需将 GrDirectContext::resetContext()
添加到 resizeGL
函数即可。
上下文通常假设没有外人在底层 3D API 的上下文/设备/任何内容中设置状态。
此调用通知上下文状态已修改,应重新发送。不应该频繁调用以获得良好的性能。标志位、状态取决于上下文使用哪个后端,GL 或 D3D(将来可能)。
resetGLTextureBindings()
似乎也有效,而且开销可能更低。
所以问题是 Qt 修改了 OpenGL 状态,但 Skia 假设状态没有被修改。