From: José Fonseca Date: Tue, 11 Dec 2012 07:20:26 +0000 (+0000) Subject: Merge branch 'trim-auto' X-Git-Url: https://git.cworth.org/git?p=apitrace;a=commitdiff_plain;h=9db16b3989481f8d6dfc8932d760fcc16217ecbd;hp=4f74cc67dd2b46d2abc0be5663c992410420091b Merge branch 'trim-auto' Conflicts: cli/CMakeLists.txt --- diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt index e5ad36d..5f8e116 100644 --- a/cli/CMakeLists.txt +++ b/cli/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable (apitrace cli_trace.cpp cli_trim.cpp cli_resources.cpp + trace_analyzer.cpp ) target_link_libraries (apitrace diff --git a/cli/cli_dump_images.cpp b/cli/cli_dump_images.cpp index 0e2dd8c..34e4d05 100644 --- a/cli/cli_dump_images.cpp +++ b/cli/cli_dump_images.cpp @@ -44,17 +44,20 @@ usage(void) std::cout << "usage apitrace dump-images [OPTIONS] TRACE_FILE\n" << synopsis << "\n" "\n" - " -h, --help show this help message and exit\n" - " --calls=CALLSET dump images only for specified calls\n" - " (default value is \"*/frame\" which\n" - " which dumps an image for each frame)\n" - " -o, --output=PREFIX prefix to use in naming output files\n" - " (default is trace filename without extension)\n" + " -h, --help show this help message and exit\n" + " --calls=CALLSET dump images only for specified calls\n" + " (default value is \"*/frame\" which\n" + " which dumps an image for each frame)\n" + " --call-nos[=BOOL] use call numbers in image filenames,\n" + " otherwise use sequental numbers (default=yes)\n" + " -o, --output=PREFIX prefix to use in naming output files\n" + " (default is trace filename without extension)\n" "\n"; } enum { CALLS_OPT = CHAR_MAX + 1, + CALL_NOS_OPT, }; const static char * @@ -64,6 +67,7 @@ const static struct option longOptions[] = { {"help", no_argument, 0, 'h'}, {"calls", required_argument, 0, CALLS_OPT}, + {"call-nos", optional_argument, 0, CALL_NOS_OPT}, {"output", required_argument, 0, 'o'}, {0, 0, 0, 0} }; @@ -75,6 +79,7 @@ command(int argc, char *argv[]) const char *calls = NULL; const char *traceName = NULL; const char *output = NULL; + std::string call_nos; int opt; while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) { @@ -85,6 +90,9 @@ command(int argc, char *argv[]) case CALLS_OPT: calls = optarg; break; + case CALL_NOS_OPT: + call_nos = std::string("--call-nos=") + optarg; + break; case 'o': output = optarg; break; @@ -126,6 +134,9 @@ command(int argc, char *argv[]) opts.push_back(calls); else opts.push_back("*/frame"); + if (!call_nos.empty()) { + opts.push_back(call_nos.c_str()); + } return executeRetrace(opts, traceName); } diff --git a/cli/cli_trim.cpp b/cli/cli_trim.cpp index eea5553..afad283 100644 --- a/cli/cli_trim.cpp +++ b/cli/cli_trim.cpp @@ -24,15 +24,18 @@ * **************************************************************************/ - +#include #include #include // for CHAR_MAX #include +#include + #include "cli.hpp" #include "os_string.hpp" +#include "trace_analyzer.hpp" #include "trace_callset.hpp" #include "trace_parser.hpp" #include "trace_writer.hpp" @@ -46,53 +49,288 @@ usage(void) << "usage: apitrace trim [OPTIONS] TRACE_FILE...\n" << synopsis << "\n" "\n" - " -h, --help show this help message and exit\n" - " --calls=CALLSET only retain specified calls\n" - " --thread=THREAD_ID only retain calls from specified thread\n" - " -o, --output=TRACE_FILE output trace file\n" + " -h, --help Show detailed help for trim options and exit\n" + " --calls=CALLSET Include specified calls in the trimmed output.\n" + " --frames=FRAMESET Include specified frames in the trimmed output.\n" + " --deps Include additional calls to satisfy dependencies\n" + " --prune Omit uninteresting calls from the trace output\n" + " -a, --auto Trim automatically to calls specified in --calls/--frames\n" + " Equivalent to both --deps and --prune\n" + " --print-callset Print the final set of calls included in output\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" + "\n" + " --calls=CALLSET Include specified calls in the trimmed output.\n" + " --frames=FRAMESET Include specified frames in the trimmed output.\n" + "\n" + " --deps Perform dependency analysis and include dependent\n" + " calls as needed, (even if those calls were not\n" + " explicitly requested with --calls or --frames).\n" + "\n" + " --prune Omit calls with no side effects, even if the call\n" + " is within the range specified by --calls/--frames.\n" + "\n" + " -a, --auto Use dependency analysis and pruning\n" + " of uninteresting calls the resulting trace may\n" + " include more and less calls than specified.\n" + " This option is equivalent\n" + " to passing both --deps and --prune.\n" + "\n" + " --print-callset Print to stdout the final set of calls included\n" + " in the trim output. This can be useful for\n" + " tweaking trimmed callset from --auto on the\n" + " command-line.\n" + " Use --calls=@FILE to read callset from a file.\n" + "\n" + " --thread=THREAD_ID Only retain calls from specified thread\n" + "\n" + " -o, --output=TRACE_FILE Output trace file\n" "\n" ; } enum { CALLS_OPT = CHAR_MAX + 1, + FRAMES_OPT, + DEPS_OPT, + PRUNE_OPT, THREAD_OPT, + PRINT_CALLSET_OPT, }; const static char * -shortOptions = "ho:"; +shortOptions = "ho:x"; const static struct option longOptions[] = { {"help", no_argument, 0, 'h'}, {"calls", required_argument, 0, CALLS_OPT}, + {"frames", required_argument, 0, FRAMES_OPT}, + {"deps", no_argument, 0, DEPS_OPT}, + {"prune", no_argument, 0, PRUNE_OPT}, + {"auto", no_argument, 0, 'a'}, {"thread", required_argument, 0, THREAD_OPT}, {"output", required_argument, 0, 'o'}, + {"print-callset", no_argument, 0, PRINT_CALLSET_OPT}, {0, 0, 0, 0} }; +struct stringCompare { + bool operator() (const char *a, const char *b) const { + return strcmp(a, b) < 0; + } +}; + +struct trim_options { + /* Calls to be included in trace. */ + trace::CallSet calls; + + /* Frames to be included in trace. */ + trace::CallSet frames; + + /* Whether dependency analysis should be performed. */ + bool dependency_analysis; + + /* Whether uninteresting calls should be pruned.. */ + bool prune_uninteresting; + + /* Output filename */ + std::string output; + + /* Emit only calls from this thread (-1 == all threads) */ + int thread; + + /* Print resulting callset */ + int print_callset; +}; + +static int +trim_trace(const char *filename, struct trim_options *options) +{ + trace::ParseBookmark beginning; + trace::Parser p; + TraceAnalyzer analyzer; + std::set *required; + unsigned frame; + int call_range_first, call_range_last; + + if (!p.open(filename)) { + std::cerr << "error: failed to open " << filename << "\n"; + return 1; + } + + /* Mark the beginning so we can return here for pass 2. */ + p.getBookmark(beginning); + + /* In pass 1, analyze which calls are needed. */ + frame = 0; + trace::Call *call; + while ((call = p.parse_call())) { + + /* There's no use doing any work past the last call or frame + * requested by the user. */ + if (call->no > options->calls.getLast() || + frame > options->frames.getLast()) { + + delete call; + break; + } + + /* If requested, ignore all calls not belonging to the specified thread. */ + if (options->thread != -1 && call->thread_id != options->thread) { + goto NEXT; + } + + /* Also, prune if uninteresting (unless the user asked for no pruning. */ + if (options->prune_uninteresting && call->flags & trace::CALL_FLAG_VERBOSE) { + goto NEXT; + } + + /* 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) || + options->frames.contains(frame, call->flags)) { + + analyzer.require(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); + } + + NEXT: + if (call->flags & trace::CALL_FLAG_END_FRAME) + frame++; + + delete call; + } + + /* Prepare output file and writer for output. */ + if (options->output.empty()) { + os::String base(filename); + base.trimExtension(); + + options->output = std::string(base.str()) + std::string("-trim.trace"); + } + + trace::Writer writer; + if (!writer.open(options->output.c_str())) { + std::cerr << "error: failed to create " << filename << "\n"; + return 1; + } + + /* Reset bookmark for pass 2. */ + p.setBookmark(beginning); + + /* In pass 2, emit the calls that are required. */ + required = analyzer.get_required(); + + frame = 0; + call_range_first = -1; + call_range_last = -1; + while ((call = p.parse_call())) { + + /* There's no use doing any work past the last call or frame + * requested by the user. */ + if (call->no > options->calls.getLast() || + frame > options->frames.getLast()) { + + break; + } + + if (required->find(call->no) != required->end()) { + writer.writeCall(call); + + if (options->print_callset) { + if (call_range_first < 0) { + call_range_first = call->no; + printf ("%d", call_range_first); + } else if (call->no != call_range_last + 1) { + if (call_range_last != call_range_first) + printf ("-%d", call_range_last); + call_range_first = call->no; + printf (",%d", call_range_first); + } + call_range_last = call->no; + } + } + + if (call->flags & trace::CALL_FLAG_END_FRAME) { + frame++; + } + + delete call; + } + + if (options->print_callset) { + if (call_range_last != call_range_first) + printf ("-%d\n", call_range_last); + } + + std::cerr << "Trimmed trace is available as " << options->output << "\n"; + + return 0; +} + static int command(int argc, char *argv[]) { - std::string output; - trace::CallSet calls(trace::FREQUENCY_ALL); - int thread = -1; - int i; + struct trim_options options; + + options.calls = trace::CallSet(trace::FREQUENCY_NONE); + options.frames = trace::CallSet(trace::FREQUENCY_NONE); + options.dependency_analysis = false; + options.prune_uninteresting = false; + options.output = ""; + options.thread = -1; + options.print_callset = 0; int opt; while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) { switch (opt) { case 'h': - usage(); + help(); return 0; case CALLS_OPT: - calls = trace::CallSet(optarg); + options.calls = trace::CallSet(optarg); + break; + case FRAMES_OPT: + options.frames = trace::CallSet(optarg); + break; + case DEPS_OPT: + options.dependency_analysis = true; + break; + case PRUNE_OPT: + options.prune_uninteresting = true; + break; + case 'a': + options.dependency_analysis = true; + options.prune_uninteresting = true; break; case THREAD_OPT: - thread = atoi(optarg); + options.thread = atoi(optarg); break; case 'o': - output = optarg; + options.output = optarg; + break; + case PRINT_CALLSET_OPT: + options.print_callset = 1; break; default: std::cerr << "error: unexpected option `" << opt << "`\n"; @@ -101,49 +339,42 @@ command(int argc, char *argv[]) } } + /* If neither of --calls nor --frames was set, default to the + * entire set of calls. */ + if (options.calls.empty() && options.frames.empty()) { + options.calls = trace::CallSet(trace::FREQUENCY_ALL); + } + if (optind >= argc) { std::cerr << "error: apitrace trim requires a trace file as an argument.\n"; usage(); return 1; } - for (i = optind; i < argc; ++i) { - trace::Parser p; - if (!p.open(argv[i])) { - return 1; - } - - if (output.empty()) { - os::String base(argv[i]); - base.trimExtension(); - - output = std::string(base.str()) + std::string("-trim.trace"); - } - - trace::Writer writer; - if (!writer.open(output.c_str())) { - std::cerr << "error: failed to create " << argv[i] << "\n"; - return 1; - } - - trace::Call *call; - while ((call = p.parse_call())) { - if (calls.contains(*call) && - (thread == -1 || call->thread_id == thread)) { - writer.writeCall(call); - } - delete call; + if (argc > optind + 1) { + std::cerr << "error: extraneous arguments:"; + for (int i = optind + 1; i < argc; i++) { + std::cerr << " " << argv[i]; } + std::cerr << "\n"; + usage(); + return 1; + } - std::cout << "Trimmed trace is available as " << output << "\n"; + if (options.dependency_analysis) { + std::cerr << + "Note: The dependency analysis in \"apitrace trim\" is still experimental.\n" + " We hope that it will be useful, but it may lead to incorrect results.\n" + " If you find a trace that misbehaves while trimming, please share that\n" + " by sending email to apitrace@lists.freedesktop.org, cworth@cworth.org\n"; } - return 0; + return trim_trace(argv[optind], &options); } const Command trim_command = { "trim", synopsis, - usage, + help, command }; diff --git a/cli/trace_analyzer.cpp b/cli/trace_analyzer.cpp new file mode 100644 index 0000000..9a4553e --- /dev/null +++ b/cli/trace_analyzer.cpp @@ -0,0 +1,710 @@ +/************************************************************************** + * Copyright 2012 Intel corporation + * + * 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 + +#include "trace_analyzer.hpp" + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define STRNCMP_LITERAL(var, literal) strncmp((var), (literal), sizeof (literal) -1) + +/* 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 +TraceAnalyzer::renderingHasSideEffect(void) +{ + return transformFeedbackActive || framebufferObjectActive; +} + +/* Provide: Record that the given call affects the given resource + * as a side effect. */ +void +TraceAnalyzer::provide(std::string resource, trace::CallNo call_no) +{ + resources[resource].insert(call_no); +} + +/* Like provide, but with a simply-formatted string, (appending an + * integer to the given string). */ +void +TraceAnalyzer::providef(std::string resource, + int resource_no, + trace::CallNo call_no) +{ + std::stringstream ss; + ss << resource << resource_no; + provide(ss.str(), call_no); +} + +/* Link: Establish a dependency between resource 'resource' and + * resource 'dependency'. This dependency is captured by name so + * that if the list of calls that provide 'dependency' grows + * before 'resource' is consumed, those calls will still be + * captured. */ +void +TraceAnalyzer::link(std::string resource, std::string dependency) +{ + dependencies[resource].insert(dependency); +} + +/* Like link, but with a simply-formatted string, (appending an + * integer to the given string). */ +void +TraceAnalyzer::linkf(std::string resource, std::string dependency, int dep_no) +{ + + std::stringstream ss; + ss << dependency << dep_no; + link(resource, ss.str()); +} + +/* Unlink: Remove dependency from 'resource' on 'dependency'. */ +void +TraceAnalyzer::unlink(std::string resource, std::string dependency) +{ + dependencies[resource].erase(dependency); + if (dependencies[resource].size() == 0) { + dependencies.erase(resource); + } +} + +/* Like unlink, but with a simply-formated string, (appending an + * integer to the given string). */ +void +TraceAnalyzer::unlinkf(std::string resource, std::string dependency, int dep_no) +{ + + std::stringstream ss; + ss << dependency << dep_no; + unlink(resource, ss.str()); +} + +/* Unlink all: Remove dependencies from 'resource' to all other + * resources. */ +void +TraceAnalyzer::unlinkAll(std::string resource) +{ + dependencies.erase(resource); +} + +/* Resolve: Recursively compute all calls providing 'resource', + * (including linked dependencies of 'resource' on other + * resources). */ +std::set +TraceAnalyzer::resolve(std::string resource) +{ + std::set *deps; + std::set::iterator dep; + + std::set *calls; + std::set::iterator call; + + std::set result, deps_set; + + /* Recursively chase dependencies. */ + if (dependencies.count(resource)) { + deps = &dependencies[resource]; + for (dep = deps->begin(); dep != deps->end(); dep++) { + deps_set = resolve(*dep); + for (call = deps_set.begin(); call != deps_set.end(); call++) { + result.insert(*call); + } + } + } + + /* Also look for calls that directly provide 'resource' */ + if (resources.count(resource)) { + calls = &resources[resource]; + for (call = calls->begin(); call != calls->end(); call++) { + result.insert(*call); + } + } + + return result; +} + +/* Consume: Resolve all calls that provide the given resource, and + * add them to the required list. Then clear the call list for + * 'resource' along with any dependencies. */ +void +TraceAnalyzer::consume(std::string resource) +{ + + std::set calls; + std::set::iterator call; + + calls = resolve(resource); + + dependencies.erase(resource); + resources.erase(resource); + + for (call = calls.begin(); call != calls.end(); call++) { + required.insert(*call); + } +} + +void +TraceAnalyzer::stateTrackPreCall(trace::Call *call) +{ + + const char *name = call->name(); + + if (strcmp(name, "glBegin") == 0) { + insideBeginEnd = true; + return; + } + + if (strcmp(name, "glBeginTransformFeedback") == 0) { + transformFeedbackActive = true; + return; + } + + if (strcmp(name, "glActiveTexture") == 0) { + activeTextureUnit = static_cast(call->arg(0).toSInt()); + return; + } + + if (strcmp(name, "glBindTexture") == 0) { + GLenum target; + GLuint texture; + + target = static_cast(call->arg(0).toSInt()); + texture = call->arg(1).toUInt(); + + if (texture == 0) { + texture_map.erase(target); + } else { + texture_map[target] = texture; + } + + return; + } + + if (strcmp(name, "glUseProgram") == 0) { + activeProgram = call->arg(0).toUInt(); + } + + if (strcmp(name, "glBindFramebuffer") == 0) { + GLenum target; + GLuint framebuffer; + + target = static_cast(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; + } + + if (strcmp(name, "glNewList") == 0) { + GLuint list = call->arg(0).toUInt(); + + insideNewEndList = list; + } +} + +void +TraceAnalyzer::stateTrackPostCall(trace::Call *call) +{ + + const char *name = call->name(); + + if (strcmp(name, "glEnd") == 0) { + insideBeginEnd = false; + return; + } + + if (strcmp(name, "glEndTransformFeedback") == 0) { + transformFeedbackActive = false; + return; + } + + /* If this swapbuffers was included in the trace then it will + * have already consumed all framebuffer dependencies. If not, + * then clear them now so that they don't carry over into the + * next frame. */ + if (call->flags & trace::CALL_FLAG_SWAP_RENDERTARGET && + call->flags & trace::CALL_FLAG_END_FRAME) { + dependencies.erase("framebuffer"); + resources.erase("framebuffer"); + return; + } + + if (strcmp(name, "glEndList") == 0) { + insideNewEndList = 0; + } +} + +void +TraceAnalyzer::recordSideEffects(trace::Call *call) +{ + + const char *name = call->name(); + + /* Handle display lists before any other processing. */ + + /* FIXME: If we encode the list of commands that are executed + * immediately (as opposed to those that are compiled into a + * display list) then we could generate a "display-list-X" + * resource just as we do for "texture-X" resources and only + * emit it in the trace if a glCallList(X) is emitted. For + * now, simply punt and include anything within glNewList and + * glEndList in the trim output. This guarantees that display + * lists will work, but does not trim out unused display + * lists. */ + if (insideNewEndList != 0) { + provide("state", call->no); + + /* Also, any texture bound inside a display list is + * conservatively considered required. */ + if (strcmp(name, "glBindTexture") == 0) { + GLuint texture = call->arg(1).toUInt(); + + linkf("state", "texture-", texture); + } + + return; + } + + /* 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; + } + + if (strcmp(name, "glGenTextures") == 0) { + const trace::Array *textures = dynamic_cast(&call->arg(1)); + size_t i; + GLuint texture; + + if (textures) { + for (i = 0; i < textures->size(); i++) { + texture = textures->values[i]->toUInt(); + providef("texture-", texture, call->no); + } + } + return; + } + + /* FIXME: When we start tracking framebuffer objects as their own + * resources, we will want to link the FBO to the given texture + * resource, (and to this call). For now, just link render state + * to the texture, and force this call to be required. */ + if (strcmp(name, "glFramebufferTexture2D") == 0) { + GLuint texture; + + texture = call->arg(3).toUInt(); + + linkf("render-state", "texture-", texture); + + required.insert(call->no); + } + + if (strcmp(name, "glBindTexture") == 0) { + GLenum target; + GLuint texture; + + std::stringstream ss_target, ss_texture; + + target = static_cast(call->arg(0).toSInt()); + texture = call->arg(1).toUInt(); + + ss_target << "texture-unit-" << activeTextureUnit << "-target-" << target; + ss_texture << "texture-" << texture; + + resources.erase(ss_target.str()); + provide(ss_target.str(), call->no); + + unlinkAll(ss_target.str()); + link(ss_target.str(), ss_texture.str()); + + /* FIXME: This really shouldn't be necessary. The effect + * this provide() has is that all glBindTexture calls will + * be preserved in the output trace (never trimmed). Carl + * has a trace ("btr") where a glBindTexture call should + * not be necessary at all, (it's immediately followed + * with a glBindTexture to a different texture and no + * intervening texture-related calls), yet this 'provide' + * makes the difference between a trim_stress test failing + * and passing. + * + * More investigation is necessary, but for now, be + * conservative and don't trim. */ + provide("state", call->no); + + return; + } + + /* FIXME: Need to handle glMultTetImage and friends. */ + if (STRNCMP_LITERAL(name, "glTexImage") == 0 || + STRNCMP_LITERAL(name, "glTexSubImage") == 0 || + STRNCMP_LITERAL(name, "glCopyTexImage") == 0 || + STRNCMP_LITERAL(name, "glCopyTexSubImage") == 0 || + STRNCMP_LITERAL(name, "glCompressedTexImage") == 0 || + STRNCMP_LITERAL(name, "glCompressedTexSubImage") == 0 || + strcmp(name, "glInvalidateTexImage") == 0 || + strcmp(name, "glInvalidateTexSubImage") == 0) { + + std::set *calls; + std::set::iterator c; + std::stringstream ss_target, ss_texture; + + GLenum target = static_cast(call->arg(0).toSInt()); + + ss_target << "texture-unit-" << activeTextureUnit << "-target-" << target; + ss_texture << "texture-" << texture_map[target]; + + /* The texture resource depends on this call and any calls + * providing the given texture target. */ + provide(ss_texture.str(), call->no); + + if (resources.count(ss_target.str())) { + calls = &resources[ss_target.str()]; + for (c = calls->begin(); c != calls->end(); c++) { + provide(ss_texture.str(), *c); + } + } + + return; + } + + if (strcmp(name, "glEnable") == 0) { + GLenum cap; + + cap = static_cast(call->arg(0).toSInt()); + + if (cap == GL_TEXTURE_1D || + cap == GL_TEXTURE_2D || + cap == GL_TEXTURE_3D || + cap == GL_TEXTURE_CUBE_MAP) + { + std::stringstream ss; + + ss << "texture-unit-" << activeTextureUnit << "-target-" << cap; + + link("render-state", ss.str()); + } + + provide("state", call->no); + return; + } + + if (strcmp(name, "glDisable") == 0) { + GLenum cap; + + cap = static_cast(call->arg(0).toSInt()); + + if (cap == GL_TEXTURE_1D || + cap == GL_TEXTURE_2D || + cap == GL_TEXTURE_3D || + cap == GL_TEXTURE_CUBE_MAP) + { + std::stringstream ss; + + ss << "texture-unit-" << activeTextureUnit << "-target-" << cap; + + unlink("render-state", ss.str()); + } + + provide("state", call->no); + return; + } + + if (strcmp(name, "glCreateShader") == 0 || + strcmp(name, "glCreateShaderObjectARB") == 0) { + + GLuint shader = call->ret->toUInt(); + providef("shader-", shader, call->no); + return; + } + + if (strcmp(name, "glShaderSource") == 0 || + strcmp(name, "glShaderSourceARB") == 0 || + strcmp(name, "glCompileShader") == 0 || + strcmp(name, "glCompileShaderARB") == 0 || + strcmp(name, "glGetShaderiv") == 0 || + strcmp(name, "glGetShaderInfoLog") == 0) { + + GLuint shader = call->arg(0).toUInt(); + providef("shader-", shader, call->no); + return; + } + + if (strcmp(name, "glCreateProgram") == 0 || + strcmp(name, "glCreateProgramObjectARB") == 0) { + + GLuint program = call->ret->toUInt(); + providef("program-", program, call->no); + return; + } + + if (strcmp(name, "glAttachShader") == 0 || + strcmp(name, "glAttachObjectARB") == 0) { + + GLuint program, shader; + std::stringstream ss_program, ss_shader; + + program = call->arg(0).toUInt(); + shader = call->arg(1).toUInt(); + + ss_program << "program-" << program; + ss_shader << "shader-" << shader; + + link(ss_program.str(), ss_shader.str()); + provide(ss_program.str(), call->no); + + return; + } + + if (strcmp(name, "glDetachShader") == 0 || + strcmp(name, "glDetachObjectARB") == 0) { + + GLuint program, shader; + std::stringstream ss_program, ss_shader; + + program = call->arg(0).toUInt(); + shader = call->arg(1).toUInt(); + + ss_program << "program-" << program; + ss_shader << "shader-" << shader; + + unlink(ss_program.str(), ss_shader.str()); + + return; + } + + if (strcmp(name, "glUseProgram") == 0 || + strcmp(name, "glUseProgramObjectARB") == 0) { + + GLuint program; + + program = call->arg(0).toUInt(); + + unlinkAll("render-program-state"); + + if (program == 0) { + unlink("render-state", "render-program-state"); + provide("state", call->no); + } else { + std::stringstream ss; + + ss << "program-" << program; + + link("render-state", "render-program-state"); + link("render-program-state", ss.str()); + + provide(ss.str(), call->no); + } + + return; + } + + if (strcmp(name, "glGetUniformLocation") == 0 || + strcmp(name, "glGetUniformLocationARB") == 0 || + strcmp(name, "glGetFragDataLocation") == 0 || + strcmp(name, "glGetFragDataLocationEXT") == 0 || + strcmp(name, "glGetSubroutineUniformLocation") == 0 || + strcmp(name, "glGetProgramResourceLocation") == 0 || + strcmp(name, "glGetProgramResourceLocationIndex") == 0 || + strcmp(name, "glGetVaryingLocationNV") == 0) { + + GLuint program = call->arg(0).toUInt(); + + providef("program-", program, call->no); + + return; + } + + /* For any call that accepts 'location' as its first argument, + * perform a lookup in our location->program map and add a + * dependence on the program we find there. */ + if (call->sig->num_args > 0 && + strcmp(call->sig->arg_names[0], "location") == 0) { + + providef("program-", activeProgram, call->no); + + /* We can't easily tell if this uniform is being used to + * associate a sampler in the shader with a texture + * unit. The conservative option is to assume that it is + * and create a link from the active program to any bound + * textures for the given unit number. + * + * FIXME: We should be doing the same thing for calls to + * glUniform1iv. */ + if (strcmp(name, "glUniform1i") == 0 || + strcmp(name, "glUniform1iARB") == 0) { + + GLint max_unit = MAX(GL_MAX_TEXTURE_COORDS, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS); + + GLint unit = call->arg(1).toSInt(); + std::stringstream ss_program; + std::stringstream ss_texture; + + if (unit < max_unit) { + + ss_program << "program-" << activeProgram; + + ss_texture << "texture-unit-" << GL_TEXTURE0 + unit << "-target-"; + + /* We don't know what target(s) might get bound to + * this texture unit, so conservatively link to + * all. Only bound textures will actually get inserted + * into the output call stream. */ + linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_1D); + linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_2D); + linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_3D); + linkf(ss_program.str(), ss_texture.str(), GL_TEXTURE_CUBE_MAP); + } + } + + return; + } + + /* FIXME: We cut a huge swath by assuming that any unhandled + * call that has a first argument named "program" should not + * be included in the trimmed output unless the program of + * that number is also included. + * + * This heuristic is correct for many cases, but we should + * actually carefully verify if this includes some calls + * inappropriately, or if it misses some. + */ + if (strcmp(name, "glLinkProgram") == 0 || + strcmp(name, "glLinkProgramARB") == 0 || + (call->sig->num_args > 0 && + (strcmp(call->sig->arg_names[0], "program") == 0 || + strcmp(call->sig->arg_names[0], "programObj") == 0))) { + + GLuint program = call->arg(0).toUInt(); + providef("program-", program, call->no); + 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) { + + std::set calls; + std::set::iterator c; + + provide("framebuffer", call->no); + + calls = resolve("render-state"); + + for (c = calls.begin(); c != calls.end(); c++) { + provide("framebuffer", *c); + } + + /* In some cases, rendering has side effects beyond the + * framebuffer update. */ + if (renderingHasSideEffect()) { + provide("state", call->no); + for (c = calls.begin(); c != calls.end(); c++) { + provide("state", *c); + } + } + + return; + } + + /* By default, assume this call affects the state somehow. */ + resources["state"].insert(call->no); +} + +void +TraceAnalyzer::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"); +} + +TraceAnalyzer::TraceAnalyzer(): transformFeedbackActive(false), + framebufferObjectActive(false), + insideBeginEnd(false), + insideNewEndList(0), + activeTextureUnit(GL_TEXTURE0) +{ + /* Nothing needed. */ +} + +TraceAnalyzer::~TraceAnalyzer() +{ + /* Nothing needed. */ +} + +/* Analyze this call by tracking state and recording all the + * resources provided by this call as side effects.. */ +void +TraceAnalyzer::analyze(trace::Call *call) +{ + + stateTrackPreCall(call); + + recordSideEffects(call); + + stateTrackPostCall(call); +} + +/* Require this call and all of its dependencies to be included in + * the final trace. */ +void +TraceAnalyzer::require(trace::Call *call) +{ + + /* First, find and insert all calls that this call depends on. */ + requireDependencies(call); + + /* Then insert this call itself. */ + required.insert(call->no); +} + +/* Return a set of all the required calls, (both those calls added + * explicitly with require() and those implicitly depended + * upon. */ +std::set * +TraceAnalyzer::get_required(void) +{ + return &required; +} diff --git a/cli/trace_analyzer.hpp b/cli/trace_analyzer.hpp new file mode 100644 index 0000000..6619c57 --- /dev/null +++ b/cli/trace_analyzer.hpp @@ -0,0 +1,86 @@ +/************************************************************************** + * Copyright 2012 Intel corporation + * + * 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 + +#include +#include + +#include "trace_callset.hpp" +#include "trace_parser.hpp" + +class TraceAnalyzer { +private: + std::map > resources; + std::map > dependencies; + + std::map texture_map; + + std::set required; + + bool transformFeedbackActive; + bool framebufferObjectActive; + bool insideBeginEnd; + GLuint insideNewEndList; + GLenum activeTextureUnit; + GLuint activeProgram; + + void provide(std::string resource, trace::CallNo call_no); + void providef(std::string resource, int resource_no, trace::CallNo call_no); + + void link(std::string resource, std::string dependency); + void linkf(std::string resource, std::string dependency, int dep_no); + + void unlink(std::string resource, std::string dependency); + void unlinkf(std::string resource, std::string dependency, int dep_no); + void unlinkAll(std::string resource); + + void stateTrackPreCall(trace::Call *call); + void recordSideEffects(trace::Call *call); + void stateTrackPostCall(trace::Call *call); + + bool renderingHasSideEffect(void); + std::set resolve(std::string resource); + + void consume(std::string resource); + void requireDependencies(trace::Call *call); + +public: + TraceAnalyzer(); + ~TraceAnalyzer(); + + /* Analyze this call by tracking state and recording all the + * resources provided by this call as side effects.. */ + void analyze(trace::Call *call); + + /* Require this call and all of its dependencies to be included in + * the final trace. */ + void require(trace::Call *call); + + /* Return a set of all the required calls, (both those calls added + * explicitly with require() and those implicitly depended + * upon. */ + std::set *get_required(void); +}; diff --git a/common/trace_callset.cpp b/common/trace_callset.cpp index 3c33087..93d145f 100644 --- a/common/trace_callset.cpp +++ b/common/trace_callset.cpp @@ -224,7 +224,7 @@ public: }; -CallSet::CallSet(const char *string) +CallSet::CallSet(const char *string): limits(std::numeric_limits::min(), std::numeric_limits::max()) { if (*string == '@') { FileCallSetParser parser(*this, &string[1]); @@ -236,7 +236,7 @@ CallSet::CallSet(const char *string) } -CallSet::CallSet(CallFlags freq) { +CallSet::CallSet(CallFlags freq): limits(std::numeric_limits::min(), std::numeric_limits::max()) { if (freq != FREQUENCY_NONE) { CallNo start = std::numeric_limits::min(); CallNo stop = std::numeric_limits::max(); diff --git a/common/trace_callset.hpp b/common/trace_callset.hpp index b679d94..4a4a52d 100644 --- a/common/trace_callset.hpp +++ b/common/trace_callset.hpp @@ -48,6 +48,7 @@ #define _TRACE_CALLSET_HPP_ +#include #include #include "trace_model.hpp" @@ -106,12 +107,15 @@ namespace trace { // A collection of call ranges class CallSet { + private: + CallRange limits; + public: // TODO: use binary tree to speed up lookups typedef std::list< CallRange > RangeList; RangeList ranges; - CallSet() {} + CallSet(): limits(std::numeric_limits::min(), std::numeric_limits::max()) {} CallSet(CallFlags freq); @@ -128,6 +132,16 @@ namespace trace { if (range.start <= range.stop && range.freq != FREQUENCY_NONE) { + if (empty()) { + limits.start = range.start; + limits.stop = range.stop; + } else { + if (range.start < limits.start) + limits.start = range.start; + if (range.stop > limits.stop) + limits.stop = range.stop; + } + RangeList::iterator it = ranges.begin(); while (it != ranges.end() && it->start < range.start) { ++it; @@ -155,6 +169,14 @@ namespace trace { contains(const trace::Call &call) { return contains(call.no, call.flags); } + + CallNo getFirst() { + return limits.start; + } + + CallNo getLast() { + return limits.stop; + } }; diff --git a/retrace/retrace_main.cpp b/retrace/retrace_main.cpp index 63d3dab..ca03745 100644 --- a/retrace/retrace_main.cpp +++ b/retrace/retrace_main.cpp @@ -35,6 +35,7 @@ #include "image.hpp" #include "trace_callset.hpp" #include "trace_dump.hpp" +#include "trace_option.hpp" #include "retrace.hpp" @@ -71,6 +72,7 @@ bool profiling = false; bool profilingGpuTimes = false; bool profilingCpuTimes = false; bool profilingPixelsDrawn = false; +bool useCallNos = true; unsigned frameNo = 0; unsigned callNo = 0; @@ -92,6 +94,8 @@ Dumper *dumper = &defaultDumper; */ static void takeSnapshot(unsigned call_no) { + static unsigned snapshot_no = 0; + assert(snapshotPrefix || comparePrefix); image::Image *ref = NULL; @@ -116,10 +120,13 @@ takeSnapshot(unsigned call_no) { if (snapshotPrefix) { if (snapshotPrefix[0] == '-' && snapshotPrefix[1] == 0) { char comment[21]; - snprintf(comment, sizeof comment, "%u", call_no); + snprintf(comment, sizeof comment, "%u", + useCallNos ? call_no : snapshot_no); src->writePNM(std::cout, comment); } else { - os::String filename = os::String::format("%s%010u.png", snapshotPrefix, call_no); + os::String filename = os::String::format("%s%010u.png", + snapshotPrefix, + useCallNos ? call_no : snapshot_no); if (src->writePNG(filename) && retrace::verbosity >= 0) { std::cout << "Wrote " << filename << "\n"; } @@ -133,6 +140,8 @@ takeSnapshot(unsigned call_no) { delete src; + snapshot_no++; + return; } @@ -507,6 +516,7 @@ usage(const char *argv0) { " --ppd pixels drawn profiling (pixels drawn per draw call)\n" " -c, --compare=PREFIX compare against snapshots with given PREFIX\n" " -C, --calls=CALLSET calls to compare (default is every frame)\n" + " --call-nos[=BOOL] use call numbers in snapshot filenames\n" " --core use core profile\n" " --db use a double buffer visual (default)\n" " --driver=DRIVER force driver type (`hw`, `sw`, `ref`, `null`, or driver module name)\n" @@ -519,7 +529,8 @@ usage(const char *argv0) { } enum { - CORE_OPT = CHAR_MAX + 1, + CALL_NOS_OPT = CHAR_MAX + 1, + CORE_OPT, DB_OPT, DRIVER_OPT, PCPU_OPT, @@ -534,6 +545,7 @@ shortOptions = "bc:C:D:hs:S:vw"; const static struct option longOptions[] = { {"benchmark", no_argument, 0, 'b'}, + {"call-nos", optional_argument, 0, CALL_NOS_OPT }, {"calls", required_argument, 0, 'C'}, {"compare", required_argument, 0, 'c'}, {"core", no_argument, 0, CORE_OPT}, @@ -578,6 +590,9 @@ int main(int argc, char **argv) retrace::debug = false; retrace::verbosity = -1; break; + case CALL_NOS_OPT: + useCallNos = trace::boolOption(optarg); + break; case 'c': comparePrefix = optarg; if (compareFrequency.empty()) {