воскресенье, 21 ноября 2010 г.

Сцены OpenGL в виджетах GTK

Весь интернет пестрит примерами использования OpenGL. Найти их не сложно. OpenGL сам по себе является кроссплатформенным. А поэтому, чтобы создать сцену OpenGL вам нужно знать только сам OpenGL. А вот когда сама сцена создана, встает вопрос - и куда мы эту сцену будем "запихивать"? Вот тут-то и всплывает "однобокость" этого великого разнообразия примеров OpenGL: они демонстрируют создание упомянутых сцен в одном единственном окне, окне библиотеки GLUT. Эта библиотека, как я понял, и была создана всего-навсего для написания "образовательных" и демонстрационных целей. И эти цели она выполнила и даже перевыполнила. Также к ее плюсам можно добавить ее кроссплатформенность. Ее реализиция есть и для систем UNIX и для Windows. Но ведь сцены OpenGL не являются необходимыми "сами по себе". Понятно же, что приложение, которое представляет из себя одно единственное окно с ,пусть с даже очень впечатляющим и красивым, двух- или трех- мерным изображением, вряд ли представит из себя какой-либо интерес, кроме, как мы уже указали, демонстрационного.
Здесь мы рассмотрим как вставить сцену OpenGL в виджет GTK. Интерфейс любого приложения GTK является набором, комбинацией так-называемых виджетов, размещенных, как укажет программист, на окнах приложения. Четкого определения, что такое виджет нет, из-за того, что это очень общее, объемное, понятие. Причем его "общность" истинна, так как на самом деле понять, что такое виджет совсем просто. Сложнее рассказать. Виджет - это "нечто", что можно отобразить на эране, окне приложения, диалоге. Любая кнопка, элемент управления, некая область, которая четко отделена и распознаваема от остальной части дилога, окна и есть виджет. Виджет GTK- это просто некий тип, структура GtkWidget.
Вот мы и подобрались вплотную к основной теме. Рассмотрим два виджета, которые позволяют рисовать сцены OpenGL в себя. Эти виджеты: gtkglarea и gtkglext. Имеются их реализации в виде пакетов для. самых распространенных по-крайней мере, дистрибутивов Linux. Также есть их порты для FreeBSD, что заставляет меня предполагать, что они есть для всех систем BSD.
Рассмотрим сначала gtkglarea.
Создаем файл gtkglarea_demo.c следующего содержания (вообще-то это совсем немного измененный для собственных нужд файл взятый отсюда):


#include <GL/gl.h>
#include <GL/glu.h>
#include <gtk/gtk.h>
#include <gtkgl/gtkglarea.h>

gint glarea_button_press (GtkWidget*, GdkEventButton*);
gint glarea_draw (GtkWidget*, GdkEventExpose*);
gint glarea_reshape (GtkWidget*, GdkEventConfigure*);
gint glarea_init (GtkWidget*);
gint glarea_destroy (GtkWidget*);
int main (int, char**);

gint
glarea_button_press (GtkWidget* widget, GdkEventButton* event) {
int x = event->x;
int y = event->y;
if (event->button == 1) {
g_print ("Button 1 press (%d, %d)\n", x, y);
return TRUE;
}
if (event->button == 2) {
g_print ("Button 1 press (%d, %d)\n", x, y);
return TRUE;
}
return FALSE;
}

gint
glarea_draw (GtkWidget* widget, GdkEventExpose* event) {
if (event->count > 0) {
return(TRUE);
}
g_print ("Expose Event\n");
if (gtk_gl_area_make_current(GTK_GL_AREA(widget))) {
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
/* */
/* Insert your drawing code here. */
/* */
gtk_gl_area_swapbuffers (GTK_GL_AREA(widget));
}
return (TRUE);
}

gint
glarea_reshape (GtkWidget* widget, GdkEventConfigure* event) {
int w = widget->allocation.width;
int h = widget->allocation.height;
g_print ("Reshape Event\n");
if (gtk_gl_area_make_current (GTK_GL_AREA(widget))) {
/* This is an example 2D reshape function. Writing reshape */
/* functions is beyond the scope of this demo. Check the */
/* red book or the www.opengl.org for more information on */
/* how to write reshape code to suit your needs. */
glViewport (0, 0, w, h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluOrtho2D (-(w>>1), (w>>1), -(h>>1), h>>1);
glMatrixMode (GL_MODELVIEW);
}
return (TRUE);
}

gint
glarea_init (GtkWidget* widget) {
g_print ("Realize Event\n");
if (gtk_gl_area_make_current (GTK_GL_AREA(widget))) {
/* Insert your OpenGL initialization code here */
}
return TRUE;
}

gint
glarea_destroy (GtkWidget* widget) {
g_print ("GTK GL Area Destroy Event\n");
/* Insert any required cleanup */
/* code here. */
return TRUE;
}

int
main (int argc, char** argv) {
GtkWidget* window;
GtkWidget* button_quit;
GtkWidget* box_main;
GtkWidget* glarea;
/* These attributes are passed to glXChooseVisual by the */
/* gdk (see gdk_gl_choose_visual in gdkgl.c from the GtkGlarea distro). */
int attrlist[] = {
GDK_GL_RGBA,
GDK_GL_DOUBLEBUFFER,
GDK_GL_DEPTH_SIZE, 1,
GDK_GL_NONE
};
gtk_init (&argc, &argv);
if(gdk_gl_query() == FALSE) {
g_print("OpenGL not supported!\n");
return (1);
}

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW(window), "GtkGLArea Demo For Blogger");
gtk_quit_add_destroy (1, GTK_OBJECT(window));
gtk_signal_connect (GTK_OBJECT(window), "delete_event", GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
gtk_container_set_border_width (GTK_CONTAINER(window), 10);
gtk_widget_show (window);

box_main = gtk_vbox_new (FALSE, 10);
gtk_container_add (GTK_CONTAINER(window), box_main);
gtk_widget_show (box_main);

glarea = gtk_gl_area_new(attrlist);
gtk_widget_set_events(GTK_WIDGET(glarea),
GDK_EXPOSURE_MASK|
GDK_BUTTON_PRESS_MASK);
gtk_signal_connect (GTK_OBJECT(glarea), "button_press_event", GTK_SIGNAL_FUNC(glarea_button_press), NULL);
gtk_signal_connect (GTK_OBJECT(glarea), "expose_event", GTK_SIGNAL_FUNC(glarea_draw), NULL);
gtk_signal_connect (GTK_OBJECT(glarea), "configure_event", GTK_SIGNAL_FUNC(glarea_reshape), NULL);
gtk_signal_connect (GTK_OBJECT(glarea), "realize", GTK_SIGNAL_FUNC(glarea_init), NULL);
gtk_signal_connect (GTK_OBJECT(glarea), "destroy", GTK_SIGNAL_FUNC (glarea_destroy), NULL);
gtk_widget_set_usize(GTK_WIDGET(glarea), 256, 256);
gtk_box_pack_start (GTK_BOX(box_main), glarea, FALSE, TRUE, 0);
gtk_widget_show (glarea);

button_quit = gtk_button_new_with_label ("Quit");
gtk_signal_connect (GTK_OBJECT(button_quit), "clicked", GTK_SIGNAL_FUNC(gtk_main_quit), NULL);
gtk_box_pack_start (GTK_BOX(box_main), button_quit, FALSE, TRUE, 0);
gtk_widget_show (button_quit);

gtk_main ();

return (0);
}


И компиллируем его следующей командой:
#gcc gtkglarea_demo.c -o gtkglarea_demo `pkg-config gtk+-2.0 glib-2.0 gtkgl-2.0 --cflags --libs`
Запускаем на выполнение gtkglarea_demo и видим диалоговое окно с черным квадратом, что и есть (пустая) сцена OpenGL и кнопкой выхода под нашим виджетом.
Файл gtkglarea.h содержится в пакете libgtkgl2.0-dev.
Рассмотрим теперь "конкурента", то есть gtkglext.
Аналогично, создаем файл gtkglext_demo.c с содержимым (он является упрощенной и измененной версией файла simple.c из пакета libgtkglext1-dev):


#include <stdlib.h>
#include <gtk/gtk.h>
#include <gtk/gtkgl.h>
#include <GL/gl.h>
#include <GL/glu.h>

static void
realize (GtkWidget *widget, gpointer data){
GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);

if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
return;

glClearColor (0.0, 0.0, 0.0, 0.0);
glClearDepth (1.0);
glViewport (0, 0, widget->allocation.width, widget->allocation.height);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (40.0, 1.0, 1.0, 10.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
gluLookAt ( 0.0, 0.0, 3.0,
0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
glTranslatef (0.0, 0.0, -3.0);

gdk_gl_drawable_gl_end (gldrawable);
}

static gboolean
configure_event (GtkWidget *widget, GdkEventConfigure *event, gpointer data){
GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);

if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
return FALSE;
glViewport (0, 0, widget->allocation.width, widget->allocation.height);
gdk_gl_drawable_gl_end (gldrawable);

return TRUE;
}

static gboolean
expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data){
GdkGLContext *glcontext = gtk_widget_get_gl_context (widget);
GdkGLDrawable *gldrawable = gtk_widget_get_gl_drawable (widget);

if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
return FALSE;
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (gdk_gl_drawable_is_double_buffered (gldrawable))
gdk_gl_drawable_swap_buffers (gldrawable);
else
glFlush ();
gdk_gl_drawable_gl_end (gldrawable);

return TRUE;
}

int
main (int argc, char *argv[]){
GdkGLConfig *glconfig;
gint major, minor;
GtkWidget *window;
GtkWidget *vbox;
GtkWidget *drawing_area;
GtkWidget *button;

gtk_init (&argc, &argv);
gtk_gl_init (&argc, &argv);
gdk_gl_query_version (&major, &minor);
g_print ("\nOpenGL extension version - %d.%d\n",major, minor);

glconfig = gdk_gl_config_new_by_mode ( GDK_GL_MODE_RGB |
GDK_GL_MODE_DEPTH |
GDK_GL_MODE_DOUBLE);
if (glconfig == NULL){
glconfig = gdk_gl_config_new_by_mode ( GDK_GL_MODE_RGB |
GDK_GL_MODE_DEPTH);
if (glconfig == NULL)
exit (1);
}

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "GtkGLExt Demo For Blogger");
gtk_container_set_reallocate_redraws (GTK_CONTAINER (window), TRUE);
g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
gtk_container_set_border_width (GTK_CONTAINER(window), 10);
gtk_widget_show (window);

vbox = gtk_vbox_new (FALSE, 10);
gtk_container_add (GTK_CONTAINER (window), vbox);
gtk_widget_show (vbox);

drawing_area = gtk_drawing_area_new ();
gtk_widget_set_size_request (drawing_area, 256, 256);
gtk_widget_set_gl_capability ( drawing_area,
glconfig,
NULL,
TRUE,
GDK_GL_RGBA_TYPE);
g_signal_connect_after (G_OBJECT (drawing_area), "realize", G_CALLBACK (realize), NULL);
g_signal_connect (G_OBJECT (drawing_area), "configure_event", G_CALLBACK (configure_event), NULL);
g_signal_connect (G_OBJECT (drawing_area), "expose_event", G_CALLBACK (expose_event), NULL);
gtk_box_pack_start (GTK_BOX (vbox), drawing_area, TRUE, TRUE, 0);
gtk_widget_show (drawing_area);

button = gtk_button_new_with_label ("Quit");
g_signal_connect (G_OBJECT (button), "clicked",G_CALLBACK (gtk_main_quit), NULL);
gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
gtk_widget_show (button);

gtk_main ();

return 0;
}


Компиллируем
#gcc gtkglext_demo.c -o gtkglext_demo `pkg-config gtk+-2.0 glib-2.0 gtkglext-1.0 --cflags --libs`
И получаем исполняемый файл gtkglext_demo, который дает тот же результат, что и предыдущий пример, только заголовок окна приложения соответственно изменен.
Коротко обсудим разницу между этими двумя протыми приложениями.
Интерфейс gtkglarea можно посмотреть в файле gtkglarea.h из пакета libgtkgl2.0-dev.
Там можно увидеть, что этот виджет является прямым потомком виджета GtkDrawingArea. Еще одним полем в структуре, соответствующей этому виджету, есть переменная типа GdkGLContext. И в первом примере мы с этим виджетом обращаемся стандартно: создаем его своим собственным конструктором, пакуем, устанавливаем сигналы, показываем. Когда рисуем в этот виджет, то пользуемся функцией gtk_gl_area_make_current.
В случае с gtkglext работа происходит несколько иначе. Создается объект типа GdkGLConfig и потом он "привязывается" к виджету GtkDrawingArea. Затем, когда посылаем команды OpenGL в этот виджет, то создаем объекты типа GdkGLContext и GdkGLDrawable, после чего заключаем команды OpenGL в "функциональные скобки" gdk_gl_drawable_gl_begin и gdk_gl_drawable_gl_end.
Оба примера были протестированы на squeeze Debian.