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