freedreno: fix coverity negative array index warning
[mesa.git] / src / gallium / drivers / freedreno / freedreno_query_hw.c
1 /* -*- mode: C; c-file-style: "k&r"; tab-width 4; indent-tabs-mode: t; -*- */
2
3 /*
4 * Copyright (C) 2014 Rob Clark <robclark@freedesktop.org>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice (including the next
14 * paragraph) shall be included in all copies or substantial portions of the
15 * Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 *
25 * Authors:
26 * Rob Clark <robclark@freedesktop.org>
27 */
28
29 #include "pipe/p_state.h"
30 #include "util/u_memory.h"
31 #include "util/u_inlines.h"
32
33 #include "freedreno_query_hw.h"
34 #include "freedreno_context.h"
35 #include "freedreno_util.h"
36
37 struct fd_hw_sample_period {
38 struct fd_hw_sample *start, *end;
39 struct list_head list;
40 };
41
42 /* maps query_type to sample provider idx: */
43 static int pidx(unsigned query_type)
44 {
45 switch (query_type) {
46 case PIPE_QUERY_OCCLUSION_COUNTER:
47 return 0;
48 case PIPE_QUERY_OCCLUSION_PREDICATE:
49 return 1;
50 case PIPE_QUERY_TIME_ELAPSED:
51 return 2;
52 default:
53 return -1;
54 }
55 }
56
57 static struct fd_hw_sample *
58 get_sample(struct fd_context *ctx, struct fd_ringbuffer *ring,
59 unsigned query_type)
60 {
61 struct fd_hw_sample *samp = NULL;
62 int idx = pidx(query_type);
63
64 assume(idx >= 0); /* query never would have been created otherwise */
65
66 if (!ctx->sample_cache[idx]) {
67 ctx->sample_cache[idx] =
68 ctx->sample_providers[idx]->get_sample(ctx, ring);
69 }
70
71 fd_hw_sample_reference(ctx, &samp, ctx->sample_cache[idx]);
72
73 return samp;
74 }
75
76 static void
77 clear_sample_cache(struct fd_context *ctx)
78 {
79 int i;
80
81 for (i = 0; i < ARRAY_SIZE(ctx->sample_cache); i++)
82 fd_hw_sample_reference(ctx, &ctx->sample_cache[i], NULL);
83 }
84
85 static bool
86 is_active(struct fd_hw_query *hq, enum fd_render_stage stage)
87 {
88 return !!(hq->provider->active & stage);
89 }
90
91
92 static void
93 resume_query(struct fd_context *ctx, struct fd_hw_query *hq,
94 struct fd_ringbuffer *ring)
95 {
96 int idx = pidx(hq->provider->query_type);
97 assert(!hq->period);
98 ctx->active_providers |= (1 << idx);
99 hq->period = util_slab_alloc(&ctx->sample_period_pool);
100 list_inithead(&hq->period->list);
101 hq->period->start = get_sample(ctx, ring, hq->base.type);
102 /* NOTE: util_slab_alloc() does not zero out the buffer: */
103 hq->period->end = NULL;
104 }
105
106 static void
107 pause_query(struct fd_context *ctx, struct fd_hw_query *hq,
108 struct fd_ringbuffer *ring)
109 {
110 int idx = pidx(hq->provider->query_type);
111 assert(hq->period && !hq->period->end);
112 assert(ctx->active_providers & (1 << idx));
113 hq->period->end = get_sample(ctx, ring, hq->base.type);
114 list_addtail(&hq->period->list, &hq->current_periods);
115 hq->period = NULL;
116 }
117
118 static void
119 destroy_periods(struct fd_context *ctx, struct list_head *list)
120 {
121 struct fd_hw_sample_period *period, *s;
122 LIST_FOR_EACH_ENTRY_SAFE(period, s, list, list) {
123 fd_hw_sample_reference(ctx, &period->start, NULL);
124 fd_hw_sample_reference(ctx, &period->end, NULL);
125 list_del(&period->list);
126 util_slab_free(&ctx->sample_period_pool, period);
127 }
128 }
129
130 static void
131 fd_hw_destroy_query(struct fd_context *ctx, struct fd_query *q)
132 {
133 struct fd_hw_query *hq = fd_hw_query(q);
134
135 destroy_periods(ctx, &hq->periods);
136 destroy_periods(ctx, &hq->current_periods);
137 list_del(&hq->list);
138
139 free(hq);
140 }
141
142 static boolean
143 fd_hw_begin_query(struct fd_context *ctx, struct fd_query *q)
144 {
145 struct fd_hw_query *hq = fd_hw_query(q);
146 if (q->active)
147 return false;
148
149 /* begin_query() should clear previous results: */
150 destroy_periods(ctx, &hq->periods);
151
152 if (is_active(hq, ctx->stage))
153 resume_query(ctx, hq, ctx->ring);
154
155 q->active = true;
156
157 /* add to active list: */
158 list_del(&hq->list);
159 list_addtail(&hq->list, &ctx->active_queries);
160 return true;
161 }
162
163 static void
164 fd_hw_end_query(struct fd_context *ctx, struct fd_query *q)
165 {
166 struct fd_hw_query *hq = fd_hw_query(q);
167 /* there are a couple special cases, which don't have
168 * a matching ->begin_query():
169 */
170 if (skip_begin_query(q->type) && !q->active) {
171 fd_hw_begin_query(ctx, q);
172 }
173 if (!q->active)
174 return;
175 if (is_active(hq, ctx->stage))
176 pause_query(ctx, hq, ctx->ring);
177 q->active = false;
178 /* move to current list: */
179 list_del(&hq->list);
180 list_addtail(&hq->list, &ctx->current_queries);
181 }
182
183 /* helper to get ptr to specified sample: */
184 static void * sampptr(struct fd_hw_sample *samp, uint32_t n, void *ptr)
185 {
186 return ((char *)ptr) + (samp->tile_stride * n) + samp->offset;
187 }
188
189 static boolean
190 fd_hw_get_query_result(struct fd_context *ctx, struct fd_query *q,
191 boolean wait, union pipe_query_result *result)
192 {
193 struct fd_hw_query *hq = fd_hw_query(q);
194 const struct fd_hw_sample_provider *p = hq->provider;
195 struct fd_hw_sample_period *period;
196
197 if (q->active)
198 return false;
199
200 /* if the app tries to read back the query result before the
201 * batch is submitted, that forces us to flush so that there
202 * are actually results to wait for:
203 */
204 if (!LIST_IS_EMPTY(&hq->list)) {
205 /* if app didn't actually trigger any cmdstream, then
206 * we have nothing to do:
207 */
208 if (!ctx->needs_flush)
209 return true;
210 DBG("reading query result forces flush!");
211 fd_context_render(&ctx->base);
212 }
213
214 util_query_clear_result(result, q->type);
215
216 if (LIST_IS_EMPTY(&hq->periods))
217 return true;
218
219 assert(LIST_IS_EMPTY(&hq->list));
220 assert(LIST_IS_EMPTY(&hq->current_periods));
221 assert(!hq->period);
222
223 /* if !wait, then check the last sample (the one most likely to
224 * not be ready yet) and bail if it is not ready:
225 */
226 if (!wait) {
227 int ret;
228
229 period = LIST_ENTRY(struct fd_hw_sample_period,
230 hq->periods.prev, list);
231
232 ret = fd_bo_cpu_prep(period->end->bo, ctx->screen->pipe,
233 DRM_FREEDRENO_PREP_READ | DRM_FREEDRENO_PREP_NOSYNC);
234 if (ret)
235 return false;
236
237 fd_bo_cpu_fini(period->end->bo);
238 }
239
240 /* sum the result across all sample periods: */
241 LIST_FOR_EACH_ENTRY(period, &hq->periods, list) {
242 struct fd_hw_sample *start = period->start;
243 struct fd_hw_sample *end = period->end;
244 unsigned i;
245
246 /* start and end samples should be from same batch: */
247 assert(start->bo == end->bo);
248 assert(start->num_tiles == end->num_tiles);
249
250 for (i = 0; i < start->num_tiles; i++) {
251 void *ptr;
252
253 fd_bo_cpu_prep(start->bo, ctx->screen->pipe,
254 DRM_FREEDRENO_PREP_READ);
255
256 ptr = fd_bo_map(start->bo);
257
258 p->accumulate_result(ctx, sampptr(period->start, i, ptr),
259 sampptr(period->end, i, ptr), result);
260
261 fd_bo_cpu_fini(start->bo);
262 }
263 }
264
265 return true;
266 }
267
268 static const struct fd_query_funcs hw_query_funcs = {
269 .destroy_query = fd_hw_destroy_query,
270 .begin_query = fd_hw_begin_query,
271 .end_query = fd_hw_end_query,
272 .get_query_result = fd_hw_get_query_result,
273 };
274
275 struct fd_query *
276 fd_hw_create_query(struct fd_context *ctx, unsigned query_type)
277 {
278 struct fd_hw_query *hq;
279 struct fd_query *q;
280 int idx = pidx(query_type);
281
282 if ((idx < 0) || !ctx->sample_providers[idx])
283 return NULL;
284
285 hq = CALLOC_STRUCT(fd_hw_query);
286 if (!hq)
287 return NULL;
288
289 hq->provider = ctx->sample_providers[idx];
290
291 list_inithead(&hq->periods);
292 list_inithead(&hq->current_periods);
293 list_inithead(&hq->list);
294
295 q = &hq->base;
296 q->funcs = &hw_query_funcs;
297 q->type = query_type;
298
299 return q;
300 }
301
302 struct fd_hw_sample *
303 fd_hw_sample_init(struct fd_context *ctx, uint32_t size)
304 {
305 struct fd_hw_sample *samp = util_slab_alloc(&ctx->sample_pool);
306 pipe_reference_init(&samp->reference, 1);
307 samp->size = size;
308 debug_assert(util_is_power_of_two(size));
309 ctx->next_sample_offset = align(ctx->next_sample_offset, size);
310 samp->offset = ctx->next_sample_offset;
311 /* NOTE: util_slab_alloc() does not zero out the buffer: */
312 samp->bo = NULL;
313 samp->num_tiles = 0;
314 samp->tile_stride = 0;
315 ctx->next_sample_offset += size;
316 return samp;
317 }
318
319 void
320 __fd_hw_sample_destroy(struct fd_context *ctx, struct fd_hw_sample *samp)
321 {
322 if (samp->bo)
323 fd_bo_del(samp->bo);
324 util_slab_free(&ctx->sample_pool, samp);
325 }
326
327 static void
328 prepare_sample(struct fd_hw_sample *samp, struct fd_bo *bo,
329 uint32_t num_tiles, uint32_t tile_stride)
330 {
331 if (samp->bo) {
332 assert(samp->bo == bo);
333 assert(samp->num_tiles == num_tiles);
334 assert(samp->tile_stride == tile_stride);
335 return;
336 }
337 samp->bo = fd_bo_ref(bo);
338 samp->num_tiles = num_tiles;
339 samp->tile_stride = tile_stride;
340 }
341
342 static void
343 prepare_query(struct fd_hw_query *hq, struct fd_bo *bo,
344 uint32_t num_tiles, uint32_t tile_stride)
345 {
346 struct fd_hw_sample_period *period, *s;
347
348 /* prepare all the samples in the query: */
349 LIST_FOR_EACH_ENTRY_SAFE(period, s, &hq->current_periods, list) {
350 prepare_sample(period->start, bo, num_tiles, tile_stride);
351 prepare_sample(period->end, bo, num_tiles, tile_stride);
352
353 /* move from current_periods list to periods list: */
354 list_del(&period->list);
355 list_addtail(&period->list, &hq->periods);
356 }
357 }
358
359 static void
360 prepare_queries(struct fd_context *ctx, struct fd_bo *bo,
361 uint32_t num_tiles, uint32_t tile_stride,
362 struct list_head *list, bool remove)
363 {
364 struct fd_hw_query *hq, *s;
365 LIST_FOR_EACH_ENTRY_SAFE(hq, s, list, list) {
366 prepare_query(hq, bo, num_tiles, tile_stride);
367 if (remove)
368 list_delinit(&hq->list);
369 }
370 }
371
372 /* called from gmem code once total storage requirements are known (ie.
373 * number of samples times number of tiles)
374 */
375 void
376 fd_hw_query_prepare(struct fd_context *ctx, uint32_t num_tiles)
377 {
378 uint32_t tile_stride = ctx->next_sample_offset;
379 struct fd_bo *bo;
380
381 if (ctx->query_bo)
382 fd_bo_del(ctx->query_bo);
383
384 if (tile_stride > 0) {
385 bo = fd_bo_new(ctx->dev, tile_stride * num_tiles,
386 DRM_FREEDRENO_GEM_CACHE_WCOMBINE |
387 DRM_FREEDRENO_GEM_TYPE_KMEM);
388 } else {
389 bo = NULL;
390 }
391
392 ctx->query_bo = bo;
393 ctx->query_tile_stride = tile_stride;
394
395 prepare_queries(ctx, bo, num_tiles, tile_stride,
396 &ctx->active_queries, false);
397 prepare_queries(ctx, bo, num_tiles, tile_stride,
398 &ctx->current_queries, true);
399
400 /* reset things for next batch: */
401 ctx->next_sample_offset = 0;
402 }
403
404 void
405 fd_hw_query_prepare_tile(struct fd_context *ctx, uint32_t n,
406 struct fd_ringbuffer *ring)
407 {
408 uint32_t tile_stride = ctx->query_tile_stride;
409 uint32_t offset = tile_stride * n;
410
411 /* bail if no queries: */
412 if (tile_stride == 0)
413 return;
414
415 fd_wfi(ctx, ring);
416 OUT_PKT0 (ring, HW_QUERY_BASE_REG, 1);
417 OUT_RELOCW(ring, ctx->query_bo, offset, 0, 0);
418 }
419
420 void
421 fd_hw_query_set_stage(struct fd_context *ctx, struct fd_ringbuffer *ring,
422 enum fd_render_stage stage)
423 {
424 /* special case: internal blits (like mipmap level generation)
425 * go through normal draw path (via util_blitter_blit()).. but
426 * we need to ignore the FD_STAGE_DRAW which will be set, so we
427 * don't enable queries which should be paused during internal
428 * blits:
429 */
430 if ((ctx->stage == FD_STAGE_BLIT) &&
431 (stage != FD_STAGE_NULL))
432 return;
433
434 if (stage != ctx->stage) {
435 struct fd_hw_query *hq;
436 LIST_FOR_EACH_ENTRY(hq, &ctx->active_queries, list) {
437 bool was_active = is_active(hq, ctx->stage);
438 bool now_active = is_active(hq, stage);
439
440 if (now_active && !was_active)
441 resume_query(ctx, hq, ring);
442 else if (was_active && !now_active)
443 pause_query(ctx, hq, ring);
444 }
445 }
446 clear_sample_cache(ctx);
447 ctx->stage = stage;
448 }
449
450 /* call the provider->enable() for all the hw queries that were active
451 * in the current batch. This sets up perfctr selector regs statically
452 * for the duration of the batch.
453 */
454 void
455 fd_hw_query_enable(struct fd_context *ctx, struct fd_ringbuffer *ring)
456 {
457 for (int idx = 0; idx < MAX_HW_SAMPLE_PROVIDERS; idx++) {
458 if (ctx->active_providers & (1 << idx)) {
459 assert(ctx->sample_providers[idx]);
460 if (ctx->sample_providers[idx]->enable)
461 ctx->sample_providers[idx]->enable(ctx, ring);
462 }
463 }
464 ctx->active_providers = 0; /* clear it for next frame */
465 }
466
467 void
468 fd_hw_query_register_provider(struct pipe_context *pctx,
469 const struct fd_hw_sample_provider *provider)
470 {
471 struct fd_context *ctx = fd_context(pctx);
472 int idx = pidx(provider->query_type);
473
474 assert((0 <= idx) && (idx < MAX_HW_SAMPLE_PROVIDERS));
475 assert(!ctx->sample_providers[idx]);
476
477 ctx->sample_providers[idx] = provider;
478 }
479
480 void
481 fd_hw_query_init(struct pipe_context *pctx)
482 {
483 struct fd_context *ctx = fd_context(pctx);
484
485 util_slab_create(&ctx->sample_pool, sizeof(struct fd_hw_sample),
486 16, UTIL_SLAB_SINGLETHREADED);
487 util_slab_create(&ctx->sample_period_pool, sizeof(struct fd_hw_sample_period),
488 16, UTIL_SLAB_SINGLETHREADED);
489 list_inithead(&ctx->active_queries);
490 list_inithead(&ctx->current_queries);
491 }
492
493 void
494 fd_hw_query_fini(struct pipe_context *pctx)
495 {
496 struct fd_context *ctx = fd_context(pctx);
497
498 util_slab_destroy(&ctx->sample_pool);
499 util_slab_destroy(&ctx->sample_period_pool);
500 }