1 /**
2  * \file video.c
3  * \brief Native GTK3 UI video stuff
4  *
5  * \author Marco van den Heuvel <blackystardust68@yahoo.com>
6  * \author Michael C. Martin <mcmartin@gmail.com>
7  */
8 
9 /* This file is part of VICE, the Versatile Commodore Emulator.
10  * See README for copyright notice.
11  *
12  *  This program is free software; you can redistribute it and/or modify
13  *  it under the terms of the GNU General Public License as published by
14  *  the Free Software Foundation; either version 2 of the License, or
15  *  (at your option) any later version.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
25  *  02111-1307  USA.
26  *
27  */
28 
29 #include "vice.h"
30 
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <string.h>
34 
35 #include "debug_gtk3.h"
36 
37 #include "archdep.h"
38 #include "cmdline.h"
39 #include "lib.h"
40 #include "log.h"
41 #include "machine.h"
42 #include "palette.h"
43 #include "raster.h"
44 #include "resources.h"
45 #include "ui.h"
46 #include "videoarch.h"
47 
48 /** \brief Enable extra debugging info */
49 #define VICE_DEBUG_NATIVE_GTK3
50 
51 /** \brief  Log for Gtk3-native video messages
52  */
53 static log_t    gtk3video_log = LOG_ERR;
54 
55 /** \brief  Keep aspect ratio when resizing */
56 static int keepaspect = 0;
57 
58 /** \brief  Use true aspect ratio */
59 static int trueaspect = 0;
60 
61 /** \brief  Display depth in bits (8, 15, 16, 24, 32) */
62 static int display_depth = 24;
63 
64 
65 /** \brief  Set KeepAspectRatio resource (bool)
66  *
67  * The display will be updated to reflect any changes this makes.
68  *
69  * \param[in]   val     new value
70  * \param[in]   param   extra parameter (unused)
71  *
72  * \return 0
73  */
set_keepaspect(int val,void * param)74 static int set_keepaspect(int val, void *param)
75 {
76     keepaspect = val ? 1 : 0;
77     ui_trigger_resize();
78     return 0;
79 }
80 
81 
82 /** \brief  Set TrueAspectRatio resource (bool)
83  *
84  * The display will be updated to reflect any changes this makes.
85  *
86  * \param[in]   val     new value
87  * \param[in]   param   extra parameter (unused)
88  *
89  * \return 0
90  */
set_trueaspect(int val,void * param)91 static int set_trueaspect(int val, void *param)
92 {
93     trueaspect = val ? 1 : 0;
94     ui_trigger_resize();
95     return 0;
96 }
97 
98 /** \brief Set the display color depth.
99  *  \param     val   new color depth
100  *  \param[in] param extra parameter (unused).
101  *  \return  Zero on success, nonzero on illegal argument
102  *  \warning Neither Cairo nor GTK3's OpenGL system actually respect
103  *           this value.
104  */
set_display_depth(int val,void * param)105 static int set_display_depth(int val, void *param)
106 {
107     if (val != 0 && val != 8 && val != 15 && val != 16 && val != 24 && val != 32) {
108         return -1;
109     }
110     display_depth = val;
111     return 0;
112 }
113 
114 /** \brief  Command line options related to generic video output
115  */
116 static const cmdline_option_t cmdline_options[] =
117 {
118     { "-trueaspect", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
119       NULL, NULL, "TrueAspectRatio", (resource_value_t)1,
120       NULL, "Enable true aspect ratio" },
121     { "+trueaspect", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
122       NULL, NULL, "TrueAspectRatio", (resource_value_t)0,
123       NULL, "Disable true aspect ratio" },
124     { "-keepaspect", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
125       NULL, NULL, "KeepAspectRatio", (resource_value_t)1,
126       NULL, "Keep aspect ratio when scaling" },
127     { "+keepaspect", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
128       NULL, NULL, "KeepAspectRatio", (resource_value_t)0,
129       NULL, "Do not keep aspect ratio when scaling (freescale)" },
130     CMDLINE_LIST_END
131 };
132 
133 
134 /** \brief  Integer/boolean resources related to video output
135  */
136 static const resource_int_t resources_int[] = {
137     { "KeepAspectRatio", 1, RES_EVENT_NO, NULL,
138       &keepaspect, set_keepaspect, NULL },
139     { "TrueAspectRatio", 1, RES_EVENT_NO, NULL,
140       &trueaspect, set_trueaspect, NULL },
141     { "DisplayDepth", 0, RES_EVENT_NO, NULL,
142       &display_depth, set_display_depth, NULL },
143     RESOURCE_INT_LIST_END
144 };
145 
146 /** \brief  Arch-specific initialization for a video canvas
147  *  \param[inout] canvas The canvas being initialized
148  *  \sa video_canvas_create
149  */
video_arch_canvas_init(struct video_canvas_s * canvas)150 void video_arch_canvas_init(struct video_canvas_s *canvas)
151 {
152     canvas->video_draw_buffer_callback = NULL;
153 }
154 
155 
156 /** \brief  Initialize command line options for generic video resouces
157  *
158  * \return  0 on success, < 0 on failure
159  */
video_arch_cmdline_options_init(void)160 int video_arch_cmdline_options_init(void)
161 {
162     if (machine_class != VICE_MACHINE_VSID) {
163         return cmdline_register_options(cmdline_options);
164     }
165     return 0;
166 }
167 
168 
169 /** \brief  Initialize video-related resources
170  *
171  * \return  0 on success, < on failure
172  */
video_arch_resources_init(void)173 int video_arch_resources_init(void)
174 {
175     if (machine_class != VICE_MACHINE_VSID) {
176         return resources_register_int(resources_int);
177     }
178     return 0;
179 }
180 
181 /** \brief Clean up any memory held by arch-specific video resources. */
video_arch_resources_shutdown(void)182 void video_arch_resources_shutdown(void)
183 {
184 }
185 
186 /** \brief Query whether a canvas is resizable.
187  *  \param canvas The canvas to query
188  *  \return TRUE if the canvas can be resized.
189  */
video_canvas_can_resize(video_canvas_t * canvas)190 char video_canvas_can_resize(video_canvas_t *canvas)
191 {
192     return 1;
193 }
194 
195 /** \brief Create a new video_canvas_s.
196  *  \param[inout] canvas A freshly allocated canvas object.
197  *  \param[in]    width  Pointer to a width value. May be NULL if canvas
198  *                       size is not yet known.
199  *  \param[in]    height Pointer to a height value. May be NULL if canvas
200  *                       size is not yet known.
201  *  \param        mapped Unused.
202  *  \return The completely initialized canvas. The window that holds
203  *          it will be visible in the UI at time of return.
204  */
video_canvas_create(video_canvas_t * canvas,unsigned int * width,unsigned int * height,int mapped)205 video_canvas_t *video_canvas_create(video_canvas_t *canvas,
206                                     unsigned int *width, unsigned int *height,
207                                     int mapped)
208 {
209     canvas->initialized = 0;
210     canvas->created = 0;
211     canvas->renderer_context = NULL;
212     canvas->blank_ptr = NULL;
213     canvas->pen_ptr = NULL;
214     canvas->still_frame_callback_id = 0;
215     canvas->pen_x = -1;
216     canvas->pen_y = -1;
217     canvas->pen_buttons = 0;
218     ui_create_main_window(canvas);
219     if (width && height && canvas->renderer_backend) {
220         canvas->renderer_backend->update_context(canvas, *width, *height);
221     }
222 
223     ui_display_main_window(canvas->window_index);
224 
225     canvas->created = 1;
226     canvas->initialized = 1;
227     return canvas;
228 }
229 
230 /** \brief Free a previously created video canvas and all its
231  *         components.
232  *  \param[in] canvas The canvas to destroy.
233  */
video_canvas_destroy(struct video_canvas_s * canvas)234 void video_canvas_destroy(struct video_canvas_s *canvas)
235 {
236     if (canvas != NULL) {
237         if (canvas->renderer_backend) {
238             canvas->renderer_backend->destroy_context(canvas);
239         }
240         if (canvas->blank_ptr) {
241             g_object_unref(G_OBJECT(canvas->blank_ptr));
242             canvas->blank_ptr = NULL;
243         }
244         if (canvas->pen_ptr) {
245             g_object_unref(G_OBJECT(canvas->pen_ptr));
246             canvas->pen_ptr = NULL;
247         }
248     }
249 }
250 
251 /** \brief Update the display on a video canvas to reflect the machine
252  *         state.
253  * \param canvas The canvas to update.
254  * \param xs     A parameter to forward to video_canvas_render()
255  * \param ys     A parameter to forward to video_canvas_render()
256  * \param xi     X coordinate of the leftmost pixel to update
257  * \param yi     Y coordinate of the topmost pixel to update
258  * \param w      Width of the rectangle to update
259  * \param h      Height of the rectangle to update
260  */
video_canvas_refresh(struct video_canvas_s * canvas,unsigned int xs,unsigned int ys,unsigned int xi,unsigned int yi,unsigned int w,unsigned int h)261 void video_canvas_refresh(struct video_canvas_s *canvas,
262                           unsigned int xs, unsigned int ys,
263                           unsigned int xi, unsigned int yi,
264                           unsigned int w, unsigned int h)
265 {
266     if (console_mode || video_disabled_mode || !canvas) {
267         return;
268     }
269 
270     xi *= canvas->videoconfig->scalex;
271     w *= canvas->videoconfig->scalex;
272 
273     yi *= canvas->videoconfig->scaley;
274     h *= canvas->videoconfig->scaley;
275 
276     if (canvas->renderer_backend) {
277         canvas->renderer_backend->refresh_rect(canvas, xs, ys, xi, yi, w, h);
278     }
279 }
280 
281 /** \brief Update canvas size to match the draw buffer size requested
282  *         by the emulation core.
283  * \param canvas The video canvas to update.
284  * \param resize_canvas Ignored - the canvas will always resize.
285  */
286 
video_canvas_resize(struct video_canvas_s * canvas,char resize_canvas)287 void video_canvas_resize(struct video_canvas_s *canvas, char resize_canvas)
288 {
289     if (!canvas || !canvas->drawing_area) {
290         return;
291     } else {
292         int new_width = canvas->draw_buffer->canvas_physical_width;
293         int new_height = canvas->draw_buffer->canvas_physical_height;
294 
295         if (new_width <= 0 || new_height <= 0) {
296             /* Ignore impossible dimensions, but complain about it */
297             fprintf(stderr, "%s:%d: warning: function %s called with impossible dimensions\n", __FILE__, __LINE__, __func__);
298             return;
299         }
300 
301         if (canvas->renderer_backend) {
302             canvas->renderer_backend->update_context(canvas, new_width, new_height);
303         }
304 
305         /* Set the palette */
306         if (video_canvas_set_palette(canvas, canvas->palette) < 0) {
307             fprintf(stderr, "Setting palette for this mode failed. (Try 16/24/32 bpp.)");
308             archdep_vice_exit(-1);
309         }
310     }
311 }
312 
313 /** \brief React to changes of aspect ratio in the physical system.
314  *
315  *  For GTK3 this means we need to compute a new minimum size for our
316  *  display windows.
317  *
318  *  \param canvas The canvas whose widgets need their sizes updated.
319  */
video_canvas_adjust_aspect_ratio(struct video_canvas_s * canvas)320 void video_canvas_adjust_aspect_ratio(struct video_canvas_s *canvas)
321 {
322     int width = canvas->draw_buffer->canvas_physical_width;
323     int height = canvas->draw_buffer->canvas_physical_height;
324     if (keepaspect && trueaspect) {
325         width *= canvas->geometry->pixel_aspect_ratio;
326     }
327 
328     /* Finally alter our minimum size so the GUI may respect the new minima/maxima */
329     gtk_widget_set_size_request(canvas->drawing_area, width, height);
330 }
331 
332 /** \brief Assign a palette to the canvas.
333  * \param canvas The canvas to update the palette
334  * \param palette The new palette to assign
335  * \return Zero on success, nonzero on failure
336  */
video_canvas_set_palette(struct video_canvas_s * canvas,struct palette_s * palette)337 int video_canvas_set_palette(struct video_canvas_s *canvas,
338                              struct palette_s *palette)
339 {
340     if (!canvas || !palette) {
341         return 0; /* No palette, nothing to do */
342     }
343     canvas->palette = palette;
344     if (canvas->renderer_backend) {
345         canvas->renderer_backend->set_palette(canvas);
346     }
347     return 0;
348 }
349 
350 /** \brief Perform any frontend-specific initialization.
351  *  \return 0 on success, nonzero on failure
352  */
video_init(void)353 int video_init(void)
354 {
355     if (gtk3video_log == LOG_ERR) {
356         gtk3video_log = log_open("Gtk3Video");
357     }
358     return 0;
359 }
360 
361 /** \brief Perform any frontend-specific uninitialization. */
video_shutdown(void)362 void video_shutdown(void)
363 {
364     /* It's a no-op */
365 }
366