#include "trace_parser.hpp"
#include "trace_writer.hpp"
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define STRNCMP_LITERAL(var, literal) strncmp((var), (literal), sizeof (literal) -1)
static const char *synopsis = "Create a new trace by trimming an existing trace.";
" --no-prune Do not prune uninteresting calls from the trace.\n"
" -x, --exact Trim exactly to calls specified in --calls/--frames\n"
" Equivalent to both --no-deps and --no-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"
;
" --calls and --frames. This option is equivalent\n"
" to passing both --no-deps and --no-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"
+ " debugging trim operations by using a modified\n"
+ " callset on the command-line along with --exact.\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"
PRUNE_OPT,
NO_PRUNE_OPT,
THREAD_OPT,
+ PRINT_CALLSET_OPT,
};
const static char *
{"exact", no_argument, 0, 'x'},
{"thread", required_argument, 0, THREAD_OPT},
{"output", required_argument, 0, 'o'},
+ {"print-callset", no_argument, 0, PRINT_CALLSET_OPT},
{0, 0, 0, 0}
};
bool transformFeedbackActive;
bool framebufferObjectActive;
bool insideBeginEnd;
+ GLuint insideNewEndList;
GLuint activeProgram;
+ GLenum activeTextureUnit;
/* Rendering often has no side effects, but it can in some cases,
* (such as when transform feedback is active, or when rendering
return;
}
+ if (strcmp(name, "glActiveTexture") == 0) {
+ activeTextureUnit = static_cast<GLenum>(call->arg(0).toSInt());
+ return;
+ }
+
if (strcmp(name, "glBindTexture") == 0) {
GLenum target;
GLuint texture;
}
return;
}
+
+ if (strcmp(name, "glNewList") == 0) {
+ GLuint list = call->arg(0).toUInt();
+
+ insideNewEndList = list;
+ }
}
void stateTrackPostCall(trace::Call *call) {
resources.erase("framebuffer");
return;
}
+
+ if (strcmp(name, "glEndList") == 0) {
+ insideNewEndList = 0;
+ }
}
void 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;
target = static_cast<GLenum>(call->arg(0).toSInt());
texture = call->arg(1).toUInt();
- ss_target << "texture-target-" << target;
+ ss_target << "texture-unit-" << activeTextureUnit << "-target-" << target;
ss_texture << "texture-" << texture;
resources.erase(ss_target.str());
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;
}
GLenum target = static_cast<GLenum>(call->arg(0).toSInt());
- ss_target << "texture-target-" << target;
+ ss_target << "texture-unit-" << activeTextureUnit << "-target-" << target;
ss_texture << "texture-" << texture_map[target];
/* The texture resource depends on this call and any calls
cap == GL_TEXTURE_3D ||
cap == GL_TEXTURE_CUBE_MAP)
{
- linkf("render-state", "texture-target-", cap);
+ std::stringstream ss;
+
+ ss << "texture-unit-" << activeTextureUnit << "-target-" << cap;
+
+ link("render-state", ss.str());
}
provide("state", call->no);
cap == GL_TEXTURE_3D ||
cap == GL_TEXTURE_CUBE_MAP)
{
- unlinkf("render-state", "texture-target-", cap);
+ std::stringstream ss;
+
+ ss << "texture-unit-" << activeTextureUnit << "-target-" << cap;
+
+ unlink("render-state", ss.str());
}
provide("state", call->no);
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;
}
public:
TraceAnalyzer(): transformFeedbackActive(false),
framebufferObjectActive(false),
- insideBeginEnd(false)
+ insideBeginEnd(false),
+ insideNewEndList(0),
+ activeTextureUnit(GL_TEXTURE0)
{}
~TraceAnalyzer() {}
/* Emit only calls from this thread (-1 == all threads) */
int thread;
+
+ /* Print resulting callset */
+ int print_callset;
};
static int
TraceAnalyzer analyzer;
std::set<unsigned> *required;
unsigned frame;
+ int call_range_first, call_range_last;
if (!p.open(filename)) {
std::cerr << "error: failed to open " << filename << "\n";
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
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) {
delete call;
}
+ if (options->print_callset) {
+ if (call_range_last != call_range_first)
+ printf ("-%d\n", call_range_last);
+ }
+
std::cout << "Trimmed trace is available as " << options->output << "\n";
return 0;
options.prune_uninteresting = true;
options.output = "";
options.thread = -1;
+ options.print_callset = 0;
int opt;
while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) {
case 'o':
options.output = optarg;
break;
+ case PRINT_CALLSET_OPT:
+ options.print_callset = 1;
+ break;
default:
std::cerr << "error: unexpected option `" << opt << "`\n";
usage();