1/**************************************************************************
2 *
3 * Copyright 2013 Marek Olšák <maraeo@gmail.com>
4 * All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sub license, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice (including the
15 * next paragraph) shall be included in all copies or substantial portions
16 * of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
21 * IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
22 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 **************************************************************************/
27
28/* This file contains code for reading values from pipe queries
29 * for displaying on the HUD. To prevent stalls when reading queries, we
30 * keep a list of busy queries in a ring. We read only those queries which
31 * are idle.
32 */
33
34#include "hud/hud_private.h"
35#include "pipe/p_screen.h"
36#include "util/os_time.h"
37#include "util/u_math.h"
38#include "util/u_memory.h"
39#include <stdio.h>
40
41// Must be a power of two
42#define NUM_QUERIES 8
43
44struct hud_batch_query_context {
45   unsigned num_query_types;
46   unsigned allocated_query_types;
47   unsigned *query_types;
48
49   boolean failed;
50   struct pipe_query *query[NUM_QUERIES];
51   union pipe_query_result *result[NUM_QUERIES];
52   unsigned head, pending, results;
53};
54
55void
56hud_batch_query_update(struct hud_batch_query_context *bq,
57                       struct pipe_context *pipe)
58{
59   if (!bq || bq->failed)
60      return;
61
62   if (bq->query[bq->head])
63      pipe->end_query(pipe, bq->query[bq->head]);
64
65   bq->results = 0;
66
67   while (bq->pending) {
68      unsigned idx = (bq->head - bq->pending + 1) % NUM_QUERIES;
69      struct pipe_query *query = bq->query[idx];
70
71      if (!bq->result[idx])
72         bq->result[idx] = MALLOC(sizeof(bq->result[idx]->batch[0]) *
73                                  bq->num_query_types);
74      if (!bq->result[idx]) {
75         fprintf(stderr, "gallium_hud: out of memory.\n");
76         bq->failed = TRUE;
77         return;
78      }
79
80      if (!pipe->get_query_result(pipe, query, FALSE, bq->result[idx]))
81         break;
82
83      ++bq->results;
84      --bq->pending;
85   }
86
87   bq->head = (bq->head + 1) % NUM_QUERIES;
88
89   if (bq->pending == NUM_QUERIES) {
90      fprintf(stderr,
91              "gallium_hud: all queries busy after %i frames, dropping data.\n",
92              NUM_QUERIES);
93
94      assert(bq->query[bq->head]);
95
96      pipe->destroy_query(pipe, bq->query[bq->head]);
97      bq->query[bq->head] = NULL;
98   }
99
100   ++bq->pending;
101
102   if (!bq->query[bq->head]) {
103      bq->query[bq->head] = pipe->create_batch_query(pipe,
104                                                     bq->num_query_types,
105                                                     bq->query_types);
106
107      if (!bq->query[bq->head]) {
108         fprintf(stderr,
109                 "gallium_hud: create_batch_query failed. You may have "
110                 "selected too many or incompatible queries.\n");
111         bq->failed = TRUE;
112         return;
113      }
114   }
115}
116
117void
118hud_batch_query_begin(struct hud_batch_query_context *bq,
119                      struct pipe_context *pipe)
120{
121   if (!bq || bq->failed || !bq->query[bq->head])
122      return;
123
124   if (!pipe->begin_query(pipe, bq->query[bq->head])) {
125      fprintf(stderr,
126              "gallium_hud: could not begin batch query. You may have "
127              "selected too many or incompatible queries.\n");
128      bq->failed = TRUE;
129   }
130}
131
132static boolean
133batch_query_add(struct hud_batch_query_context **pbq,
134                unsigned query_type, unsigned *result_index)
135{
136   struct hud_batch_query_context *bq = *pbq;
137   unsigned i;
138
139   if (!bq) {
140      bq = CALLOC_STRUCT(hud_batch_query_context);
141      if (!bq)
142         return false;
143      *pbq = bq;
144   }
145
146   for (i = 0; i < bq->num_query_types; ++i) {
147      if (bq->query_types[i] == query_type) {
148         *result_index = i;
149         return true;
150      }
151   }
152
153   if (bq->num_query_types == bq->allocated_query_types) {
154      unsigned new_alloc = MAX2(16, bq->allocated_query_types * 2);
155      unsigned *new_query_types
156         = REALLOC(bq->query_types,
157                   bq->allocated_query_types * sizeof(unsigned),
158                   new_alloc * sizeof(unsigned));
159      if (!new_query_types)
160         return false;
161      bq->query_types = new_query_types;
162      bq->allocated_query_types = new_alloc;
163   }
164
165   bq->query_types[bq->num_query_types] = query_type;
166   *result_index = bq->num_query_types++;
167   return true;
168}
169
170void
171hud_batch_query_cleanup(struct hud_batch_query_context **pbq,
172                        struct pipe_context *pipe)
173{
174   struct hud_batch_query_context *bq = *pbq;
175   unsigned idx;
176
177   if (!bq)
178      return;
179
180   *pbq = NULL;
181
182   if (bq->query[bq->head] && !bq->failed)
183      pipe->end_query(pipe, bq->query[bq->head]);
184
185   for (idx = 0; idx < NUM_QUERIES; ++idx) {
186      if (bq->query[idx])
187         pipe->destroy_query(pipe, bq->query[idx]);
188      FREE(bq->result[idx]);
189   }
190
191   FREE(bq->query_types);
192   FREE(bq);
193}
194
195struct query_info {
196   struct hud_batch_query_context *batch;
197   enum pipe_query_type query_type;
198
199   /** index to choose fields in pipe_query_data_pipeline_statistics,
200    * for example.
201    */
202   unsigned result_index;
203   enum pipe_driver_query_result_type result_type;
204   enum pipe_driver_query_type type;
205
206   /* Ring of queries. If a query is busy, we use another slot. */
207   struct pipe_query *query[NUM_QUERIES];
208   unsigned head, tail;
209
210   uint64_t last_time;
211   uint64_t results_cumulative;
212   unsigned num_results;
213};
214
215static void
216query_new_value_batch(struct query_info *info)
217{
218   struct hud_batch_query_context *bq = info->batch;
219   unsigned result_index = info->result_index;
220   unsigned idx = (bq->head - bq->pending) % NUM_QUERIES;
221   unsigned results = bq->results;
222
223   while (results) {
224      info->results_cumulative += bq->result[idx]->batch[result_index].u64;
225      ++info->num_results;
226
227      --results;
228      idx = (idx - 1) % NUM_QUERIES;
229   }
230}
231
232static void
233query_new_value_normal(struct query_info *info, struct pipe_context *pipe)
234{
235   if (info->last_time) {
236      if (info->query[info->head])
237         pipe->end_query(pipe, info->query[info->head]);
238
239      /* read query results */
240      while (1) {
241         struct pipe_query *query = info->query[info->tail];
242         union pipe_query_result result;
243         uint64_t *res64 = (uint64_t *)&result;
244
245         if (query && pipe->get_query_result(pipe, query, FALSE, &result)) {
246            if (info->type == PIPE_DRIVER_QUERY_TYPE_FLOAT) {
247               assert(info->result_index == 0);
248               info->results_cumulative += (uint64_t) (result.f * 1000.0f);
249            }
250            else {
251               info->results_cumulative += res64[info->result_index];
252            }
253            info->num_results++;
254
255            if (info->tail == info->head)
256               break;
257
258            info->tail = (info->tail+1) % NUM_QUERIES;
259         }
260         else {
261            /* the oldest query is busy */
262            if ((info->head+1) % NUM_QUERIES == info->tail) {
263               /* all queries are busy, throw away the last query and create
264                * a new one */
265               fprintf(stderr,
266                       "gallium_hud: all queries are busy after %i frames, "
267                       "can't add another query\n",
268                       NUM_QUERIES);
269               if (info->query[info->head])
270                  pipe->destroy_query(pipe, info->query[info->head]);
271               info->query[info->head] =
272                     pipe->create_query(pipe, info->query_type, 0);
273            }
274            else {
275               /* the last query is busy, we need to add a new one we can use
276                * for this frame */
277               info->head = (info->head+1) % NUM_QUERIES;
278               if (!info->query[info->head]) {
279                  info->query[info->head] =
280                        pipe->create_query(pipe, info->query_type, 0);
281               }
282            }
283            break;
284         }
285      }
286   }
287   else {
288      /* initialize */
289      info->query[info->head] = pipe->create_query(pipe, info->query_type, 0);
290   }
291}
292
293static void
294begin_query(struct hud_graph *gr, struct pipe_context *pipe)
295{
296   struct query_info *info = gr->query_data;
297
298   assert(!info->batch);
299   if (info->query[info->head])
300      pipe->begin_query(pipe, info->query[info->head]);
301}
302
303static void
304query_new_value(struct hud_graph *gr, struct pipe_context *pipe)
305{
306   struct query_info *info = gr->query_data;
307   uint64_t now = os_time_get();
308
309   if (info->batch) {
310      query_new_value_batch(info);
311   } else {
312      query_new_value_normal(info, pipe);
313   }
314
315   if (!info->last_time) {
316      info->last_time = now;
317      return;
318   }
319
320   if (info->num_results && info->last_time + gr->pane->period <= now) {
321      double value;
322
323      switch (info->result_type) {
324      default:
325      case PIPE_DRIVER_QUERY_RESULT_TYPE_AVERAGE:
326         value = info->results_cumulative / info->num_results;
327         break;
328      case PIPE_DRIVER_QUERY_RESULT_TYPE_CUMULATIVE:
329         value = info->results_cumulative;
330         break;
331      }
332
333      if (info->type == PIPE_DRIVER_QUERY_TYPE_FLOAT) {
334         value /= 1000.0;
335      }
336
337      hud_graph_add_value(gr, value);
338
339      info->last_time = now;
340      info->results_cumulative = 0;
341      info->num_results = 0;
342   }
343}
344
345static void
346free_query_info(void *ptr, struct pipe_context *pipe)
347{
348   struct query_info *info = ptr;
349
350   if (!info->batch && info->last_time) {
351      int i;
352
353      pipe->end_query(pipe, info->query[info->head]);
354
355      for (i = 0; i < ARRAY_SIZE(info->query); i++) {
356         if (info->query[i]) {
357            pipe->destroy_query(pipe, info->query[i]);
358         }
359      }
360   }
361   FREE(info);
362}
363
364
365/**
366 * \param result_index  to select fields of pipe_query_data_pipeline_statistics,
367 *                      for example.
368 */
369void
370hud_pipe_query_install(struct hud_batch_query_context **pbq,
371                       struct hud_pane *pane,
372                       const char *name,
373                       enum pipe_query_type query_type,
374                       unsigned result_index,
375                       uint64_t max_value, enum pipe_driver_query_type type,
376                       enum pipe_driver_query_result_type result_type,
377                       unsigned flags)
378{
379   struct hud_graph *gr;
380   struct query_info *info;
381
382   gr = CALLOC_STRUCT(hud_graph);
383   if (!gr)
384      return;
385
386   strncpy(gr->name, name, sizeof(gr->name));
387   gr->name[sizeof(gr->name) - 1] = '\0';
388   gr->query_data = CALLOC_STRUCT(query_info);
389   if (!gr->query_data)
390      goto fail_gr;
391
392   gr->query_new_value = query_new_value;
393   gr->free_query_data = free_query_info;
394
395   info = gr->query_data;
396   info->result_type = result_type;
397   info->type = type;
398
399   if (flags & PIPE_DRIVER_QUERY_FLAG_BATCH) {
400      if (!batch_query_add(pbq, query_type, &info->result_index))
401         goto fail_info;
402      info->batch = *pbq;
403   } else {
404      gr->begin_query = begin_query;
405      info->query_type = query_type;
406      info->result_index = result_index;
407   }
408
409   hud_pane_add_graph(pane, gr);
410   pane->type = type; /* must be set before updating the max_value */
411
412   if (pane->max_value < max_value)
413      hud_pane_set_max_value(pane, max_value);
414   return;
415
416fail_info:
417   FREE(info);
418fail_gr:
419   FREE(gr);
420}
421
422boolean
423hud_driver_query_install(struct hud_batch_query_context **pbq,
424                         struct hud_pane *pane, struct pipe_screen *screen,
425                         const char *name)
426{
427   struct pipe_driver_query_info query = { 0 };
428   unsigned num_queries, i;
429   boolean found = FALSE;
430
431   if (!screen->get_driver_query_info)
432      return FALSE;
433
434   num_queries = screen->get_driver_query_info(screen, 0, NULL);
435
436   for (i = 0; i < num_queries; i++) {
437      if (screen->get_driver_query_info(screen, i, &query) &&
438          strcmp(query.name, name) == 0) {
439         found = TRUE;
440         break;
441      }
442   }
443
444   if (!found)
445      return FALSE;
446
447   hud_pipe_query_install(pbq, pane, query.name, query.query_type, 0,
448                          query.max_value.u64, query.type, query.result_type,
449                          query.flags);
450
451   return TRUE;
452}
453