#include <limits.h> // for CHAR_MAX
#include <getopt.h>
+#include <GL/gl.h>
+#include <GL/glext.h>
+
#include <set>
#include "cli.hpp"
static void
usage(void)
+{
+ std::cout
+ << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n"
+ << synopsis << "\n"
+ "\n"
+ " -h, --help Show detailed help for trim options and exit\n"
+ " --calls=CALLSET Include specified calls in the trimmed output.\n"
+ " --deps Include additional calls to satisfy dependencies\n"
+ " --no-deps Do not include calls from dependency analysis\n"
+ " --prune Omit uninteresting calls from the trace output\n"
+ " --no-prune Do not prune uninteresting calls from the trace.\n"
+ " -x, --exact Include exactly the calls specified in --calls\n"
+ " Equivalent to both --no-deps and --no-prune\n"
+ " --thread=THREAD_ID Only retain calls from specified thread\n"
+ " -o, --output=TRACE_FILE Output trace file\n"
+ ;
+}
+
+static void
+help()
{
std::cout
<< "usage: apitrace trim [OPTIONS] TRACE_FILE...\n"
<< synopsis << "\n"
"\n"
" -h, --help Show this help message and exit\n"
- " --calls=CALLSET Include specified calls in the trimmed output\n"
+ "\n"
+ " --calls=CALLSET Include specified calls in the trimmed output.\n"
+ " Note that due to dependency analysis and pruning\n"
+ " of uninteresting calls the resulting trace may\n"
+ " include more and less calls than specified.\n"
+ " See --no-deps, --no-prune, and --exact to change\n"
+ " this behavior.\n"
+ "\n"
" --deps Perform dependency analysis and include dependent\n"
- " calls as needed. This is the default behavior.\n"
- " --no-deps Do not perform dependency analysis. Include only\n"
- " those calls explicitly listed in --calls\n"
+ " calls as needed, (even if those calls were not\n"
+ " explicitly requested with --calls). This is the\n"
+ " default behavior. See --no-deps and --exact.\n"
+ "\n"
+ " --no-deps Do not perform dependency analysis. In this mode\n"
+ " the trimmed trace will never include calls from\n"
+ " outside the range specified in --calls.\n"
+ "\n"
+ " --prune Omit calls that have no side effects, even if the\n"
+ " call is within the range specified by --calls.\n"
+ " This is the default behavior. See --no-prune\n"
+ "\n"
+ " --no-prune Do not prune uninteresting calls from the trace.\n"
+ " In this mode the trimmed trace will never omit\n"
+ " any calls within the range specified in --calls.\n"
+ "\n"
+ " -x, --exact Trim the trace to exactly the calls specified in\n"
+ " --calls. This option is equivalent to passing\n"
+ " both --no-deps and --no-prune.\n"
+ "\n"
" --thread=THREAD_ID Only retain calls from specified thread\n"
+ "\n"
" -o, --output=TRACE_FILE Output trace file\n"
"\n"
;
CALLS_OPT = CHAR_MAX + 1,
DEPS_OPT,
NO_DEPS_OPT,
+ PRUNE_OPT,
+ NO_PRUNE_OPT,
THREAD_OPT,
};
const static char *
-shortOptions = "ho:";
+shortOptions = "ho:x";
const static struct option
longOptions[] = {
{"calls", required_argument, 0, CALLS_OPT},
{"deps", no_argument, 0, DEPS_OPT},
{"no-deps", no_argument, 0, NO_DEPS_OPT},
+ {"prune", no_argument, 0, PRUNE_OPT},
+ {"no-prune", no_argument, 0, NO_PRUNE_OPT},
+ {"exact", no_argument, 0, 'x'},
{"thread", required_argument, 0, THREAD_OPT},
{"output", required_argument, 0, 'o'},
{0, 0, 0, 0}
* implicitly required by those through resource dependencies. */
std::set<unsigned> required;
+ bool transformFeedbackActive;
+ bool framebufferObjectActive;
+ bool insideBeginEnd;
+
+ /* Rendering often has no side effects, but it can in some cases,
+ * (such as when transform feedback is active, or when rendering
+ * targets a framebuffer object). */
+ bool renderingHasSideEffect() {
+ return transformFeedbackActive || framebufferObjectActive;
+ }
+
+ /* Provide: Record that the given call affects the given resource
+ * as a side effect. */
+ void provide(const char *resource, trace::CallNo call_no) {
+ resources[resource].insert(call_no);
+ }
+
+ /* Consume: Add all calls that provide the given resource to the
+ * required list, then clear the list for this resource. */
+ void consume(const char *resource) {
+
+ std::set<unsigned> *calls;
+ std::set<unsigned>::iterator call;
+
+ /* Insert as required all calls that provide 'resource',
+ * then clear these calls. */
+ if (resources.count(resource)) {
+ calls = &resources[resource];
+ for (call = calls->begin(); call != calls->end(); call++) {
+ required.insert(*call);
+ }
+ resources.erase(resource);
+ }
+ }
+
+ void stateTrackPreCall(trace::Call *call) {
+
+ if (strcmp(call->name(), "glBegin") == 0) {
+ insideBeginEnd = true;
+ return;
+ }
+
+ if (strcmp(call->name(), "glBeginTransformFeedback") == 0) {
+ transformFeedbackActive = true;
+ return;
+ }
+
+ if (strcmp(call->name(), "glBindFramebuffer") == 0) {
+ GLenum target;
+ GLuint framebuffer;
+
+ target = static_cast<GLenum>(call->arg(0).toSInt());
+ framebuffer = call->arg(1).toUInt();
+
+ if (target == GL_FRAMEBUFFER || target == GL_DRAW_FRAMEBUFFER) {
+ if (framebuffer == 0) {
+ framebufferObjectActive = false;
+ } else {
+ framebufferObjectActive = true;
+ }
+ }
+ return;
+ }
+ }
+
+ void stateTrackPostCall(trace::Call *call) {
+
+ if (strcmp(call->name(), "glEnd") == 0) {
+ insideBeginEnd = false;
+ return;
+ }
+
+ if (strcmp(call->name(), "glEndTransformFeedback") == 0) {
+ transformFeedbackActive = false;
+ return;
+ }
+
+ if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
+ call->flags & trace::CALL_FLAG_END_FRAME) {
+ resources.erase("framebuffer");
+ return;
+ }
+ }
+
+ void recordSideEffects(trace::Call *call) {
+ /* If call is flagged as no side effects, then we are done here. */
+ if (call->flags & trace::CALL_FLAG_NO_SIDE_EFFECTS) {
+ return;
+ }
+
+ /* Similarly, swap-buffers calls don't have interesting side effects. */
+ if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
+ call->flags & trace::CALL_FLAG_END_FRAME) {
+ return;
+ }
+
+ /* Handle all rendering operations, (even though only glEnd is
+ * flagged as a rendering operation we treat everything from
+ * glBegin through glEnd as a rendering operation). */
+ if (call->flags & trace::CALL_FLAG_RENDER ||
+ insideBeginEnd) {
+
+ provide("framebuffer", call->no);
+
+ /* In some cases, rendering has side effects beyond the
+ * framebuffer update. */
+ if (renderingHasSideEffect()) {
+ provide("state", call->no);
+ }
+
+ return;
+ }
+
+ /* By default, assume this call affects the state somehow. */
+ resources["state"].insert(call->no);
+ }
+
+ void requireDependencies(trace::Call *call) {
+
+ /* Swap-buffers calls depend on framebuffer state. */
+ if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET &&
+ call->flags & trace::CALL_FLAG_END_FRAME) {
+ consume("framebuffer");
+ }
+
+ /* By default, just assume this call depends on generic state. */
+ consume("state");
+ }
+
+
public:
- TraceAnalyzer() {}
+ TraceAnalyzer(): transformFeedbackActive(false),
+ framebufferObjectActive(false),
+ insideBeginEnd(false)
+ {}
+
~TraceAnalyzer() {}
- /* Compute and record all the resources provided by this call. */
+ /* Analyze this call by tracking state and recording all the
+ * resources provided by this call as side effects.. */
void analyze(trace::Call *call) {
- resources["state"].insert(call->no);
+
+ stateTrackPreCall(call);
+
+ recordSideEffects(call);
+
+ stateTrackPostCall(call);
}
/* Require this call and all of its dependencies to be included in
* the final trace. */
void require(trace::Call *call) {
- std::set<unsigned> *dependencies;
- std::set<unsigned>::iterator i;
/* First, find and insert all calls that this call depends on. */
- dependencies = &resources["state"];
- for (i = dependencies->begin(); i != dependencies->end(); i++) {
- required.insert(*i);
- }
- resources["state"].clear();
+ requireDependencies(call);
/* Then insert this call itself. */
required.insert(call->no);
/* Whether dependency analysis should be performed. */
bool dependency_analysis;
+ /* Whether uninteresting calls should be pruned.. */
+ bool prune_uninteresting;
+
/* Output filename */
std::string output;
/* In pass 1, analyze which calls are needed. */
trace::Call *call;
while ((call = p.parse_call())) {
+
+ /* There's no use doing any work past the last call requested
+ * by the user. */
+ if (call->no > options->calls.getLast())
+ break;
+
/* If requested, ignore all calls not belonging to the specified thread. */
if (options->thread != -1 && call->thread_id != options->thread)
continue;
- /* If this call is included in the user-specified call
- * set, then we don't need to perform any analysis on
- * it. We know it must be included. */
+ /* Also, prune if uninteresting (unless the user asked for no pruning. */
+ if (options->prune_uninteresting && call->flags & trace::CALL_FLAG_UNINTERESTING) {
+ continue;
+ }
+
+ /* If this call is included in the user-specified call set,
+ * then require it (and all dependencies) in the trimmed
+ * output. */
if (options->calls.contains(*call)) {
analyzer.require(call);
- } else {
- if (options->dependency_analysis)
- analyzer.analyze(call);
+ }
+
+ /* Regardless of whether we include this call or not, we do
+ * some dependency tracking (unless disabled by the user). We
+ * do this even for calls we have included in the output so
+ * that any state updates get performed. */
+ if (options->dependency_analysis) {
+ analyzer.analyze(call);
}
}
required = analyzer.get_required();
while ((call = p.parse_call())) {
+
+ /* There's no use doing any work past the last call requested
+ * by the user. */
+ if (call->no > options->calls.getLast())
+ break;
+
if (required->find(call->no) != required->end()) {
writer.writeCall(call);
}
options.calls = trace::CallSet(trace::FREQUENCY_ALL);
options.dependency_analysis = true;
+ options.prune_uninteresting = true;
options.output = "";
options.thread = -1;
while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) {
switch (opt) {
case 'h':
- usage();
+ help();
return 0;
case CALLS_OPT:
options.calls = trace::CallSet(optarg);
case NO_DEPS_OPT:
options.dependency_analysis = false;
break;
+ case PRUNE_OPT:
+ options.prune_uninteresting = true;
+ break;
+ case NO_PRUNE_OPT:
+ options.prune_uninteresting = false;
+ break;
+ case 'x':
+ options.dependency_analysis = false;
+ options.prune_uninteresting = false;
+ break;
case THREAD_OPT:
options.thread = atoi(optarg);
break;
const Command trim_command = {
"trim",
synopsis,
- usage,
+ help,
command
};