Одна особенно сложная вещь для безопасной настройки — это GLX. Проблема в том, что должно быть выделено довольно много ресурсов и освобождено в правильном порядке в случае ошибок в любой момент во время инициализации.
Вот что я написал на C (только соответствующие части).
int gfx_init(struct gfx *g)
{
int vis_attriblist[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
XSetWindowAttributes wa;
XVisualInfo *vis_info;
int r = 0;
g->xdpy = XOpenDisplay(NULL);
if (g->xdpy == NULL) {
r = -1;
LOG_ERROR("Could not open X Display");
goto xopendisplay_failed;
}
vis_info = glXChooseVisual(g->xdpy, DefaultScreen(g->xdpy),
vis_attriblist);
if (vis_info == NULL) {
r = -1;
LOG_ERROR("Couldn't get an RGBA, double-buffered visual"
" (GLX available?)\n");
goto glxchoosevisual_failed;
}
g->xcolormap = XCreateColormap(g->xdpy, DefaultRootWindow(g->xdpy),
vis_info->visual, AllocNone);
if (gfx_has_xerror(g) /* Checks if there are errors
by flushing Xlib's protocol buffer
with a custom error handler set.
Not included here */) {
r = -1;
LOG_ERROR("Failed to create colormap");
goto xcreatecolormap_failed;
}
wa.colormap = g->xcolormap;
wa.event_mask = StructureNotifyMask | VisibilityChangeMask;
g->xwindow = XCreateWindow(g->xdpy, DefaultRootWindow(g->xdpy),
0,0,1280,1024, 0, vis_info->depth,
InputOutput, vis_info->visual, CWColormap |
CWEventMask, &wa);
if (gfx_has_xerror(g)) {
r = -1;
LOG_ERROR("Failed to create X11 Window");
goto xcreatewindow_failed;
}
g->glxctx = glXCreateContext(g->xdpy, vis_info, NULL, True);
if (g->glxctx == NULL) {
r = -1;
LOG_ERROR("Failed to create GLX context");
goto glxcreatecontext_failed;
}
if (glXMakeCurrent(g->xdpy, g->xwindow, g->glxctx) == False) {
r = -1;
LOG_ERROR("Failed to make context current");
goto glxmakecurrent_failed;
}
g->xa_wmdeletewindow = XInternAtom(g->xdpy, "WM_DELETE_WINDOW", False);
if (gfx_has_xerror(g)) {
r = -1;
LOG_ERROR("XInternAtom failed");
goto xinternatom_failed;
}
XSetWMProtocols(g->xdpy, g->xwindow, &g->xa_wmdeletewindow, 1);
if (gfx_has_xerror(g)) {
r = -1;
LOG_ERROR("XSetWMProtocols failed");
goto xsetwmprotocols_failed;
}
glClearColor(1,1,1,1);
glColor4f(0,0,0,1);
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (glGetError() != GL_NO_ERROR) {
r = -1;
LOG_ERROR("There have been GL errors");
goto gotglerror;
}
XMapWindow(g->xdpy, g->xwindow);
XFlush(g->xdpy);
if (r < 0) {
gotglerror:
xsetwmprotocols_failed:
xinternatom_failed:
glXMakeCurrent(g->xdpy, None, NULL);
glxmakecurrent_failed:
glXDestroyContext(g->xdpy, g->glxctx);
glxcreatecontext_failed:
XDestroyWindow(g->xdpy, g->xwindow);
xcreatewindow_failed:
XFreeColormap(g->xdpy, g->xcolormap);
}
xcreatecolormap_failed:
/* This is a local resource which must be destroyed
in case of success as well */
XFree(vis_info);
if (r < 0) {
glxchoosevisual_failed:
XCloseDisplay(g->xdpy);
}
xopendisplay_failed:
return r;
}
На самом деле я вполне доволен этим. Я думаю, что это хороший стиль C. Единственная проблема заключается в том, что для функции gfx_destroy
код для части освобождения памяти gfx_init
должен быть как бы продублирован, но это очень просто.
Я хотел бы знать, как выполнить эту инициализацию в хорошем стиле RAII C++. В частности, существует зависимость между распределением элементов, и воображаемый конструктор RAII class Gfx
должен инициализироваться в правильном порядке или выдать исключение и гарантировать, что исходная созданная часть снова будет удалена.
Таким образом, естественным прогрессом является написание коротких оболочек для выделенных типов. Например.
struct MyDisplay {
Display *dpy;
MyDisplay() : dpy(XOpenDisplay(NULL)) { if (!dpy) throw "XOpenDisplay()"; }
~MyDisplay() { XCloseDisplay(dpy); }
};
struct MyXVisualInfo {
XVisualInfo *info;
static int vis_attriblist[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None };
MyXVisualInfo(Display *dpy)
: info(glXChooseVisual(dpy, DefaultScreen(dpy), vis_attriblist) {
if (!info)
throw "glXChooseVisual()";
}
~MyXVisualInfo() {
XFree(info);
}
};
И класс Gfx
:
class Gfx {
MyDisplay mydpy_;
MyXVisualInfo myinfo_;
/* ... */
public:
Gfx::Gfx() : mydpy_(), myinfo_(mydpy_.dpy) /* , ... */ {}
};
Но на данный момент у нас есть проблема: (это неправда, см. ответ @MarkB)myinfo_(mydpy.dpy)
на самом деле передает неопределенное значение конструктору MyXVisualInfo. Является ли это препятствием для членов класса RAII?
Кроме того, если конструктору необходимо выделить временные ресурсы, как это имеет место для myinfo_
, которые я не хотел бы хранить внутри класса, я думаю, что нет способа избежать входа в тело конструктора, и присвоения членам оттуда, используя ресурсы, локальные для тела конструктора. Это означает, что участники будут подвергаться дополнительной конструкции и деконструкции, что недопустимо, поскольку есть побочные эффекты.
Единственное, о чем я могу думать, это использовать unique_ptr
:
class Gfx {
unique_ptr<MyDisplay> mydpy_;
unique_ptr<MyColormap> mycolormap_;
/* ... */
public:
Gfx() {
mydpy_.reset(new MyDisplay());
MyXVisualInfo myinfo(dpy);
mycolormap_.reset(new MyXColormap(mydpy_->dpy,
DefaultRootWindow(mydpy_->dpy),
myinfo->info, AllocNone));
}
};
Теперь это явно перепроектировано и стало кладжом для обслуживания. Также нехорошо вводить unique_ptr, который имеет ненужные накладные расходы.
Разве нет чистого подхода, который делал бы то, что делает версия C, в чистой манере RAII?