SDK Multiplataforma en C logo

SDK Multiplataforma en C

¡Hola Gráficos 3D!

❮ Anterior
Siguiente ❯

GLHello es una aplicación de escritorio que integra vistas con gráficos 3D junto con otros controles de interfaz. El código fuente está en la carpeta /demo/glhello de la distribución del SDK.

Captura de una aplicación con gráficos 3D. Versión Windows.
Figura 1: Versión Windows.
Captura de una aplicación con gráficos 3D. Versión macOS.
Figura 2: Versión macOS.
Captura de una aplicación con gráficos 3D. Versión Linux.
Figura 3: Versión Linux.
Listado 1: demo/glhello/glhello.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/* GLDemo Application */

#include "nappgui.h"
#include "glhello.h"
#include "res_glhello.h"

#include <ogl3d/ogl3d.h>
#include "ogl1.h"
#include "ogl2.h"
#include "ogl3.h"

typedef struct _app_t App;

struct _app_t
{
    Window *window;
    Panel *glpanel;
    View *glview;
    Label *errlabel;
    real32_t ptscale;
    OGL1 *ogl1;
    OGL2 *ogl2;
    OGL3 *ogl3;
    uint32_t api;
    real32_t angle;
    real32_t scale;
    Pixbuf *texdata;
};

static App *APP = NULL;

/*---------------------------------------------------------------------------*/

static void i_destroy_gl_apps(App *app)
{
    if (app->ogl1 != NULL)
        ogl1_destroy(&app->ogl1);

    if (app->ogl2 != NULL)
        ogl2_destroy(&app->ogl2);

    if (app->ogl3 != NULL)
        ogl3_destroy(&app->ogl3);
}

/*---------------------------------------------------------------------------*/

static void i_set_glcontext(App *app, const uint32_t index)
{
    if (index != app->api)
    {
        String *err = NULL;

        app->api = index;
        i_destroy_gl_apps(app);

        switch (index)
        {
        case 0:
        {
            oglerr_t glerr;
            app->ogl1 = ogl1_create(app->glview, &glerr);
            if (app->ogl1 == NULL)
                err = str_printf("Error creating OpenGL 1.1 context\n%s", ogl3d_err_str(glerr));
            break;
        }

        case 1:
        {
            oglerr_t glerr;
            app->ogl2 = ogl2_create(app->glview, &glerr);
            if (app->ogl2 == NULL)
                err = str_printf("Error creating OpenGL 2.1 context\n%s", ogl3d_err_str(glerr));
            break;
        }

        case 2:
        {
            oglerr_t glerr;
            app->ogl3 = ogl3_create(app->glview, &glerr);
            if (app->ogl3 == NULL)
                err = str_printf("Error creating OpenGL 3.3 context\n%s", ogl3d_err_str(glerr));
            break;
        }

        case 3:
            err = str_printf("Error creating OpenGL 4.3 context\n%s", "Not available");
            break;

        case 4:
            err = str_printf("Error creating DirectX 9 context\n%s", "Not available");
            break;

        case 5:
            err = str_printf("Error creating DirectX 9.c context\n%s", "Not available");
            break;

        case 6:
            err = str_printf("Error creating DirectX 10 context\n%s", "Not available");
            break;

        case 7:
            err = str_printf("Error creating DirectX 11 context\n%s", "Not available");
            break;

        case 8:
            err = str_printf("Error creating DirectX 12 context\n%s", "Not available");
            break;

        case 9:
            err = str_printf("Error creating Vulkan context\n%s", "Not available");
            break;

        case 10:
            err = str_printf("Error creating Metal context\n%s", "Not available");
            break;
        }

        if (err == NULL)
        {
            panel_visible_layout(app->glpanel, 0);
        }
        else
        {
            label_text(app->errlabel, tc(err));
            str_destroy(&err);
            panel_visible_layout(app->glpanel, 1);
        }

        panel_update(app->glpanel);
        view_update(app->glview);
    }
}

/*---------------------------------------------------------------------------*/

static void i_OnSelect(App *app, Event *e)
{
    const EvButton *p = event_params(e, EvButton);
    i_set_glcontext(app, p->index);
}

/*---------------------------------------------------------------------------*/

static void i_OnDraw(App *app, Event *e)
{
    const EvDraw *p = event_params(e, EvDraw);
    /* Some displays (macOS Retina) have double-scaled pixels */
    real32_t width = p->width * app->ptscale;
    real32_t height = p->height * app->ptscale;
    switch (app->api)
    {
    case 0:
        ogl1_draw(app->ogl1, width, height, app->angle, app->scale);
        break;
    case 1:
        ogl2_draw(app->ogl2, width, height, app->angle, app->scale);
        break;
    case 2:
        ogl3_draw(app->ogl3, width, height, app->angle, app->scale);
        break;
    case 3:
        break;
    case 4:
        break;
    case 5:
        break;
    case 6:
        break;
    case 7:
        break;
    case 8:
        break;
    case 9:
        break;
    case 10:
        break;
    }
}

/*---------------------------------------------------------------------------*/

static void i_OnSize(App *app, Event *e)
{
    const EvSize *p = event_params(e, EvSize);
    real32_t width = p->width * app->ptscale;
    real32_t height = p->height * app->ptscale;
    switch (app->api)
    {
    case 0:
        ogl1_resize(app->ogl1, width, height);
        break;
    case 1:
        ogl2_resize(app->ogl2, width, height);
        break;
    case 2:
        ogl3_resize(app->ogl3, width, height);
        break;
    case 3:
        break;
    case 4:
        break;
    case 5:
        break;
    case 6:
        break;
    case 7:
        break;
    case 8:
        break;
    case 9:
        break;
    case 10:
        break;
    }
}

/*---------------------------------------------------------------------------*/

static void i_OnAngle(App *app, Event *e)
{
    const EvSlider *p = event_params(e, EvSlider);
    app->angle = p->pos;
    view_update(app->glview);
}

/*---------------------------------------------------------------------------*/

static void i_OnScale(App *app, Event *e)
{
    const EvSlider *p = event_params(e, EvSlider);
    app->scale = 2 * p->pos;
    view_update(app->glview);
}

/*---------------------------------------------------------------------------*/

static Panel *i_gl_panel(App *app)
{
    Panel *panel = panel_create();
    View *view = view_create();
    Label *label = label_multiline();
    Font *font = font_system(20, ekFNORMAL);
    Layout *layout1 = layout_create(1, 1);
    Layout *layout2 = layout_create(1, 1);
    view_size(view, s2df(512, 512));
    view_OnDraw(view, listener(app, i_OnDraw, App));
    view_OnSize(view, listener(app, i_OnSize, App));
    layout_view(layout1, view, 0, 0);
    label_font(label, font);
    label_align(label, ekCENTER);
    layout_label(layout2, label, 0, 0);
    layout_hsize(layout2, 0, 512);
    layout_vsize(layout2, 0, 512);
    layout_halign(layout2, 0, 0, ekCENTER);
    layout_valign(layout2, 0, 0, ekTOP);
    font_destroy(&font);
    panel_layout(panel, layout1);
    panel_layout(panel, layout2);
    app->errlabel = label;
    app->glpanel = panel;
    app->glview = view;
    view_point_scale(app->glview, &app->ptscale);
    return panel;
}

/*---------------------------------------------------------------------------*/

static Layout *i_rlayout(App *app)
{
    Layout *layout = layout_create(1, 3);
    Panel *panel = i_gl_panel(app);
    Slider *slider1 = slider_create();
    Slider *slider2 = slider_create();
    slider_OnMoved(slider1, listener(app, i_OnAngle, App));
    slider_OnMoved(slider2, listener(app, i_OnScale, App));
    slider_value(slider2, .5f);
    layout_panel(layout, panel, 0, 0);
    layout_slider(layout, slider1, 0, 1);
    layout_slider(layout, slider2, 0, 2);
    layout_vmargin(layout, 0, 10);
    layout_vexpand(layout, 0);
    return layout;
}

/*---------------------------------------------------------------------------*/

static Panel *i_panel(App *app)
{
    Panel *panel = panel_create();
    Layout *layout1 = layout_create(2, 1);
    ListBox *listbox = listbox_create();
    Layout *layout2 = i_rlayout(app);
    listbox_add_elem(listbox, "OpenGL 1.1/Fixed", NULL);
    listbox_add_elem(listbox, "OpenGL 2.1/GLSL", NULL);
    listbox_add_elem(listbox, "OpenGL 3.3", NULL);
    listbox_add_elem(listbox, "OpenGL 4", NULL);
    listbox_add_elem(listbox, "DirectX 9/Fixed", NULL);
    listbox_add_elem(listbox, "DirectX 9.c/HLSL", NULL);
    listbox_add_elem(listbox, "DirectX 10", NULL);
    listbox_add_elem(listbox, "DirectX 11", NULL);
    listbox_add_elem(listbox, "DirectX 12", NULL);
    listbox_add_elem(listbox, "Vulkan", NULL);
    listbox_add_elem(listbox, "Metal", NULL);
    listbox_select(listbox, 0, TRUE);
    listbox_OnSelect(listbox, listener(app, i_OnSelect, App));
    listbox_size(listbox, s2df(150, 300));
    layout_listbox(layout1, listbox, 0, 0);
    layout_layout(layout1, layout2, 1, 0);
    panel_layout(panel, layout1);
    layout_valign(layout1, 0, 0, ekTOP);
    layout_hsize(layout1, 0, 150);
    layout_hexpand(layout1, 1);
    layout_margin(layout1, 10);
    layout_hmargin(layout1, 0, 10);
    return panel;
}

/*---------------------------------------------------------------------------*/

static Window *i_window(App *app)
{
    Panel *panel = i_panel(app);
    Window *window = window_create(ekWINDOW_STDRES);
    window_panel(window, panel);
    window_title(window, "Hello 3D Render");
    i_set_glcontext(app, 0);
    return window;
}

/*---------------------------------------------------------------------------*/

static void i_OnClose(App *app, Event *e)
{
    unref(app);
    unref(e);
    osapp_finish();
}

/*---------------------------------------------------------------------------*/

static App *i_create(void)
{
    App *app = NULL;
    app = heap_new0(App);
    APP = app;
    ogl3d_start();
    app->api = UINT32_MAX;
    app->window = i_window(app);
    app->angle = 0;
    app->scale = 1;
    window_origin(app->window, v2df(500.f, 200.f));
    window_OnClose(app->window, listener(app, i_OnClose, App));
    window_show(app->window);
    return app;
}

/*---------------------------------------------------------------------------*/

static void i_destroy(App **app)
{
    cassert_no_null(app);
    cassert_no_null(*app);

    i_destroy_gl_apps(*app);
    window_destroy(&(*app)->window);

    if ((*app)->texdata != NULL)
        pixbuf_destroy(&(*app)->texdata);

    ogl3d_finish();
    heap_delete(app, App);
}

/*---------------------------------------------------------------------------*/

void glhello_texdata(const byte_t **texdata, uint32_t *texwidth, uint32_t *texheight, pixformat_t *texformat)
{
    if (APP->texdata == NULL)
    {
        ResPack *pack = res_glhello_respack("");
        APP->texdata = image_pixels(image_from_resource(pack, WALL_PNG), ekFIMAGE);
        respack_destroy(&pack);
    }

    *texdata = pixbuf_data(APP->texdata);
    *texwidth = pixbuf_width(APP->texdata);
    *texheight = pixbuf_height(APP->texdata);
    *texformat = pixbuf_format(APP->texdata);
}

/*---------------------------------------------------------------------------*/

#include "osmain.h"
osmain(i_create, i_destroy, "", App)
Listado 2: demo/glhello/ogl1.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/* OpenGL 1 Demo */

#include "ogl1.h"
#include "glhello.h"
#include <ogl3d/ogl3d.h>
#include <sewer/cassert.h>
#include <core/heap.h>
#include <gui/view.h>

#include "nowarn.hxx"
#include <ogl3d/glew.h>
#include "warn.hxx"

struct _ogl1_t
{
    OGLCtx *ctx;
    GLboolean init;
    GLuint texture;
};

/*---------------------------------------------------------------------------*/

OGL1 *ogl1_create(View *view, oglerr_t *err)
{
    void *nview = view_native(view);
    OGLCtx *ctx = NULL;
    OGLProps props;
    props.api = ekOGL_1_1;
    props.hdaccel = TRUE;
    props.color_bpp = 32;
    props.depth_bpp = 0;
    props.stencil_bpp = 0;
    props.aux_buffers = 0;
    props.transparent = FALSE;
    props.shared = NULL;
    ctx = ogl3d_context(&props, nview, err);

    if (ctx != NULL)
    {
        OGL1 *ogl = heap_new0(OGL1);
        ogl->ctx = ctx;
        ogl->init = GL_FALSE;
        return ogl;
    }

    return NULL;
}

/*---------------------------------------------------------------------------*/

void ogl1_destroy(OGL1 **ogl)
{
    cassert_no_null(ogl);
    cassert_no_null(*ogl);
    ogl3d_begin_draw((*ogl)->ctx);

    if ((*ogl)->init == GL_TRUE)
    {
        GLenum err;
        glBindTexture(GL_TEXTURE_2D, 0);
        glDeleteTextures(1, &(*ogl)->texture);
        cassert((err = glGetError()) == GL_NO_ERROR);
        unref(err);
    }

    ogl3d_end_draw((*ogl)->ctx);
    ogl3d_destroy(&(*ogl)->ctx);
    heap_delete(ogl, OGL1);
}

/*---------------------------------------------------------------------------*/

/* Data to be stored in GPU memory */
static void i_device_data(OGL1 *ogl)
{
    const byte_t *texdata = NULL;
    uint32_t texwidth, texheight;
    pixformat_t texformat;
    glhello_texdata(&texdata, &texwidth, &texheight, &texformat);
    cassert(texformat == ekRGB24);
    glGenTextures(1, &ogl->texture);
    cassert(glGetError() == GL_NO_ERROR);
    glBindTexture(GL_TEXTURE_2D, ogl->texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, (GLsizei)texwidth, (GLsizei)texheight, 0, GL_RGB, GL_UNSIGNED_BYTE, (const void *)texdata);
    cassert(glGetError() == GL_NO_ERROR);
}

/*---------------------------------------------------------------------------*/

void ogl1_draw(OGL1 *ogl, const real32_t width, const real32_t height, const real32_t angle, const real32_t scale)
{
    GLenum err;

    cassert_no_null(ogl);
    ogl3d_begin_draw(ogl->ctx);

    if (ogl->init == GL_FALSE)
    {
        i_device_data(ogl);
        ogl->init = GL_TRUE;
    }

    glViewport(0, 0, (GLsizei)width, (GLsizei)height);
    glClearColor(.8f, .8f, .8f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glScalef(scale, scale, 0);
    glRotatef(angle * 360, 0, 0, 1);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, ogl->texture);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    /* Not in GL 1.1 */
    /* glTexEnvf(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS); */
    /* glTexEnvf(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE); */
    cassert((err = glGetError()) == GL_NO_ERROR);

    glBegin(GL_TRIANGLES);
    glColor3f(1, 0, 0);
    glTexCoord2f(.5, 0);
    glVertex3f(0, 1, 0);
    glColor3f(0, 1, 0);
    glTexCoord2f(0, 1);
    glVertex3f(-1, -1, 0);
    glColor3f(0, 0, 1);
    glTexCoord2f(1, 1);
    glVertex3f(1, -1, 0);
    glEnd();
    unref(err);

    ogl3d_end_draw(ogl->ctx);
}

/*---------------------------------------------------------------------------*/

void ogl1_resize(OGL1 *ogl, const real32_t width, const real32_t height)
{
    cassert_no_null(ogl);
    ogl3d_set_size(ogl->ctx, (uint32_t)width, (uint32_t)height);
}
Listado 3: demo/glhello/ogl2.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
/* OpenGL 2 Demo */

#include "ogl2.h"
#include "glhello.h"
#include <ogl3d/ogl3d.h>
#include <sewer/bmath.h>
#include <sewer/cassert.h>
#include <core/heap.h>
#include <gui/view.h>

#include "nowarn.hxx"
#include <ogl3d/glew.h>
#include "warn.hxx"

struct _ogl2_t
{
    OGLCtx *ctx;
    GLboolean init;
    GLuint texture;
    GLuint vbo;
    GLuint ibo;
    GLuint pshader;
    GLint pos_vertex;
    GLint col_vertex;
    GLint tex_vertex;
    GLint mvp_uniform;
    GLint tex_uniform;
};

/*---------------------------------------------------------------------------*/

static const GLchar *i_VS = "#version 120\n"
                            "uniform mat4 uMVP;\n"
                            "attribute vec3 vPos;\n"
                            "attribute vec3 vColor;\n"
                            "attribute vec2 vTex;\n"
                            "varying vec3 outColor;\n"
                            "varying vec2 texCoord;\n"
                            "void main(void) {\n"
                            "   gl_Position = uMVP * vec4(vPos.xyz, 1);\n"
                            "   outColor = vColor;\n"
                            "   texCoord = vTex;\n"
                            "}\n ";

static const GLchar *i_FS = "#version 120\n"
                            "varying vec3 outColor;\n"
                            "varying vec2 texCoord;\n"
                            "uniform sampler2D uTexture;\n"
                            "void main (void) {\n"
                            "    gl_FragColor = vec4(outColor.xyz, 1.0);\n"
                            "    gl_FragColor *= texture2D(uTexture, texCoord);\n"
                            "}\n";

/*---------------------------------------------------------------------------*/

OGL2 *ogl2_create(View *view, oglerr_t *err)
{
    void *nview = view_native(view);
    OGLCtx *ctx = NULL;
    OGLProps props;
    props.api = ekOGL_2_1;
    props.hdaccel = TRUE;
    props.color_bpp = 32;
    props.depth_bpp = 0;
    props.stencil_bpp = 0;
    props.aux_buffers = 0;
    props.transparent = FALSE;
    props.shared = NULL;
    ctx = ogl3d_context(&props, nview, err);

    if (ctx != NULL)
    {
        OGL2 *ogl = heap_new0(OGL2);
        ogl->ctx = ctx;
        ogl->init = GL_FALSE;
        return ogl;
    }

    return NULL;
}

/*---------------------------------------------------------------------------*/

void ogl2_destroy(OGL2 **ogl)
{
    cassert_no_null(ogl);
    cassert_no_null(*ogl);
    ogl3d_begin_draw((*ogl)->ctx);

    /* Delete all objects in device (GPU) space */
    if ((*ogl)->init == GL_TRUE)
    {
        GLenum err;

        /* Unset all device objects */
        glBindTexture(GL_TEXTURE_2D, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glUseProgram(0);

        /* Delete the texture */
        if ((*ogl)->texture != 0)
        {
            glDeleteTextures(1, &(*ogl)->texture);
            cassert((err = glGetError()) == GL_NO_ERROR);
            (*ogl)->texture = 0;
        }

        /* Delete the shader */
        if ((*ogl)->pshader != 0)
        {
            glDeleteProgram((*ogl)->pshader);
            cassert((err = glGetError()) == GL_NO_ERROR);
            (*ogl)->pshader = 0;
        }

        /* Delete the Vertex Buffer Object */
        if ((*ogl)->vbo != 0)
        {
            glDeleteBuffers(1, &(*ogl)->vbo);
            cassert((err = glGetError()) == GL_NO_ERROR);
            (*ogl)->vbo = 0;
        }

        /* Delete the Index(Element) Buffer Object */
        if ((*ogl)->ibo != 0)
        {
            glDeleteBuffers(1, &(*ogl)->ibo);
            cassert((err = glGetError()) == GL_NO_ERROR);
            (*ogl)->ibo = 0;
        }

        unref(err);
    }

    ogl3d_end_draw((*ogl)->ctx);
    ogl3d_destroy(&(*ogl)->ctx);
    heap_delete(ogl, OGL2);
}

/*---------------------------------------------------------------------------*/

/* Data to be stored in GPU memory */
static void i_device_data(OGL2 *ogl)
{
    float vertices[] = {
        0, 1, 0, 1, 0, 0, .5f, 0, /* v0 pos, color, tex */
        -1, -1, 0, 0, 1, 0, 0, 1, /* v1 pos, color, tex */
        1, -1, 0, 0, 0, 1, 1, 1}; /* v2 pos, color, tex */
    uint32_t indices[] = {0, 1, 2};
    const byte_t *texdata;
    uint32_t texwidth, texheight;
    pixformat_t texformat;
    GLuint vshader;
    GLuint fshader;
    GLint status;
    GLenum err;
    char info[512];
    const char *version = cast_const(glGetString(GL_VERSION), char);
    const char *renderer = cast_const(glGetString(GL_RENDERER), char);
    unref(version);
    unref(renderer);

    /* Texture */
    glhello_texdata(&texdata, &texwidth, &texheight, &texformat);
    cassert(texformat == ekRGB24);
    glGenTextures(1, &ogl->texture);
    cassert(glGetError() == GL_NO_ERROR);
    glBindTexture(GL_TEXTURE_2D, ogl->texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, (GLsizei)texwidth, (GLsizei)texheight, 0, GL_RGB, GL_UNSIGNED_BYTE, (const void *)texdata);
    cassert(glGetError() == GL_NO_ERROR);

    /* Vertex Buffer Object */
    glGenBuffers(1, &ogl->vbo);
    glBindBuffer(GL_ARRAY_BUFFER, ogl->vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    /* Index Buffer */
    glGenBuffers(1, &ogl->ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ogl->ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    /* Vertex Shader */
    vshader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vshader, 1, &i_VS, NULL);
    glCompileShader(vshader);
    glGetShaderiv(vshader, GL_COMPILE_STATUS, &status);
    glGetShaderInfoLog(vshader, 512, NULL, info);
    cassert(status != 0);

    /* Pixel Shader */
    fshader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fshader, 1, &i_FS, NULL);
    glCompileShader(fshader);
    glGetShaderiv(fshader, GL_COMPILE_STATUS, &status);
    glGetShaderInfoLog(fshader, 512, NULL, info);
    cassert(status != 0);

    /* Shader Program */
    ogl->pshader = glCreateProgram();
    glAttachShader(ogl->pshader, vshader);
    glAttachShader(ogl->pshader, fshader);

    /* The association between an attribute variable name and a generic attribute
     index can be specified at any time by calling glBindAttribLocation.
     Attribute bindings do not go into effect until glLinkProgram is called.
     After a program object has been linked successfully, the index values for
     attribute variables remain fixed until the next link command occurs.
     The attribute values can only be queried after a link if the link was
     successful. glGetAttribLocation returns the binding that actually went
     into effect the last time glLinkProgram was called for the specified
     program object. Attribute bindings that have been specified since the last
     link operation are not returned by glGetAttribLocation. */
    glBindAttribLocation(ogl->pshader, 0, "vPos");
    cassert((err = glGetError()) == GL_NO_ERROR);
    glBindAttribLocation(ogl->pshader, 1, "vColor");
    cassert((err = glGetError()) == GL_NO_ERROR);
    glBindAttribLocation(ogl->pshader, 2, "vTex");
    cassert((err = glGetError()) == GL_NO_ERROR);
    glLinkProgram(ogl->pshader);
    glGetProgramiv(ogl->pshader, GL_LINK_STATUS, &status);
    glGetProgramInfoLog(ogl->pshader, 512, NULL, info);
    cassert(status != 0);

    /* Shader Parameters */
    ogl->pos_vertex = glGetAttribLocation(ogl->pshader, "vPos");
    cassert((err = glGetError()) == GL_NO_ERROR);
    cassert(ogl->pos_vertex == 0);

    ogl->col_vertex = glGetAttribLocation(ogl->pshader, "vColor");
    cassert((err = glGetError()) == GL_NO_ERROR);
    cassert(ogl->col_vertex == 1);

    ogl->tex_vertex = glGetAttribLocation(ogl->pshader, "vTex");
    cassert((err = glGetError()) == GL_NO_ERROR);
    cassert(ogl->tex_vertex == 2);

    ogl->mvp_uniform = glGetUniformLocation(ogl->pshader, "uMVP");
    cassert((err = glGetError()) == GL_NO_ERROR);

    ogl->tex_uniform = glGetUniformLocation(ogl->pshader, "uTexture");
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Delete vertex and pixel shared already linked */
    glDeleteShader(vshader);
    cassert((err = glGetError()) == GL_NO_ERROR);

    glDeleteShader(fshader);
    cassert((err = glGetError()) == GL_NO_ERROR);

    unref(err);
}

/*---------------------------------------------------------------------------*/

static void i_scale_rotate_Z(GLfloat *m, const real32_t a, const real32_t s)
{
    real32_t ca = bmath_cosf(a);
    real32_t sa = bmath_sinf(a);
    m[0] = s * ca;
    m[1] = s * sa;
    m[2] = 0;
    m[3] = 0;
    m[4] = -s * sa;
    m[5] = s * ca;
    m[6] = 0;
    m[7] = 0;
    m[8] = 0;
    m[9] = 0;
    m[10] = 1;
    m[11] = 0;
    m[12] = 0;
    m[13] = 0;
    m[14] = 0;
    m[15] = 1;
}

/*---------------------------------------------------------------------------*/

void ogl2_draw(OGL2 *ogl, const real32_t width, const real32_t height, const real32_t angle, const real32_t scale)
{
    GLfloat m[16];
    GLenum err;

    cassert_no_null(ogl);
    ogl3d_begin_draw(ogl->ctx);

    if (ogl->init == GL_FALSE)
    {
        i_device_data(ogl);
        ogl->init = GL_TRUE;
    }

    glViewport(0, 0, (GLsizei)width, (GLsizei)height);
    glClearColor(.8f, .8f, 0.8f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    /* Set the texture */
    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, ogl->texture);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Set vertex buffer */
    glBindBuffer(GL_ARRAY_BUFFER, ogl->vbo);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Set index buffer */
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ogl->ibo);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Enable vertex attributes */
    glVertexAttribPointer((GLuint)ogl->pos_vertex, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0);
    cassert((err = glGetError()) == GL_NO_ERROR);
    glEnableVertexAttribArray((GLuint)ogl->pos_vertex);
    cassert((err = glGetError()) == GL_NO_ERROR);
    glVertexAttribPointer((GLuint)ogl->col_vertex, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(3 * sizeof(float)));
    cassert((err = glGetError()) == GL_NO_ERROR);
    glEnableVertexAttribArray((GLuint)ogl->col_vertex);
    cassert((err = glGetError()) == GL_NO_ERROR);
    glVertexAttribPointer((GLuint)ogl->tex_vertex, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(6 * sizeof(float)));
    cassert((err = glGetError()) == GL_NO_ERROR);
    glEnableVertexAttribArray((GLuint)ogl->tex_vertex);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Set the shader */
    glUseProgram(ogl->pshader);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Model-View-Projection and draw */
    i_scale_rotate_Z(m, angle * 2 * kBMATH_PIf, scale);
    glUniformMatrix4fv(ogl->mvp_uniform, 1, GL_FALSE, m);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
    cassert((err = glGetError()) == GL_NO_ERROR);

    ogl3d_end_draw(ogl->ctx);
}

/*---------------------------------------------------------------------------*/

void ogl2_resize(OGL2 *ogl, const real32_t width, const real32_t height)
{
    cassert_no_null(ogl);
    ogl3d_set_size(ogl->ctx, (uint32_t)width, (uint32_t)height);
}
Listado 4: demo/glhello/ogl3.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/* OpenGL 3 Demo */

#include "ogl3.h"
#include "glhello.h"
#include <ogl3d/ogl3d.h>
#include <sewer/bmath.h>
#include <sewer/cassert.h>
#include <core/heap.h>
#include <gui/view.h>

#include "nowarn.hxx"
#include <ogl3d/glew.h>
#include "warn.hxx"

struct _ogl3_t
{
    OGLCtx *ctx;
    GLboolean init;
    GLuint texture;
    GLuint vbo;
    GLuint vao;
    GLuint ibo;
    GLuint pshader;
    GLint mvp_uniform;
    GLint tex_uniform;
};

/*---------------------------------------------------------------------------*/

static const GLchar *i_VS = "#version 330\n"
                            "uniform mat4 uMVP;\n"
                            "layout(location = 0) in vec3 vPos;\n"
                            "layout(location = 1) in vec3 vColor;\n"
                            "layout(location = 2) in vec2 vTex;\n"
                            "out vec3 outColor;\n"
                            "out vec2 texCoord;\n"
                            "void main(void) {\n"
                            "   gl_Position = uMVP * vec4(vPos.xyz, 1);\n"
                            "   outColor = vColor;\n"
                            "   texCoord = vTex;\n"
                            "}\n";

static const char_t *i_FS = "#version 330\n"
                            "in vec3 outColor;\n"
                            "in vec2 texCoord;\n"
                            "out vec4 fragColor;\n"
                            "uniform sampler2D uTexture;\n"
                            "void main (void) {\n"
                            "   fragColor = vec4(outColor.xyz, 1.0);\n"
                            "   fragColor *= texture(uTexture, texCoord);\n"
                            "}\n";

/*---------------------------------------------------------------------------*/

OGL3 *ogl3_create(View *view, oglerr_t *err)
{
    void *nview = view_native(view);
    OGLCtx *ctx = NULL;
    OGLProps props;
    props.api = ekOGL_3_3;
    props.hdaccel = TRUE;
    props.color_bpp = 32;
    props.depth_bpp = 0;
    props.stencil_bpp = 0;
    props.aux_buffers = 0;
    props.transparent = FALSE;
    props.shared = NULL;
    ctx = ogl3d_context(&props, nview, err);

    if (ctx != NULL)
    {
        OGL3 *ogl = heap_new0(OGL3);
        ogl->ctx = ctx;
        ogl->init = GL_FALSE;
        return ogl;
    }

    return NULL;
}

/*---------------------------------------------------------------------------*/

void ogl3_destroy(OGL3 **ogl)
{
    cassert_no_null(ogl);
    cassert_no_null(*ogl);
    ogl3d_begin_draw((*ogl)->ctx);

    /* Delete all objects in device (GPU) space */
    if ((*ogl)->init == GL_TRUE)
    {
        GLenum err;

        /* Unset all device objects */
        glBindTexture(GL_TEXTURE_2D, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
        glUseProgram(0);

        /* Delete the texture */
        if ((*ogl)->texture != 0)
        {
            glDeleteTextures(1, &(*ogl)->texture);
            cassert((err = glGetError()) == GL_NO_ERROR);
            (*ogl)->texture = 0;
        }
        
        /* Delete the shader */
        if ((*ogl)->pshader != 0)
        {
            glDeleteProgram((*ogl)->pshader);
            cassert((err = glGetError()) == GL_NO_ERROR);
            (*ogl)->pshader = 0;
        }

        /* Delete the Vertex Buffer Object */
        if ((*ogl)->vbo != 0)
        {
            glDeleteBuffers(1, &(*ogl)->vbo);
            cassert((err = glGetError()) == GL_NO_ERROR);
            (*ogl)->vbo = 0;
        }

        /* Delete the Vertex Array Object */
        if ((*ogl)->vao != 0)
        {
            glDeleteVertexArrays(1, &(*ogl)->vao);
            cassert((err = glGetError()) == GL_NO_ERROR);
            (*ogl)->vao = 0;
        }

        /* Delete the Index(Element) Buffer Object */
        if ((*ogl)->ibo != 0)
        {
            glDeleteBuffers(1, &(*ogl)->ibo);
            cassert((err = glGetError()) == GL_NO_ERROR);
            (*ogl)->ibo = 0;
        }

        unref(err);
    }

    ogl3d_end_draw((*ogl)->ctx);
    ogl3d_destroy(&(*ogl)->ctx);
    heap_delete(ogl, OGL3);
}

/*---------------------------------------------------------------------------*/

/* Data to be stored in GPU memory */
static void i_device_data(OGL3 *ogl)
{
    const float vertices[] = {
        0, 1, 0, 1, 0, 0, .5f, 0, /* v0 pos, color, tex */
        -1, -1, 0, 0, 1, 0, 0, 1, /* v1 pos, color, tex */
        1, -1, 0, 0, 0, 1, 1, 1}; /* v2 pos, color, tex */
    uint32_t indices[] = {0, 1, 2};
    const byte_t *texdata = NULL;
    uint32_t texwidth, texheight;
    pixformat_t texformat;
    GLuint vshader;
    GLuint fshader;
    GLint status;
    GLenum err;
    char info[512];
    const char *version = cast_const(glGetString(GL_VERSION), char);
    const char *renderer = cast_const(glGetString(GL_RENDERER), char);
    unref(version);
    unref(renderer);

    /* Texture */
    glhello_texdata(&texdata, &texwidth, &texheight, &texformat);
    cassert(texformat == ekRGB24);
    glGenTextures(1, &ogl->texture);
    cassert(glGetError() == GL_NO_ERROR);
    glBindTexture(GL_TEXTURE_2D, ogl->texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, (GLsizei)texwidth, (GLsizei)texheight, 0, GL_RGB, GL_UNSIGNED_BYTE, cast_const(texdata, void));
    cassert(glGetError() == GL_NO_ERROR);

    /* Vertex Array Object */
    glGenVertexArrays(1, &ogl->vao);
    glBindVertexArray(ogl->vao);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Vertex Buffer Object */
    glGenBuffers(1, &ogl->vbo);
    glBindBuffer(GL_ARRAY_BUFFER, ogl->vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Enable vertex attributes */
    /* 0 = layout(location = 0) vPos */
    glVertexAttribPointer(0 /* vPos */, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)0);
    cassert((err = glGetError()) == GL_NO_ERROR);
    glEnableVertexAttribArray(0 /* vPos */);
    cassert((err = glGetError()) == GL_NO_ERROR);
    /* 1 = layout(location = 1) vColor */
    glVertexAttribPointer(1 /* vColor */, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(3 * sizeof(float)));
    cassert((err = glGetError()) == GL_NO_ERROR);
    glEnableVertexAttribArray(1 /* vColor */);
    cassert((err = glGetError()) == GL_NO_ERROR);
    /* 2 = layout(location = 2) vTex */
    glVertexAttribPointer(2 /* vTex */, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *)(6 * sizeof(float)));
    cassert((err = glGetError()) == GL_NO_ERROR);
    glEnableVertexAttribArray(2 /* vTex */);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Index Buffer */
    glGenBuffers(1, &ogl->ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ogl->ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Vertex Shader */
    vshader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vshader, 1, &i_VS, NULL);
    glCompileShader(vshader);
    glGetShaderiv(vshader, GL_COMPILE_STATUS, &status);
    glGetShaderInfoLog(vshader, 512, NULL, info);
    cassert(status != 0);

    /* Pixel Shader */
    fshader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fshader, 1, &i_FS, NULL);
    glCompileShader(fshader);
    glGetShaderiv(fshader, GL_COMPILE_STATUS, &status);
    glGetShaderInfoLog(fshader, 512, NULL, info);
    cassert(status != 0);

    /* Shader Program */
    ogl->pshader = glCreateProgram();
    glAttachShader(ogl->pshader, vshader);
    glAttachShader(ogl->pshader, fshader);
    glLinkProgram(ogl->pshader);
    glGetProgramiv(ogl->pshader, GL_LINK_STATUS, &status);
    glGetProgramInfoLog(ogl->pshader, 512, NULL, info);
    cassert(status != 0);

    /* Shader uniform access */
    ogl->mvp_uniform = glGetUniformLocation(ogl->pshader, "uMVP");
    cassert((err = glGetError()) == GL_NO_ERROR);

    ogl->tex_uniform = glGetUniformLocation(ogl->pshader, "uTexture");
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Delete vertex and pixel shared already linked */
    glDeleteShader(vshader);
    cassert((err = glGetError()) == GL_NO_ERROR);

    glDeleteShader(fshader);
    cassert((err = glGetError()) == GL_NO_ERROR);

    unref(err);
}

/*---------------------------------------------------------------------------*/

static void i_scale_rotate_Z(GLfloat *m, const real32_t a, const real32_t s)
{
    real32_t ca = bmath_cosf(a);
    real32_t sa = bmath_sinf(a);
    m[0] = s * ca;
    m[1] = s * sa;
    m[2] = 0;
    m[3] = 0;
    m[4] = -s * sa;
    m[5] = s * ca;
    m[6] = 0;
    m[7] = 0;
    m[8] = 0;
    m[9] = 0;
    m[10] = 1;
    m[11] = 0;
    m[12] = 0;
    m[13] = 0;
    m[14] = 0;
    m[15] = 1;
}

/*---------------------------------------------------------------------------*/

void ogl3_draw(OGL3 *ogl, const real32_t width, const real32_t height, const real32_t angle, const real32_t scale)
{
    GLfloat m[16];
    GLenum err;

    cassert_no_null(ogl);
    ogl3d_begin_draw(ogl->ctx);

    if (ogl->init == GL_FALSE)
    {
        i_device_data(ogl);
        ogl->init = GL_TRUE;
    }

    glViewport(0, 0, (GLsizei)width, (GLsizei)height);
    glClearColor(.8f, .8f, 0.8f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    /* Set the texture */
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, ogl->texture);
    cassert((err = glGetError()) == GL_NO_ERROR);
    
    /* Set vertex array object */
    glBindVertexArray(ogl->vao);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Set index buffer object */
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ogl->ibo);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Set the shader */
    glUseProgram(ogl->pshader);
    cassert((err = glGetError()) == GL_NO_ERROR);

    /* Model-View-Projection and draw */
    i_scale_rotate_Z(m, angle * 2 * kBMATH_PIf, scale);
    glUniformMatrix4fv(ogl->mvp_uniform, 1, GL_FALSE, m);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0);
    cassert((err = glGetError()) == GL_NO_ERROR);

    ogl3d_end_draw(ogl->ctx);
}

/*---------------------------------------------------------------------------*/

void ogl3_resize(OGL3 *ogl, const real32_t width, const real32_t height)
{
    cassert_no_null(ogl);
    ogl3d_set_size(ogl->ctx, (uint32_t)width, (uint32_t)height);
}
❮ Anterior
Siguiente ❯