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