1 /**
2  * \file opengl_renderer.c
3  * \brief   OpenGL-based renderer for the GTK3 backend.
4  *
5  * \author Michael C. Martin <mcmartin@gmail.com>
6  */
7 
8 /* This file is part of VICE, the Versatile Commodore Emulator.
9  * See README for copyright notice.
10  *
11  *  This program is free software; you can redistribute it and/or modify
12  *  it under the terms of the GNU General Public License as published by
13  *  the Free Software Foundation; either version 2 of the License, or
14  *  (at your option) any later version.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
24  *  02111-1307  USA.
25  *
26  */
27 
28 #include "opengl_renderer.h"
29 
30 #ifdef HAVE_GTK3_OPENGL
31 
32 #include <string.h>
33 
34 #ifdef MACOSX_SUPPORT
35 #include <OpenGL/gl3.h>
36 #else
37 #ifdef HAVE_GTK3_GLEW
38 #include <GL/glew.h>
39 #else
40 #define GL_GLEXT_PROTOTYPES
41 #include <GL/gl.h>
42 #endif
43 #endif
44 
45 #include "lib.h"
46 #include "resources.h"
47 #include "ui.h"
48 #include "video.h"
49 
50 /** \brief The screen has not changed, so the texture may be used
51  *         unchanged */
52 #define RENDER_MODE_STATIC      0
53 /** \brief The texture must be completely recreated from scratch */
54 #define RENDER_MODE_NEW_TEXTURE 1
55 /** \brief The texture needs some or all of its pixels updated */
56 #define RENDER_MODE_DIRTY_RECT  2
57 
58 /** \brief Rendering context for the OpenGL backend.
59  *  \sa video_canvas_s::renderer_context */
60 typedef struct vice_opengl_renderer_context_s {
61     /** \brief The OpenGL program that comprises our vertex and
62      *         fragment shaders. */
63     GLuint program;
64     /** \brief The index of the "position" parameter in the shader
65      *         program. */
66     GLuint position_index;
67     /** \brief The index of the "texCoord" parameter in the shader
68      *         program. */
69     GLuint tex_coord_index;
70     /** \brief The vertex buffer object that holds our vertex data. */
71     GLuint vbo;
72     /** \brief The vertex array object that gives structure to our
73      *         vertex data. */
74     GLuint vao;
75     /** \brief The texture identifier for the GPU's copy of our
76      *         machine display. */
77     GLuint texture;
78     /** \brief Width of the texture, in pixels. */
79     unsigned int width;
80     /** \brief Height of the texture, in pixels. */
81     unsigned int height;
82     /** \brief The raw pixel data that is the CPU's copy of our
83      * machine display. */
84     unsigned char *backbuffer;
85     /** \brief Fraction of the window width the scaled machine display
86      *         takes up (1.0f=entire width). */
87     float scale_x;
88     /** \brief Fraction of the window height the scaled machine display
89      *         takes up (1.0f=entire height). */
90     float scale_y;
91     /** \brief X coordinate of leftmost pixel that needs to be updated
92      * in the texture. */
93     unsigned int dirty_x;
94     /** \brief Y coordinate of topmost pixel that needs to be updated
95      * in the texture. */
96     unsigned int dirty_y;
97     /** \brief Width of the rectangle that needs to be updated in the
98      * texture. */
99     unsigned int dirty_w;
100     /** \brief Height of the rectangle that needs to be updated in the
101      * texture. */
102     unsigned int dirty_h;
103     /** \brief What preprocessing the texture will need pre-rendering.
104      *
105      * Must be one of RENDER_MODE_STATIC, RENDER_MODE_NEW_TEXTURE, or
106      * RENDER_MODE_DIRTY_RECT.
107      */
108     unsigned int render_mode;
109 } context_t;
110 
111 /** \brief Raw geometry for the machine screen.
112  *
113  * The first sixteen elements describe a rectangle the size of the
114  * entire display area, and the last eight assign texture coordinates
115  * to each corner.
116  */
117 static float vertexData[] = {
118         -1.0f,    -1.0f, 0.0f, 1.0f,
119          1.0f,    -1.0f, 0.0f, 1.0f,
120         -1.0f,     1.0f, 0.0f, 1.0f,
121          1.0f,     1.0f, 0.0f, 1.0f,
122          0.0f,     1.0f,
123          1.0f,     1.0f,
124          0.0f,     0.0f,
125          1.0f,     0.0f
126 };
127 
128 /** \brief Our renderer's vertex shader.
129  *
130  * This simply scales the geometry it is provided and provides
131  * smoothly interpolated texture coordinates between each vertex. The
132  * world coordinates remain [-1, 1] in all dimensions. */
133 static const char *vertexShader = "#version 150\n"
134     "uniform vec4 scale;\n"
135     "in vec4 position;\n"
136     "in vec2 tex;\n"
137     "smooth out vec2 texCoord;\n"
138     "void main() {\n"
139     "  gl_Position = position * scale;\n"
140     "  texCoord = tex;\n"
141     "}\n";
142 
143 /** \brief Our renderer's fragment shader.
144  *
145  * This does nothing but texture lookups based on the values fed to it
146  * by the vertex shader. */
147 static const char *fragmentShader = "#version 150\n"
148     "uniform sampler2D sampler;\n"
149     "smooth in vec2 texCoord;\n"
150     "out vec4 outputColor;\n"
151     "void main() { outputColor = texture2D(sampler, texCoord); }\n";
152 
153 /** \brief Compile a shader.
154  *
155  *  If the shader cannot be compiled, error messages from OpenGL will
156  *  be dumped to stdout.
157  *
158  *  \param shader_type The kind of shader being compiled. Must be
159  *                     either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
160  *  \param text        The shader source.
161  *  \return The identifier of the shader.
162  */
create_shader(GLenum shader_type,const char * text)163 static GLuint create_shader(GLenum shader_type, const char *text)
164 {
165     GLuint shader = glCreateShader(shader_type);
166     GLint status = 0;
167     glShaderSource(shader, 1, &text, NULL);
168     glCompileShader(shader);
169     glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
170     if (status == GL_FALSE) {
171         GLint info_log_length;
172         GLchar *info_log;
173         const char *shader_type_name = NULL;
174         glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_length);
175         info_log = lib_malloc(sizeof(GLchar) * (info_log_length + 1));
176         glGetShaderInfoLog(shader, info_log_length, NULL, info_log);
177 
178         switch(shader_type)
179         {
180         case GL_VERTEX_SHADER: shader_type_name = "vertex"; break;
181         case GL_FRAGMENT_SHADER: shader_type_name = "fragment"; break;
182         default: shader_type_name = "unknown"; break;
183         }
184 
185         fprintf(stderr, "Compile failure in %s shader:\n%s\n", shader_type_name, info_log);
186         lib_free(info_log);
187     }
188 
189     return shader;
190 }
191 
192 /** \brief Compile and link the renderer's shaders.
193  *
194  *  If successful, the vice_opengl_renderer_context_s::program,
195  *  vice_opengl_renderer_context_s::position_index, and
196  *  vice_opengl_renderer_context_s::tex_coord_index fields will be
197  *  filled in with values for future use.
198  *
199  *  \param ctx The renderer context that will receive the results.
200  */
create_shader_program(context_t * ctx)201 static void create_shader_program(context_t *ctx)
202 {
203     GLuint program = glCreateProgram();
204     GLuint vert = create_shader(GL_VERTEX_SHADER, vertexShader);
205     GLuint frag = create_shader(GL_FRAGMENT_SHADER, fragmentShader);
206     GLint status;
207 
208     glAttachShader(program, vert);
209     glAttachShader(program, frag);
210     glLinkProgram(program);
211     glGetProgramiv (program, GL_LINK_STATUS, &status);
212     if (status == GL_FALSE)
213     {
214         GLint info_log_length;
215         GLchar *info_log;
216 
217         glGetProgramiv(program, GL_INFO_LOG_LENGTH, &info_log_length);
218         info_log = lib_malloc(sizeof(GLchar) * (info_log_length + 1));
219         glGetProgramInfoLog(program, info_log_length, NULL, info_log);
220         fprintf(stderr, "Linker failure: %s\n", info_log);
221         lib_free(info_log);
222     }
223 
224     glDeleteShader(vert);
225     glDeleteShader(frag);
226     ctx->position_index = glGetAttribLocation(program, "position");
227     ctx->tex_coord_index = glGetAttribLocation(program, "tex");
228     ctx->program = program;
229 }
230 
231 /** \brief GTK3 callback when setting up the OpenGL context.
232  *
233  * This is also where OpenGL compatibility is checked. If the system
234  * is not OpenGL 3.2-compatible, or if the area otherwise fails to
235  * initialize, errors will be logged to stderr.
236  *
237  * \param area      The widget being initialized.
238  * \param user_data The video_canvas_s associated with this widget.
239  *
240  * \warning If initialization fails, the display will completely fail
241  *          to render. However, some experimentation has shown that
242  *          some displays will successfully render as a 3.2 context
243  *          even when the driver only purports to support up to
244  *          2.1. It is not clear exactly what the requirements truly
245  *          are.
246  */
realize_opengl_cb(GtkGLArea * area,gpointer user_data)247 static void realize_opengl_cb (GtkGLArea *area, gpointer user_data)
248 {
249     video_canvas_t *canvas = (video_canvas_t *)user_data;
250     context_t *ctx = NULL;
251     GError *err = NULL;
252     GLenum glErr;
253 
254     gtk_gl_area_make_current(area);
255     err = gtk_gl_area_get_error(area);
256     if (err != NULL) {
257         fprintf(stderr, "CRITICAL: Could not realize GL context: %s\n", err->message);
258         return;
259     }
260     if (canvas->renderer_context) {
261         fprintf(stderr, "WARNING: Re-realizing the GtkGL area! This will leak.\n");
262     }
263     ctx = lib_malloc(sizeof(context_t));
264     memset(ctx, 0, sizeof(context_t));
265     canvas->renderer_context = ctx;
266 #ifdef HAVE_GTK3_GLEW
267     glewExperimental = GL_TRUE;
268     glErr = glewInit();
269     if (glErr != GLEW_OK) {
270         fprintf(stderr, "GTKGL: Could not initialize GLEW\n");
271     }
272     if (!GLEW_VERSION_3_2) {
273         fprintf(stderr, "GTKGL: OpenGL version 3.2 not supported in this context\n");
274     }
275 #endif
276 
277     create_shader_program(ctx);
278     glGenBuffers(1, &ctx->vbo);
279     glBindBuffer(GL_ARRAY_BUFFER, ctx->vbo);
280     glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
281     glBindBuffer(GL_ARRAY_BUFFER, 0);
282     glGenVertexArrays(1, &ctx->vao);
283     glGenTextures(1, &ctx->texture);
284 }
285 
286 /** \brief OpenGL render callback.
287  *  \param area   The widget being rendered.
288  *  \param unused The GDK context that wraps OpenGL.
289  *  \param data   The video_canvas_s associated with this widget.
290  *  \return TRUE if no further processing is needed on this event.
291  *  \todo It should be possible to select GL_NEAREST or GL_LINEAR when
292  *        deciding how to scale textures.
293  */
render_opengl_cb(GtkGLArea * area,GdkGLContext * unused,gpointer data)294 static gboolean render_opengl_cb (GtkGLArea *area, GdkGLContext *unused, gpointer data)
295 {
296     video_canvas_t *canvas = data;
297     context_t *ctx = canvas ? (context_t *)canvas->renderer_context : NULL;
298 
299     if (ui_is_fullscreen()) {
300         glClearColor (0.0f,0.0f,0.0f,1.0f);
301     } else {
302         glClearColor (0.5f,0.5f,0.5f,1.0f);
303     }
304     glClear (GL_COLOR_BUFFER_BIT);
305 
306     if (!ctx) {
307         /* Nothing else to do */
308         return TRUE;
309     }
310     glActiveTexture(GL_TEXTURE0);
311     if (ctx->render_mode == RENDER_MODE_NEW_TEXTURE) {
312         if (ctx->texture == 0) {
313             fprintf(stderr, "GTKGL CRITICAL: No texture generated!\n");
314         }
315         glBindTexture(GL_TEXTURE_2D, ctx->texture);
316         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
317         glPixelStorei(GL_UNPACK_ROW_LENGTH, ctx->width);
318         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ctx->width, ctx->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, ctx->backbuffer);
319         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
320         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
321         glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
322         glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
323         /* These should be selectable as GL_LINEAR or GL_NEAREST */
324         glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
325         glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
326         glBindTexture(GL_TEXTURE_2D, 0);
327     } else if (ctx->render_mode == RENDER_MODE_DIRTY_RECT) {
328         glBindTexture(GL_TEXTURE_2D, ctx->texture);
329         glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
330         glPixelStorei(GL_UNPACK_ROW_LENGTH, ctx->width);
331         glTexSubImage2D(GL_TEXTURE_2D, 0, ctx->dirty_x, ctx->dirty_y, ctx->dirty_w, ctx->dirty_h, GL_RGBA, GL_UNSIGNED_BYTE, ctx->backbuffer + 4 * (ctx->width * ctx->dirty_y + ctx->dirty_x));
332         glBindTexture(GL_TEXTURE_2D, 0);
333     }
334     ctx->render_mode = RENDER_MODE_STATIC;
335     if (ctx->program) {
336         GLuint scale_uniform, sampler_uniform;
337 
338         glUseProgram(ctx->program);
339 
340         glBindVertexArray(ctx->vao);
341         glBindBuffer(GL_ARRAY_BUFFER, ctx->vbo);
342         glEnableVertexAttribArray(0);
343         glEnableVertexAttribArray(1);
344         glVertexAttribPointer(ctx->position_index, 4, GL_FLOAT, GL_FALSE, 0, 0);
345         glVertexAttribPointer(ctx->tex_coord_index, 2, GL_FLOAT, GL_FALSE, 0, (void*)64);
346 
347         scale_uniform = glGetUniformLocation(ctx->program, "scale");
348         glUniform4f(scale_uniform, ctx->scale_x, ctx->scale_y, 1.0f, 1.0f);
349         sampler_uniform = glGetUniformLocation(ctx->program, "sampler");
350         glUniform1i(sampler_uniform, 0);
351 
352         glBindTexture(GL_TEXTURE_2D, ctx->texture);
353         glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
354         glBindTexture(GL_TEXTURE_2D, 0);
355 
356         glDisableVertexAttribArray(ctx->position_index);
357         glDisableVertexAttribArray(ctx->tex_coord_index);
358         glUseProgram(0);
359     }
360 
361     return TRUE;
362 }
363 
364 /** \brief OpenGL viewport resize callback, called when the user or OS
365  *         has resized the window but the machine screen remains
366  *         intact.
367  *  \param area      The widget being resized.
368  *  \param width     The new viewport width.
369  *  \param height    The new viewport height.
370  *  \param user_data The video_canvas_s associated with this widget.
371  */
372 static void
resize_opengl_cb(GtkGLArea * area,gint width,gint height,gpointer user_data)373 resize_opengl_cb (GtkGLArea *area, gint width, gint height, gpointer user_data)
374 {
375     video_canvas_t *canvas = (video_canvas_t *)user_data;
376     context_t *ctx = canvas ? (context_t *)canvas->renderer_context : NULL;
377     int keepaspect = 1, trueaspect = 0;
378     if (!ctx || ctx->width == 0 || ctx->height == 0) {
379         return;
380     }
381 
382     if (width <= 0) {
383         width = 1;
384     }
385     if (height <= 0) {
386         height = 1;
387     }
388 
389     resources_get_int("KeepAspectRatio", &keepaspect);
390     resources_get_int("TrueAspectRatio", &trueaspect);
391 
392     if (keepaspect) {
393         float canvas_aspect, viewport_aspect;
394 
395         viewport_aspect = (float)width / (float)height;
396         canvas_aspect = (float)ctx->width / (float)ctx->height;
397         if (trueaspect) {
398             canvas_aspect *= canvas->geometry->pixel_aspect_ratio;
399         }
400         if (canvas_aspect < viewport_aspect) {
401             ctx->scale_x = canvas_aspect / viewport_aspect;
402             ctx->scale_y = 1.0f;
403         } else {
404             ctx->scale_x = 1.0f;
405             ctx->scale_y = viewport_aspect / canvas_aspect;
406         }
407     } else {
408         ctx->scale_x = 1.0f;
409         ctx->scale_y = 1.0f;
410     }
411     canvas->screen_display_w = (double)width * ctx->scale_x;
412     canvas->screen_display_h = (double)height * ctx->scale_y;
413     canvas->screen_origin_x = ((double)width - canvas->screen_display_w) / 2.0;
414     canvas->screen_origin_y = ((double)height - canvas->screen_display_h) / 2.0;
415 }
416 
417 /** \brief OpenGL implementation of create_widget.
418  *
419  *  \param canvas The canvas to create the widget for.
420  *  \return The newly created canvas.
421  *  \sa vice_renderer_backend_s::create_widget
422  */
vice_opengl_create_widget(video_canvas_t * canvas)423 static GtkWidget *vice_opengl_create_widget(video_canvas_t *canvas)
424 {
425     GtkWidget *widget = gtk_gl_area_new();
426     gtk_widget_set_hexpand(widget, TRUE);
427     gtk_widget_set_vexpand(widget, TRUE);
428     canvas->drawing_area = widget;
429     canvas->renderer_context = NULL;
430     g_signal_connect (widget, "realize", G_CALLBACK (realize_opengl_cb), canvas);
431     g_signal_connect (widget, "render", G_CALLBACK (render_opengl_cb), canvas);
432     g_signal_connect (widget, "resize", G_CALLBACK (resize_opengl_cb), canvas);
433     return widget;
434 }
435 
436 /** \brief OpenGL implementation of destroy_context.
437  *
438  *  \param canvas The canvas whose renderer_context is to be
439  *                deleted
440  *  \sa vice_renderer_backend_s::destroy_context
441  */
vice_opengl_destroy_context(video_canvas_t * canvas)442 static void vice_opengl_destroy_context(video_canvas_t *canvas)
443 {
444     if (canvas) {
445         context_t *ctx = (context_t *)canvas->renderer_context;
446         if (ctx == NULL) {
447             return;
448         }
449         /* TODO: delete textures, shaders, backbuffers, etc */
450         if (ctx->backbuffer != NULL) {
451             lib_free(ctx->backbuffer);
452         }
453         canvas->renderer_context = NULL;
454         lib_free(ctx);
455     }
456 }
457 
458 /** \brief OpenGL implementation of update_context.
459  * \param canvas The canvas being resized or initially created.
460  * \param width The new width for the machine's screen.
461  * \param height The new height for the machine's screen.
462  * \sa vice_renderer_backend_s::update_context
463  */
vice_opengl_update_context(video_canvas_t * canvas,unsigned int width,unsigned int height)464 static void vice_opengl_update_context(video_canvas_t *canvas, unsigned int width, unsigned int height)
465 {
466     context_t *ctx = canvas ? (context_t *)canvas->renderer_context : NULL;
467     if (ctx) {
468         double aspect = 1.0;
469         int keepaspect = 1, trueaspect = 0;
470         gint widget_width, widget_height;
471         if (ctx->width == width && ctx->height == height) {
472             return;
473         }
474         if (ctx->backbuffer) {
475             lib_free(ctx->backbuffer);
476         }
477         ctx->width = width;
478         ctx->height = height;
479         ctx->backbuffer = lib_malloc(width * height * 4);
480         ctx->render_mode = RENDER_MODE_NEW_TEXTURE;
481 
482         resources_get_int("KeepAspectRatio", &keepaspect);
483         resources_get_int("TrueAspectRatio", &trueaspect);
484         if (keepaspect && trueaspect) {
485             aspect = canvas->geometry->pixel_aspect_ratio;
486         }
487 
488         /* Configure the matrix to fit it in the widget as it exists */
489         widget_width = gtk_widget_get_allocated_width(canvas->drawing_area);
490         widget_height = gtk_widget_get_allocated_height(canvas->drawing_area);
491         resize_opengl_cb(GTK_GL_AREA(canvas->drawing_area), widget_width, widget_height, canvas);
492 
493         /* Fix the widget's size request */
494         gtk_widget_set_size_request(canvas->drawing_area, width * aspect, height);
495     }
496 }
497 
498 /** \brief OpenGL implementation of refresh_rect.
499  * \param canvas The canvas being rendered to
500  * \param xs     A parameter to forward to video_canvas_render()
501  * \param ys     A parameter to forward to video_canvas_render()
502  * \param xi     X coordinate of the leftmost pixel to update
503  * \param yi     Y coordinate of the topmost pixel to update
504  * \param w      Width of the rectangle to update
505  * \param h      Height of the rectangle to update
506  * \sa vice_renderer_backend_s::refresh_rect */
vice_opengl_refresh_rect(video_canvas_t * canvas,unsigned int xs,unsigned int ys,unsigned int xi,unsigned int yi,unsigned int w,unsigned int h)507 static void vice_opengl_refresh_rect(video_canvas_t *canvas,
508                                      unsigned int xs, unsigned int ys,
509                                      unsigned int xi, unsigned int yi,
510                                      unsigned int w, unsigned int h)
511 {
512     context_t *ctx = (context_t *)canvas->renderer_context;
513     if (!ctx || !ctx->backbuffer) {
514         return;
515     }
516 
517     if (((xi + w) > ctx->width) || ((yi + h) > ctx->height)) {
518         /* Trying to draw outside canvas? */
519         fprintf(stderr, "Attempt to draw outside canvas!\nXI%u YI%u W%u H%u CW%u CH%u\n", xi, yi, w, h, ctx->width, ctx->height);
520         return;
521     }
522 
523     video_canvas_render(canvas, ctx->backbuffer, w, h, xs, ys, xi, yi, ctx->width * 4, 32);
524 
525     if (ctx->render_mode == RENDER_MODE_STATIC) {
526         ctx->render_mode = RENDER_MODE_DIRTY_RECT;
527         ctx->dirty_x = xi;
528         ctx->dirty_y = yi;
529         ctx->dirty_w = w;
530         ctx->dirty_h = h;
531     } else if (ctx->render_mode == RENDER_MODE_DIRTY_RECT) {
532         unsigned int x1 = ctx->dirty_x;
533         unsigned int y1 = ctx->dirty_y;
534         unsigned int x2 = ctx->dirty_x + ctx->dirty_w;
535         unsigned int y2 = ctx->dirty_y + ctx->dirty_h;
536         if (x1 > xi) {
537             x1 = xi;
538         }
539         if (y1 > yi) {
540             y1 = yi;
541         }
542         if (x2 < xi + w) {
543             x2 = xi + w;
544         }
545         if (y2 < yi + h) {
546             y2 = yi + h;
547         }
548         ctx->dirty_x = x1;
549         ctx->dirty_y = y1;
550         ctx->dirty_w = x2-x1;
551         ctx->dirty_h = y2-y1;
552         /* Render mode stays DIRTY_RECT */
553     }
554     /* Render mode NEW_TEXTURE has no effect; it stays just as new */
555 
556     gtk_widget_queue_draw(canvas->drawing_area);
557 }
558 
559 /** \brief OpenGL implementation of set_palette.
560  * \param canvas The canvas being initialized
561  * \sa vice_renderer_backend_s::set_palette */
vice_opengl_set_palette(video_canvas_t * canvas)562 static void vice_opengl_set_palette(video_canvas_t *canvas)
563 {
564     int i;
565     struct palette_s *palette = canvas ? canvas->palette : NULL;
566     if (!palette) {
567         return;
568     }
569     /* If we get this far we know canvas is also non-NULL */
570 
571     for (i = 0; i < palette->num_entries; i++) {
572         palette_entry_t color = palette->entries[i];
573         uint32_t color_code = color.red | (color.green << 8) | (color.blue << 16) | (0xff << 24);
574         video_render_setphysicalcolor(canvas->videoconfig, i, color_code, 32);
575     }
576 
577     for (i = 0; i < 256; i++) {
578         video_render_setrawrgb(i, i, i << 8, i << 16);
579     }
580     video_render_setrawalpha(255 << 24);
581     video_render_initraw(canvas->videoconfig);
582 }
583 
584 vice_renderer_backend_t vice_opengl_backend = {
585     vice_opengl_create_widget,
586     vice_opengl_update_context,
587     vice_opengl_destroy_context,
588     vice_opengl_refresh_rect,
589     vice_opengl_set_palette
590 };
591 
592 #endif
593