├── .github └── workflows │ └── build.yml ├── .gitignore ├── Makefile ├── README.md ├── background.c ├── background.h ├── gui.c ├── gui.h ├── main.c ├── matrix.c ├── matrix.h ├── model.c ├── model.h ├── program.c ├── program.h ├── screenshot.png ├── shaders ├── bkgd │ ├── fragment.glsl │ └── vertex.glsl └── cube │ ├── fragment.glsl │ └── vertex.glsl ├── textures ├── background.png └── background.svg ├── util.h ├── view.c └── view.h /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | Build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v3 11 | - name: Install dependencies 12 | run: | 13 | sudo apt-get update 14 | sudo apt-get install libgl-dev 15 | sudo apt-get install libgtk-3-dev 16 | sudo apt-get install librsvg2-bin 17 | - name: Compile 18 | run: make 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | gtk3-opengl 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # GTK+-3 introduced the native GtkGLArea in 3.16. 2 | # Check that we have at least that version: 3 | ifneq ($(shell pkg-config --atleast-version=3.16 gtk+-3.0 && echo 1 || echo 0),1) 4 | $(error $(shell pkg-config --print-errors --atleast-version=3.16 gtk+-3.0)) 5 | endif 6 | 7 | BIN = gtk3-opengl 8 | 9 | CFLAGS += -std=c99 -DGL_GLEXT_PROTOTYPES 10 | CFLAGS += $(shell pkg-config --cflags gtk+-3.0 gl) 11 | LIBS += $(shell pkg-config --libs gtk+-3.0 gl) 12 | LIBS += -lm 13 | 14 | OBJS = $(patsubst %.c,%.o,$(wildcard *.c)) 15 | OBJS += $(patsubst %.glsl,%.o,$(wildcard shaders/*/*.glsl)) 16 | OBJS += $(patsubst %.svg,%.o,$(wildcard textures/*.svg)) 17 | 18 | .PHONY: clean 19 | 20 | $(BIN): $(OBJS) 21 | $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) 22 | 23 | textures/%.png: textures/%.svg 24 | rsvg-convert --format png --output $@ $^ 25 | 26 | %.o: %.c 27 | $(CC) $(CFLAGS) -o $@ -c $^ 28 | 29 | %.o: %.glsl 30 | $(LD) -r -b binary -o $@ $^ 31 | 32 | %.o: %.png 33 | $(LD) -r -b binary -o $@ $^ 34 | 35 | clean: 36 | $(RM) $(BIN) $(OBJS) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gtk3-opengl 2 | 3 | [![Build](https://github.com/aklomp/gtk3-opengl/actions/workflows/build.yml/badge.svg)](https://github.com/aklomp/gtk3-opengl/actions/workflows/build.yml) 4 | 5 | A small test program to figure out how best to combine GTK+ 3's `GtkGLArea`, 6 | GDK 3's `GdkFrameClock`, and modern OpenGL. Will open a small window with a 7 | rotating, shaded cube that you can interact with with the mouse. 8 | 9 | ![Screenshot](screenshot.png) 10 | 11 | ## License 12 | 13 | This repository is licensed under the GPL version 3. 14 | -------------------------------------------------------------------------------- /background.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "program.h" 5 | 6 | static GLuint texture; 7 | static GLuint vao, vbo; 8 | 9 | // Each vertex has space and texture coordinates: 10 | struct vertex { 11 | float x; 12 | float y; 13 | float u; 14 | float v; 15 | } __attribute__((packed)); 16 | 17 | void 18 | background_set_window (int width, int height) 19 | { 20 | float wd = (float)width / 16; 21 | float ht = (float)height / 16; 22 | 23 | // The background quad is made of four vertices: 24 | // 25 | // 3--2 26 | // | | 27 | // 0--1 28 | // 29 | struct vertex vertex[4] = { 30 | { -1, -1, 0, 0 }, // Bottom left 31 | { 1, -1, wd, 0 }, // Bottom right 32 | { 1, 1, wd, ht }, // Top right 33 | { -1, 1, 0, ht }, // Top left 34 | }; 35 | 36 | GLint loc_vertex = program_bkgd_loc(LOC_BKGD_VERTEX); 37 | GLint loc_texture = program_bkgd_loc(LOC_BKGD_TEXTURE); 38 | 39 | glBindVertexArray(vao); 40 | 41 | glEnableVertexAttribArray(loc_vertex); 42 | glEnableVertexAttribArray(loc_texture); 43 | 44 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 45 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertex), vertex, GL_STATIC_DRAW); 46 | 47 | glVertexAttribPointer(loc_vertex, 2, GL_FLOAT, GL_FALSE, 48 | sizeof(struct vertex), 49 | (void *) offsetof(struct vertex, x)); 50 | 51 | glVertexAttribPointer(loc_texture, 2, GL_FLOAT, GL_FALSE, 52 | sizeof(struct vertex), 53 | (void *) offsetof(struct vertex, u)); 54 | 55 | glBindVertexArray(0); 56 | } 57 | 58 | void 59 | background_draw (void) 60 | { 61 | // Array of indices. We define two counterclockwise triangles: 62 | // 0-2-3 and 2-0-1 63 | static GLubyte index[6] = { 64 | 0, 2, 3, 65 | 2, 0, 1, 66 | }; 67 | 68 | program_bkgd_use(); 69 | glActiveTexture(GL_TEXTURE0); 70 | glBindTexture(GL_TEXTURE_2D, texture); 71 | glBindVertexArray(vao); 72 | glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, index); 73 | glBindVertexArray(0); 74 | } 75 | 76 | void 77 | background_init (void) 78 | { 79 | // Inline data declaration: 80 | extern char _binary_textures_background_png_start[]; 81 | extern char _binary_textures_background_png_end[]; 82 | 83 | char *start = _binary_textures_background_png_start; 84 | size_t len = _binary_textures_background_png_end 85 | - _binary_textures_background_png_start; 86 | 87 | GInputStream *stream; 88 | GdkPixbuf *pixbuf; 89 | 90 | // Create an input stream from inline data: 91 | stream = g_memory_input_stream_new_from_data(start, len, NULL); 92 | 93 | // Generate a pixbuf from the input stream: 94 | pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, NULL); 95 | 96 | // Destroy the stream: 97 | g_object_unref(stream); 98 | 99 | // Generate an OpenGL texture from pixbuf; 100 | // hack a bit by not accounting for pixbuf rowstride: 101 | glGenTextures(1, &texture); 102 | glBindTexture(GL_TEXTURE_2D, texture); 103 | 104 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 105 | gdk_pixbuf_get_width(pixbuf), 106 | gdk_pixbuf_get_height(pixbuf), 0, GL_RGB, GL_UNSIGNED_BYTE, 107 | gdk_pixbuf_get_pixels(pixbuf)); 108 | 109 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 110 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 111 | 112 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 113 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 114 | 115 | // Generate empty buffer: 116 | glGenBuffers(1, &vbo); 117 | 118 | // Generate empty vertex array object: 119 | glGenVertexArrays(1, &vao); 120 | } 121 | -------------------------------------------------------------------------------- /background.h: -------------------------------------------------------------------------------- 1 | void background_draw (void); 2 | void background_init (void); 3 | void background_set_window (int width, int height); 4 | -------------------------------------------------------------------------------- /gui.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "background.h" 7 | #include "matrix.h" 8 | #include "model.h" 9 | #include "program.h" 10 | #include "util.h" 11 | #include "view.h" 12 | 13 | // Hold init data for GTK signals: 14 | struct signal { 15 | const gchar *signal; 16 | GCallback handler; 17 | GdkEventMask mask; 18 | }; 19 | 20 | static gboolean panning = FALSE; 21 | 22 | static void 23 | on_resize (GtkGLArea *area, gint width, gint height) 24 | { 25 | view_set_window(width, height); 26 | background_set_window(width, height); 27 | } 28 | 29 | static gboolean 30 | on_render (GtkGLArea *glarea, GdkGLContext *context) 31 | { 32 | // Clear canvas: 33 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 34 | 35 | // Draw background: 36 | background_draw(); 37 | 38 | // Draw model: 39 | model_draw(); 40 | 41 | // Don't propagate signal: 42 | return TRUE; 43 | } 44 | 45 | static void 46 | on_realize (GtkGLArea *glarea) 47 | { 48 | // Make current: 49 | gtk_gl_area_make_current(glarea); 50 | 51 | // Print version info: 52 | const GLubyte* renderer = glGetString(GL_RENDERER); 53 | const GLubyte* version = glGetString(GL_VERSION); 54 | printf("Renderer: %s\n", renderer); 55 | printf("OpenGL version supported %s\n", version); 56 | 57 | // Enable depth buffer: 58 | gtk_gl_area_set_has_depth_buffer(glarea, TRUE); 59 | 60 | // Init programs: 61 | programs_init(); 62 | 63 | // Init background: 64 | background_init(); 65 | 66 | // Init model: 67 | model_init(); 68 | 69 | // Get frame clock: 70 | GdkGLContext *glcontext = gtk_gl_area_get_context(glarea); 71 | GdkWindow *glwindow = gdk_gl_context_get_window(glcontext); 72 | GdkFrameClock *frame_clock = gdk_window_get_frame_clock(glwindow); 73 | 74 | // Connect update signal: 75 | g_signal_connect_swapped 76 | ( frame_clock 77 | , "update" 78 | , G_CALLBACK(gtk_gl_area_queue_render) 79 | , glarea 80 | ) ; 81 | 82 | // Start updating: 83 | gdk_frame_clock_begin_updating(frame_clock); 84 | } 85 | 86 | static gboolean 87 | on_button_press (GtkWidget *widget, GdkEventButton *event) 88 | { 89 | GtkAllocation allocation; 90 | gtk_widget_get_allocation(widget, &allocation); 91 | 92 | if (event->button == 1) 93 | if (panning == FALSE) { 94 | panning = TRUE; 95 | model_pan_start(event->x, allocation.height - event->y); 96 | } 97 | 98 | return FALSE; 99 | } 100 | 101 | static gboolean 102 | on_button_release (GtkWidget *widget, GdkEventButton *event) 103 | { 104 | if (event->button == 1) 105 | panning = FALSE; 106 | 107 | return FALSE; 108 | } 109 | 110 | static gboolean 111 | on_motion_notify (GtkWidget *widget, GdkEventMotion *event) 112 | { 113 | GtkAllocation allocation; 114 | gtk_widget_get_allocation(widget, &allocation); 115 | 116 | if (panning == TRUE) 117 | model_pan_move(event->x, allocation.height - event->y); 118 | 119 | return FALSE; 120 | } 121 | 122 | static gboolean 123 | on_scroll (GtkWidget* widget, GdkEventScroll *event) 124 | { 125 | switch (event->direction) 126 | { 127 | case GDK_SCROLL_UP: 128 | view_z_decrease(); 129 | break; 130 | 131 | case GDK_SCROLL_DOWN: 132 | view_z_increase(); 133 | break; 134 | 135 | default: 136 | break; 137 | } 138 | 139 | return FALSE; 140 | } 141 | 142 | static void 143 | connect_signals (GtkWidget *widget, struct signal *signals, size_t members) 144 | { 145 | FOREACH_NELEM (signals, members, s) { 146 | gtk_widget_add_events(widget, s->mask); 147 | g_signal_connect(widget, s->signal, s->handler, NULL); 148 | } 149 | } 150 | 151 | static void 152 | connect_window_signals (GtkWidget *window) 153 | { 154 | struct signal signals[] = { 155 | { "destroy", G_CALLBACK(gtk_main_quit), 0 }, 156 | }; 157 | 158 | connect_signals(window, signals, NELEM(signals)); 159 | } 160 | 161 | static void 162 | connect_glarea_signals (GtkWidget *glarea) 163 | { 164 | struct signal signals[] = { 165 | { "realize", G_CALLBACK(on_realize), 0 }, 166 | { "render", G_CALLBACK(on_render), 0 }, 167 | { "resize", G_CALLBACK(on_resize), 0 }, 168 | { "scroll-event", G_CALLBACK(on_scroll), GDK_SCROLL_MASK }, 169 | { "button-press-event", G_CALLBACK(on_button_press), GDK_BUTTON_PRESS_MASK }, 170 | { "button-release-event", G_CALLBACK(on_button_release), GDK_BUTTON_RELEASE_MASK }, 171 | { "motion-notify-event", G_CALLBACK(on_motion_notify), GDK_BUTTON1_MOTION_MASK }, 172 | }; 173 | 174 | connect_signals(glarea, signals, NELEM(signals)); 175 | } 176 | 177 | bool 178 | gui_init (int *argc, char ***argv) 179 | { 180 | // Initialize GTK: 181 | if (!gtk_init_check(argc, argv)) { 182 | fputs("Could not initialize GTK", stderr); 183 | return false; 184 | } 185 | 186 | return true; 187 | } 188 | 189 | bool 190 | gui_run (void) 191 | { 192 | // Create toplevel window, add GtkGLArea: 193 | GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); 194 | GtkWidget *glarea = gtk_gl_area_new(); 195 | gtk_container_add(GTK_CONTAINER(window), glarea); 196 | 197 | // Connect GTK signals: 198 | connect_window_signals(window); 199 | connect_glarea_signals(glarea); 200 | 201 | gtk_widget_show_all(window); 202 | 203 | // Enter GTK event loop: 204 | gtk_main(); 205 | 206 | return true; 207 | } 208 | -------------------------------------------------------------------------------- /gui.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern bool gui_init (int *argc, char ***argv); 4 | extern bool gui_run (void); 5 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "gui.h" 2 | 3 | // Application entry point 4 | int 5 | main (int argc, char **argv) 6 | { 7 | return gui_init(&argc, &argv) && gui_run() ? 0 : 1; 8 | } 9 | -------------------------------------------------------------------------------- /matrix.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void 4 | mat_frustum (float *matrix, float angle_of_view, float aspect_ratio, float z_near, float z_far) 5 | { 6 | matrix[0] = 1.0f / tanf(angle_of_view); 7 | matrix[1] = 0.0f; 8 | matrix[2] = 0.0f; 9 | matrix[3] = 0.0f; 10 | matrix[4] = 0.0f; 11 | matrix[5] = aspect_ratio / tanf(angle_of_view); 12 | matrix[6] = 0.0f; 13 | matrix[7] = 0.0f; 14 | matrix[8] = 0.0f; 15 | matrix[9] = 0.0f; 16 | matrix[10] = (z_far + z_near) / (z_far - z_near); 17 | matrix[11] = 1.0f; 18 | matrix[12] = 0.0f; 19 | matrix[13] = 0.0f; 20 | matrix[14] = -2.0f * z_far * z_near / (z_far - z_near); 21 | matrix[15] = 0.0f; 22 | } 23 | 24 | void 25 | mat_translate (float *matrix, float dx, float dy, float dz) 26 | { 27 | matrix[0] = 1; 28 | matrix[1] = 0; 29 | matrix[2] = 0; 30 | matrix[3] = 0; 31 | matrix[4] = 0; 32 | matrix[5] = 1; 33 | matrix[6] = 0; 34 | matrix[7] = 0; 35 | matrix[8] = 0; 36 | matrix[9] = 0; 37 | matrix[10] = 1; 38 | matrix[11] = 0; 39 | matrix[12] = dx; 40 | matrix[13] = dy; 41 | matrix[14] = dz; 42 | matrix[15] = 1; 43 | } 44 | 45 | static void 46 | normalize (float *x, float *y, float *z) 47 | { 48 | float d = sqrtf((*x) * (*x) + (*y) * (*y) + (*z) * (*z)); 49 | *x /= d; 50 | *y /= d; 51 | *z /= d; 52 | } 53 | 54 | void 55 | mat_rotate (float *matrix, float x, float y, float z, float angle) 56 | { 57 | normalize(&x, &y, &z); 58 | 59 | float s = sinf(angle); 60 | float c = cosf(angle); 61 | float m = 1 - c; 62 | 63 | matrix[0] = m * x * x + c; 64 | matrix[1] = m * x * y - z * s; 65 | matrix[2] = m * z * x + y * s; 66 | matrix[3] = 0; 67 | matrix[4] = m * x * y + z * s; 68 | matrix[5] = m * y * y + c; 69 | matrix[6] = m * y * z - x * s; 70 | matrix[7] = 0; 71 | matrix[8] = m * z * x - y * s; 72 | matrix[9] = m * y * z + x * s; 73 | matrix[10] = m * z * z + c; 74 | matrix[11] = 0; 75 | matrix[12] = 0; 76 | matrix[13] = 0; 77 | matrix[14] = 0; 78 | matrix[15] = 1; 79 | } 80 | 81 | void 82 | mat_multiply (float *matrix, float *a, float *b) 83 | { 84 | float result[16]; 85 | for (int c = 0; c < 4; c++) { 86 | for (int r = 0; r < 4; r++) { 87 | int index = c * 4 + r; 88 | float total = 0; 89 | for (int i = 0; i < 4; i++) { 90 | int p = i * 4 + r; 91 | int q = c * 4 + i; 92 | total += a[p] * b[q]; 93 | } 94 | result[index] = total; 95 | } 96 | } 97 | for (int i = 0; i < 16; i++) 98 | matrix[i] = result[i]; 99 | } 100 | -------------------------------------------------------------------------------- /matrix.h: -------------------------------------------------------------------------------- 1 | void mat_frustum (float *matrix, float angle_of_view, float aspect_ratio, float z_near, float z_far); 2 | void mat_translate (float *matrix, float dx, float dy, float dz); 3 | void mat_rotate (float *matrix, float x, float y, float z, float angle); 4 | void mat_multiply (float *matrix, float *a, float *b); 5 | -------------------------------------------------------------------------------- /model.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "matrix.h" 6 | #include "program.h" 7 | #include "util.h" 8 | 9 | struct point { 10 | float x; 11 | float y; 12 | float z; 13 | } __attribute__((packed)); 14 | 15 | struct color { 16 | float r; 17 | float g; 18 | float b; 19 | } __attribute__((packed)); 20 | 21 | // Each vertex has position, normal and color: 22 | struct vertex { 23 | struct point pos; 24 | struct point normal; 25 | struct color color; 26 | } __attribute__((packed)); 27 | 28 | // Each triangle has three vertices: 29 | struct triangle { 30 | struct vertex vert[3]; 31 | } __attribute__((packed)); 32 | 33 | // Each corner point has a position and a color: 34 | struct corner { 35 | struct point pos; 36 | struct color color; 37 | } __attribute__((packed)); 38 | 39 | // Each face has a single normal, four corner points, 40 | // and two triangles: 41 | struct face { 42 | struct corner corner[4]; 43 | struct point normal; 44 | struct triangle tri[2]; 45 | } __attribute__((packed)); 46 | 47 | // Each cube has six faces: 48 | struct cube { 49 | struct face face[6]; 50 | } __attribute__((packed)); 51 | 52 | static GLuint vao, vbo; 53 | static float matrix[16] = { 0 }; 54 | 55 | // Mouse movement: 56 | static struct { 57 | int x; 58 | int y; 59 | } pan; 60 | 61 | // Cube rotation axis: 62 | static struct point rot = { 63 | .x = 0.0f, 64 | .y = 1.0f, 65 | .z = 0.0f, 66 | }; 67 | 68 | // Return the cross product of two vectors: 69 | static void 70 | cross (struct point *result, const struct point *a, const struct point *b) 71 | { 72 | result->x = a->y * b->z - a->z * b->y; 73 | result->y = a->z * b->x - a->x * b->z; 74 | result->z = a->x * b->y - a->y * b->x; 75 | } 76 | 77 | // Initialize the model: 78 | void 79 | model_init (void) 80 | { 81 | // Define our cube: 82 | struct cube cube = 83 | { .face[0].corner = 84 | { { 0, 1, 0 } 85 | , { 1, 0, 0 } 86 | , { 0, 0, 0 } 87 | , { 1, 1, 0 } 88 | } 89 | , .face[1].corner = 90 | { { 0, 0, 0 } 91 | , { 1, 0, 1 } 92 | , { 0, 0, 1 } 93 | , { 1, 0, 0 } 94 | } 95 | , .face[2].corner = 96 | { { 1, 0, 0 } 97 | , { 1, 1, 1 } 98 | , { 1, 0, 1 } 99 | , { 1, 1, 0 } 100 | } 101 | , .face[3].corner = 102 | { { 1, 1, 0 } 103 | , { 0, 1, 1 } 104 | , { 1, 1, 1 } 105 | , { 0, 1, 0 } 106 | } 107 | , .face[4].corner = 108 | { { 0, 1, 0 } 109 | , { 0, 0, 1 } 110 | , { 0, 1, 1 } 111 | , { 0, 0, 0 } 112 | } 113 | , .face[5].corner = 114 | { { 0, 1, 1 } 115 | , { 1, 0, 1 } 116 | , { 1, 1, 1 } 117 | , { 0, 0, 1 } 118 | } 119 | } ; 120 | 121 | // Generate colors for each corner based on its position: 122 | FOREACH (cube.face, face) { 123 | FOREACH (face->corner, corner) { 124 | corner->color.r = corner->pos.x * 0.8f + 0.1f; 125 | corner->color.g = corner->pos.y * 0.8f + 0.1f; 126 | corner->color.b = corner->pos.z * 0.8f + 0.1f; 127 | } 128 | } 129 | 130 | // Center cube on the origin by translating corner points: 131 | FOREACH (cube.face, face) { 132 | FOREACH (face->corner, corner) { 133 | corner->pos.x -= 0.5f; 134 | corner->pos.y -= 0.5f; 135 | corner->pos.z -= 0.5f; 136 | } 137 | } 138 | 139 | // Face normals are cross product of two ribs: 140 | FOREACH (cube.face, face) { 141 | 142 | // First rib is (corner 3 - corner 0): 143 | struct point a = { 144 | .x = face->corner[3].pos.x - face->corner[0].pos.x, 145 | .y = face->corner[3].pos.y - face->corner[0].pos.y, 146 | .z = face->corner[3].pos.z - face->corner[0].pos.z, 147 | }; 148 | 149 | // Second rib is (corner 2 - corner 0): 150 | struct point b = { 151 | .x = face->corner[2].pos.x - face->corner[0].pos.x, 152 | .y = face->corner[2].pos.y - face->corner[0].pos.y, 153 | .z = face->corner[2].pos.z - face->corner[0].pos.z, 154 | }; 155 | 156 | // Face normal is cross product of these two ribs: 157 | cross(&face->normal, &a, &b); 158 | } 159 | 160 | // Create two triangles for each face: 161 | FOREACH (cube.face, face) { 162 | 163 | // Corners to compose triangles of, chosen in 164 | // such a way that both triangles rotate CCW: 165 | int index[2][3] = { { 0, 2, 1 }, { 1, 3, 0 } }; 166 | 167 | for (int t = 0; t < 2; t++) { 168 | for (int v = 0; v < 3; v++) { 169 | int c = index[t][v]; 170 | struct corner *corner = &face->corner[c]; 171 | struct vertex *vertex = &face->tri[t].vert[v]; 172 | 173 | vertex->pos = corner->pos; 174 | vertex->normal = face->normal; 175 | vertex->color = corner->color; 176 | } 177 | } 178 | } 179 | 180 | // Copy vertices into separate array for drawing: 181 | struct vertex vertex[6 * 2 * 3]; 182 | struct vertex *cur = vertex; 183 | 184 | FOREACH (cube.face, face) { 185 | FOREACH (face->tri, tri) { 186 | for (int v = 0; v < 3; v++) { 187 | *cur++ = tri->vert[v]; 188 | } 189 | } 190 | } 191 | 192 | // Generate empty buffer: 193 | glGenBuffers(1, &vbo); 194 | 195 | // Generate empty vertex array object: 196 | glGenVertexArrays(1, &vao); 197 | 198 | // Set as current vertex array: 199 | glBindVertexArray(vao); 200 | glBindBuffer(GL_ARRAY_BUFFER, vbo); 201 | 202 | // Add vertex, color and normal data to buffers: 203 | struct { 204 | enum LocCube loc; 205 | const void *ptr; 206 | } 207 | map[] = { 208 | { .loc = LOC_CUBE_VERTEX 209 | , .ptr = (void *) offsetof(struct vertex, pos) 210 | } , 211 | { .loc = LOC_CUBE_VCOLOR 212 | , .ptr = (void *) offsetof(struct vertex, color) 213 | } , 214 | { .loc = LOC_CUBE_NORMAL 215 | , .ptr = (void *) offsetof(struct vertex, normal) 216 | } , 217 | }; 218 | 219 | FOREACH (map, m) { 220 | GLint loc = program_cube_loc(m->loc); 221 | glEnableVertexAttribArray(loc); 222 | glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, sizeof(struct vertex), m->ptr); 223 | } 224 | 225 | // Upload vertex data: 226 | glBufferData(GL_ARRAY_BUFFER, sizeof(vertex), vertex, GL_STATIC_DRAW); 227 | } 228 | 229 | void 230 | model_draw (void) 231 | { 232 | static float angle = 0.0f; 233 | 234 | // Rotate slightly: 235 | angle += 0.01f; 236 | 237 | // Setup rotation matrix: 238 | mat_rotate(matrix, rot.x, rot.y, rot.z, angle); 239 | 240 | // Use our own shaders: 241 | program_cube_use(); 242 | 243 | // Don't clip against background: 244 | glClear(GL_DEPTH_BUFFER_BIT); 245 | 246 | // Draw all the triangles in the buffer: 247 | glBindVertexArray(vao); 248 | glDrawArrays(GL_TRIANGLES, 0, 12 * 3); 249 | } 250 | 251 | const float * 252 | model_matrix (void) 253 | { 254 | return matrix; 255 | } 256 | 257 | void 258 | model_pan_start (int x, int y) 259 | { 260 | pan.x = x; 261 | pan.y = y; 262 | } 263 | 264 | void 265 | model_pan_move (int x, int y) 266 | { 267 | int dx = pan.x - x; 268 | int dy = pan.y - y; 269 | 270 | // Rotation vector is perpendicular to (dx, dy): 271 | rot.x = dy; 272 | rot.y = -dx; 273 | } 274 | -------------------------------------------------------------------------------- /model.h: -------------------------------------------------------------------------------- 1 | void model_init (void); 2 | void model_draw (void); 3 | const float *model_matrix(void); 4 | void model_pan_start (int x, int y); 5 | void model_pan_move (int x, int y); 6 | -------------------------------------------------------------------------------- /program.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "model.h" 7 | #include "view.h" 8 | #include "program.h" 9 | #include "util.h" 10 | 11 | // Define inline data: 12 | #define DATA_DEF(x) \ 13 | extern const uint8_t _binary_shaders_## x ##_glsl_start[]; \ 14 | extern const uint8_t _binary_shaders_## x ##_glsl_end[]; 15 | 16 | #define SHADER(x) \ 17 | { .buf = _binary_shaders_ ## x ## _glsl_start \ 18 | , .end = _binary_shaders_ ## x ## _glsl_end \ 19 | } 20 | 21 | // Inline data definitions: 22 | DATA_DEF (bkgd_vertex) 23 | DATA_DEF (bkgd_fragment) 24 | DATA_DEF (cube_vertex) 25 | DATA_DEF (cube_fragment) 26 | 27 | // Shader structure: 28 | struct shader { 29 | const uint8_t *buf; 30 | const uint8_t *end; 31 | GLuint id; 32 | }; 33 | 34 | // Location definitions: 35 | enum loc_type { 36 | UNIFORM, 37 | ATTRIBUTE, 38 | }; 39 | 40 | struct loc { 41 | const char *name; 42 | enum loc_type type; 43 | GLint id; 44 | }; 45 | 46 | static struct loc loc_bkgd[] = { 47 | [LOC_BKGD_VERTEX] = { "vertex", ATTRIBUTE }, 48 | [LOC_BKGD_TEXTURE] = { "texture", ATTRIBUTE }, 49 | }; 50 | 51 | static struct loc loc_cube[] = { 52 | [LOC_CUBE_VIEW] = { "view_matrix", UNIFORM }, 53 | [LOC_CUBE_MODEL] = { "model_matrix", UNIFORM }, 54 | [LOC_CUBE_VERTEX] = { "vertex", ATTRIBUTE }, 55 | [LOC_CUBE_VCOLOR] = { "vcolor", ATTRIBUTE }, 56 | [LOC_CUBE_NORMAL] = { "normal", ATTRIBUTE }, 57 | }; 58 | 59 | // Programs: 60 | enum { 61 | BKGD, 62 | CUBE, 63 | }; 64 | 65 | // Program structure: 66 | static struct program { 67 | struct { 68 | struct shader vert; 69 | struct shader frag; 70 | } shader; 71 | struct loc *loc; 72 | size_t nloc; 73 | GLuint id; 74 | } 75 | programs[] = { 76 | [BKGD] = { 77 | .shader.vert = SHADER (bkgd_vertex), 78 | .shader.frag = SHADER (bkgd_fragment), 79 | .loc = loc_bkgd, 80 | .nloc = NELEM(loc_bkgd), 81 | }, 82 | [CUBE] = { 83 | .shader.vert = SHADER (cube_vertex), 84 | .shader.frag = SHADER (cube_fragment), 85 | .loc = loc_cube, 86 | .nloc = NELEM(loc_cube), 87 | }, 88 | }; 89 | 90 | static void 91 | check_compile (GLuint shader) 92 | { 93 | GLint length; 94 | 95 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); 96 | 97 | if (length <= 1) 98 | return; 99 | 100 | GLchar *log = calloc(length, sizeof(GLchar)); 101 | glGetShaderInfoLog(shader, length, NULL, log); 102 | fprintf(stderr, "glCompileShader failed:\n%s\n", log); 103 | free(log); 104 | } 105 | 106 | static void 107 | check_link (GLuint program) 108 | { 109 | GLint status, length; 110 | 111 | glGetProgramiv(program, GL_LINK_STATUS, &status); 112 | if (status != GL_FALSE) 113 | return; 114 | 115 | glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length); 116 | GLchar *log = calloc(length, sizeof(GLchar)); 117 | glGetProgramInfoLog(program, length, NULL, log); 118 | fprintf(stderr, "glLinkProgram failed: %s\n", log); 119 | free(log); 120 | } 121 | 122 | static void 123 | create_shader (struct shader *shader, GLenum type) 124 | { 125 | const GLchar *buf = (const GLchar *) shader->buf; 126 | GLint len = shader->end - shader->buf; 127 | 128 | shader->id = glCreateShader(type); 129 | glShaderSource(shader->id, 1, &buf, &len); 130 | glCompileShader(shader->id); 131 | 132 | check_compile(shader->id); 133 | } 134 | 135 | static void 136 | program_init (struct program *p) 137 | { 138 | struct shader *vert = &p->shader.vert; 139 | struct shader *frag = &p->shader.frag; 140 | 141 | create_shader(vert, GL_VERTEX_SHADER); 142 | create_shader(frag, GL_FRAGMENT_SHADER); 143 | 144 | p->id = glCreateProgram(); 145 | 146 | glAttachShader(p->id, vert->id); 147 | glAttachShader(p->id, frag->id); 148 | 149 | glLinkProgram(p->id); 150 | check_link(p->id); 151 | 152 | glDetachShader(p->id, vert->id); 153 | glDetachShader(p->id, frag->id); 154 | 155 | glDeleteShader(vert->id); 156 | glDeleteShader(frag->id); 157 | 158 | FOREACH_NELEM (p->loc, p->nloc, l) { 159 | switch (l->type) 160 | { 161 | case UNIFORM: 162 | l->id = glGetUniformLocation(p->id, l->name); 163 | break; 164 | 165 | case ATTRIBUTE: 166 | l->id = glGetAttribLocation(p->id, l->name); 167 | break; 168 | } 169 | } 170 | } 171 | 172 | void 173 | programs_init (void) 174 | { 175 | FOREACH (programs, p) 176 | program_init(p); 177 | } 178 | 179 | void 180 | program_cube_use (void) 181 | { 182 | glUseProgram(programs[CUBE].id); 183 | 184 | glUniformMatrix4fv(loc_cube[LOC_CUBE_VIEW ].id, 1, GL_FALSE, view_matrix()); 185 | glUniformMatrix4fv(loc_cube[LOC_CUBE_MODEL].id, 1, GL_FALSE, model_matrix()); 186 | } 187 | 188 | void 189 | program_bkgd_use (void) 190 | { 191 | glUseProgram(programs[BKGD].id); 192 | 193 | glUniform1i(glGetUniformLocation(programs[BKGD].id, "tex"), 0); 194 | } 195 | 196 | GLint 197 | program_bkgd_loc (const enum LocBkgd index) 198 | { 199 | return loc_bkgd[index].id; 200 | } 201 | 202 | GLint 203 | program_cube_loc (const enum LocCube index) 204 | { 205 | return loc_cube[index].id; 206 | } 207 | -------------------------------------------------------------------------------- /program.h: -------------------------------------------------------------------------------- 1 | void programs_init (void); 2 | void program_cube_use (void); 3 | void program_bkgd_use (void); 4 | 5 | enum LocBkgd { 6 | LOC_BKGD_VERTEX, 7 | LOC_BKGD_TEXTURE, 8 | }; 9 | 10 | enum LocCube { 11 | LOC_CUBE_VIEW, 12 | LOC_CUBE_MODEL, 13 | LOC_CUBE_VERTEX, 14 | LOC_CUBE_VCOLOR, 15 | LOC_CUBE_NORMAL, 16 | }; 17 | 18 | GLint program_bkgd_loc (const enum LocBkgd); 19 | GLint program_cube_loc (const enum LocCube); 20 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aklomp/gtk3-opengl/9b337697f30b80e7fc0212b9a99fcf718e828b3e/screenshot.png -------------------------------------------------------------------------------- /shaders/bkgd/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | uniform sampler2D tex; 4 | in vec2 ftex; 5 | 6 | out vec4 fragcolor; 7 | 8 | void main (void) 9 | { 10 | fragcolor = texture(tex, ftex); 11 | } 12 | -------------------------------------------------------------------------------- /shaders/bkgd/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec2 vertex; 4 | in vec2 texture; 5 | 6 | out vec2 ftex; 7 | 8 | void main (void) 9 | { 10 | ftex = texture; 11 | gl_Position = vec4(vertex, 0.5, 1.0); 12 | } 13 | -------------------------------------------------------------------------------- /shaders/cube/fragment.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec3 fcolor; 4 | in vec3 fpos; 5 | in float fdot; 6 | 7 | out vec4 fragcolor; 8 | 9 | void main (void) 10 | { 11 | if (!gl_FrontFacing) 12 | return; 13 | 14 | /* Get gamma-corrected (linear) color values */ 15 | vec3 linear = pow(fcolor, vec3(1.0 / 2.2)); 16 | 17 | /* Get distance-related light falloff factor: */ 18 | float dst = distance(vec3(0, 0, 2), fpos) * 0.4; 19 | 20 | /* Scale these by fdot: */ 21 | vec3 scaled = linear * vec3(fdot * dst); 22 | 23 | /* Restore gamma and output this color: */ 24 | fragcolor = vec4(pow(scaled, vec3(2.2)), 0.0); 25 | } 26 | -------------------------------------------------------------------------------- /shaders/cube/vertex.glsl: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform mat4 view_matrix; 4 | uniform mat4 model_matrix; 5 | 6 | in vec3 vertex; 7 | in vec3 vcolor; 8 | in vec3 normal; 9 | 10 | out vec3 fcolor; 11 | out vec3 fpos; 12 | out float fdot; 13 | 14 | void main (void) 15 | { 16 | vec4 modelspace = model_matrix * vec4(vertex, 1.0); 17 | 18 | gl_Position = view_matrix * modelspace; 19 | fcolor = vcolor; 20 | 21 | /* Sight vector is straight down in world coords: (0, 0, -1) */ 22 | vec4 sight = vec4(0, 0, -1.0, 0.0); 23 | 24 | /* Transform vertex normal to world coordinates: */ 25 | vec4 wnormal = model_matrix * vec4(normal, 0.0); 26 | 27 | /* Get cosine of the angle between sight and normal: */ 28 | fdot = dot(sight, wnormal); 29 | 30 | /* Feed position to fragment shader: */ 31 | fpos = modelspace.xyz; 32 | } 33 | -------------------------------------------------------------------------------- /textures/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aklomp/gtk3-opengl/9b337697f30b80e7fc0212b9a99fcf718e828b3e/textures/background.png -------------------------------------------------------------------------------- /textures/background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | // Get number of elements in an array: 2 | #define NELEM(array) (sizeof(array) / sizeof(*(array))) 3 | 4 | // Loop over an array of given size: 5 | #define FOREACH_NELEM(array, nelem, iter) \ 6 | for (__typeof__(*(array)) *iter = (array); \ 7 | iter < (array) + (nelem); \ 8 | iter++) 9 | 10 | // Loop over an array of known size: 11 | #define FOREACH(array, iter) \ 12 | FOREACH_NELEM(array, NELEM(array), iter) 13 | -------------------------------------------------------------------------------- /view.c: -------------------------------------------------------------------------------- 1 | #include "matrix.h" 2 | 3 | static struct { 4 | float matrix[16]; 5 | float width; 6 | float height; 7 | float z; 8 | } 9 | state = { 10 | .z = 2.0f, 11 | }; 12 | 13 | const float * 14 | view_matrix (void) 15 | { 16 | return state.matrix; 17 | } 18 | 19 | static void 20 | view_recalc (void) 21 | { 22 | float aspect_ratio = state.width / state.height; 23 | float matrix_frustum[16]; 24 | float matrix_translate[16]; 25 | 26 | // Create frustum matrix: 27 | mat_frustum(matrix_frustum, 0.7, aspect_ratio, 0.5, 6); 28 | 29 | // Create frustum translation matrix: 30 | mat_translate(matrix_translate, 0, 0, state.z); 31 | 32 | // Combine into perspective matrix: 33 | mat_multiply(state.matrix, matrix_frustum, matrix_translate); 34 | } 35 | 36 | void 37 | view_set_window (int width, int height) 38 | { 39 | state.width = width; 40 | state.height = height; 41 | view_recalc(); 42 | } 43 | 44 | void 45 | view_z_decrease (void) 46 | { 47 | if (state.z > 1.5f) { 48 | state.z -= 0.1f; 49 | view_recalc(); 50 | } 51 | } 52 | 53 | void 54 | view_z_increase (void) 55 | { 56 | if (state.z < 5.0f) { 57 | state.z += 0.1f; 58 | view_recalc(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /view.h: -------------------------------------------------------------------------------- 1 | const float *view_matrix (void); 2 | void view_set_window (int width, int height); 3 | void view_z_decrease (void); 4 | void view_z_increase (void); 5 | --------------------------------------------------------------------------------