1 /**
2 * \file directx_renderer.c
3 * \brief DirectX-based renderer for the GTK3 backend.
4 *
5 * \author David Hogan <david.q.hogan@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 "vice.h"
29
30 #ifdef WIN32_COMPILE
31
32 #include "directx_renderer.h"
33 #include "directx_renderer_impl.h"
34
35 #include <assert.h>
36 #include <gtk/gtk.h>
37 #include <gdk/gdkwin32.h>
38 #include <math.h>
39 #include <windows.h>
40 #include <strsafe.h>
41
42 #include "lib.h"
43 #include "log.h"
44 #include "palette.h"
45 #include "render_queue.h"
46 #include "resources.h"
47 #include "ui.h"
48
49 #define CANVAS_LOCK() pthread_mutex_lock(&canvas->lock)
50 #define CANVAS_UNLOCK() pthread_mutex_unlock(&canvas->lock)
51 #define RENDER_LOCK() pthread_mutex_lock(&context->render_lock)
52 #define RENDER_UNLOCK() pthread_mutex_unlock(&context->render_lock)
53
54 typedef vice_directx_renderer_context_t context_t;
55
56 #define VICE_DIRECTX_WINDOW_CLASS "VICE_DIRECTX_WINDOW_CLASS"
57
58 static WNDCLASS window_class;
59
60 static void on_widget_realized(GtkWidget *widget, gpointer data);
61 static void on_widget_unrealized(GtkWidget *widget, gpointer data);
62 static void on_widget_resized(GtkWidget *widget, GdkRectangle *allocation, gpointer data);
63
WindowProcedure(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)64 static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
65 {
66 switch (message) {
67 case WM_NCHITTEST:
68 /* We don't want mouse events - send them to the gdk parent window */
69 return HTTRANSPARENT;
70
71 case WM_PAINT:
72 /* We need to repaint the current bitmap, which we do in the render thread */
73 {
74 video_canvas_t *canvas = (video_canvas_t *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
75 context_t *context = canvas->renderer_context;
76
77 /* Prevent this event from being reposted continually */
78 ValidateRect(hwnd, NULL);
79
80 CANVAS_LOCK();
81 /* If there is no pending render job, schedule one */
82 if (!render_queue_length(context->render_queue)) {
83 render_thread_push_job(context->render_thread, render_thread_render);
84 }
85 CANVAS_UNLOCK();
86 }
87 return 0;
88
89 case WM_DISPLAYCHANGE:
90 InvalidateRect(hwnd, NULL, FALSE);
91 return 0;
92 }
93
94 return DefWindowProc(hwnd, message, wParam, lParam);
95 }
96
vice_directx_initialise_canvas(video_canvas_t * canvas)97 static void vice_directx_initialise_canvas(video_canvas_t *canvas)
98 {
99 context_t *context;
100
101 /* First create the context_t that we'll need everywhere */
102 context = lib_calloc(1, sizeof(context_t));
103
104 context->canvas_lock = canvas->lock;
105 pthread_mutex_init(&context->render_lock, NULL);
106 canvas->renderer_context = context;
107
108 g_signal_connect (canvas->event_box, "realize", G_CALLBACK (on_widget_realized), canvas);
109 g_signal_connect (canvas->event_box, "unrealize", G_CALLBACK (on_widget_unrealized), canvas);
110 g_signal_connect_unlocked(canvas->event_box, "size-allocate", G_CALLBACK(on_widget_resized), canvas);
111 }
112
vice_directx_destroy_context(video_canvas_t * canvas)113 static void vice_directx_destroy_context(video_canvas_t *canvas)
114 {
115 context_t *context;
116
117 CANVAS_LOCK();
118
119 context = canvas->renderer_context;
120
121 if (context) {
122 pthread_mutex_destroy(&context->render_lock);
123 lib_free(context);
124 canvas->renderer_context = NULL;
125 }
126
127 CANVAS_UNLOCK();
128 }
129
on_widget_realized(GtkWidget * widget,gpointer data)130 static void on_widget_realized(GtkWidget *widget, gpointer data)
131 {
132 video_canvas_t *canvas = data;
133 context_t *context = canvas->renderer_context;
134
135 /* Register the native window class if that hasn't happened yet */
136 if (!window_class.lpszClassName) {
137 window_class.lpszClassName = VICE_DIRECTX_WINDOW_CLASS;
138 window_class.hInstance = GetModuleHandle(NULL);
139 window_class.lpfnWndProc = WindowProcedure;
140 window_class.style = CS_HREDRAW | CS_VREDRAW;
141 window_class.cbWndExtra = sizeof(context_t *);
142
143 if (!RegisterClass(&window_class)) {
144 vice_directx_impl_log_windows_error("RegisterClass");
145 return;
146 }
147 }
148
149 /* Create a native child window for DirectX to render onto */
150 if (!context->window) {
151 context->window =
152 CreateWindowEx(
153 0,
154 VICE_DIRECTX_WINDOW_CLASS,
155 NULL,
156 WS_CHILD,
157 0, 0, 1, 1, /* we resize it when the underlying event_box gets resized */
158 gdk_win32_window_get_handle(gtk_widget_get_window(gtk_widget_get_toplevel(widget))),
159 NULL,
160 GetModuleHandle(NULL),
161 NULL);
162
163 if (!context->window) {
164 vice_directx_impl_log_windows_error("CreateWindowEx");
165 return;
166 }
167
168 // Make the context available to the windowproc
169 SetWindowLongPtr(context->window, GWLP_USERDATA, (LONG_PTR)canvas);
170
171 ShowWindow(context->window, SW_SHOW);
172 }
173
174 context->render_queue = render_queue_create();
175 context->render_bg_colour.a = 1.0f;
176
177 /* Create an exclusive single thread 'pool' for executing render jobs */
178 context->render_thread = render_thread_create(vice_directx_impl_async_render, canvas);
179 }
180
on_widget_unrealized(GtkWidget * widget,gpointer data)181 static void on_widget_unrealized(GtkWidget *widget, gpointer data)
182 {
183 video_canvas_t *canvas = data;
184 context_t *context = canvas->renderer_context;
185
186 CANVAS_LOCK();
187
188 vice_directx_destroy_context_impl(context);
189
190 if (context->window) {
191 DestroyWindow(context->window);
192 context->window = NULL;
193 }
194
195 render_queue_destroy(context->render_queue);
196 context->render_queue = NULL;
197
198 CANVAS_UNLOCK();
199 }
200
201 /** The underlying GtkDrawingArea has changed size (possibly before being realised) */
on_widget_resized(GtkWidget * widget,GdkRectangle * allocation,gpointer data)202 static void on_widget_resized(GtkWidget *widget, GdkRectangle *allocation, gpointer data)
203 {
204 video_canvas_t *canvas = data;
205 context_t *context;
206 gint viewport_x, viewport_y;
207 gint gtk_scale = gtk_widget_get_scale_factor(widget);
208
209 CANVAS_LOCK();
210
211 context = canvas->renderer_context;
212 if (!context) {
213 CANVAS_UNLOCK();
214 return;
215 }
216
217 gtk_widget_translate_coordinates(widget, gtk_widget_get_toplevel(widget), 0, 0, &viewport_x, &viewport_y);
218
219 context->viewport_x = viewport_x * gtk_scale;
220 context->viewport_y = viewport_y * gtk_scale;
221 context->viewport_width = allocation->width * gtk_scale;
222 context->viewport_height = allocation->height * gtk_scale;
223
224 /* Set the background colour */
225 if (ui_is_fullscreen()) {
226 context->render_bg_colour.r = 0.0f;
227 context->render_bg_colour.g = 0.0f;
228 context->render_bg_colour.b = 0.0f;
229 } else {
230 context->render_bg_colour.r = 0.5f;
231 context->render_bg_colour.g = 0.5f;
232 context->render_bg_colour.b = 0.5f;
233 }
234
235 /* Update the size of the native child window to match the gtk drawing area */
236 if (context->window) {
237 MoveWindow(context->window, context->viewport_x, context->viewport_y, context->viewport_width, context->viewport_height, TRUE);
238 if (!render_queue_length(context->render_queue)) {
239 render_thread_push_job(context->render_thread, render_thread_render);
240 }
241 }
242
243 CANVAS_UNLOCK();
244 }
245
246 /******/
247
248 /** \brief The emulated screen size or aspect ratio has changed */
vice_directx_update_context(video_canvas_t * canvas,unsigned int width,unsigned int height)249 static void vice_directx_update_context(video_canvas_t *canvas, unsigned int width, unsigned int height)
250 {
251 context_t *context;
252
253 CANVAS_LOCK();
254
255 context = canvas->renderer_context;
256
257 context->emulated_width_next = width;
258 context->emulated_height_next = height;
259 context->pixel_aspect_ratio_next = canvas->geometry->pixel_aspect_ratio;
260
261 CANVAS_UNLOCK();
262 }
263
264 /** \brief It's time to draw a complete emulated frame */
vice_directx_refresh_rect(video_canvas_t * canvas,unsigned int xs,unsigned int ys,unsigned int xi,unsigned int yi,unsigned int w,unsigned int h)265 static void vice_directx_refresh_rect(video_canvas_t *canvas,
266 unsigned int xs, unsigned int ys,
267 unsigned int xi, unsigned int yi,
268 unsigned int w, unsigned int h)
269 {
270 context_t *context;
271 backbuffer_t *backbuffer;
272 int pixel_data_size_bytes;
273
274 CANVAS_LOCK();
275
276 context = canvas->renderer_context;
277 if (!context || !context->render_queue) {
278 CANVAS_UNLOCK();
279 return;
280 }
281
282 /* Obtain an unused backbuffer to render to */
283 pixel_data_size_bytes = context->emulated_width_next * context->emulated_height_next * 4;
284 backbuffer = render_queue_get_from_pool(context->render_queue, pixel_data_size_bytes);
285
286 if (!backbuffer) {
287 CANVAS_UNLOCK();
288 return;
289 }
290
291 backbuffer->width = context->emulated_width_next;
292 backbuffer->height = context->emulated_height_next;
293 backbuffer->pixel_aspect_ratio = context->pixel_aspect_ratio_next;
294
295 CANVAS_UNLOCK();
296
297 video_canvas_render(canvas, backbuffer->pixel_data, w, h, xs, ys, xi, yi, backbuffer->width * 4, 32);
298
299 CANVAS_LOCK();
300 render_queue_enqueue_for_display(context->render_queue, backbuffer);
301 render_thread_push_job(context->render_thread, render_thread_render);
302 CANVAS_UNLOCK();
303 }
304
vice_directx_on_ui_frame_clock(GdkFrameClock * clock,video_canvas_t * canvas)305 static void vice_directx_on_ui_frame_clock(GdkFrameClock *clock, video_canvas_t *canvas)
306 {
307 context_t *context = canvas->renderer_context;
308
309 ui_update_statusbars();
310
311 CANVAS_LOCK();
312
313 /* TODO we really shouldn't be setting this every frame! */
314 gtk_widget_set_size_request(canvas->event_box, context->window_min_width, context->window_min_height);
315
316 CANVAS_UNLOCK();
317 }
318
vice_directx_set_palette(video_canvas_t * canvas)319 static void vice_directx_set_palette(video_canvas_t *canvas)
320 {
321 int i;
322 struct palette_s *palette = canvas ? canvas->palette : NULL;
323 if (!palette) {
324 return;
325 }
326
327 for (i = 0; i < palette->num_entries; i++) {
328 palette_entry_t color = palette->entries[i];
329 uint32_t color_code = color.red | (color.green << 8) | (color.blue << 16) | (0xffU << 24);
330 video_render_setphysicalcolor(canvas->videoconfig, i, color_code, 32);
331 }
332
333 for (i = 0; i < 256; i++) {
334 video_render_setrawrgb(i, i, i << 8, i << 16);
335 }
336 video_render_setrawalpha(0xffU << 24);
337 video_render_initraw(canvas->videoconfig);
338 }
339
340 vice_renderer_backend_t vice_directx_backend = {
341 vice_directx_initialise_canvas,
342 vice_directx_update_context,
343 vice_directx_destroy_context,
344 vice_directx_refresh_rect,
345 vice_directx_on_ui_frame_clock,
346 vice_directx_set_palette
347 };
348
349 #endif
350