blob: 949c9222ef4956b0720cdfa72ca0a565256735d8 [file] [log] [blame]
Alexander Trukhin833b5d72016-01-14 06:00:46 +03001/**************************************************************************
2 *
3 * Copyright 2015 Alexander Trukhin
4 * All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 *
24 **************************************************************************/
25
Alexander Trukhinb90d8002015-08-14 15:55:14 +030026#include "metric_backend_opengl.hpp"
27#include "os_time.hpp"
28#include "os_memory.hpp"
29
30void
31MetricBackend_opengl::Storage::addData(QueryBoundary boundary, int64_t data) {
32 this->data[boundary].push_back(data);
33}
34
35int64_t* MetricBackend_opengl::Storage::getData(QueryBoundary boundary,
36 unsigned eventId)
37{
38 return &(data[boundary][eventId]);
39}
40
41Metric_opengl::Metric_opengl(unsigned gId, unsigned id, const std::string &name,
42 const std::string &desc, MetricNumType nT, MetricType t)
43 : m_gId(gId), m_id(id), m_name(name), m_desc(desc), m_nType(nT),
44 m_type(t), available(false)
45{
46 for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) {
47 profiled[i] = false;
48 enabled[i] = false;
49 }
50}
51
52unsigned Metric_opengl::id() {
53 return m_id;
54}
55
56unsigned Metric_opengl::groupId() {
57 return m_gId;
58}
59
60std::string Metric_opengl::name() {
61 return m_name;
62}
63
64std::string Metric_opengl::description() {
65 return m_desc;
66}
67
68MetricNumType Metric_opengl::numType() {
69 return m_nType;
70}
71
72MetricType Metric_opengl::type() {
73 return m_type;
74}
75
Alexander Trukhinca8139c2015-08-26 14:58:26 +030076MetricBackend_opengl::MetricBackend_opengl(glretrace::Context* context,
77 MmapAllocator<char> &alloc)
78 : alloc(alloc)
Alexander Trukhinb90d8002015-08-14 15:55:14 +030079{
80 glprofile::Profile currentProfile = context->actualProfile();
81 supportsTimestamp = currentProfile.versionGreaterOrEqual(glprofile::API_GL, 3, 3) ||
82 context->hasExtension("GL_ARB_timer_query");
83 supportsElapsed = context->hasExtension("GL_EXT_timer_query") || supportsTimestamp;
84 supportsOcclusion = currentProfile.versionGreaterOrEqual(glprofile::API_GL, 1, 5);
85
86 #ifdef __APPLE__
87 // GL_TIMESTAMP doesn't work on Apple. GL_TIME_ELAPSED still does however.
88 // http://lists.apple.com/archives/mac-opengl/2014/Nov/threads.html#00001
89 supportsTimestamp = false;
90 #endif
91
92 // Add metrics below
93 metrics.emplace_back(0, 0, "CPU Start", "", CNT_NUM_INT64, CNT_TYPE_TIMESTAMP);
94 metrics.emplace_back(0, 1, "CPU Duration", "", CNT_NUM_INT64, CNT_TYPE_DURATION);
95 metrics.emplace_back(1, 0, "GPU Start", "", CNT_NUM_INT64, CNT_TYPE_TIMESTAMP);
96 metrics.emplace_back(1, 1, "GPU Duration", "", CNT_NUM_INT64, CNT_TYPE_DURATION);
97 metrics.emplace_back(1, 2, "Pixels Drawn", "", CNT_NUM_INT64, CNT_TYPE_GENERIC);
98 metrics.emplace_back(0, 2, "VSIZE Start", "", CNT_NUM_INT64, CNT_TYPE_GENERIC);
99 metrics.emplace_back(0, 3, "VSIZE Duration", "", CNT_NUM_INT64, CNT_TYPE_GENERIC);
100 metrics.emplace_back(0, 4, "RSS Start", "", CNT_NUM_INT64, CNT_TYPE_GENERIC);
101 metrics.emplace_back(0, 5, "RSS Duration", "", CNT_NUM_INT64, CNT_TYPE_GENERIC);
102
103 metrics[METRIC_CPU_START].available = true;
104 metrics[METRIC_CPU_DURATION].available = true;
105 metrics[METRIC_CPU_VSIZE_START].available = true;
106 metrics[METRIC_CPU_VSIZE_DURATION].available = true;
107 metrics[METRIC_CPU_RSS_START].available = true;
108 metrics[METRIC_CPU_RSS_DURATION].available = true;
109 if (supportsTimestamp) metrics[METRIC_GPU_START].available = true;
110 if (supportsElapsed) {
111 GLint bits = 0;
112 glGetQueryiv(GL_TIME_ELAPSED, GL_QUERY_COUNTER_BITS, &bits);
113 if (bits) metrics[METRIC_GPU_DURATION].available = true;
114 }
115 if (supportsOcclusion) {
116 metrics[METRIC_GPU_PIXELS].available = true;
117 }
118
119 // populate lookups
120 for (auto &m : metrics) {
121 idLookup[std::make_pair(m.groupId(), m.id())] = &m;
122 nameLookup[m.name()] = &m;
123 }
124}
125
126int64_t MetricBackend_opengl::getCurrentTime(void) {
127 if (supportsTimestamp && cpugpuSync) {
128 /* Get the current GL time without stalling */
129 GLint64 timestamp = 0;
130 glGetInteger64v(GL_TIMESTAMP, &timestamp);
131 return timestamp;
132 } else {
133 return os::getTime();
134 }
135}
136
137int64_t MetricBackend_opengl::getTimeFrequency(void) {
138 if (supportsTimestamp && cpugpuSync) {
139 return 1000000000;
140 } else {
141 return os::timeFrequency;
142 }
143}
144
145
146bool MetricBackend_opengl::isSupported() {
147 return true;
148 // though individual metrics might be not supported
149}
150
151void MetricBackend_opengl::enumGroups(enumGroupsCallback callback, void* userData) {
152 callback(0, 0, userData); // cpu group
153 callback(1, 0, userData); // gpu group
154}
155
156std::string MetricBackend_opengl::getGroupName(unsigned group) {
157 switch(group) {
158 case 0:
159 return "CPU";
160 case 1:
161 return "GPU";
162 default:
163 return "";
164 }
165}
166
167void MetricBackend_opengl::enumMetrics(unsigned group, enumMetricsCallback callback, void* userData) {
168 for (auto &m : metrics) {
169 if (m.groupId() == group && m.available) {
170 callback(&m, 0, userData);
171 }
172 }
173}
174
175std::unique_ptr<Metric>
176MetricBackend_opengl::getMetricById(unsigned groupId, unsigned metricId) {
177 auto entryToCopy = idLookup.find(std::make_pair(groupId, metricId));
178 if (entryToCopy != idLookup.end()) {
179 return std::unique_ptr<Metric>(new Metric_opengl(*entryToCopy->second));
180 } else {
181 return nullptr;
182 }
183}
184
185std::unique_ptr<Metric>
186MetricBackend_opengl::getMetricByName(std::string metricName) {
187 auto entryToCopy = nameLookup.find(metricName);
188 if (entryToCopy != nameLookup.end()) {
189 return std::unique_ptr<Metric>(new Metric_opengl(*entryToCopy->second));
190 } else {
191 return nullptr;
192 }
193}
194
195
196int MetricBackend_opengl::enableMetric(Metric* metric, QueryBoundary pollingRule) {
197 // metric is not necessarily the same object as in metrics[]
198 auto entry = idLookup.find(std::make_pair(metric->groupId(), metric->id()));
199 if ((entry != idLookup.end()) && entry->second->available) {
200 entry->second->enabled[pollingRule] = true;
201 return 0;
202 }
203 return 1;
204}
205
206unsigned MetricBackend_opengl::generatePasses() {
207 // draw calls profiling not needed if all calls are profiled
208 for (int i = 0; i < METRIC_LIST_END; i++) {
209 if (metrics[i].enabled[QUERY_BOUNDARY_CALL]) {
210 metrics[i].enabled[QUERY_BOUNDARY_DRAWCALL] = false;
211 }
212 }
213 // setup storage for profiled metrics
214 for (int i = 0; i < METRIC_LIST_END; i++) {
215 for (int j = 0; j < QUERY_BOUNDARY_LIST_END; j++) {
216 if (metrics[i].enabled[j]) {
Alexander Trukhinca8139c2015-08-26 14:58:26 +0300217 data[i][j] = std::unique_ptr<Storage>(new Storage(alloc));
Alexander Trukhinb90d8002015-08-14 15:55:14 +0300218 }
219 }
220 }
221 // check if GL queries are needed
222 glQueriesNeededAnyBoundary = false;
223 for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) {
224 if (metrics[METRIC_GPU_START].enabled[i] ||
225 metrics[METRIC_GPU_DURATION].enabled[i] ||
226 metrics[METRIC_GPU_PIXELS].enabled[i])
227 {
228 glQueriesNeeded[i] = true;
229 glQueriesNeededAnyBoundary = true;
230 } else {
231 glQueriesNeeded[i] = false;
232 }
233 }
234 // check if CPU <-> GPU sync is required
235 // this is the case if any gpu time is requested
236 cpugpuSync = false;
237 for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) {
238 if (metrics[METRIC_GPU_START].enabled[i] ||
239 metrics[METRIC_GPU_DURATION].enabled[i])
240 {
241 cpugpuSync = true;
242 break;
243 }
244 }
245 // check if two passes are needed
246 // GL_TIME_ELAPSED (gpu dur) and GL_SAMPLES_PASSED (pixels) cannot be nested
247 if (!supportsTimestamp &&
248 metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_FRAME] &&
249 (metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_CALL] ||
250 metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_DRAWCALL]))
251 {
252 twoPasses = true;
253 }
254 if (metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_FRAME] &&
255 (metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_CALL] ||
256 metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_DRAWCALL]))
257 {
258 twoPasses = true;
259 }
260
261 curPass = 1;
262 return twoPasses ? 2 : 1;
263}
264
265void MetricBackend_opengl::beginPass() {
266 if (curPass == 1) {
267 for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) {
268 for (auto &m : metrics) {
269 if (m.enabled[i]) m.profiled[i] = true;
270 }
271 }
272 // profile frames in first pass
273 if (twoPasses) {
274 if (!supportsTimestamp) {
275 metrics[METRIC_GPU_DURATION].profiled[QUERY_BOUNDARY_DRAWCALL] = false;
276 metrics[METRIC_GPU_DURATION].profiled[QUERY_BOUNDARY_CALL] = false;
277 }
278 metrics[METRIC_GPU_PIXELS].profiled[QUERY_BOUNDARY_DRAWCALL] = false;
279 metrics[METRIC_GPU_PIXELS].profiled[QUERY_BOUNDARY_CALL] = false;
280 }
281 }
282 else if (curPass == 2) {
283 for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) {
284 for (auto &m : metrics) {
285 m.profiled[i] = false;
286 }
287 }
288 // profile calls/draw calls in second pass
289 if (!supportsTimestamp) {
290 if (metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_DRAWCALL]) {
291 metrics[METRIC_GPU_DURATION].profiled[QUERY_BOUNDARY_DRAWCALL] = true;
292 }
293 if (metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_CALL]) {
294 metrics[METRIC_GPU_DURATION].profiled[QUERY_BOUNDARY_CALL] = true;
295 }
296 }
297 if (metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_DRAWCALL]) {
298 metrics[METRIC_GPU_PIXELS].profiled[QUERY_BOUNDARY_DRAWCALL] = true;
299 }
300 if (metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_CALL]) {
301 metrics[METRIC_GPU_PIXELS].profiled[QUERY_BOUNDARY_CALL] = true;
302 }
303 }
304 // setup times
305 cpuTimeScale = 1.0E9 / getTimeFrequency();
306 baseTime = getCurrentTime() * cpuTimeScale;
307}
308
309void MetricBackend_opengl::processQueries() {
310 int64_t gpuStart, gpuEnd, pixels;
311 for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) {
312 QueryBoundary boundary = static_cast<QueryBoundary>(i);
313 while (!queries[i].empty()) {
314 auto &query = queries[i].front();
315 if (metrics[METRIC_GPU_START].profiled[i]) {
316 glGetQueryObjecti64v(query[QUERY_GPU_START], GL_QUERY_RESULT,
317 &gpuStart);
318 int64_t value = gpuStart - baseTime;
319 data[METRIC_GPU_START][i]->addData(boundary, value);
320 }
321 if (metrics[METRIC_GPU_DURATION].profiled[i]) {
322 if (supportsTimestamp) {
323 glGetQueryObjecti64v(query[QUERY_GPU_DURATION], GL_QUERY_RESULT,
324 &gpuEnd);
325 gpuEnd -= gpuStart;
326 } else {
327 glGetQueryObjecti64vEXT(query[QUERY_GPU_DURATION], GL_QUERY_RESULT,
328 &gpuEnd);
329 }
330 data[METRIC_GPU_DURATION][i]->addData(boundary, gpuEnd);
331 }
332 if (metrics[METRIC_GPU_PIXELS].profiled[i]) {
333 if (supportsTimestamp) {
334 glGetQueryObjecti64v(query[QUERY_OCCLUSION], GL_QUERY_RESULT, &pixels);
335 } else if (supportsElapsed) {
336 glGetQueryObjecti64vEXT(query[QUERY_OCCLUSION], GL_QUERY_RESULT, &pixels);
337 } else {
338 uint32_t pixels32;
339 glGetQueryObjectuiv(query[QUERY_OCCLUSION], GL_QUERY_RESULT, &pixels32);
340 pixels = static_cast<int64_t>(pixels32);
341 }
342 data[METRIC_GPU_PIXELS][i]->addData(boundary, pixels);
343 }
344 glDeleteQueries(QUERY_LIST_END, query.data());
345 queries[i].pop();
346 }
347 }
348}
349
350void MetricBackend_opengl::endPass() {
351 // process rest of the queries (it can be the last frame)
Alexander Trukhinb90d8002015-08-14 15:55:14 +0300352 processQueries();
353 curPass++;
354}
355
Alexander Trukhin1d0bf772015-08-24 23:17:43 +0300356void MetricBackend_opengl::pausePass() {
357 if (queryInProgress[QUERY_BOUNDARY_FRAME]) endQuery(QUERY_BOUNDARY_FRAME);
358 processQueries();
359}
360
361void MetricBackend_opengl::continuePass() {
362 // TODO if context switches check what it actually supports
363}
364
Alexander Trukhinb90d8002015-08-14 15:55:14 +0300365void MetricBackend_opengl::beginQuery(QueryBoundary boundary) {
366 // GPU related
367 if (glQueriesNeeded[boundary]) {
368 std::array<GLuint, QUERY_LIST_END> query;
369 glGenQueries(QUERY_LIST_END, query.data());
370
371 if (metrics[METRIC_GPU_START].profiled[boundary] ||
372 (metrics[METRIC_GPU_DURATION].profiled[boundary] && supportsTimestamp))
373 {
374 glQueryCounter(query[QUERY_GPU_START], GL_TIMESTAMP);
375 }
376 if (metrics[METRIC_GPU_DURATION].profiled[boundary] && !supportsTimestamp) {
377 glBeginQuery(GL_TIME_ELAPSED, query[QUERY_GPU_DURATION]);
378 }
379 if (metrics[METRIC_GPU_PIXELS].profiled[boundary]) {
380 glBeginQuery(GL_SAMPLES_PASSED, query[QUERY_OCCLUSION]);
381 }
382 queries[boundary].push(std::move(query));
383 }
384
385
386 // CPU related
387 if (metrics[METRIC_CPU_START].profiled[boundary] ||
388 metrics[METRIC_CPU_DURATION].profiled[boundary])
389 {
390 cpuStart[boundary] = getCurrentTime();
391 if (metrics[METRIC_CPU_START].profiled[boundary]) {
392 int64_t time = cpuStart[boundary] * cpuTimeScale - baseTime;
393 data[METRIC_CPU_START][boundary]->addData(boundary, time);
394 }
395 }
396 if (metrics[METRIC_CPU_VSIZE_START].profiled[boundary] ||
397 metrics[METRIC_CPU_VSIZE_DURATION].profiled[boundary])
398 {
399 vsizeStart[boundary] = os::getVsize();
400 if (metrics[METRIC_CPU_VSIZE_START].profiled[boundary]) {
401 int64_t time = vsizeStart[boundary];
402 data[METRIC_CPU_VSIZE_START][boundary]->addData(boundary, time);
403 }
404 }
405 if (metrics[METRIC_CPU_RSS_START].profiled[boundary] ||
406 metrics[METRIC_CPU_RSS_DURATION].profiled[boundary])
407 {
408 rssStart[boundary] = os::getRss();
409 if (metrics[METRIC_CPU_RSS_START].profiled[boundary]) {
410 int64_t time = rssStart[boundary];
411 data[METRIC_CPU_RSS_START][boundary]->addData(boundary, time);
412 }
413 }
Alexander Trukhin1d0bf772015-08-24 23:17:43 +0300414 queryInProgress[boundary] = true;
Alexander Trukhinb90d8002015-08-14 15:55:14 +0300415 // DRAWCALL is a CALL
416 if (boundary == QUERY_BOUNDARY_DRAWCALL) beginQuery(QUERY_BOUNDARY_CALL);
417}
418
419void MetricBackend_opengl::endQuery(QueryBoundary boundary) {
Alexander Trukhin1d0bf772015-08-24 23:17:43 +0300420 if (queryInProgress[boundary]) {
421 // CPU related
422 if (metrics[METRIC_CPU_DURATION].profiled[boundary])
423 {
424 cpuEnd[boundary] = getCurrentTime();
425 int64_t time = (cpuEnd[boundary] - cpuStart[boundary]) * cpuTimeScale;
426 data[METRIC_CPU_DURATION][boundary]->addData(boundary, time);
Alexander Trukhinb90d8002015-08-14 15:55:14 +0300427 }
Alexander Trukhin1d0bf772015-08-24 23:17:43 +0300428 if (metrics[METRIC_CPU_VSIZE_DURATION].profiled[boundary])
429 {
430 vsizeEnd[boundary] = os::getVsize();
431 int64_t time = vsizeEnd[boundary] - vsizeStart[boundary];
432 data[METRIC_CPU_VSIZE_DURATION][boundary]->addData(boundary, time);
Alexander Trukhinb90d8002015-08-14 15:55:14 +0300433 }
Alexander Trukhin1d0bf772015-08-24 23:17:43 +0300434 if (metrics[METRIC_CPU_RSS_DURATION].profiled[boundary])
435 {
436 rssEnd[boundary] = os::getRss();
437 int64_t time = rssEnd[boundary] - rssStart[boundary];
438 data[METRIC_CPU_RSS_DURATION][boundary]->addData(boundary, time);
439 }
440 // GPU related
441 if (glQueriesNeeded[boundary]) {
442 std::array<GLuint, QUERY_LIST_END> &query = queries[boundary].back();
443 if (metrics[METRIC_GPU_DURATION].profiled[boundary] && supportsTimestamp) {
444 // GL_TIME_ELAPSED cannot be used in nested queries
445 // so prefer this if timestamps are supported
446 glQueryCounter(query[QUERY_GPU_DURATION], GL_TIMESTAMP);
447 }
448 if (metrics[METRIC_GPU_PIXELS].profiled[boundary]) {
449 glEndQuery(GL_SAMPLES_PASSED);
450 }
451 }
452 queryInProgress[boundary] = false;
Alexander Trukhinb90d8002015-08-14 15:55:14 +0300453 }
454 // DRAWCALL is a CALL
455 if (boundary == QUERY_BOUNDARY_DRAWCALL) endQuery(QUERY_BOUNDARY_CALL);
456 // clear queries after each frame
457 if (boundary == QUERY_BOUNDARY_FRAME && glQueriesNeededAnyBoundary) {
458 processQueries();
459 }
460}
461
462void MetricBackend_opengl::enumDataQueryId(unsigned id, enumDataCallback callback,
463 QueryBoundary boundary, void* userData) {
464 for (int i = 0; i < METRIC_LIST_END; i++) {
465 Metric_opengl &metric = metrics[i];
466 if (metric.enabled[boundary]) {
467 callback(&metric, id, data[i][boundary]->getData(boundary, id), 0,
468 userData);
469 }
470 }
471}
472
473unsigned MetricBackend_opengl::getNumPasses() {
474 return twoPasses ? 2 : 1;
475}
476
477MetricBackend_opengl&
Alexander Trukhinca8139c2015-08-26 14:58:26 +0300478MetricBackend_opengl::getInstance(glretrace::Context* context, MmapAllocator<char> &alloc) {
479 static MetricBackend_opengl backend(context, alloc);
Alexander Trukhinb90d8002015-08-14 15:55:14 +0300480 return backend;
481}