aboutsummaryrefslogtreecommitdiffstats
path: root/src/gallium/state_trackers/wgl/stw_framebuffer.c
blob: 06b5c8da3c24d008a9a9964bafb3dea67d2554b8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
/**************************************************************************
 *
 * Copyright 2008-2009 Vmware, Inc.
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sub license, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 **************************************************************************/

#include <windows.h>

#include "pipe/p_screen.h"
#include "util/u_memory.h"
#include "hud/hud_context.h"
#include "os/os_time.h"
#include "state_tracker/st_api.h"

#include "stw_icd.h"
#include "stw_framebuffer.h"
#include "stw_device.h"
#include "stw_winsys.h"
#include "stw_tls.h"
#include "stw_context.h"
#include "stw_st.h"


/**
 * Search the framebuffer with the matching HWND while holding the
 * stw_dev::fb_mutex global lock.
 * If a stw_framebuffer is found, lock it and return the pointer.
 * Else, return NULL.
 */
static struct stw_framebuffer *
stw_framebuffer_from_hwnd_locked(HWND hwnd)
{
   struct stw_framebuffer *fb;

   for (fb = stw_dev->fb_head; fb != NULL; fb = fb->next)
      if (fb->hWnd == hwnd) {
         stw_framebuffer_lock(fb);
         assert(fb->mutex.RecursionCount == 1);
         return fb;
      }

   return NULL;
}


/**
 * Decrement the reference count on the given stw_framebuffer object.
 * If the reference count hits zero, destroy the object.
 *
 * Note: Both stw_dev::fb_mutex and stw_framebuffer::mutex must already be
 * locked.  After this function completes, the fb's mutex will be unlocked.
 */
void
stw_framebuffer_release_locked(struct stw_framebuffer *fb)
{
   struct stw_framebuffer **link;

   assert(fb);
   assert(stw_own_mutex(&fb->mutex));
   assert(stw_own_mutex(&stw_dev->fb_mutex));

   /* check the reference count */
   fb->refcnt--;
   if (fb->refcnt) {
      stw_framebuffer_unlock(fb);
      return;
   }

   /* remove this stw_framebuffer from the device's linked list */
   link = &stw_dev->fb_head;
   while (*link != fb)
      link = &(*link)->next;
   assert(*link);
   *link = fb->next;
   fb->next = NULL;

   if (fb->shared_surface)
      stw_dev->stw_winsys->shared_surface_close(stw_dev->screen,
                                                fb->shared_surface);

   stw_st_destroy_framebuffer_locked(fb->stfb);

   stw_framebuffer_unlock(fb);

   DeleteCriticalSection(&fb->mutex);

   FREE( fb );
}


/**
 * Query the size of the given framebuffer's on-screen window and update
 * the stw_framebuffer's width/height.
 */
static void
stw_framebuffer_get_size(struct stw_framebuffer *fb)
{
   LONG width, height;
   RECT client_rect;
   RECT window_rect;
   POINT client_pos;

   /*
    * Sanity checking.
    */
   assert(fb->hWnd);
   assert(fb->width && fb->height);
   assert(fb->client_rect.right  == fb->client_rect.left + fb->width);
   assert(fb->client_rect.bottom == fb->client_rect.top  + fb->height);

   /*
    * Get the client area size.
    */
   if (!GetClientRect(fb->hWnd, &client_rect)) {
      return;
   }

   assert(client_rect.left == 0);
   assert(client_rect.top == 0);
   width  = client_rect.right  - client_rect.left;
   height = client_rect.bottom - client_rect.top;

   fb->minimized = width == 0 || height == 0;

   if (width <= 0 || height <= 0) {
      /*
       * When the window is minimized GetClientRect will return zeros.  Simply
       * preserve the current window size, until the window is restored or
       * maximized again.
       */
      return;
   }

   if (width != fb->width || height != fb->height) {
      fb->must_resize = TRUE;
      fb->width = width;
      fb->height = height;
   }

   client_pos.x = 0;
   client_pos.y = 0;
   if (ClientToScreen(fb->hWnd, &client_pos) &&
       GetWindowRect(fb->hWnd, &window_rect)) {
      fb->client_rect.left = client_pos.x - window_rect.left;
      fb->client_rect.top  = client_pos.y - window_rect.top;
   }

   fb->client_rect.right  = fb->client_rect.left + fb->width;
   fb->client_rect.bottom = fb->client_rect.top  + fb->height;

#if 0
   debug_printf("\n");
   debug_printf("%s: hwnd = %p\n", __FUNCTION__, fb->hWnd);
   debug_printf("%s: client_position = (%li, %li)\n",
                __FUNCTION__, client_pos.x, client_pos.y);
   debug_printf("%s: window_rect = (%li, %li) - (%li, %li)\n",
                __FUNCTION__,
                window_rect.left, window_rect.top,
                window_rect.right, window_rect.bottom);
   debug_printf("%s: client_rect = (%li, %li) - (%li, %li)\n",
                __FUNCTION__,
                fb->client_rect.left, fb->client_rect.top,
                fb->client_rect.right, fb->client_rect.bottom);
#endif
}


/**
 * @sa http://msdn.microsoft.com/en-us/library/ms644975(VS.85).aspx
 * @sa http://msdn.microsoft.com/en-us/library/ms644960(VS.85).aspx
 */
LRESULT CALLBACK
stw_call_window_proc(int nCode, WPARAM wParam, LPARAM lParam)
{
   struct stw_tls_data *tls_data;
   PCWPSTRUCT pParams = (PCWPSTRUCT)lParam;
   struct stw_framebuffer *fb;

   tls_data = stw_tls_get_data();
   if (!tls_data)
      return 0;

   if (nCode < 0 || !stw_dev)
       return CallNextHookEx(tls_data->hCallWndProcHook, nCode, wParam, lParam);

   /* We check that the stw_dev object is initialized before we try to do
    * anything with it.  Otherwise, in multi-threaded programs there's a
    * chance of executing this code before the stw_dev object is fully
    * initialized.
    */
   if (stw_dev && stw_dev->initialized) {
      if (pParams->message == WM_WINDOWPOSCHANGED) {
         /* We handle WM_WINDOWPOSCHANGED instead of WM_SIZE because according
          * to http://blogs.msdn.com/oldnewthing/archive/2008/01/15/7113860.aspx
          * WM_SIZE is generated from WM_WINDOWPOSCHANGED by DefWindowProc so it
          * can be masked out by the application.
          */
         LPWINDOWPOS lpWindowPos = (LPWINDOWPOS)pParams->lParam;
         if ((lpWindowPos->flags & SWP_SHOWWINDOW) ||
             !(lpWindowPos->flags & SWP_NOMOVE) ||
             !(lpWindowPos->flags & SWP_NOSIZE)) {
            fb = stw_framebuffer_from_hwnd( pParams->hwnd );
            if (fb) {
               /* Size in WINDOWPOS includes the window frame, so get the size
                * of the client area via GetClientRect.
                */
               stw_framebuffer_get_size(fb);
               stw_framebuffer_unlock(fb);
            }
         }
      }
      else if (pParams->message == WM_DESTROY) {
         stw_lock_framebuffers(stw_dev);
         fb = stw_framebuffer_from_hwnd_locked( pParams->hwnd );
         if (fb)
            stw_framebuffer_release_locked(fb);
         stw_unlock_framebuffers(stw_dev);
      }
   }

   return CallNextHookEx(tls_data->hCallWndProcHook, nCode, wParam, lParam);
}


/**
 * Create a new stw_framebuffer object which corresponds to the given
 * HDC/window.  If successful, we return the new stw_framebuffer object
 * with its mutex locked.
 */
struct stw_framebuffer *
stw_framebuffer_create(HDC hdc, int iPixelFormat)
{
   HWND hWnd;
   struct stw_framebuffer *fb;
   const struct stw_pixelformat_info *pfi;

   /* We only support drawing to a window. */
   hWnd = WindowFromDC( hdc );
   if (!hWnd)
      return NULL;

   fb = CALLOC_STRUCT( stw_framebuffer );
   if (fb == NULL)
      return NULL;

   fb->hWnd = hWnd;
   fb->iPixelFormat = iPixelFormat;

   /*
    * We often need a displayable pixel format to make GDI happy. Set it
    * here (always 1, i.e., out first pixel format) where appropriate.
    */
   fb->iDisplayablePixelFormat = iPixelFormat <= stw_dev->pixelformat_count
      ? iPixelFormat : 1;

   fb->pfi = pfi = stw_pixelformat_get_info( iPixelFormat );
   fb->stfb = stw_st_create_framebuffer( fb );
   if (!fb->stfb) {
      FREE( fb );
      return NULL;
   }

   fb->refcnt = 1;

   /*
    * Windows can be sometimes have zero width and or height, but we ensure
    * a non-zero framebuffer size at all times.
    */

   fb->must_resize = TRUE;
   fb->width  = 1;
   fb->height = 1;
   fb->client_rect.left   = 0;
   fb->client_rect.top    = 0;
   fb->client_rect.right  = fb->client_rect.left + fb->width;
   fb->client_rect.bottom = fb->client_rect.top  + fb->height;

   stw_framebuffer_get_size(fb);

   InitializeCriticalSection(&fb->mutex);

   /* This is the only case where we lock the stw_framebuffer::mutex before
    * stw_dev::fb_mutex, since no other thread can know about this framebuffer
    * and we must prevent any other thread from destroying it before we return.
    */
   stw_framebuffer_lock(fb);

   stw_lock_framebuffers(stw_dev);
   fb->next = stw_dev->fb_head;
   stw_dev->fb_head = fb;
   stw_unlock_framebuffers(stw_dev);

   return fb;
}


/**
 * Update the framebuffer's size if necessary.
 */
void
stw_framebuffer_update(struct stw_framebuffer *fb)
{
   assert(fb->stfb);
   assert(fb->height);
   assert(fb->width);

   /* XXX: It would be nice to avoid checking the size again -- in theory
    * stw_call_window_proc would have cought the resize and stored the right
    * size already, but unfortunately threads created before the DllMain is
    * called don't get a DLL_THREAD_ATTACH notification, and there is no way
    * to know of their existing without using the not very portable PSAPI.
    */
   stw_framebuffer_get_size(fb);
}


/**
 * Try to free all stw_framebuffer objects associated with the device.
 */
void
stw_framebuffer_cleanup(void)
{
   struct stw_framebuffer *fb;
   struct stw_framebuffer *next;

   if (!stw_dev)
      return;

   stw_lock_framebuffers(stw_dev);

   fb = stw_dev->fb_head;
   while (fb) {
      next = fb->next;

      stw_framebuffer_lock(fb);
      stw_framebuffer_release_locked(fb);

      fb = next;
   }
   stw_dev->fb_head = NULL;

   stw_unlock_framebuffers(stw_dev);
}


/**
 * Given an hdc, return the corresponding stw_framebuffer.
 * The returned stw_framebuffer will have its mutex locked.
 */
static struct stw_framebuffer *
stw_framebuffer_from_hdc_locked(HDC hdc)
{
   HWND hwnd;

   hwnd = WindowFromDC(hdc);
   if (!hwnd) {
      return NULL;
   }

   return stw_framebuffer_from_hwnd_locked(hwnd);
}


/**
 * Given an HDC, return the corresponding stw_framebuffer.
 * The returned stw_framebuffer will have its mutex locked.
 */
struct stw_framebuffer *
stw_framebuffer_from_hdc(HDC hdc)
{
   struct stw_framebuffer *fb;

   if (!stw_dev)
      return NULL;

   stw_lock_framebuffers(stw_dev);
   fb = stw_framebuffer_from_hdc_locked(hdc);
   stw_unlock_framebuffers(stw_dev);

   return fb;
}


/**
 * Given an HWND, return the corresponding stw_framebuffer.
 * The returned stw_framebuffer will have its mutex locked.
 */
struct stw_framebuffer *
stw_framebuffer_from_hwnd(HWND hwnd)
{
   struct stw_framebuffer *fb;

   stw_lock_framebuffers(stw_dev);
   fb = stw_framebuffer_from_hwnd_locked(hwnd);
   stw_unlock_framebuffers(stw_dev);

   return fb;
}


BOOL APIENTRY
DrvSetPixelFormat(HDC hdc, LONG iPixelFormat)
{
   uint count;
   uint index;
   struct stw_framebuffer *fb;

   if (!stw_dev)
      return FALSE;

   index = (uint) iPixelFormat - 1;
   count = stw_pixelformat_get_count();
   if (index >= count)
      return FALSE;

   fb = stw_framebuffer_from_hdc_locked(hdc);
   if (fb) {
      /*
       * SetPixelFormat must be called only once.  However ignore
       * pbuffers, for which the framebuffer object is created first.
       */
      boolean bPbuffer = fb->bPbuffer;

      stw_framebuffer_unlock( fb );

      return bPbuffer;
   }

   fb = stw_framebuffer_create(hdc, iPixelFormat);
   if (!fb) {
      return FALSE;
   }

   stw_framebuffer_unlock( fb );

   /* Some applications mistakenly use the undocumented wglSetPixelFormat
    * function instead of SetPixelFormat, so we call SetPixelFormat here to
    * avoid opengl32.dll's wglCreateContext to fail */
   if (GetPixelFormat(hdc) == 0) {
      BOOL bRet = SetPixelFormat(hdc, iPixelFormat, NULL);
      if (!bRet) {
	  debug_printf("SetPixelFormat failed\n");
      }
   }

   return TRUE;
}


int
stw_pixelformat_get(HDC hdc)
{
   int iPixelFormat = 0;
   struct stw_framebuffer *fb;

   fb = stw_framebuffer_from_hdc(hdc);
   if (fb) {
      iPixelFormat = fb->iPixelFormat;
      stw_framebuffer_unlock(fb);
   }

   return iPixelFormat;
}


BOOL APIENTRY
DrvPresentBuffers(HDC hdc, PGLPRESENTBUFFERSDATA data)
{
   struct stw_framebuffer *fb;
   struct pipe_screen *screen;
   struct pipe_resource *res;

   if (!stw_dev)
      return FALSE;

   fb = stw_framebuffer_from_hdc( hdc );
   if (fb == NULL)
      return FALSE;

   screen = stw_dev->screen;

   res = (struct pipe_resource *)data->pPrivateData;

   if (data->hSharedSurface != fb->hSharedSurface) {
      if (fb->shared_surface) {
         stw_dev->stw_winsys->shared_surface_close(screen, fb->shared_surface);
         fb->shared_surface = NULL;
      }

      fb->hSharedSurface = data->hSharedSurface;

      if (data->hSharedSurface &&
         stw_dev->stw_winsys->shared_surface_open) {
         fb->shared_surface =
            stw_dev->stw_winsys->shared_surface_open(screen,
                                                     fb->hSharedSurface);
      }
   }

   if (!fb->minimized) {
      if (fb->shared_surface) {
         stw_dev->stw_winsys->compose(screen,
                                      res,
                                      fb->shared_surface,
                                      &fb->client_rect,
                                      data->PresentHistoryToken);
      }
      else {
         stw_dev->stw_winsys->present( screen, res, hdc );
      }
   }

   stw_framebuffer_update(fb);
   stw_notify_current_locked(fb);

   stw_framebuffer_unlock(fb);

   return TRUE;
}


/**
 * Queue a composition.
 *
 * The stw_framebuffer object must have its mutex locked.  The mutex will
 * be unlocked here before returning.
 */
BOOL
stw_framebuffer_present_locked(HDC hdc,
                               struct stw_framebuffer *fb,
                               struct pipe_resource *res)
{
   if (stw_dev->callbacks.wglCbPresentBuffers &&
      stw_dev->stw_winsys->compose) {
      GLCBPRESENTBUFFERSDATA data;

      memset(&data, 0, sizeof data);
      data.magic1 = 2;
      data.magic2 = 0;
      data.AdapterLuid = stw_dev->AdapterLuid;
      data.rect = fb->client_rect;
      data.pPrivateData = (void *)res;

      stw_notify_current_locked(fb);
      stw_framebuffer_unlock(fb);

      return stw_dev->callbacks.wglCbPresentBuffers(hdc, &data);
   }
   else {
      struct pipe_screen *screen = stw_dev->screen;

      stw_dev->stw_winsys->present( screen, res, hdc );

      stw_framebuffer_update(fb);
      stw_notify_current_locked(fb);
      stw_framebuffer_unlock(fb);

      return TRUE;
   }
}


/**
 * This is called just before issuing the buffer swap/present.
 * We query the current time and determine if we should sleep before
 * issuing the swap/present.
 * This is a bit of a hack and is certainly not very accurate but it
 * basically works.
 * This is for the WGL_ARB_swap_interval extension.
 */
static void
wait_swap_interval(struct stw_framebuffer *fb)
{
   /* Note: all time variables here are in units of microseconds */
   int64_t cur_time = os_time_get_nano() / 1000;

   if (fb->prev_swap_time != 0) {
      /* Compute time since previous swap */
      int64_t delta = cur_time - fb->prev_swap_time;
      int64_t min_swap_period =
         1.0e6 / stw_dev->refresh_rate * stw_dev->swap_interval;

      /* If time since last swap is less than wait period, wait.
       * Note that it's possible for the delta to be negative because of
       * rollover.  See https://bugs.freedesktop.org/show_bug.cgi?id=102241
       */
      if ((delta >= 0) && (delta < min_swap_period)) {
         float fudge = 1.75f;  /* emperical fudge factor */
         int64_t wait = (min_swap_period - delta) * fudge;
         os_time_sleep(wait);
      }
   }

   fb->prev_swap_time = cur_time;
}


BOOL APIENTRY
DrvSwapBuffers(HDC hdc)
{
   struct stw_context *ctx;
   struct stw_framebuffer *fb;

   if (!stw_dev)
      return FALSE;

   fb = stw_framebuffer_from_hdc( hdc );
   if (fb == NULL)
      return FALSE;

   if (!(fb->pfi->pfd.dwFlags & PFD_DOUBLEBUFFER)) {
      stw_framebuffer_unlock(fb);
      return TRUE;
   }

   ctx = stw_current_context();
   if (ctx) {
      if (ctx->hud) {
         /* Display the HUD */
         struct pipe_resource *back =
            stw_get_framebuffer_resource(fb->stfb, ST_ATTACHMENT_BACK_LEFT);
         if (back) {
            hud_draw(ctx->hud, back);
         }
      }

      if (ctx->current_framebuffer == fb) {
         /* flush current context */
         ctx->st->flush(ctx->st, ST_FLUSH_END_OF_FRAME, NULL);
      }
   }

   if (stw_dev->swap_interval != 0) {
      wait_swap_interval(fb);
   }

   return stw_st_swap_framebuffer_locked(hdc, fb->stfb);
}


BOOL APIENTRY
DrvSwapLayerBuffers(HDC hdc, UINT fuPlanes)
{
   if (fuPlanes & WGL_SWAP_MAIN_PLANE)
      return DrvSwapBuffers(hdc);

   return FALSE;
}