Cleanup: comment line length (windowmanager)
[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, adjust \a r_mouse_xy
284  * so that drawn cursor and handled mouse position are matching visually.
285  */
286 void wm_stereo3d_mouse_offset_apply(wmWindow *win, int *r_mouse_xy)
287 {
288   if (!WM_stereo3d_enabled(win, false)) {
289     return;
290   }
291
292   if (win->stereo3d_format->display_mode == S3D_DISPLAY_SIDEBYSIDE) {
293     const int half_x = win->sizex / 2;
294     /* right half of the screen */
295     if (r_mouse_xy[0] > half_x) {
296       r_mouse_xy[0] -= half_x;
297     }
298     r_mouse_xy[0] *= 2;
299   }
300   else if (win->stereo3d_format->display_mode == S3D_DISPLAY_TOPBOTTOM) {
301     const int half_y = win->sizey / 2;
302     /* upper half of the screen */
303     if (r_mouse_xy[1] > half_y) {
304       r_mouse_xy[1] -= half_y;
305     }
306     r_mouse_xy[1] *= 2;
307   }
308 }
309
310 /************************** Stereo 3D operator **********************************/
311 typedef struct Stereo3dData {
312   Stereo3dFormat stereo3d_format;
313 } Stereo3dData;
314
315 static bool wm_stereo3d_set_properties(bContext *UNUSED(C), wmOperator *op)
316 {
317   Stereo3dData *s3dd = op->customdata;
318   Stereo3dFormat *s3d = &s3dd->stereo3d_format;
319   PropertyRNA *prop;
320   bool is_set = false;
321
322   prop = RNA_struct_find_property(op->ptr, "display_mode");
323   if (RNA_property_is_set(op->ptr, prop)) {
324     s3d->display_mode = RNA_property_enum_get(op->ptr, prop);
325     is_set = true;
326   }
327
328   prop = RNA_struct_find_property(op->ptr, "anaglyph_type");
329   if (RNA_property_is_set(op->ptr, prop)) {
330     s3d->anaglyph_type = RNA_property_enum_get(op->ptr, prop);
331     is_set = true;
332   }
333
334   prop = RNA_struct_find_property(op->ptr, "interlace_type");
335   if (RNA_property_is_set(op->ptr, prop)) {
336     s3d->interlace_type = RNA_property_enum_get(op->ptr, prop);
337     is_set = true;
338   }
339
340   prop = RNA_struct_find_property(op->ptr, "use_interlace_swap");
341   if (RNA_property_is_set(op->ptr, prop)) {
342     if (RNA_property_boolean_get(op->ptr, prop)) {
343       s3d->flag |= S3D_INTERLACE_SWAP;
344     }
345     else {
346       s3d->flag &= ~S3D_INTERLACE_SWAP;
347     }
348     is_set = true;
349   }
350
351   prop = RNA_struct_find_property(op->ptr, "use_sidebyside_crosseyed");
352   if (RNA_property_is_set(op->ptr, prop)) {
353     if (RNA_property_boolean_get(op->ptr, prop)) {
354       s3d->flag |= S3D_SIDEBYSIDE_CROSSEYED;
355     }
356     else {
357       s3d->flag &= ~S3D_SIDEBYSIDE_CROSSEYED;
358     }
359     is_set = true;
360   }
361
362   return is_set;
363 }
364
365 static void wm_stereo3d_set_init(bContext *C, wmOperator *op)
366 {
367   Stereo3dData *s3dd;
368   wmWindow *win = CTX_wm_window(C);
369
370   op->customdata = s3dd = MEM_callocN(sizeof(Stereo3dData), __func__);
371
372   /* store the original win stereo 3d settings in case of cancel */
373   s3dd->stereo3d_format = *win->stereo3d_format;
374 }
375
376 int wm_stereo3d_set_exec(bContext *C, wmOperator *op)
377 {
378   wmWindowManager *wm = CTX_wm_manager(C);
379   wmWindow *win_src = CTX_wm_window(C);
380   wmWindow *win_dst = NULL;
381   const bool is_fullscreen = WM_window_is_fullscreen(win_src);
382   char prev_display_mode = win_src->stereo3d_format->display_mode;
383   Stereo3dData *s3dd;
384   bool ok = true;
385
386   if (G.background) {
387     return OPERATOR_CANCELLED;
388   }
389
390   if (op->customdata == NULL) {
391     /* no invoke means we need to set the operator properties here */
392     wm_stereo3d_set_init(C, op);
393     wm_stereo3d_set_properties(C, op);
394   }
395
396   s3dd = op->customdata;
397   *win_src->stereo3d_format = s3dd->stereo3d_format;
398
399   if (prev_display_mode == S3D_DISPLAY_PAGEFLIP &&
400       prev_display_mode != win_src->stereo3d_format->display_mode) {
401     /* in case the hardware supports pageflip but not the display */
402     if ((win_dst = wm_window_copy_test(C, win_src, false, false))) {
403       /* pass */
404     }
405     else {
406       BKE_report(
407           op->reports,
408           RPT_ERROR,
409           "Failed to create a window without quad-buffer support, you may experience flickering");
410       ok = false;
411     }
412   }
413   else if (win_src->stereo3d_format->display_mode == S3D_DISPLAY_PAGEFLIP) {
414     const bScreen *screen = WM_window_get_active_screen(win_src);
415
416     /* ED_workspace_layout_duplicate() can't handle other cases yet T44688 */
417     if (screen->state != SCREENNORMAL) {
418       BKE_report(
419           op->reports, RPT_ERROR, "Failed to switch to Time Sequential mode when in fullscreen");
420       ok = false;
421     }
422     /* pageflip requires a new window to be created with the proper OS flags */
423     else if ((win_dst = wm_window_copy_test(C, win_src, false, false))) {
424       if (wm_stereo3d_quadbuffer_supported()) {
425         BKE_report(op->reports, RPT_INFO, "Quad-buffer window successfully created");
426       }
427       else {
428         wm_window_close(C, wm, win_dst);
429         win_dst = NULL;
430         BKE_report(op->reports, RPT_ERROR, "Quad-buffer not supported by the system");
431         ok = false;
432       }
433     }
434     else {
435       BKE_report(op->reports,
436                  RPT_ERROR,
437                  "Failed to create a window compatible with the time sequential display method");
438       ok = false;
439     }
440   }
441
442   if (wm_stereo3d_is_fullscreen_required(s3dd->stereo3d_format.display_mode)) {
443     if (!is_fullscreen) {
444       BKE_report(op->reports, RPT_INFO, "Stereo 3D Mode requires the window to be fullscreen");
445     }
446   }
447
448   MEM_freeN(op->customdata);
449
450   if (ok) {
451     if (win_dst) {
452       wm_window_close(C, wm, win_src);
453     }
454
455     WM_event_add_notifier(C, NC_WINDOW, NULL);
456     return OPERATOR_FINISHED;
457   }
458   else {
459     /* without this, the popup won't be freed freed properly T44688 */
460     CTX_wm_window_set(C, win_src);
461     win_src->stereo3d_format->display_mode = prev_display_mode;
462     return OPERATOR_CANCELLED;
463   }
464 }
465
466 int wm_stereo3d_set_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
467 {
468   wm_stereo3d_set_init(C, op);
469
470   if (wm_stereo3d_set_properties(C, op)) {
471     return wm_stereo3d_set_exec(C, op);
472   }
473   else {
474     return WM_operator_props_dialog_popup(C, op, 250, 100);
475   }
476 }
477
478 void wm_stereo3d_set_draw(bContext *UNUSED(C), wmOperator *op)
479 {
480   Stereo3dData *s3dd = op->customdata;
481   PointerRNA stereo3d_format_ptr;
482   uiLayout *layout = op->layout;
483   uiLayout *col;
484
485   RNA_pointer_create(NULL, &RNA_Stereo3dDisplay, &s3dd->stereo3d_format, &stereo3d_format_ptr);
486
487   col = uiLayoutColumn(layout, false);
488   uiItemR(col, &stereo3d_format_ptr, "display_mode", 0, NULL, ICON_NONE);
489
490   switch (s3dd->stereo3d_format.display_mode) {
491     case S3D_DISPLAY_ANAGLYPH: {
492       uiItemR(col, &stereo3d_format_ptr, "anaglyph_type", 0, NULL, ICON_NONE);
493       break;
494     }
495     case S3D_DISPLAY_INTERLACE: {
496       uiItemR(col, &stereo3d_format_ptr, "interlace_type", 0, NULL, ICON_NONE);
497       uiItemR(col, &stereo3d_format_ptr, "use_interlace_swap", 0, NULL, ICON_NONE);
498       break;
499     }
500     case S3D_DISPLAY_SIDEBYSIDE: {
501       uiItemR(col, &stereo3d_format_ptr, "use_sidebyside_crosseyed", 0, NULL, ICON_NONE);
502       /* fall-through */
503     }
504     case S3D_DISPLAY_PAGEFLIP:
505     case S3D_DISPLAY_TOPBOTTOM:
506     default: {
507       break;
508     }
509   }
510 }
511
512 bool wm_stereo3d_set_check(bContext *UNUSED(C), wmOperator *UNUSED(op))
513 {
514   /* the check function guarantees that the menu is updated to show the
515    * sub-options when an enum change (e.g., it shows the anaglyph options
516    * when anaglyph is on, and the interlace options when this is on */
517   return true;
518 }
519
520 void wm_stereo3d_set_cancel(bContext *UNUSED(C), wmOperator *op)
521 {
522   MEM_freeN(op->customdata);
523   op->customdata = NULL;
524 }