Cleanup: GPU_select never took NULL rect
[blender-staging.git] / source / blender / gpu / intern / gpu_select.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2014 Blender Foundation.
19  * All rights reserved.
20  *
21  * Contributor(s): Antony Riakiotakis.
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  */
25
26 /** \file blender/gpu/intern/gpu_select.c
27  *  \ingroup gpu
28  *
29  * Interface for accessing gpu-related methods for selection. The semantics will be
30  * similar to glRenderMode(GL_SELECT) since the goal is to maintain compatibility.
31  */
32 #include "GPU_select.h"
33 #include "GPU_extensions.h"
34 #include "GPU_glew.h"
35  
36 #include "MEM_guardedalloc.h"
37
38 #include "DNA_userdef_types.h"
39
40 #include "BLI_utildefines.h"
41
42 /* Ad hoc number of queries to allocate to skip doing many glGenQueries */
43 #define ALLOC_QUERIES 200
44
45 typedef struct GPUQueryState {
46         /* To ignore selection id calls when not initialized */
47         bool select_is_active;
48         /* Tracks whether a query has been issued so that gpu_load_id can end the previous one */
49         bool query_issued;
50         /* array holding the OpenGL query identifiers */
51         unsigned int *queries;
52         /* array holding the id corresponding to each query */
53         unsigned int *id;
54         /* number of queries in *queries and *id */
55         unsigned int num_of_queries;
56         /* index to the next query to start */
57         unsigned int active_query;
58         /* flag to cache user preference for occlusion based selection */
59         bool use_gpu_select;
60         /* cache on initialization */
61         unsigned int *buffer;
62         /* buffer size (stores number of integers, for actual size multiply by sizeof integer)*/
63         unsigned int bufsize;
64         /* mode of operation */
65         char mode;
66         unsigned int index;
67         int oldhits;
68 } GPUQueryState;
69
70 static GPUQueryState g_query_state = {0};
71
72 /**
73  * initialize and provide buffer for results
74  */
75 void GPU_select_begin(unsigned int *buffer, unsigned int bufsize, const rctf *input, char mode, int oldhits)
76 {
77         g_query_state.select_is_active = true;
78         g_query_state.query_issued = false;
79         g_query_state.active_query = 0;
80         g_query_state.use_gpu_select = GPU_select_query_check_active();
81         g_query_state.num_of_queries = 0;
82         g_query_state.bufsize = bufsize;
83         g_query_state.buffer = buffer;
84         g_query_state.mode = mode;
85         g_query_state.index = 0;
86         g_query_state.oldhits = oldhits;
87
88         if (!g_query_state.use_gpu_select) {
89                 glSelectBuffer(bufsize, (GLuint *)buffer);
90                 glRenderMode(GL_SELECT);
91                 glInitNames();
92                 glPushName(-1);
93         }
94         else {
95                 float viewport[4];
96
97                 g_query_state.num_of_queries = ALLOC_QUERIES;
98
99                 g_query_state.queries = MEM_mallocN(g_query_state.num_of_queries * sizeof(*g_query_state.queries), "gpu selection queries");
100                 g_query_state.id = MEM_mallocN(g_query_state.num_of_queries * sizeof(*g_query_state.id), "gpu selection ids");
101                 glGenQueries(g_query_state.num_of_queries, g_query_state.queries);
102
103                 glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_VIEWPORT_BIT);
104                 /* disable writing to the framebuffer */
105                 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
106
107                 /* In order to save some fill rate we minimize the viewport using rect.
108                  * We need to get the region of the scissor so that our geometry doesn't
109                  * get rejected before the depth test. Should probably cull rect against
110                  * scissor for viewport but this is a rare case I think */
111                 glGetFloatv(GL_SCISSOR_BOX, viewport);
112                 glViewport(viewport[0], viewport[1], (int)(input->xmax - input->xmin), (int)(input->ymax - input->ymin));
113
114                 /* occlusion queries operates on fragments that pass tests and since we are interested on all
115                  * objects in the view frustum independently of their order, we need to disable the depth test */
116                 if (mode == GPU_SELECT_ALL) {
117                         glDisable(GL_DEPTH_TEST);
118                         glDepthMask(GL_FALSE);
119                 }
120                 else if (mode == GPU_SELECT_NEAREST_FIRST_PASS) {
121                         glClear(GL_DEPTH_BUFFER_BIT);
122                         glEnable(GL_DEPTH_TEST);
123                         glDepthMask(GL_TRUE);
124                         glDepthFunc(GL_LEQUAL);
125                 }
126                 else if (mode == GPU_SELECT_NEAREST_SECOND_PASS) {
127                         glEnable(GL_DEPTH_TEST);
128                         glDepthMask(GL_FALSE);
129                         glDepthFunc(GL_EQUAL);
130                 }
131         }
132 }
133
134 /**
135  * loads a new selection id and ends previous query, if any. In second pass of selection it also returns
136  * if id has been hit on the first pass already.
137  * Thus we can skip drawing un-hit objects.
138  *
139  * \warning We rely on the order of object rendering on passes to be the same for this to work.
140  */
141 bool GPU_select_load_id(unsigned int id)
142 {
143         /* if no selection mode active, ignore */
144         if (!g_query_state.select_is_active)
145                 return true;
146
147         if (!g_query_state.use_gpu_select) {
148                 glLoadName(id);
149         }
150         else {
151                 if (g_query_state.query_issued) {
152                         glEndQuery(GL_SAMPLES_PASSED);
153                 }
154                 /* if required, allocate extra queries */
155                 if (g_query_state.active_query == g_query_state.num_of_queries) {
156                         g_query_state.num_of_queries += ALLOC_QUERIES;
157                         g_query_state.queries = MEM_reallocN(g_query_state.queries, g_query_state.num_of_queries * sizeof(*g_query_state.queries));
158                         g_query_state.id = MEM_reallocN(g_query_state.id, g_query_state.num_of_queries * sizeof(*g_query_state.id));
159                         glGenQueries(ALLOC_QUERIES, &g_query_state.queries[g_query_state.active_query]);
160                 }
161
162                 glBeginQuery(GL_SAMPLES_PASSED, g_query_state.queries[g_query_state.active_query]);
163                 g_query_state.id[g_query_state.active_query] = id;
164                 g_query_state.active_query++;
165                 g_query_state.query_issued = true;
166
167                 if (g_query_state.mode == GPU_SELECT_NEAREST_SECOND_PASS && g_query_state.index < g_query_state.oldhits) {
168                         if (g_query_state.buffer[g_query_state.index * 4 + 3] == id) {
169                                 g_query_state.index++;
170                                 return true;
171                         }
172                         else {
173                                 return false;
174                         }
175                 }
176         }
177
178         return true;
179 }
180
181 /**
182  * Cleanup and flush selection results to buffer.
183  * Return number of hits and hits in buffer.
184  * if \a dopass is true, we will do a second pass with occlusion queries to get the closest hit.
185  */
186 unsigned int GPU_select_end(void)
187 {
188         unsigned int hits = 0;
189         if (!g_query_state.use_gpu_select) {
190                 glPopName();
191                 hits = glRenderMode(GL_RENDER);
192         }
193         else {
194                 int i;
195
196                 if (g_query_state.query_issued) {
197                         glEndQuery(GL_SAMPLES_PASSED);
198                 }
199
200                 for (i = 0; i < g_query_state.active_query; i++) {
201                         unsigned int result;
202                         glGetQueryObjectuiv(g_query_state.queries[i], GL_QUERY_RESULT, &result);
203                         if (result > 0) {
204                                 if (g_query_state.mode != GPU_SELECT_NEAREST_SECOND_PASS) {
205                                         int maxhits = g_query_state.bufsize / 4;
206
207                                         if (hits < maxhits) {
208                                                 g_query_state.buffer[hits * 4] = 1;
209                                                 g_query_state.buffer[hits * 4 + 1] = 0xFFFF;
210                                                 g_query_state.buffer[hits * 4 + 2] = 0xFFFF;
211                                                 g_query_state.buffer[hits * 4 + 3] = g_query_state.id[i];
212
213                                                 hits++;
214                                         }
215                                         else {
216                                                 hits = -1;
217                                                 break;
218                                         }
219                                 }
220                                 else {
221                                         int j;
222                                         /* search in buffer and make selected object first */
223                                         for (j = 0; j < g_query_state.oldhits; j++) {
224                                                 if (g_query_state.buffer[j * 4 + 3] == g_query_state.id[i]) {
225                                                         g_query_state.buffer[j * 4 + 1] = 0;
226                                                         g_query_state.buffer[j * 4 + 2] = 0;
227                                                 }
228                                         }
229                                         break;
230                                 }
231                         }
232                 }
233
234                 glDeleteQueries(g_query_state.num_of_queries, g_query_state.queries);
235                 MEM_freeN(g_query_state.queries);
236                 MEM_freeN(g_query_state.id);
237                 glPopAttrib();
238                 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
239         }
240
241         g_query_state.select_is_active = false;
242
243         return hits;
244 }
245
246 /**
247  * has user activated?
248  */
249 bool GPU_select_query_check_active(void)
250 {
251         return ((U.gpu_select_method == USER_SELECT_USE_OCCLUSION_QUERY) ||
252                 ((U.gpu_select_method == USER_SELECT_AUTO) &&
253                  (GPU_type_matches(GPU_DEVICE_ATI, GPU_OS_ANY, GPU_DRIVER_ANY) ||
254                   /* unsupported by nouveau, gallium 0.4, see: T47940 */
255                   GPU_type_matches(GPU_DEVICE_NVIDIA, GPU_OS_UNIX, GPU_DRIVER_OPENSOURCE))));
256
257 }