47f860e76677bba83ab6a68db392000aee5d2d9a
[blender.git] / source / blender / windowmanager / intern / wm_stereo.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2015 by Blender Foundation
17  * All rights reserved.
18  */
19
20 /** \file
21  * \ingroup wm
22  */
23
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "DNA_listBase.h"
28
29 #include "RNA_access.h"
30
31 #include "MEM_guardedalloc.h"
32
33 #include "BLI_utildefines.h"
34
35 #include "BIF_gl.h"
36
37 #include "BKE_context.h"
38 #include "BKE_global.h"
39 #include "BKE_report.h"
40
41 #include "GHOST_C-api.h"
42
43 #include "ED_screen.h"
44
45 #include "GPU_immediate.h"
46 #include "GPU_framebuffer.h"
47 #include "GPU_texture.h"
48
49 #include "WM_api.h"
50 #include "WM_types.h"
51 #include "wm.h"
52 #include "wm_draw.h"
53 #include "wm_window.h"
54
55 #include "UI_interface.h"
56 #include "UI_resources.h"
57
58 static eGPUInterlaceShader interlace_gpu_id_from_type(eStereo3dInterlaceType interlace_type)
59 {
60   switch (interlace_type) {
61     case S3D_INTERLACE_ROW:
62       return GPU_SHADER_INTERLACE_ROW;
63     case S3D_INTERLACE_COLUMN:
64       return GPU_SHADER_INTERLACE_COLUMN;
65     case S3D_INTERLACE_CHECKERBOARD:
66     default:
67       return GPU_SHADER_INTERLACE_CHECKER;
68   }
69 }
70
71 void wm_stereo3d_draw_interlace(wmWindow *win, ARegion *ar)
72 {
73   bool swap = (win->stereo3d_format->flag & S3D_INTERLACE_SWAP) != 0;
74   enum eStereo3dInterlaceType interlace_type = win->stereo3d_format->interlace_type;
75
76   /* wmOrtho for the screen has this same offset */
77   float halfx = GLA_PIXEL_OFS / ar->winx;
78   float halfy = GLA_PIXEL_OFS / ar->winy;
79
80   GPUVertFormat *format = immVertexFormat();
81   uint texcoord = GPU_vertformat_attr_add(format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
82   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
83
84   /* leave GL_TEXTURE0 as the latest active texture */
85   for (int view = 1; view >= 0; view--) {
86     GPUTexture *texture = wm_draw_region_texture(ar, view);
87     glActiveTexture(GL_TEXTURE0 + view);
88     glBindTexture(GL_TEXTURE_2D, GPU_texture_opengl_bindcode(texture));
89   }
90
91   immBindBuiltinProgram(GPU_SHADER_2D_IMAGE_INTERLACE);
92   immUniform1i("image_a", (swap) ? 1 : 0);
93   immUniform1i("image_b", (swap) ? 0 : 1);
94
95   immUniform1i("interlace_id", interlace_gpu_id_from_type(interlace_type));
96
97   immBegin(GPU_PRIM_TRI_FAN, 4);
98
99   immAttr2f(texcoord, halfx, halfy);
100   immVertex2f(pos, ar->winrct.xmin, ar->winrct.ymin);
101
102   immAttr2f(texcoord, 1.0f + halfx, halfy);
103   immVertex2f(pos, ar->winrct.xmax + 1, ar->winrct.ymin);
104
105   immAttr2f(texcoord, 1.0f + halfx, 1.0f + halfy);
106   immVertex2f(pos, ar->winrct.xmax + 1, ar->winrct.ymax + 1);
107
108   immAttr2f(texcoord, halfx, 1.0f + halfy);
109   immVertex2f(pos, ar->winrct.xmin, ar->winrct.ymax + 1);
110
111   immEnd();
112   immUnbindProgram();
113
114   for (int view = 1; view >= 0; view--) {
115     glActiveTexture(GL_TEXTURE0 + view);
116     glBindTexture(GL_TEXTURE_2D, 0);
117   }
118 }
119
120 void wm_stereo3d_draw_anaglyph(wmWindow *win, ARegion *ar)
121 {
122   for (int view = 0; view < 2; view++) {
123     int bit = view + 1;
124
125     switch (win->stereo3d_format->anaglyph_type) {
126       case S3D_ANAGLYPH_REDCYAN:
127         glColorMask((1 & bit) ? GL_TRUE : GL_FALSE,
128                     (2 & bit) ? GL_TRUE : GL_FALSE,
129                     (2 & bit) ? GL_TRUE : GL_FALSE,
130                     GL_FALSE);
131         break;
132       case S3D_ANAGLYPH_GREENMAGENTA:
133         glColorMask((2 & bit) ? GL_TRUE : GL_FALSE,
134                     (1 & bit) ? GL_TRUE : GL_FALSE,
135                     (2 & bit) ? GL_TRUE : GL_FALSE,
136                     GL_FALSE);
137         break;
138       case S3D_ANAGLYPH_YELLOWBLUE:
139         glColorMask((1 & bit) ? GL_TRUE : GL_FALSE,
140                     (1 & bit) ? GL_TRUE : GL_FALSE,
141                     (2 & bit) ? GL_TRUE : GL_FALSE,
142                     GL_FALSE);
143         break;
144     }
145
146     wm_draw_region_blend(ar, view, false);
147   }
148
149   glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
150 }
151
152 void wm_stereo3d_draw_sidebyside(wmWindow *win, int view)
153 {
154   bool cross_eyed = (win->stereo3d_format->flag & S3D_SIDEBYSIDE_CROSSEYED) != 0;
155
156   GPUVertFormat *format = immVertexFormat();
157   uint texcoord = GPU_vertformat_attr_add(format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
158   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
159
160   immBindBuiltinProgram(GPU_SHADER_2D_IMAGE);
161
162   int soffx = WM_window_pixels_x(win) * 0.5f;
163   if (view == STEREO_LEFT_ID) {
164     if (!cross_eyed) {
165       soffx = 0;
166     }
167   }
168   else {  //RIGHT_LEFT_ID
169     if (cross_eyed) {
170       soffx = 0;
171     }
172   }
173
174   const int sizex = WM_window_pixels_x(win);
175   const int sizey = WM_window_pixels_y(win);
176
177   /* wmOrtho for the screen has this same offset */
178   const float halfx = GLA_PIXEL_OFS / sizex;
179   const float halfy = GLA_PIXEL_OFS / sizex;
180
181   immUniform1i("image", 0); /* texture is already bound to GL_TEXTURE0 unit */
182
183   immBegin(GPU_PRIM_TRI_FAN, 4);
184
185   immAttr2f(texcoord, halfx, halfy);
186   immVertex2f(pos, soffx, 0.0f);
187
188   immAttr2f(texcoord, 1.0f + halfx, halfy);
189   immVertex2f(pos, soffx + (sizex * 0.5f), 0.0f);
190
191   immAttr2f(texcoord, 1.0f + halfx, 1.0f + halfy);
192   immVertex2f(pos, soffx + (sizex * 0.5f), sizey);
193
194   immAttr2f(texcoord, halfx, 1.0f + halfy);
195   immVertex2f(pos, soffx, sizey);
196
197   immEnd();
198
199   immUnbindProgram();
200 }
201
202 void wm_stereo3d_draw_topbottom(wmWindow *win, int view)
203 {
204   GPUVertFormat *format = immVertexFormat();
205   uint texcoord = GPU_vertformat_attr_add(format, "texCoord", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
206   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
207
208   immBindBuiltinProgram(GPU_SHADER_2D_IMAGE);
209
210   int soffy;
211   if (view == STEREO_LEFT_ID) {
212     soffy = WM_window_pixels_y(win) * 0.5f;
213   }
214   else { /* STEREO_RIGHT_ID */
215     soffy = 0;
216   }
217
218   const int sizex = WM_window_pixels_x(win);
219   const int sizey = WM_window_pixels_y(win);
220
221   /* wmOrtho for the screen has this same offset */
222   const float halfx = GLA_PIXEL_OFS / sizex;
223   const float halfy = GLA_PIXEL_OFS / sizex;
224
225   immUniform1i("image", 0); /* texture is already bound to GL_TEXTURE0 unit */
226
227   immBegin(GPU_PRIM_TRI_FAN, 4);
228
229   immAttr2f(texcoord, halfx, halfy);
230   immVertex2f(pos, 0.0f, soffy);
231
232   immAttr2f(texcoord, 1.0f + halfx, halfy);
233   immVertex2f(pos, sizex, soffy);
234
235   immAttr2f(texcoord, 1.0f + halfx, 1.0f + halfy);
236   immVertex2f(pos, sizex, soffy + (sizey * 0.5f));
237
238   immAttr2f(texcoord, halfx, 1.0f + halfy);
239   immVertex2f(pos, 0.0f, soffy + (sizey * 0.5f));
240
241   immEnd();
242
243   immUnbindProgram();
244 }
245
246 static bool wm_stereo3d_quadbuffer_supported(void)
247 {
248   GLboolean stereo = GL_FALSE;
249   glGetBooleanv(GL_STEREO, &stereo);
250   return stereo == GL_TRUE;
251 }
252
253 static bool wm_stereo3d_is_fullscreen_required(eStereoDisplayMode stereo_display)
254 {
255   return ELEM(stereo_display, S3D_DISPLAY_SIDEBYSIDE, S3D_DISPLAY_TOPBOTTOM);
256 }
257
258 bool WM_stereo3d_enabled(wmWindow *win, bool skip_stereo3d_check)
259 {
260   const bScreen *screen = WM_window_get_active_screen(win);
261   const Scene *scene = WM_window_get_active_scene(win);
262
263   /* some 3d methods change the window arrangement, thus they shouldn't
264    * toggle on/off just because there is no 3d elements being drawn */
265   if (wm_stereo3d_is_fullscreen_required(win->stereo3d_format->display_mode)) {
266     return GHOST_GetWindowState(win->ghostwin) == GHOST_kWindowStateFullScreen;
267   }
268
269   if ((skip_stereo3d_check == false) && (ED_screen_stereo3d_required(screen, scene) == false)) {
270     return false;
271   }
272
273   /* some 3d methods change the window arrangement, thus they shouldn't
274    * toggle on/off just because there is no 3d elements being drawn */
275   if (wm_stereo3d_is_fullscreen_required(win->stereo3d_format->display_mode)) {
276     return GHOST_GetWindowState(win->ghostwin) == GHOST_kWindowStateFullScreen;
277   }
278
279   return true;
280 }
281
282 /**
283  * If needed, this adjusts \a r_mouse_xy so that drawn cursor and handled mouse position are matching visually.
284  */
285 void wm_stereo3d_mouse_offset_apply(wmWindow *win, int *r_mouse_xy)
286 {
287   if (!WM_stereo3d_enabled(win, false)) {
288     return;
289   }
290
291   if (win->stereo3d_format->display_mode == S3D_DISPLAY_SIDEBYSIDE) {
292     const int half_x = win->sizex / 2;
293     /* right half of the screen */
294     if (r_mouse_xy[0] > half_x) {
295       r_mouse_xy[0] -= half_x;
296     }
297     r_mouse_xy[0] *= 2;
298   }
299   else if (win->stereo3d_format->display_mode == S3D_DISPLAY_TOPBOTTOM) {
300     const int half_y = win->sizey / 2;
301     /* upper half of the screen */
302     if (r_mouse_xy[1] > half_y) {
303       r_mouse_xy[1] -= half_y;
304     }
305     r_mouse_xy[1] *= 2;
306   }
307 }
308
309 /************************** Stereo 3D operator **********************************/
310 typedef struct Stereo3dData {
311   Stereo3dFormat stereo3d_format;
312 } Stereo3dData;
313
314 static bool wm_stereo3d_set_properties(bContext *UNUSED(C), wmOperator *op)
315 {
316   Stereo3dData *s3dd = op->customdata;
317   Stereo3dFormat *s3d = &s3dd->stereo3d_format;
318   PropertyRNA *prop;
319   bool is_set = false;
320
321   prop = RNA_struct_find_property(op->ptr, "display_mode");
322   if (RNA_property_is_set(op->ptr, prop)) {
323     s3d->display_mode = RNA_property_enum_get(op->ptr, prop);
324     is_set = true;
325   }
326
327   prop = RNA_struct_find_property(op->ptr, "anaglyph_type");
328   if (RNA_property_is_set(op->ptr, prop)) {
329     s3d->anaglyph_type = RNA_property_enum_get(op->ptr, prop);
330     is_set = true;
331   }
332
333   prop = RNA_struct_find_property(op->ptr, "interlace_type");
334   if (RNA_property_is_set(op->ptr, prop)) {
335     s3d->interlace_type = RNA_property_enum_get(op->ptr, prop);
336     is_set = true;
337   }
338
339   prop = RNA_struct_find_property(op->ptr, "use_interlace_swap");
340   if (RNA_property_is_set(op->ptr, prop)) {
341     if (RNA_property_boolean_get(op->ptr, prop)) {
342       s3d->flag |= S3D_INTERLACE_SWAP;
343     }
344     else {
345       s3d->flag &= ~S3D_INTERLACE_SWAP;
346     }
347     is_set = true;
348   }
349
350   prop = RNA_struct_find_property(op->ptr, "use_sidebyside_crosseyed");
351   if (RNA_property_is_set(op->ptr, prop)) {
352     if (RNA_property_boolean_get(op->ptr, prop)) {
353       s3d->flag |= S3D_SIDEBYSIDE_CROSSEYED;
354     }
355     else {
356       s3d->flag &= ~S3D_SIDEBYSIDE_CROSSEYED;
357     }
358     is_set = true;
359   }
360
361   return is_set;
362 }
363
364 static void wm_stereo3d_set_init(bContext *C, wmOperator *op)
365 {
366   Stereo3dData *s3dd;
367   wmWindow *win = CTX_wm_window(C);
368
369   op->customdata = s3dd = MEM_callocN(sizeof(Stereo3dData), __func__);
370
371   /* store the original win stereo 3d settings in case of cancel */
372   s3dd->stereo3d_format = *win->stereo3d_format;
373 }
374
375 int wm_stereo3d_set_exec(bContext *C, wmOperator *op)
376 {
377   wmWindowManager *wm = CTX_wm_manager(C);
378   wmWindow *win_src = CTX_wm_window(C);
379   wmWindow *win_dst = NULL;
380   const bool is_fullscreen = WM_window_is_fullscreen(win_src);
381   char prev_display_mode = win_src->stereo3d_format->display_mode;
382   Stereo3dData *s3dd;
383   bool ok = true;
384
385   if (G.background) {
386     return OPERATOR_CANCELLED;
387   }
388
389   if (op->customdata == NULL) {
390     /* no invoke means we need to set the operator properties here */
391     wm_stereo3d_set_init(C, op);
392     wm_stereo3d_set_properties(C, op);
393   }
394
395   s3dd = op->customdata;
396   *win_src->stereo3d_format = s3dd->stereo3d_format;
397
398   if (prev_display_mode == S3D_DISPLAY_PAGEFLIP &&
399       prev_display_mode != win_src->stereo3d_format->display_mode) {
400     /* in case the hardware supports pageflip but not the display */
401     if ((win_dst = wm_window_copy_test(C, win_src, false, false))) {
402       /* pass */
403     }
404     else {
405       BKE_report(
406           op->reports,
407           RPT_ERROR,
408           "Failed to create a window without quad-buffer support, you may experience flickering");
409       ok = false;
410     }
411   }
412   else if (win_src->stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP) {
413     const bScreen *screen = WM_window_get_active_screen(win_src);
414
415     /* ED_workspace_layout_duplicate() can't handle other cases yet T44688 */
416     if (screen->state != SCREENNORMAL) {
417       BKE_report(
418           op->reports, RPT_ERROR, "Failed to switch to Time Sequential mode when in fullscreen");
419       ok = false;
420     }
421     /* pageflip requires a new window to be created with the proper OS flags */
422     else if ((win_dst = wm_window_copy_test(C, win_src, false, false))) {
423       if (wm_stereo3d_quadbuffer_supported()) {
424         BKE_report(op->reports, RPT_INFO, "Quad-buffer window successfully created");
425       }
426       else {
427         wm_window_close(C, wm, win_dst);
428         win_dst = NULL;
429         BKE_report(op->reports, RPT_ERROR, "Quad-buffer not supported by the system");
430         ok = false;
431       }
432     }
433     else {
434       BKE_report(op->reports,
435                  RPT_ERROR,
436                  "Failed to create a window compatible with the time sequential display method");
437       ok = false;
438     }
439   }
440
441   if (wm_stereo3d_is_fullscreen_required(s3dd->stereo3d_format.display_mode)) {
442     if (!is_fullscreen) {
443       BKE_report(op->reports, RPT_INFO, "Stereo 3D Mode requires the window to be fullscreen");
444     }
445   }
446
447   MEM_freeN(op->customdata);
448
449   if (ok) {
450     if (win_dst) {
451       wm_window_close(C, wm, win_src);
452     }
453
454     WM_event_add_notifier(C, NC_WINDOW, NULL);
455     return OPERATOR_FINISHED;
456   }
457   else {
458     /* without this, the popup won't be freed freed properly T44688 */
459     CTX_wm_window_set(C, win_src);
460     win_src->stereo3d_format->display_mode = prev_display_mode;
461     return OPERATOR_CANCELLED;
462   }
463 }
464
465 int wm_stereo3d_set_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
466 {
467   wm_stereo3d_set_init(C, op);
468
469   if (wm_stereo3d_set_properties(C, op)) {
470     return wm_stereo3d_set_exec(C, op);
471   }
472   else {
473     return WM_operator_props_dialog_popup(C, op, 250, 100);
474   }
475 }
476
477 void wm_stereo3d_set_draw(bContext *UNUSED(C), wmOperator *op)
478 {
479   Stereo3dData *s3dd = op->customdata;
480   PointerRNA stereo3d_format_ptr;
481   uiLayout *layout = op->layout;
482   uiLayout *col;
483
484   RNA_pointer_create(NULL, &RNA_Stereo3dDisplay, &s3dd->stereo3d_format, &stereo3d_format_ptr);
485
486   col = uiLayoutColumn(layout, false);
487   uiItemR(col, &stereo3d_format_ptr, "display_mode", 0, NULL, ICON_NONE);
488
489   switch (s3dd->stereo3d_format.display_mode) {
490     case S3D_DISPLAY_ANAGLYPH: {
491       uiItemR(col, &stereo3d_format_ptr, "anaglyph_type", 0, NULL, ICON_NONE);
492       break;
493     }
494     case S3D_DISPLAY_INTERLACE: {
495       uiItemR(col, &stereo3d_format_ptr, "interlace_type", 0, NULL, ICON_NONE);
496       uiItemR(col, &stereo3d_format_ptr, "use_interlace_swap", 0, NULL, ICON_NONE);
497       break;
498     }
499     case S3D_DISPLAY_SIDEBYSIDE: {
500       uiItemR(col, &stereo3d_format_ptr, "use_sidebyside_crosseyed", 0, NULL, ICON_NONE);
501       /* fall-through */
502     }
503     case S3D_DISPLAY_PAGEFLIP:
504     case S3D_DISPLAY_TOPBOTTOM:
505     default: {
506       break;
507     }
508   }
509 }
510
511 bool wm_stereo3d_set_check(bContext *UNUSED(C), wmOperator *UNUSED(op))
512 {
513   /* the check function guarantees that the menu is updated to show the
514    * sub-options when an enum change (e.g., it shows the anaglyph options
515    * when anaglyph is on, and the interlace options when this is on */
516   return true;
517 }
518
519 void wm_stereo3d_set_cancel(bContext *UNUSED(C), wmOperator *op)
520 {
521   MEM_freeN(op->customdata);
522   op->customdata = NULL;
523 }