/************************************************************************** * * Copyright 2015 Alexander Trukhin * All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * **************************************************************************/ #include "metric_backend_opengl.hpp" #include "os_time.hpp" #include "os_memory.hpp" void MetricBackend_opengl::Storage::addData(QueryBoundary boundary, int64_t data) { this->data[boundary].push_back(data); } int64_t* MetricBackend_opengl::Storage::getData(QueryBoundary boundary, unsigned eventId) { return &(data[boundary][eventId]); } Metric_opengl::Metric_opengl(unsigned gId, unsigned id, const std::string &name, const std::string &desc, MetricNumType nT, MetricType t) : m_gId(gId), m_id(id), m_name(name), m_desc(desc), m_nType(nT), m_type(t), available(false) { for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) { profiled[i] = false; enabled[i] = false; } } unsigned Metric_opengl::id() { return m_id; } unsigned Metric_opengl::groupId() { return m_gId; } std::string Metric_opengl::name() { return m_name; } std::string Metric_opengl::description() { return m_desc; } MetricNumType Metric_opengl::numType() { return m_nType; } MetricType Metric_opengl::type() { return m_type; } MetricBackend_opengl::MetricBackend_opengl(glretrace::Context* context, MmapAllocator &alloc) : alloc(alloc) { glfeatures::Profile currentProfile = context->actualProfile(); supportsTimestamp = currentProfile.versionGreaterOrEqual(glfeatures::API_GL, 3, 3) || context->hasExtension("GL_ARB_timer_query"); supportsElapsed = context->hasExtension("GL_EXT_timer_query") || supportsTimestamp; supportsOcclusion = currentProfile.versionGreaterOrEqual(glfeatures::API_GL, 1, 5); #ifdef __APPLE__ // GL_TIMESTAMP doesn't work on Apple. GL_TIME_ELAPSED still does however. // http://lists.apple.com/archives/mac-opengl/2014/Nov/threads.html#00001 supportsTimestamp = false; #endif // Add metrics below metrics.emplace_back(0, 0, "CPU Start", "", CNT_NUM_INT64, CNT_TYPE_TIMESTAMP); metrics.emplace_back(0, 1, "CPU Duration", "", CNT_NUM_INT64, CNT_TYPE_DURATION); metrics.emplace_back(1, 0, "GPU Start", "", CNT_NUM_INT64, CNT_TYPE_TIMESTAMP); metrics.emplace_back(1, 1, "GPU Duration", "", CNT_NUM_INT64, CNT_TYPE_DURATION); metrics.emplace_back(1, 2, "Pixels Drawn", "", CNT_NUM_INT64, CNT_TYPE_GENERIC); metrics.emplace_back(0, 2, "VSIZE Start", "", CNT_NUM_INT64, CNT_TYPE_GENERIC); metrics.emplace_back(0, 3, "VSIZE Duration", "", CNT_NUM_INT64, CNT_TYPE_GENERIC); metrics.emplace_back(0, 4, "RSS Start", "", CNT_NUM_INT64, CNT_TYPE_GENERIC); metrics.emplace_back(0, 5, "RSS Duration", "", CNT_NUM_INT64, CNT_TYPE_GENERIC); metrics[METRIC_CPU_START].available = true; metrics[METRIC_CPU_DURATION].available = true; metrics[METRIC_CPU_VSIZE_START].available = true; metrics[METRIC_CPU_VSIZE_DURATION].available = true; metrics[METRIC_CPU_RSS_START].available = true; metrics[METRIC_CPU_RSS_DURATION].available = true; if (supportsTimestamp) metrics[METRIC_GPU_START].available = true; if (supportsElapsed) { GLint bits = 0; glGetQueryiv(GL_TIME_ELAPSED, GL_QUERY_COUNTER_BITS, &bits); if (bits) metrics[METRIC_GPU_DURATION].available = true; } if (supportsOcclusion) { metrics[METRIC_GPU_PIXELS].available = true; } // populate lookups for (auto &m : metrics) { idLookup[std::make_pair(m.groupId(), m.id())] = &m; nameLookup[m.name()] = &m; } } int64_t MetricBackend_opengl::getCurrentTime(void) { if (supportsTimestamp && cpugpuSync) { /* Get the current GL time without stalling */ GLint64 timestamp = 0; glGetInteger64v(GL_TIMESTAMP, ×tamp); return timestamp; } else { return os::getTime(); } } int64_t MetricBackend_opengl::getTimeFrequency(void) { if (supportsTimestamp && cpugpuSync) { return 1000000000; } else { return os::timeFrequency; } } bool MetricBackend_opengl::isSupported() { return true; // though individual metrics might be not supported } void MetricBackend_opengl::enumGroups(enumGroupsCallback callback, void* userData) { callback(0, 0, userData); // cpu group callback(1, 0, userData); // gpu group } std::string MetricBackend_opengl::getGroupName(unsigned group) { switch(group) { case 0: return "CPU"; case 1: return "GPU"; default: return ""; } } void MetricBackend_opengl::enumMetrics(unsigned group, enumMetricsCallback callback, void* userData) { for (auto &m : metrics) { if (m.groupId() == group && m.available) { callback(&m, 0, userData); } } } std::unique_ptr MetricBackend_opengl::getMetricById(unsigned groupId, unsigned metricId) { auto entryToCopy = idLookup.find(std::make_pair(groupId, metricId)); if (entryToCopy != idLookup.end()) { return std::unique_ptr(new Metric_opengl(*entryToCopy->second)); } else { return nullptr; } } std::unique_ptr MetricBackend_opengl::getMetricByName(std::string metricName) { auto entryToCopy = nameLookup.find(metricName); if (entryToCopy != nameLookup.end()) { return std::unique_ptr(new Metric_opengl(*entryToCopy->second)); } else { return nullptr; } } int MetricBackend_opengl::enableMetric(Metric* metric, QueryBoundary pollingRule) { // metric is not necessarily the same object as in metrics[] auto entry = idLookup.find(std::make_pair(metric->groupId(), metric->id())); if ((entry != idLookup.end()) && entry->second->available) { entry->second->enabled[pollingRule] = true; return 0; } return 1; } unsigned MetricBackend_opengl::generatePasses() { // draw calls profiling not needed if all calls are profiled for (int i = 0; i < METRIC_LIST_END; i++) { if (metrics[i].enabled[QUERY_BOUNDARY_CALL]) { metrics[i].enabled[QUERY_BOUNDARY_DRAWCALL] = false; } } // setup storage for profiled metrics for (int i = 0; i < METRIC_LIST_END; i++) { for (int j = 0; j < QUERY_BOUNDARY_LIST_END; j++) { if (metrics[i].enabled[j]) { data[i][j] = std::unique_ptr(new Storage(alloc)); } } } // check if GL queries are needed glQueriesNeededAnyBoundary = false; for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) { if (metrics[METRIC_GPU_START].enabled[i] || metrics[METRIC_GPU_DURATION].enabled[i] || metrics[METRIC_GPU_PIXELS].enabled[i]) { glQueriesNeeded[i] = true; glQueriesNeededAnyBoundary = true; } else { glQueriesNeeded[i] = false; } } // check if CPU <-> GPU sync is required // this is the case if any gpu time is requested cpugpuSync = false; for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) { if (metrics[METRIC_GPU_START].enabled[i] || metrics[METRIC_GPU_DURATION].enabled[i]) { cpugpuSync = true; break; } } // check if two passes are needed // GL_TIME_ELAPSED (gpu dur) and GL_SAMPLES_PASSED (pixels) cannot be nested if (!supportsTimestamp && metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_FRAME] && (metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_CALL] || metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_DRAWCALL])) { twoPasses = true; } if (metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_FRAME] && (metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_CALL] || metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_DRAWCALL])) { twoPasses = true; } curPass = 1; return twoPasses ? 2 : 1; } void MetricBackend_opengl::beginPass() { if (curPass == 1) { for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) { for (auto &m : metrics) { if (m.enabled[i]) m.profiled[i] = true; } } // profile frames in first pass if (twoPasses) { if (!supportsTimestamp) { metrics[METRIC_GPU_DURATION].profiled[QUERY_BOUNDARY_DRAWCALL] = false; metrics[METRIC_GPU_DURATION].profiled[QUERY_BOUNDARY_CALL] = false; } metrics[METRIC_GPU_PIXELS].profiled[QUERY_BOUNDARY_DRAWCALL] = false; metrics[METRIC_GPU_PIXELS].profiled[QUERY_BOUNDARY_CALL] = false; } } else if (curPass == 2) { for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) { for (auto &m : metrics) { m.profiled[i] = false; } } // profile calls/draw calls in second pass if (!supportsTimestamp) { if (metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_DRAWCALL]) { metrics[METRIC_GPU_DURATION].profiled[QUERY_BOUNDARY_DRAWCALL] = true; } if (metrics[METRIC_GPU_DURATION].enabled[QUERY_BOUNDARY_CALL]) { metrics[METRIC_GPU_DURATION].profiled[QUERY_BOUNDARY_CALL] = true; } } if (metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_DRAWCALL]) { metrics[METRIC_GPU_PIXELS].profiled[QUERY_BOUNDARY_DRAWCALL] = true; } if (metrics[METRIC_GPU_PIXELS].enabled[QUERY_BOUNDARY_CALL]) { metrics[METRIC_GPU_PIXELS].profiled[QUERY_BOUNDARY_CALL] = true; } } // setup times cpuTimeScale = 1.0E9 / getTimeFrequency(); baseTime = getCurrentTime() * cpuTimeScale; } void MetricBackend_opengl::processQueries() { int64_t gpuStart, gpuEnd, pixels; for (int i = 0; i < QUERY_BOUNDARY_LIST_END; i++) { QueryBoundary boundary = static_cast(i); while (!queries[i].empty()) { auto &query = queries[i].front(); if (metrics[METRIC_GPU_START].profiled[i]) { glGetQueryObjecti64v(query[QUERY_GPU_START], GL_QUERY_RESULT, &gpuStart); int64_t value = gpuStart - baseTime; data[METRIC_GPU_START][i]->addData(boundary, value); } if (metrics[METRIC_GPU_DURATION].profiled[i]) { if (supportsTimestamp) { glGetQueryObjecti64v(query[QUERY_GPU_DURATION], GL_QUERY_RESULT, &gpuEnd); gpuEnd -= gpuStart; } else { glGetQueryObjecti64vEXT(query[QUERY_GPU_DURATION], GL_QUERY_RESULT, &gpuEnd); } data[METRIC_GPU_DURATION][i]->addData(boundary, gpuEnd); } if (metrics[METRIC_GPU_PIXELS].profiled[i]) { if (supportsTimestamp) { glGetQueryObjecti64v(query[QUERY_OCCLUSION], GL_QUERY_RESULT, &pixels); } else if (supportsElapsed) { glGetQueryObjecti64vEXT(query[QUERY_OCCLUSION], GL_QUERY_RESULT, &pixels); } else { uint32_t pixels32; glGetQueryObjectuiv(query[QUERY_OCCLUSION], GL_QUERY_RESULT, &pixels32); pixels = static_cast(pixels32); } data[METRIC_GPU_PIXELS][i]->addData(boundary, pixels); } glDeleteQueries(QUERY_LIST_END, query.data()); queries[i].pop(); } } } void MetricBackend_opengl::endPass() { // process rest of the queries (it can be the last frame) processQueries(); curPass++; } void MetricBackend_opengl::pausePass() { if (queryInProgress[QUERY_BOUNDARY_FRAME]) endQuery(QUERY_BOUNDARY_FRAME); processQueries(); } void MetricBackend_opengl::continuePass() { // TODO if context switches check what it actually supports } void MetricBackend_opengl::beginQuery(QueryBoundary boundary) { // GPU related if (glQueriesNeeded[boundary]) { std::array query; glGenQueries(QUERY_LIST_END, query.data()); if (metrics[METRIC_GPU_START].profiled[boundary] || (metrics[METRIC_GPU_DURATION].profiled[boundary] && supportsTimestamp)) { glQueryCounter(query[QUERY_GPU_START], GL_TIMESTAMP); } if (metrics[METRIC_GPU_DURATION].profiled[boundary] && !supportsTimestamp) { glBeginQuery(GL_TIME_ELAPSED, query[QUERY_GPU_DURATION]); } if (metrics[METRIC_GPU_PIXELS].profiled[boundary]) { glBeginQuery(GL_SAMPLES_PASSED, query[QUERY_OCCLUSION]); } queries[boundary].push(std::move(query)); } // CPU related if (metrics[METRIC_CPU_START].profiled[boundary] || metrics[METRIC_CPU_DURATION].profiled[boundary]) { cpuStart[boundary] = getCurrentTime(); if (metrics[METRIC_CPU_START].profiled[boundary]) { int64_t time = cpuStart[boundary] * cpuTimeScale - baseTime; data[METRIC_CPU_START][boundary]->addData(boundary, time); } } if (metrics[METRIC_CPU_VSIZE_START].profiled[boundary] || metrics[METRIC_CPU_VSIZE_DURATION].profiled[boundary]) { vsizeStart[boundary] = os::getVsize(); if (metrics[METRIC_CPU_VSIZE_START].profiled[boundary]) { int64_t time = vsizeStart[boundary]; data[METRIC_CPU_VSIZE_START][boundary]->addData(boundary, time); } } if (metrics[METRIC_CPU_RSS_START].profiled[boundary] || metrics[METRIC_CPU_RSS_DURATION].profiled[boundary]) { rssStart[boundary] = os::getRss(); if (metrics[METRIC_CPU_RSS_START].profiled[boundary]) { int64_t time = rssStart[boundary]; data[METRIC_CPU_RSS_START][boundary]->addData(boundary, time); } } queryInProgress[boundary] = true; // DRAWCALL is a CALL if (boundary == QUERY_BOUNDARY_DRAWCALL) beginQuery(QUERY_BOUNDARY_CALL); } void MetricBackend_opengl::endQuery(QueryBoundary boundary) { if (queryInProgress[boundary]) { // CPU related if (metrics[METRIC_CPU_DURATION].profiled[boundary]) { cpuEnd[boundary] = getCurrentTime(); int64_t time = (cpuEnd[boundary] - cpuStart[boundary]) * cpuTimeScale; data[METRIC_CPU_DURATION][boundary]->addData(boundary, time); } if (metrics[METRIC_CPU_VSIZE_DURATION].profiled[boundary]) { vsizeEnd[boundary] = os::getVsize(); int64_t time = vsizeEnd[boundary] - vsizeStart[boundary]; data[METRIC_CPU_VSIZE_DURATION][boundary]->addData(boundary, time); } if (metrics[METRIC_CPU_RSS_DURATION].profiled[boundary]) { rssEnd[boundary] = os::getRss(); int64_t time = rssEnd[boundary] - rssStart[boundary]; data[METRIC_CPU_RSS_DURATION][boundary]->addData(boundary, time); } // GPU related if (glQueriesNeeded[boundary]) { std::array &query = queries[boundary].back(); if (metrics[METRIC_GPU_DURATION].profiled[boundary] && supportsTimestamp) { // GL_TIME_ELAPSED cannot be used in nested queries // so prefer this if timestamps are supported glQueryCounter(query[QUERY_GPU_DURATION], GL_TIMESTAMP); } if (metrics[METRIC_GPU_PIXELS].profiled[boundary]) { glEndQuery(GL_SAMPLES_PASSED); } } queryInProgress[boundary] = false; } // DRAWCALL is a CALL if (boundary == QUERY_BOUNDARY_DRAWCALL) endQuery(QUERY_BOUNDARY_CALL); // clear queries after each frame if (boundary == QUERY_BOUNDARY_FRAME && glQueriesNeededAnyBoundary) { processQueries(); } } void MetricBackend_opengl::enumDataQueryId(unsigned id, enumDataCallback callback, QueryBoundary boundary, void* userData) { for (int i = 0; i < METRIC_LIST_END; i++) { Metric_opengl &metric = metrics[i]; if (metric.enabled[boundary]) { callback(&metric, id, data[i][boundary]->getData(boundary, id), 0, userData); } } } unsigned MetricBackend_opengl::getNumPasses() { return twoPasses ? 2 : 1; } MetricBackend_opengl& MetricBackend_opengl::getInstance(glretrace::Context* context, MmapAllocator &alloc) { static MetricBackend_opengl backend(context, alloc); return backend; }