endif ()
add_library (common STATIC
+ common/trace_callset.cpp
common/trace_dump.cpp
common/trace_file.cpp
common/trace_file_read.cpp
===========================
+Call sets
+---------
+
+Several tools take `CALLSET` arguments, e.g:
+
+ apitrace dump --calls CALLSET foo.trace
+ glretrace -S CALLSET foo.trace
+
+The call syntax is very flexible. Here are a few examples:
+
+ * `4` one call
+
+ * `1,2,4,5` set of calls
+
+ * `"1 2 4 5"` set of calls (commas are optional and can be replaced with whitespace)
+
+ * `1-100/2` calls 1, 3, 5, ..., 99
+
+ * `1-1000/draw` all draw calls between 1 and 1000
+
+ * `1-1000/fbo` all fbo changes between calls 1 and 1000
+
+ * `frame` all calls at end of frames
+
+ * `@foo.txt` read call numbers from `foo.txt`, using the same syntax as above
+
+
+
Tracing manually
----------------
#include "trace_parser.hpp"
#include "trace_dump.hpp"
+#include "trace_callset.hpp"
enum ColorOption {
static bool verbose = false;
+static trace::CallSet calls(trace::FREQUENCY_ALL);
+
static const char *synopsis = "Dump given trace(s) to standard output.";
static void
<< synopsis << "\n"
"\n"
" -v, --verbose verbose output\n"
+ " --calls <CALLSET> Only dump specified calls\n"
" --color=<WHEN>\n"
" --colour=<WHEN> Colored syntax highlighting\n"
" WHEN is 'auto', 'always', or 'never'\n"
int i;
- for (i = 0; i < argc; ++i) {
+ for (i = 0; i < argc;) {
const char *arg = argv[i];
if (arg[0] != '-') {
break;
}
+ ++i;
+
if (!strcmp(arg, "--")) {
break;
} else if (!strcmp(arg, "--help")) {
} else if (strcmp(arg, "-v") == 0 ||
strcmp(arg, "--verbose") == 0) {
verbose = true;
+ } else if (!strcmp(arg, "--calls")) {
+ calls = trace::CallSet(argv[i++]);
} else if (!strcmp(arg, "--color=auto") ||
!strcmp(arg, "--colour=auto")) {
color = COLOR_OPTION_AUTO;
trace::Call *call;
while ((call = p.parse_call())) {
- if (verbose ||
- !(call->flags & trace::CALL_FLAG_VERBOSE)) {
- if (dumpThreadIds) {
- std::cout << std::hex << call->thread_id << std::dec << " ";
+ if (calls.contains(*call)) {
+ if (verbose ||
+ !(call->flags & trace::CALL_FLAG_VERBOSE)) {
+ if (dumpThreadIds) {
+ std::cout << std::hex << call->thread_id << std::dec << " ";
+ }
+ trace::dump(*call, std::cout, dumpFlags);
}
- trace::dump(*call, std::cout, dumpFlags);
}
delete call;
}
--- /dev/null
+/**************************************************************************
+ *
+ * Copyright 2012 VMware, Inc.
+ * 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 <assert.h>
+#include <stdlib.h>
+
+#include <limits>
+#include <fstream>
+#include <iostream>
+#include <string>
+
+#include <trace_callset.hpp>
+
+
+using namespace trace;
+
+
+// Parser class for call sets
+class CallSetParser
+{
+ CallSet &set;
+
+protected:
+ char lookahead;
+
+ CallSetParser(CallSet &_set) :
+ set(_set),
+ lookahead(0)
+ {}
+
+public:
+ void parse() {
+ skipWhiteSpace();
+ while (lookahead) {
+ assert(!isSpace());
+ parseRange();
+ // skip any comma
+ isOperator(',');
+ }
+ }
+
+private:
+ void parseRange() {
+ CallNo start = std::numeric_limits<CallNo>::min();
+ CallNo stop = std::numeric_limits<CallNo>::max();
+ CallNo step = 1;
+ CallFlags freq = FREQUENCY_ALL;
+ if (isAlpha()) {
+ freq = parseFrequency();
+ } else {
+ if (isOperator('*')) {
+ // no-change
+ } else {
+ start = parseCallNo();
+ if (isOperator('-')) {
+ if (isDigit()) {
+ stop = parseCallNo();
+ } else {
+ // no-change
+ }
+ } else {
+ stop = start;
+ }
+ }
+ if (isOperator('/')) {
+ if (isDigit()) {
+ step = parseCallNo();
+ } else {
+ freq = parseFrequency();
+ }
+ }
+ }
+ set.addRange(CallRange(start, stop, step, freq));
+ }
+
+ // match and consume an operator
+ bool isOperator(char c) {
+ if (lookahead == c) {
+ consume();
+ skipWhiteSpace();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ CallNo parseCallNo() {
+ CallNo number = 0;
+ if (isDigit()) {
+ do {
+ CallNo digit = consume() - '0';
+ number = number * 10 + digit;
+ } while (isDigit());
+ } else {
+ std::cerr << "error: expected digit, found '" << lookahead << "'\n";
+ exit(0);
+ }
+ skipWhiteSpace();
+ return number;
+ }
+
+ CallFlags parseFrequency() {
+ std::string freq;
+ if (isAlpha()) {
+ do {
+ freq.push_back(consume());
+ } while (isAlpha());
+ } else {
+ std::cerr << "error: expected frequency, found '" << lookahead << "'\n";
+ exit(0);
+ }
+ skipWhiteSpace();
+ if (freq == "frame") {
+ return FREQUENCY_FRAME;
+ } else if (freq == "rendertarget" || freq == "fbo") {
+ return FREQUENCY_RENDERTARGET;
+ } else if (freq == "render" || freq == "draw") {
+ return FREQUENCY_RENDER;
+ } else {
+ std::cerr << "error: expected frequency, found '" << freq << "'\n";
+ exit(0);
+ return FREQUENCY_NONE;
+ }
+ }
+
+ // match lookahead with a digit (does not consume)
+ bool isDigit() const {
+ return lookahead >= '0' && lookahead <= '9';
+ }
+
+ bool isAlpha() const {
+ return lookahead >= 'a' && lookahead <= 'z';
+ }
+
+ void skipWhiteSpace() {
+ while (isSpace()) {
+ consume();
+ }
+ }
+
+ bool isSpace() const {
+ return lookahead == ' ' ||
+ lookahead == '\t' ||
+ lookahead == '\r' ||
+ lookahead == '\n';
+ }
+
+ virtual char consume() = 0;
+};
+
+
+class StringCallSetParser : public CallSetParser
+{
+ const char *buf;
+
+public:
+ StringCallSetParser(CallSet &_set, const char *_buf) :
+ CallSetParser(_set),
+ buf(_buf)
+ {
+ lookahead = *buf;
+ }
+
+ char consume() {
+ char c = lookahead;
+ if (lookahead) {
+ ++buf;
+ lookahead = *buf;
+ }
+ return c;
+ }
+};
+
+
+class FileCallSetParser : public CallSetParser
+{
+ std::ifstream stream;
+
+public:
+ FileCallSetParser(CallSet &_set, const char *filename) :
+ CallSetParser(_set)
+ {
+ stream.open(filename);
+ if (!stream.is_open()) {
+ std::cerr << "error: failed to open \"" << filename << "\"\n";
+ exit(1);
+ }
+
+ stream.get(lookahead);
+ }
+
+ char consume() {
+ char c = lookahead;
+ if (stream.eof()) {
+ lookahead = 0;
+ } else {
+ stream.get(lookahead);
+ }
+ return c;
+ }
+};
+
+
+CallSet::CallSet(const char *string)
+{
+ if (*string == '@') {
+ FileCallSetParser parser(*this, &string[1]);
+ parser.parse();
+ } else {
+ StringCallSetParser parser(*this, string);
+ parser.parse();
+ }
+}
+
+
+CallSet::CallSet(CallFlags freq) {
+ if (freq != FREQUENCY_NONE) {
+ CallNo start = std::numeric_limits<CallNo>::min();
+ CallNo stop = std::numeric_limits<CallNo>::max();
+ CallNo step = 1;
+ addRange(CallRange(start, stop, step, freq));
+ assert(!empty());
+ }
+}
--- /dev/null
+/**************************************************************************
+ *
+ * Copyright 2012 VMware, Inc.
+ * 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.
+ *
+ **************************************************************************/
+
+/*
+ * Representation of call sets.
+ *
+ * Grammar:
+ *
+ * set = '@' filename
+ * | range ( ',' ? range ) *
+ *
+ * range = interval ( '/' frequency )
+ *
+ * interval = '*'
+ * | number
+ * | start_number '-' end_number
+ *
+ * frequency = divisor
+ * | "frame"
+ * | "rendertarget" | "fbo"
+ * | "render | "draw"
+ *
+ */
+
+#ifndef _TRACE_CALLSET_HPP_
+#define _TRACE_CALLSET_HPP_
+
+
+#include <list>
+
+#include "trace_model.hpp"
+
+
+namespace trace {
+
+
+ // Should match Call::no
+ typedef unsigned CallNo;
+
+
+ // Aliases for call flags
+ enum {
+ FREQUENCY_NONE = 0,
+ FREQUENCY_FRAME = CALL_FLAG_END_FRAME,
+ FREQUENCY_RENDERTARGET = CALL_FLAG_END_FRAME | CALL_FLAG_SWAP_RENDERTARGET,
+ FREQUENCY_RENDER = CALL_FLAG_RENDER,
+ FREQUENCY_ALL = 0xffffffff
+ };
+
+ // A linear range of calls
+ class CallRange
+ {
+ public:
+ CallNo start;
+ CallNo stop;
+ CallNo step;
+ CallFlags freq;
+
+ CallRange(CallNo callNo) :
+ start(callNo),
+ stop(callNo),
+ step(1),
+ freq(FREQUENCY_ALL)
+ {}
+
+ CallRange(CallNo _start, CallNo _stop, CallNo _step = 1, CallFlags _freq = FREQUENCY_ALL) :
+ start(_start),
+ stop(_stop),
+ step(_step),
+ freq(_freq)
+ {}
+
+ bool
+ contains(CallNo callNo, CallFlags callFlags) const {
+ return callNo >= start &&
+ callNo <= stop &&
+ ((callNo - start) % step) == 0 &&
+ ((callFlags & freq) ||
+ freq == FREQUENCY_ALL);
+ }
+ };
+
+
+ // A collection of call ranges
+ class CallSet
+ {
+ public:
+ // TODO: use binary tree to speed up lookups
+ typedef std::list< CallRange > RangeList;
+ RangeList ranges;
+
+ CallSet() {}
+
+ CallSet(CallFlags freq);
+
+ CallSet(const char *str);
+
+ // Not empty set
+ inline bool
+ empty() const {
+ return ranges.empty();
+ }
+
+ void
+ addRange(const CallRange & range) {
+ if (range.start <= range.stop &&
+ range.freq != FREQUENCY_NONE) {
+
+ RangeList::iterator it = ranges.begin();
+ while (it != ranges.end() && it->start < range.start) {
+ ++it;
+ }
+
+ ranges.insert(it, range);
+ }
+ }
+
+ inline bool
+ contains(CallNo callNo, CallFlags callFlags = FREQUENCY_ALL) const {
+ if (empty()) {
+ return false;
+ }
+ RangeList::const_iterator it;
+ for (it = ranges.begin(); it != ranges.end() && it->start <= callNo; ++it) {
+ if (it->contains(callNo, callFlags)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ inline bool
+ contains(const trace::Call &call) {
+ return contains(call.no, call.flags);
+ }
+ };
+
+
+ CallSet parse(const char *string);
+
+
+} /* namespace trace */
+
+
+#endif /* _TRACE_CALLSET_HPP_ */
extern long long startTime;
extern bool wait;
-enum frequency {
- FREQUENCY_NEVER = 0,
- FREQUENCY_FRAME,
- FREQUENCY_FRAMEBUFFER,
- FREQUENCY_DRAW,
-};
-
extern bool benchmark;
-extern const char *compare_prefix;
-extern const char *snapshot_prefix;
-extern enum frequency snapshot_frequency;
extern unsigned dump_state;
extern const retrace::Entry wgl_callbacks[];
extern const retrace::Entry egl_callbacks[];
-void snapshot(unsigned call_no);
void frame_complete(trace::Call &call);
void updateDrawable(int width, int height);
print ' GLint __pack_buffer = 0;'
print ' glGetIntegerv(GL_PIXEL_PACK_BUFFER_BINDING, &__pack_buffer);'
print ' if (!__pack_buffer) {'
- if function.name == 'glReadPixels':
- print ' glFinish();'
- print ' if (glretrace::snapshot_frequency == glretrace::FREQUENCY_FRAME ||'
- print ' glretrace::snapshot_frequency == glretrace::FREQUENCY_FRAMEBUFFER) {'
- print ' glretrace::snapshot(call.no);'
- print ' }'
print ' return;'
print ' }'
# Pre-snapshots
if function.name in self.bind_framebuffer_function_names:
print ' assert(call.flags & trace::CALL_FLAG_SWAP_RENDERTARGET);'
- print ' if (glretrace::snapshot_frequency == glretrace::FREQUENCY_FRAMEBUFFER) {'
- print ' glretrace::snapshot(call.no - 1);'
- print ' }'
if function.name == 'glFrameTerminatorGREMEDY':
print ' glretrace::frame_complete(call);'
return
print ' }'
if is_draw_array or is_draw_elements or is_misc_draw:
print ' assert(call.flags & trace::CALL_FLAG_RENDER);'
- print ' if (glretrace::snapshot_frequency == glretrace::FREQUENCY_DRAW) {'
- print ' glretrace::snapshot(call.no);'
- print ' }'
def invokeFunction(self, function):
#include "os_time.hpp"
#include "image.hpp"
#include "retrace.hpp"
+#include "trace_callset.hpp"
#include "glproc.hpp"
#include "glstate.hpp"
#include "glretrace.hpp"
bool wait = false;
bool benchmark = false;
-const char *compare_prefix = NULL;
-const char *snapshot_prefix = NULL;
-enum frequency snapshot_frequency = FREQUENCY_NEVER;
+static const char *compare_prefix = NULL;
+static const char *snapshot_prefix = NULL;
+static trace::CallSet snapshot_frequency;
+static trace::CallSet compare_frequency;
unsigned dump_state = ~0;
}
-void snapshot(unsigned call_no) {
- if (!drawable ||
- (!snapshot_prefix && !compare_prefix)) {
+static void
+snapshot(unsigned call_no) {
+ assert(snapshot_prefix || compare_prefix);
+
+ if (!drawable) {
return;
}
if (!drawable->visible) {
retrace::warning(call) << "could not infer drawable size (glViewport never called)\n";
}
-
- if (snapshot_frequency == FREQUENCY_FRAME ||
- snapshot_frequency == FREQUENCY_FRAMEBUFFER) {
- snapshot(call.no);
- }
}
trace::Call *call;
while ((call = parser.parse_call())) {
+ bool swapRenderTarget = call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET;
+ bool doSnapshot =
+ snapshot_frequency.contains(*call) ||
+ compare_frequency.contains(*call)
+ ;
+
+ // For calls which cause rendertargets to be swaped, we take the
+ // snapshot _before_ swapping the rendertargets.
+ if (doSnapshot && swapRenderTarget) {
+ if (call->flags & trace::CALL_FLAG_END_FRAME) {
+ // For swapbuffers/presents we still use this call number,
+ // spite not have been executed yet.
+ snapshot(call->no);
+ } else {
+ // Whereas for ordinate fbo/rendertarget changes we use the
+ // previous call's number.
+ snapshot(call->no - 1);
+ }
+ }
+
retracer.retrace(*call);
+ if (doSnapshot && !swapRenderTarget) {
+ snapshot(call->no);
+ }
+
if (!insideGlBeginEnd &&
drawable && context &&
call->no >= dump_state) {
"\n"
" -b benchmark mode (no error checking or warning messages)\n"
" -c PREFIX compare against snapshots\n"
+ " -C CALLSET calls to compare (default is every frame)\n"
" -core use core profile\n"
" -db use a double buffer visual (default)\n"
" -sb use a single buffer visual\n"
" -s PREFIX take snapshots; `-` for PNM stdout output\n"
- " -S FREQUENCY snapshot frequency: frame (default), framebuffer, or draw\n"
+ " -S CALLSET calls to snapshot (default is every frame)\n"
" -v increase output verbosity\n"
" -D CALLNO dump state at specific call no\n"
" -w wait on final frame\n";
extern "C"
int main(int argc, char **argv)
{
+ assert(compare_frequency.empty());
+ assert(snapshot_frequency.empty());
int i;
for (i = 1; i < argc; ++i) {
glws::debug = false;
} else if (!strcmp(arg, "-c")) {
compare_prefix = argv[++i];
- if (snapshot_frequency == FREQUENCY_NEVER) {
- snapshot_frequency = FREQUENCY_FRAME;
+ if (compare_frequency.empty()) {
+ compare_frequency = trace::CallSet(trace::FREQUENCY_FRAME);
+ }
+ } else if (!strcmp(arg, "-C")) {
+ compare_frequency = trace::CallSet(argv[++i]);
+ if (compare_prefix == NULL) {
+ compare_prefix = "";
}
} else if (!strcmp(arg, "-D")) {
dump_state = atoi(argv[++i]);
return 0;
} else if (!strcmp(arg, "-s")) {
snapshot_prefix = argv[++i];
- if (snapshot_frequency == FREQUENCY_NEVER) {
- snapshot_frequency = FREQUENCY_FRAME;
+ if (snapshot_frequency.empty()) {
+ snapshot_frequency = trace::CallSet(trace::FREQUENCY_FRAME);
}
if (snapshot_prefix[0] == '-' && snapshot_prefix[1] == 0) {
retrace::verbosity = -2;
}
} else if (!strcmp(arg, "-S")) {
- arg = argv[++i];
- if (!strcmp(arg, "frame")) {
- snapshot_frequency = FREQUENCY_FRAME;
- } else if (!strcmp(arg, "framebuffer")) {
- snapshot_frequency = FREQUENCY_FRAMEBUFFER;
- } else if (!strcmp(arg, "draw")) {
- snapshot_frequency = FREQUENCY_DRAW;
- } else {
- std::cerr << "error: unknown frequency " << arg << "\n";
- usage();
- return 1;
- }
+ snapshot_frequency = trace::CallSet(argv[++i]);
if (snapshot_prefix == NULL) {
snapshot_prefix = "";
}
type="float", dest="threshold", default=12.0,
help="threshold precision [default: %default]")
optparser.add_option(
- '-S', '--snapshot-frequency', metavar='FREQUENCY',
+ '-S', '--snapshot-frequency', metavar='CALLSET',
type="string", dest="snapshot_frequency", default='draw',
- help="snapshot frequency: frame, framebuffer, or draw [default: %default]")
+ help="calls to compare [default: %default]")
(options, args) = optparser.parse_args(sys.argv[1:])
ref_env = parse_env(optparser, options.ref_env)