1 /** \file quartz_renderer.c
2 * \brief Quartz-based renderer for the GTK3 backend
3 *
4 * \author Michael C. Martin <mcmartin@gmail.com>
5 */
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 "quartz_renderer.h"
29
30 #ifdef MACOSX_SUPPORT
31
32 #include <CoreGraphics/CoreGraphics.h>
33
34 #include <cairo/cairo-quartz.h>
35
36 #include "lib.h"
37 #include "resources.h"
38 #include "ui.h"
39 #include "video.h"
40
41 typedef struct vice_quartz_renderer_context_s {
42 CGContextRef bitmap_context;
43 CGLayerRef layer;
44 double scale_x, scale_y;
45 } context_t;
46
47 /* Note that the ::draw signal receives a ready-to-be-used cairo_t
48 * that is already clipped to only draw the exposed areas of the
49 * widget */
50 static gboolean
draw_canvas_quartz_cb(GtkWidget * widget,cairo_t * cr,gpointer data)51 draw_canvas_quartz_cb (GtkWidget *widget, cairo_t *cr, gpointer data)
52 {
53 video_canvas_t *canvas = (video_canvas_t *)data;
54 context_t *ctx = canvas ? (context_t *)canvas->renderer_context : NULL;
55 cairo_surface_t *target = cairo_get_target(cr);
56 CGContextRef display = NULL;
57 CGRect clip_rect;
58
59 cairo_surface_flush(target);
60 display = cairo_quartz_surface_get_cg_context(target);
61 if (!display) {
62 fprintf(stderr, "Rendering impossible as the display area isn't a Quartz surface: %d\n", cairo_surface_get_type(target));
63 return FALSE;
64 }
65
66 CGContextSaveGState(display);
67 CGContextScaleCTM(display, 1.0, -1.0);
68 clip_rect = CGContextGetClipBoundingBox(display);
69
70 if (ui_is_fullscreen()) {
71 CGContextSetRGBFillColor(display, 0.0f, 0.0f, 0.0f, 1.0f);
72 } else {
73 CGContextSetRGBFillColor(display, 0.5f, 0.5f, 0.5f, 1.0f);
74 }
75 CGContextFillRect(display, clip_rect);
76
77 if (ctx && ctx->bitmap_context) {
78 CGImageRef frame = CGBitmapContextCreateImage(ctx->bitmap_context);
79 if (!ctx->layer) {
80 CGSize targetSize = { CGBitmapContextGetWidth(ctx->bitmap_context),
81 CGBitmapContextGetHeight(ctx->bitmap_context) };
82 ctx->layer = CGLayerCreateWithContext(ctx->bitmap_context, targetSize, NULL);
83 }
84 if (ctx->layer) {
85 CGRect layer_rect = { { 0, 0 }, CGLayerGetSize(ctx->layer) };
86 CGRect img_rect = clip_rect;
87 img_rect.size.width *= ctx->scale_x;
88 img_rect.size.height *= ctx->scale_y;
89
90 CGContextDrawImage(CGLayerGetContext(ctx->layer), layer_rect, frame);
91 CGContextScaleCTM(display,
92 img_rect.size.width / layer_rect.size.width,
93 img_rect.size.height / layer_rect.size.height);
94 clip_rect = CGContextGetClipBoundingBox(display);
95 img_rect = clip_rect;
96 img_rect.size.width *= ctx->scale_x;
97 img_rect.size.height *= ctx->scale_y;
98 img_rect.origin.x += (clip_rect.size.width - img_rect.size.width) / 2;
99 img_rect.origin.y += (clip_rect.size.height - img_rect.size.height) / 2;
100 CGContextSetInterpolationQuality(display, kCGInterpolationNone);
101 CGContextDrawLayerAtPoint(display, img_rect.origin, ctx->layer);
102 } else {
103 CGRect img_rect = clip_rect;
104 img_rect.size.width *= ctx->scale_x;
105 img_rect.size.height *= ctx->scale_y;
106 img_rect.origin.x += (clip_rect.size.width - img_rect.size.width) / 2;
107 img_rect.origin.y += (clip_rect.size.height - img_rect.size.height) / 2;
108 CGContextDrawImage(display, img_rect, frame);
109 }
110 CGImageRelease(frame);
111 }
112 CGContextRestoreGState(display);
113 cairo_surface_mark_dirty(target);
114
115 return FALSE;
116 }
117
118 /** \brief Callback to handle cases where the window is resized but
119 * the canvas is not
120 */
121 static gboolean
resize_canvas_container_quartz_cb(GtkWidget * widget,GdkEventConfigure * event,gpointer data)122 resize_canvas_container_quartz_cb (GtkWidget *widget, GdkEventConfigure *event, gpointer data)
123 {
124 /* The GtkDrawingArea that holds the canvas is "widget." */
125 video_canvas_t *canvas = (video_canvas_t *)data;
126 context_t *ctx = canvas ? (context_t *)canvas->renderer_context : NULL;
127 if (ctx && ctx->bitmap_context) {
128 /* Size of source canvas */
129 double source_width = (double)CGBitmapContextGetWidth(ctx->bitmap_context);
130 double source_height = (double)CGBitmapContextGetHeight(ctx->bitmap_context);
131 /* Size of widget */
132 double width = (double)gtk_widget_get_allocated_width(widget);
133 double height = (double)gtk_widget_get_allocated_height(widget);
134 double scale_x = 1.0, scale_y = 1.0;
135 int keepaspect=1, trueaspect=0;
136 resources_get_int("KeepAspectRatio", &keepaspect);
137 resources_get_int("TrueAspectRatio", &trueaspect);
138
139 if (source_width < 1 || source_height < 1) {
140 return FALSE;
141 }
142
143 if (keepaspect) {
144 double canvas_aspect, viewport_aspect;
145
146 viewport_aspect = width / height;
147 canvas_aspect = source_width / source_height;
148 if (trueaspect) {
149 canvas_aspect *= canvas->geometry->pixel_aspect_ratio;
150 }
151 if (canvas_aspect < viewport_aspect) {
152 scale_x = canvas_aspect / viewport_aspect;
153 scale_y = 1.0f;
154 } else {
155 scale_x = 1.0f;
156 scale_y = viewport_aspect / canvas_aspect;
157 }
158 } else {
159 scale_x = 1.0;
160 scale_y = 1.0;
161 }
162 ctx->scale_x = scale_x;
163 ctx->scale_y = scale_y;
164 canvas->screen_display_w = (double)width * ctx->scale_x;
165 canvas->screen_display_h = (double)height * ctx->scale_y;
166 canvas->screen_origin_x = ((double)width - canvas->screen_display_w) / 2.0;
167 canvas->screen_origin_y = ((double)height - canvas->screen_display_h) / 2.0;
168 }
169 /* No further processing should be needed */
170 return FALSE;
171 }
172
vice_quartz_create_widget(video_canvas_t * canvas)173 static GtkWidget *vice_quartz_create_widget(video_canvas_t *canvas)
174 {
175 GtkWidget *widget = gtk_drawing_area_new();
176 gtk_widget_set_hexpand(widget, TRUE);
177 gtk_widget_set_vexpand(widget, TRUE);
178 canvas->drawing_area = widget;
179 g_signal_connect(widget, "draw", G_CALLBACK(draw_canvas_quartz_cb), canvas);
180 g_signal_connect(widget, "configure_event", G_CALLBACK(resize_canvas_container_quartz_cb), canvas);
181 return widget;
182 }
183
vice_quartz_destroy_context(video_canvas_t * canvas)184 static void vice_quartz_destroy_context(video_canvas_t *canvas)
185 {
186 if (canvas) {
187 context_t *ctx = (context_t *)canvas->renderer_context;
188 if (!ctx) {
189 return;
190 }
191 if (ctx->bitmap_context) {
192 CGContextRelease(ctx->bitmap_context);
193 }
194 if (ctx->layer) {
195 CGLayerRelease(ctx->layer);
196 }
197 lib_free(ctx);
198 canvas->renderer_context = NULL;
199 }
200 }
201
vice_quartz_update_context(video_canvas_t * canvas,unsigned int width,unsigned int height)202 static void vice_quartz_update_context(video_canvas_t *canvas, unsigned int width, unsigned int height)
203 {
204 if (canvas) {
205 context_t *ctx = (context_t *)canvas->renderer_context;
206
207 if (ctx && ctx->bitmap_context) {
208 unsigned int source_width, source_height;
209 source_width = CGBitmapContextGetWidth(ctx->bitmap_context);
210 source_height = CGBitmapContextGetHeight(ctx->bitmap_context);
211 if (source_width == width && source_height == height) {
212 /* Canvas already exists and is the proper size */
213 return;
214 }
215 } else {
216 if (width == 0 || height == 0) {
217 /* We have no surface and were asked to create a
218 * surface of area zero, so we're done */
219 return;
220 }
221 }
222 /* If we get this far, we have new dimensions for our canvas */
223 vice_quartz_destroy_context(canvas);
224 ctx = lib_malloc(sizeof(context_t));
225 if (ctx) {
226 ctx->bitmap_context = NULL;
227 ctx->layer = NULL;
228 ctx->scale_x = 1.0;
229 ctx->scale_y = 1.0;
230 }
231 canvas->renderer_context = ctx;
232 if (width != 0 && height != 0 && ctx) {
233 /* Actually create the bitmap context */
234 int keepaspect=1, trueaspect=0;
235 double aspect = 1.0;
236 CGColorSpaceRef colorspace = NULL;
237 resources_get_int("KeepAspectRatio", &keepaspect);
238 resources_get_int("TrueAspectRatio", &trueaspect);
239 if (keepaspect && trueaspect) {
240 aspect = canvas->geometry->pixel_aspect_ratio;
241 }
242
243 /* Possible TODO: If someone wants to get real fancy they
244 * could they could, at least on Macs, define reference
245 * whites and blacks and use a calibrated RGB system. As a
246 * rule we seem to address this by tuning our palettes in
247 * VICE itself, though. */
248 colorspace = CGColorSpaceCreateDeviceRGB();
249 ctx->bitmap_context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorspace, kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little);
250 CGColorSpaceRelease(colorspace);
251
252 /* Configure the matrix to fit it in the widget as it exists */
253 resize_canvas_container_quartz_cb (canvas->drawing_area, NULL, canvas);
254
255 /* Fix the widget's size request */
256 gtk_widget_set_size_request(canvas->drawing_area, width * aspect, height);
257 }
258 }
259 }
260
vice_quartz_refresh_rect(video_canvas_t * canvas,unsigned int xs,unsigned int ys,unsigned int xi,unsigned int yi,unsigned int w,unsigned int h)261 static void vice_quartz_refresh_rect(video_canvas_t *canvas,
262 unsigned int xs, unsigned int ys,
263 unsigned int xi, unsigned int yi,
264 unsigned int w, unsigned int h)
265 {
266 context_t *ctx = canvas ? (context_t *)canvas->renderer_context : NULL;
267 unsigned char *backbuffer;
268 if (!ctx || !ctx->bitmap_context) {
269 return;
270 }
271
272 if (((xi + w) > CGBitmapContextGetWidth(ctx->bitmap_context)) ||
273 ((yi+h) > CGBitmapContextGetHeight(ctx->bitmap_context))) {
274 /* Trying to draw outside canvas? */
275 fprintf(stderr, "Attempt to draw outside canvas!\nXI%u YI%u W%u H%u CW%lu CH%lu\n", xi, yi, w, h, CGBitmapContextGetWidth(ctx->bitmap_context), CGBitmapContextGetHeight(ctx->bitmap_context));
276 return;
277 }
278
279 backbuffer = CGBitmapContextGetData(ctx->bitmap_context);
280 if (!backbuffer) {
281 return;
282 }
283 video_canvas_render(canvas, backbuffer, w, h, xs, ys, xi, yi, CGBitmapContextGetBytesPerRow(ctx->bitmap_context), 32);
284 gtk_widget_queue_draw(canvas->drawing_area);
285 }
286
vice_quartz_set_palette(video_canvas_t * canvas)287 static void vice_quartz_set_palette(video_canvas_t *canvas)
288 {
289 int i;
290 struct palette_s *palette = canvas ? canvas->palette : NULL;
291 if (!palette) {
292 return;
293 }
294 /* If we get this far we know canvas is also non-NULL */
295
296 /* We use use the Device RGB color space with "no alpha, skip first", which leaves the most significant bits unused and should have R, G, B in decreasing significance in machine byte order. */
297 for (i = 0; i < palette->num_entries; i++) {
298 palette_entry_t color = palette->entries[i];
299 uint32_t rgb_color = (color.red << 16) | (color.green << 8) | color.blue;
300 video_render_setphysicalcolor(canvas->videoconfig, i, rgb_color, 32);
301 }
302
303 for (i = 0; i < 256; i++) {
304 video_render_setrawrgb(i, i << 16, i << 8, i);
305 }
306 video_render_initraw(canvas->videoconfig);
307 }
308
309 vice_renderer_backend_t vice_quartz_backend = {
310 vice_quartz_create_widget,
311 vice_quartz_update_context,
312 vice_quartz_destroy_context,
313 vice_quartz_refresh_rect,
314 vice_quartz_set_palette
315 };
316
317 #endif
318