diff options
Diffstat (limited to 'lib/mesa/src/gallium/tools/trace')
-rw-r--r-- | lib/mesa/src/gallium/tools/trace/README.txt | 39 | ||||
-rw-r--r-- | lib/mesa/src/gallium/tools/trace/TODO.txt | 9 | ||||
-rwxr-xr-x | lib/mesa/src/gallium/tools/trace/diff_state.py | 357 | ||||
-rwxr-xr-x | lib/mesa/src/gallium/tools/trace/dump.py | 34 | ||||
-rwxr-xr-x | lib/mesa/src/gallium/tools/trace/dump_state.py | 787 | ||||
-rwxr-xr-x | lib/mesa/src/gallium/tools/trace/format.py | 173 | ||||
-rwxr-xr-x | lib/mesa/src/gallium/tools/trace/model.py | 241 | ||||
-rwxr-xr-x | lib/mesa/src/gallium/tools/trace/parse.py | 400 | ||||
-rwxr-xr-x | lib/mesa/src/gallium/tools/trace/tracediff.sh | 66 |
9 files changed, 2106 insertions, 0 deletions
diff --git a/lib/mesa/src/gallium/tools/trace/README.txt b/lib/mesa/src/gallium/tools/trace/README.txt new file mode 100644 index 000000000..830cd150f --- /dev/null +++ b/lib/mesa/src/gallium/tools/trace/README.txt @@ -0,0 +1,39 @@ +These directory contains tools for manipulating traces produced by the trace +pipe driver. + + +Most debug builds of state trackers already load the trace driver by default. +To produce a trace do + + export GALLIUM_TRACE=foo.gtrace + +and run the application. You can choose any name, but the .gtrace is +recommended to avoid confusion with the .trace produced by apitrace. + + +You can dump a trace by doing + + ./dump.py foo.gtrace | less + + +You can dump a JSON file describing the static state at any given draw call +(e.g., 12345) by +doing + + ./dump_state.py -v -c 12345 foo.gtrace > foo.json + +or by specifying the n-th (e.g, 1st) draw call by doing + + ./dump_state.py -v -d 1 foo.gtrace > foo.json + +The state is derived from the call sequence in the trace file, so no dynamic +(eg. rendered textures) is included. + + +You can compare two JSON files by doing + + ./diff_state.py foo.json boo.json | less + +If you're investigating a regression in a state tracker, you can obtain a good +and bad trace, dump respective state in JSON, and then compare the states to +identify the problem. diff --git a/lib/mesa/src/gallium/tools/trace/TODO.txt b/lib/mesa/src/gallium/tools/trace/TODO.txt new file mode 100644 index 000000000..8bb8cfdc0 --- /dev/null +++ b/lib/mesa/src/gallium/tools/trace/TODO.txt @@ -0,0 +1,9 @@ +* track more state + + * constant buffers + +* organize state better (e.g., group stuff according to the place in the + pipeline) + +* write an utility that generated a simple graw C code that matches a + state dump. diff --git a/lib/mesa/src/gallium/tools/trace/diff_state.py b/lib/mesa/src/gallium/tools/trace/diff_state.py new file mode 100755 index 000000000..00853bacb --- /dev/null +++ b/lib/mesa/src/gallium/tools/trace/diff_state.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python +########################################################################## +# +# Copyright 2011 Jose Fonseca +# 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. +# +##########################################################################/ + + +import json +import optparse +import re +import difflib +import sys + + +def strip_object_hook(obj): + if '__class__' in obj: + return None + for name in obj.keys(): + if name.startswith('__') and name.endswith('__'): + del obj[name] + return obj + + +class Visitor: + + def visit(self, node, *args, **kwargs): + if isinstance(node, dict): + return self.visitObject(node, *args, **kwargs) + elif isinstance(node, list): + return self.visitArray(node, *args, **kwargs) + else: + return self.visitValue(node, *args, **kwargs) + + def visitObject(self, node, *args, **kwargs): + pass + + def visitArray(self, node, *args, **kwargs): + pass + + def visitValue(self, node, *args, **kwargs): + pass + + +class Dumper(Visitor): + + def __init__(self, stream = sys.stdout): + self.stream = stream + self.level = 0 + + def _write(self, s): + self.stream.write(s) + + def _indent(self): + self._write(' '*self.level) + + def _newline(self): + self._write('\n') + + def visitObject(self, node): + self.enter_object() + + members = node.keys() + members.sort() + for i in range(len(members)): + name = members[i] + value = node[name] + self.enter_member(name) + self.visit(value) + self.leave_member(i == len(members) - 1) + self.leave_object() + + def enter_object(self): + self._write('{') + self._newline() + self.level += 1 + + def enter_member(self, name): + self._indent() + self._write('%s: ' % name) + + def leave_member(self, last): + if not last: + self._write(',') + self._newline() + + def leave_object(self): + self.level -= 1 + self._indent() + self._write('}') + if self.level <= 0: + self._newline() + + def visitArray(self, node): + self.enter_array() + for i in range(len(node)): + value = node[i] + self._indent() + self.visit(value) + if i != len(node) - 1: + self._write(',') + self._newline() + self.leave_array() + + def enter_array(self): + self._write('[') + self._newline() + self.level += 1 + + def leave_array(self): + self.level -= 1 + self._indent() + self._write(']') + + def visitValue(self, node): + self._write(json.dumps(node, allow_nan=True)) + + + +class Comparer(Visitor): + + def __init__(self, ignore_added = False, tolerance = 2.0 ** -24): + self.ignore_added = ignore_added + self.tolerance = tolerance + + def visitObject(self, a, b): + if not isinstance(b, dict): + return False + if len(a) != len(b) and not self.ignore_added: + return False + ak = a.keys() + bk = b.keys() + ak.sort() + bk.sort() + if ak != bk and not self.ignore_added: + return False + for k in ak: + ae = a[k] + try: + be = b[k] + except KeyError: + return False + if not self.visit(ae, be): + return False + return True + + def visitArray(self, a, b): + if not isinstance(b, list): + return False + if len(a) != len(b): + return False + for ae, be in zip(a, b): + if not self.visit(ae, be): + return False + return True + + def visitValue(self, a, b): + if isinstance(a, float) or isinstance(b, float): + if a == 0: + return abs(b) < self.tolerance + else: + return abs((b - a)/a) < self.tolerance + else: + return a == b + + +class Differ(Visitor): + + def __init__(self, stream = sys.stdout, ignore_added = False): + self.dumper = Dumper(stream) + self.comparer = Comparer(ignore_added = ignore_added) + + def visit(self, a, b): + if self.comparer.visit(a, b): + return + Visitor.visit(self, a, b) + + def visitObject(self, a, b): + if not isinstance(b, dict): + self.replace(a, b) + else: + self.dumper.enter_object() + names = set(a.keys()) + if not self.comparer.ignore_added: + names.update(b.keys()) + names = list(names) + names.sort() + + for i in range(len(names)): + name = names[i] + ae = a.get(name, None) + be = b.get(name, None) + if not self.comparer.visit(ae, be): + self.dumper.enter_member(name) + self.visit(ae, be) + self.dumper.leave_member(i == len(names) - 1) + + self.dumper.leave_object() + + def visitArray(self, a, b): + if not isinstance(b, list): + self.replace(a, b) + else: + self.dumper.enter_array() + max_len = max(len(a), len(b)) + for i in range(max_len): + try: + ae = a[i] + except IndexError: + ae = None + try: + be = b[i] + except IndexError: + be = None + self.dumper._indent() + if self.comparer.visit(ae, be): + self.dumper.visit(ae) + else: + self.visit(ae, be) + if i != max_len - 1: + self.dumper._write(',') + self.dumper._newline() + + self.dumper.leave_array() + + def visitValue(self, a, b): + if a != b: + self.replace(a, b) + + def replace(self, a, b): + if isinstance(a, basestring) and isinstance(b, basestring): + if '\n' in a or '\n' in b: + a = a.splitlines() + b = b.splitlines() + differ = difflib.Differ() + result = differ.compare(a, b) + self.dumper.level += 1 + for entry in result: + self.dumper._newline() + self.dumper._indent() + tag = entry[:2] + text = entry[2:] + if tag == '? ': + tag = ' ' + prefix = ' ' + text = text.rstrip() + suffix = '' + else: + prefix = '"' + suffix = '\\n"' + line = tag + prefix + text + suffix + self.dumper._write(line) + self.dumper.level -= 1 + return + self.dumper.visit(a) + self.dumper._write(' -> ') + self.dumper.visit(b) + + def isMultilineString(self, value): + return isinstance(value, basestring) and '\n' in value + + def replaceMultilineString(self, a, b): + self.dumper.visit(a) + self.dumper._write(' -> ') + self.dumper.visit(b) + + +# +# Unfortunately JSON standard does not include comments, but this is a quite +# useful feature to have on regressions tests +# + +_token_res = [ + r'//[^\r\n]*', # comment + r'"[^"\\]*(\\.[^"\\]*)*"', # string +] + +_tokens_re = re.compile(r'|'.join(['(' + token_re + ')' for token_re in _token_res]), re.DOTALL) + + +def _strip_comment(mo): + if mo.group(1): + return '' + else: + return mo.group(0) + + +def _strip_comments(data): + '''Strip (non-standard) JSON comments.''' + return _tokens_re.sub(_strip_comment, data) + + +assert _strip_comments('''// a comment +"// a comment in a string +"''') == ''' +"// a comment in a string +"''' + + +def load(stream, strip_images = True, strip_comments = True): + if strip_images: + object_hook = strip_object_hook + else: + object_hook = None + if strip_comments: + data = stream.read() + data = _strip_comments(data) + return json.loads(data, strict=False, object_hook = object_hook) + else: + return json.load(stream, strict=False, object_hook = object_hook) + + +def main(): + optparser = optparse.OptionParser( + usage="\n\t%prog [options] <ref_json> <src_json>") + optparser.add_option( + '--keep-images', + action="store_false", dest="strip_images", default=True, + help="compare images") + + (options, args) = optparser.parse_args(sys.argv[1:]) + + if len(args) != 2: + optparser.error('incorrect number of arguments') + + a = load(open(sys.argv[1], 'rt'), options.strip_images) + b = load(open(sys.argv[2], 'rt'), options.strip_images) + + if False: + dumper = Dumper() + dumper.visit(a) + + differ = Differ() + differ.visit(a, b) + + +if __name__ == '__main__': + main() diff --git a/lib/mesa/src/gallium/tools/trace/dump.py b/lib/mesa/src/gallium/tools/trace/dump.py new file mode 100755 index 000000000..4a8ce863c --- /dev/null +++ b/lib/mesa/src/gallium/tools/trace/dump.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +########################################################################## +# +# Copyright 2008 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, sub license, 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 (including the +# next paragraph) 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 NON-INFRINGEMENT. +# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. +# +########################################################################## + + +from parse import * + + +if __name__ == '__main__': + Main().main() diff --git a/lib/mesa/src/gallium/tools/trace/dump_state.py b/lib/mesa/src/gallium/tools/trace/dump_state.py new file mode 100755 index 000000000..e726f7b2a --- /dev/null +++ b/lib/mesa/src/gallium/tools/trace/dump_state.py @@ -0,0 +1,787 @@ +#!/usr/bin/env python +########################################################################## +# +# Copyright 2008-2013, 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, sub license, 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 (including the +# next paragraph) 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 NON-INFRINGEMENT. +# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. +# +########################################################################## + + +import sys +import struct +import json +import binascii +import re +import copy + +import model +import parse as parser + + +try: + from struct import unpack_from +except ImportError: + def unpack_from(fmt, buf, offset=0): + size = struct.calcsize(fmt) + return struct.unpack(fmt, buf[offset:offset + size]) + +# +# Some constants +# +PIPE_BUFFER = 0 +PIPE_SHADER_VERTEX = 0 +PIPE_SHADER_FRAGMENT = 1 +PIPE_SHADER_GEOMETRY = 2 +PIPE_SHADER_COMPUTE = 3 +PIPE_SHADER_TYPES = 4 + + +def serialize(obj): + '''JSON serializer function for non-standard Python objects.''' + + if isinstance(obj, bytearray): + # TODO: Decide on a single way of dumping blobs + if False: + # Don't dump full blobs, but merely a description of their size and + # CRC32 hash. + crc32 = binascii.crc32(obj) + if crc32 < 0: + crc32 += 0x100000000 + return 'blob(size=%u,crc32=0x%08x)' % (len(obj), crc32) + if True: + # Dump blobs as an array of 16byte hexadecimals + res = [] + for i in range(0, len(obj), 16): + res.append(binascii.b2a_hex(obj[i: i+16])) + return res + # Dump blobs as a single hexadecimal string + return binascii.b2a_hex(obj) + + # If the object has a __json__ method, use it. + try: + method = obj.__json__ + except AttributeError: + raise TypeError(obj) + else: + return method() + + +class Struct: + """C-like struct. + + Python doesn't have C structs, but do its dynamic nature, any object is + pretty close. + """ + + def __json__(self): + '''Convert the structure to a standard Python dict, so it can be + serialized.''' + + obj = {} + for name, value in self.__dict__.items(): + if not name.startswith('_'): + obj[name] = value + return obj + + def __repr__(self): + return repr(self.__json__()) + + +class Translator(model.Visitor): + """Translate model arguments into regular Python objects""" + + def __init__(self, interpreter): + self.interpreter = interpreter + self.result = None + + def visit(self, node): + self.result = None + node.visit(self) + return self.result + + def visit_literal(self, node): + self.result = node.value + + def visit_blob(self, node): + self.result = node + + def visit_named_constant(self, node): + self.result = node.name + + def visit_array(self, node): + array = [] + for element in node.elements: + array.append(self.visit(element)) + self.result = array + + def visit_struct(self, node): + struct = Struct() + for member_name, member_node in node.members: + member_value = self.visit(member_node) + setattr(struct, member_name, member_value) + self.result = struct + + def visit_pointer(self, node): + self.result = self.interpreter.lookup_object(node.address) + + +class Dispatcher: + '''Base class for classes whose methods can dispatch Gallium calls.''' + + def __init__(self, interpreter): + self.interpreter = interpreter + + +class Global(Dispatcher): + '''Global name space. + + For calls that are not associated with objects, i.e, functions and not + methods. + ''' + + def pipe_screen_create(self): + return Screen(self.interpreter) + + def pipe_context_create(self, screen): + return screen.context_create() + + +class Transfer: + '''pipe_transfer''' + + def __init__(self, resource, usage, subresource, box): + self.resource = resource + self.usage = usage + self.subresource = subresource + self.box = box + + +class Screen(Dispatcher): + '''pipe_screen''' + + def __init__(self, interpreter): + Dispatcher.__init__(self, interpreter) + + def destroy(self): + pass + + def context_create(self): + return Context(self.interpreter) + + def is_format_supported(self, format, target, sample_count, bind, geom_flags): + pass + + def resource_create(self, templat): + resource = templat + # Normalize state to avoid spurious differences + if resource.nr_samples == 0: + resource.nr_samples = 1 + if resource.target == PIPE_BUFFER: + # We will keep track of buffer contents + resource.data = bytearray(resource.width) + # Ignore format + del resource.format + return resource + + def resource_destroy(self, resource): + self.interpreter.unregister_object(resource) + + def fence_finish(self, fence, timeout=None): + pass + + def fence_signalled(self, fence): + pass + + def fence_reference(self, dst, src): + pass + + def flush_frontbuffer(self, resource): + pass + + +class Context(Dispatcher): + '''pipe_context''' + + # Internal methods variable should be prefixed with '_' + + def __init__(self, interpreter): + Dispatcher.__init__(self, interpreter) + + # Setup initial state + self._state = Struct() + self._state.scissors = [] + self._state.viewports = [] + self._state.vertex_buffers = [] + self._state.vertex_elements = [] + self._state.vs = Struct() + self._state.gs = Struct() + self._state.fs = Struct() + self._state.vs.shader = None + self._state.gs.shader = None + self._state.fs.shader = None + self._state.vs.sampler = [] + self._state.gs.sampler = [] + self._state.fs.sampler = [] + self._state.vs.sampler_views = [] + self._state.gs.sampler_views = [] + self._state.fs.sampler_views = [] + self._state.vs.constant_buffer = [] + self._state.gs.constant_buffer = [] + self._state.fs.constant_buffer = [] + self._state.render_condition_condition = 0 + self._state.render_condition_mode = 0 + + self._draw_no = 0 + + def destroy(self): + pass + + def create_blend_state(self, state): + # Normalize state to avoid spurious differences + if not state.logicop_enable: + del state.logicop_func + if not state.rt[0].blend_enable: + del state.rt[0].rgb_src_factor + del state.rt[0].rgb_dst_factor + del state.rt[0].rgb_func + del state.rt[0].alpha_src_factor + del state.rt[0].alpha_dst_factor + del state.rt[0].alpha_func + return state + + def bind_blend_state(self, state): + # Normalize state + self._state.blend = state + + def delete_blend_state(self, state): + pass + + def create_sampler_state(self, state): + return state + + def delete_sampler_state(self, state): + pass + + def bind_sampler_states(self, shader, start, num_states, states): + # FIXME: Handle non-zero start + assert start == 0 + self._get_stage_state(shader).sampler = states + + def bind_vertex_sampler_states(self, num_states, states): + # XXX: deprecated method + self._state.vs.sampler = states + + def bind_geometry_sampler_states(self, num_states, states): + # XXX: deprecated method + self._state.gs.sampler = states + + def bind_fragment_sampler_states(self, num_states, states): + # XXX: deprecated method + self._state.fs.sampler = states + + def create_rasterizer_state(self, state): + return state + + def bind_rasterizer_state(self, state): + self._state.rasterizer = state + + def delete_rasterizer_state(self, state): + pass + + def create_depth_stencil_alpha_state(self, state): + # Normalize state to avoid spurious differences + if not state.alpha.enabled: + del state.alpha.func + del state.alpha.ref_value + for i in range(2): + if not state.stencil[i].enabled: + del state.stencil[i].func + return state + + def bind_depth_stencil_alpha_state(self, state): + self._state.depth_stencil_alpha = state + + def delete_depth_stencil_alpha_state(self, state): + pass + + _tokenLabelRE = re.compile('^\s*\d+: ', re.MULTILINE) + + def _create_shader_state(self, state): + # Strip the labels from the tokens + if state.tokens is not None: + state.tokens = self._tokenLabelRE.sub('', state.tokens) + return state + + create_vs_state = _create_shader_state + create_gs_state = _create_shader_state + create_fs_state = _create_shader_state + + def bind_vs_state(self, state): + self._state.vs.shader = state + + def bind_gs_state(self, state): + self._state.gs.shader = state + + def bind_fs_state(self, state): + self._state.fs.shader = state + + def _delete_shader_state(self, state): + return state + + delete_vs_state = _delete_shader_state + delete_gs_state = _delete_shader_state + delete_fs_state = _delete_shader_state + + def set_blend_color(self, state): + self._state.blend_color = state + + def set_stencil_ref(self, state): + self._state.stencil_ref = state + + def set_clip_state(self, state): + self._state.clip = state + + def _dump_constant_buffer(self, buffer): + if not self.interpreter.verbosity(2): + return + + data = self.real.buffer_read(buffer) + format = '4f' + index = 0 + for offset in range(0, len(data), struct.calcsize(format)): + x, y, z, w = unpack_from(format, data, offset) + sys.stdout.write('\tCONST[%2u] = {%10.4f, %10.4f, %10.4f, %10.4f}\n' % (index, x, y, z, w)) + index += 1 + sys.stdout.flush() + + def _get_stage_state(self, shader): + if shader == PIPE_SHADER_VERTEX: + return self._state.vs + if shader == PIPE_SHADER_GEOMETRY: + return self._state.gs + if shader == PIPE_SHADER_FRAGMENT: + return self._state.fs + assert False + + def set_constant_buffer(self, shader, index, constant_buffer): + self._update(self._get_stage_state(shader).constant_buffer, index, 1, [constant_buffer]) + + def set_framebuffer_state(self, state): + self._state.fb = state + + def set_polygon_stipple(self, state): + self._state.polygon_stipple = state + + def _update(self, array, start_slot, num_slots, states): + if not isinstance(states, list): + # XXX: trace is not serializing multiple scissors/viewports properly yet + num_slots = 1 + states = [states] + while len(array) < start_slot + num_slots: + array.append(None) + for i in range(num_slots): + array[start_slot + i] = states[i] + + def set_scissor_states(self, start_slot, num_scissors, states): + self._update(self._state.scissors, start_slot, num_scissors, states) + + def set_viewport_states(self, start_slot, num_viewports, states): + self._update(self._state.viewports, start_slot, num_viewports, states) + + def create_sampler_view(self, resource, templ): + templ.resource = resource + return templ + + def sampler_view_destroy(self, view): + pass + + def set_sampler_views(self, shader, start, num, views): + # FIXME: Handle non-zero start + assert start == 0 + self._get_stage_state(shader).sampler_views = views + + def set_fragment_sampler_views(self, num, views): + # XXX: deprecated + self._state.fs.sampler_views = views + + def set_geometry_sampler_views(self, num, views): + # XXX: deprecated + self._state.gs.sampler_views = views + + def set_vertex_sampler_views(self, num, views): + # XXX: deprecated + self._state.vs.sampler_views = views + + def set_vertex_buffers(self, start_slot, num_buffers, buffers): + self._update(self._state.vertex_buffers, start_slot, num_buffers, buffers) + + def create_vertex_elements_state(self, num_elements, elements): + return elements[0:num_elements] + + def bind_vertex_elements_state(self, state): + self._state.vertex_elements = state + + def delete_vertex_elements_state(self, state): + pass + + def set_index_buffer(self, ib): + self._state.index_buffer = ib + + # Don't dump more than this number of indices/vertices + MAX_ELEMENTS = 16 + + def _merge_indices(self, info): + '''Merge the vertices into our state.''' + + index_size = self._state.index_buffer.index_size + + format = { + 1: 'B', + 2: 'H', + 4: 'I', + }[index_size] + + assert struct.calcsize(format) == index_size + + if self._state.index_buffer.buffer is None: + # Could happen with index in user memory + return 0, 0 + + data = self._state.index_buffer.buffer.data + max_index, min_index = 0, 0xffffffff + + count = min(info.count, self.MAX_ELEMENTS) + indices = [] + for i in xrange(info.start, info.start + count): + offset = self._state.index_buffer.offset + i*index_size + if offset + index_size > len(data): + index = 0 + else: + index, = unpack_from(format, data, offset) + indices.append(index) + min_index = min(min_index, index) + max_index = max(max_index, index) + + self._state.indices = indices + + return min_index + info.index_bias, max_index + info.index_bias + + def _merge_vertices(self, start, count): + '''Merge the vertices into our state.''' + + count = min(count, self.MAX_ELEMENTS) + vertices = [] + for index in xrange(start, start + count): + if index >= start + 16: + sys.stdout.write('\t...\n') + break + vertex = [] + for velem in self._state.vertex_elements: + vbuf = self._state.vertex_buffers[velem.vertex_buffer_index] + if vbuf.buffer is None: + continue + + data = vbuf.buffer.data + + offset = vbuf.buffer_offset + velem.src_offset + vbuf.stride*index + format = { + 'PIPE_FORMAT_R32_FLOAT': 'f', + 'PIPE_FORMAT_R32G32_FLOAT': '2f', + 'PIPE_FORMAT_R32G32B32_FLOAT': '3f', + 'PIPE_FORMAT_R32G32B32A32_FLOAT': '4f', + 'PIPE_FORMAT_R32_UINT': 'I', + 'PIPE_FORMAT_R32G32_UINT': '2I', + 'PIPE_FORMAT_R32G32B32_UINT': '3I', + 'PIPE_FORMAT_R32G32B32A32_UINT': '4I', + 'PIPE_FORMAT_R8_UINT': 'B', + 'PIPE_FORMAT_R8G8_UINT': '2B', + 'PIPE_FORMAT_R8G8B8_UINT': '3B', + 'PIPE_FORMAT_R8G8B8A8_UINT': '4B', + 'PIPE_FORMAT_A8R8G8B8_UNORM': '4B', + 'PIPE_FORMAT_R8G8B8A8_UNORM': '4B', + 'PIPE_FORMAT_B8G8R8A8_UNORM': '4B', + 'PIPE_FORMAT_R16G16B16_SNORM': '3h', + }[velem.src_format] + + data = vbuf.buffer.data + attribute = unpack_from(format, data, offset) + vertex.append(attribute) + + vertices.append(vertex) + + self._state.vertices = vertices + + def render_condition(self, query, condition = 0, mode = 0): + self._state.render_condition_query = query + self._state.render_condition_condition = condition + self._state.render_condition_mode = mode + + def set_stream_output_targets(self, num_targets, tgs, offsets): + self._state.so_targets = tgs + self._state.offsets = offsets + + def draw_vbo(self, info): + self._draw_no += 1 + + if self.interpreter.call_no < self.interpreter.options.call and \ + self._draw_no < self.interpreter.options.draw: + return + + # Merge the all draw state + + self._state.draw = info + + if info.indexed: + min_index, max_index = self._merge_indices(info) + else: + min_index = info.start + max_index = info.start + info.count - 1 + self._merge_vertices(min_index, max_index - min_index + 1) + + self._dump_state() + + _dclRE = re.compile('^DCL\s+(IN|OUT|SAMP|SVIEW)\[([0-9]+)\].*$', re.MULTILINE) + + def _normalize_stage_state(self, stage): + + registers = {} + + if stage.shader is not None and stage.shader.tokens is not None: + for mo in self._dclRE.finditer(stage.shader.tokens): + file_ = mo.group(1) + index = mo.group(2) + register = registers.setdefault(file_, set()) + register.add(int(index)) + + if 'SAMP' in registers and 'SVIEW' not in registers: + registers['SVIEW'] = registers['SAMP'] + + mapping = [ + #("CONST", "constant_buffer"), + ("SAMP", "sampler"), + ("SVIEW", "sampler_views"), + ] + + for fileName, attrName in mapping: + register = registers.setdefault(fileName, set()) + attr = getattr(stage, attrName) + for index in range(len(attr)): + if index not in register: + attr[index] = None + while attr and attr[-1] is None: + attr.pop() + + def _dump_state(self): + '''Dump our state to JSON and terminate.''' + + state = copy.deepcopy(self._state) + + self._normalize_stage_state(state.vs) + self._normalize_stage_state(state.gs) + self._normalize_stage_state(state.fs) + + json.dump( + obj = state, + fp = sys.stdout, + default = serialize, + sort_keys = True, + indent = 4, + separators = (',', ': ') + ) + + sys.exit(0) + + def resource_copy_region(self, dst, dst_level, dstx, dsty, dstz, src, src_level, src_box): + if dst.target == PIPE_BUFFER or src.target == PIPE_BUFFER: + assert dst.target == PIPE_BUFFER and src.target == PIPE_BUFFER + assert dst_level == 0 + assert dsty == 0 + assert dstz == 0 + assert src_level == 0 + assert src_box.y == 0 + assert src_box.z == 0 + assert src_box.height == 1 + assert src_box.depth == 1 + dst.data[dstx : dstx + src_box.width] = src.data[src_box.x : src_box.x + src_box.width] + pass + + def is_resource_referenced(self, texture, face, level): + pass + + def get_transfer(self, texture, sr, usage, box): + if texture is None: + return None + transfer = Transfer(texture, sr, usage, box) + return transfer + + def tex_transfer_destroy(self, transfer): + self.interpreter.unregister_object(transfer) + + def transfer_inline_write(self, resource, level, usage, box, stride, layer_stride, data): + if resource is not None and resource.target == PIPE_BUFFER: + data = data.getValue() + assert len(data) >= box.width + assert box.x + box.width <= len(resource.data) + resource.data[box.x : box.x + box.width] = data[:box.width] + + def flush(self, flags): + # Return a fake fence + return self.interpreter.call_no + + def clear(self, buffers, color, depth, stencil): + pass + + def clear_render_target(self, dst, rgba, dstx, dsty, width, height): + pass + + def clear_depth_stencil(self, dst, clear_flags, depth, stencil, dstx, dsty, width, height): + pass + + def create_surface(self, resource, surf_tmpl): + assert resource is not None + surf_tmpl.resource = resource + return surf_tmpl + + def surface_destroy(self, surface): + self.interpreter.unregister_object(surface) + + def create_query(self, query_type, index): + return query_type + + def destroy_query(self, query): + pass + + def begin_query(self, query): + pass + + def end_query(self, query): + pass + + def create_stream_output_target(self, res, buffer_offset, buffer_size): + so_target = Struct() + so_target.resource = res + so_target.offset = buffer_offset + so_target.size = buffer_size + return so_target + + +class Interpreter(parser.TraceDumper): + '''Specialization of a trace parser that interprets the calls as it goes + along.''' + + ignoredCalls = set(( + ('pipe_screen', 'is_format_supported'), + ('pipe_screen', 'get_name'), + ('pipe_screen', 'get_vendor'), + ('pipe_screen', 'get_param'), + ('pipe_screen', 'get_paramf'), + ('pipe_screen', 'get_shader_param'), + ('pipe_context', 'clear_render_target'), # XXX workaround trace bugs + )) + + def __init__(self, stream, options): + parser.TraceDumper.__init__(self, stream, sys.stderr) + self.options = options + self.objects = {} + self.result = None + self.globl = Global(self) + self.call_no = None + + def register_object(self, address, object): + self.objects[address] = object + + def unregister_object(self, object): + # TODO + pass + + def lookup_object(self, address): + try: + return self.objects[address] + except KeyError: + # Could happen, e.g., with user memory pointers + return address + + def interpret(self, trace): + for call in trace.calls: + self.interpret_call(call) + + def handle_call(self, call): + if (call.klass, call.method) in self.ignoredCalls: + return + + self.call_no = call.no + + if self.verbosity(1): + # Write the call to stderr (as stdout would corrupt the JSON output) + sys.stderr.flush() + sys.stdout.flush() + parser.TraceDumper.handle_call(self, call) + sys.stderr.flush() + sys.stdout.flush() + + args = [(str(name), self.interpret_arg(arg)) for name, arg in call.args] + + if call.klass: + name, obj = args[0] + args = args[1:] + else: + obj = self.globl + + method = getattr(obj, call.method) + ret = method(**dict(args)) + + # Keep track of created pointer objects. + if call.ret and isinstance(call.ret, model.Pointer): + if ret is None: + sys.stderr.write('warning: NULL returned\n') + self.register_object(call.ret.address, ret) + + self.call_no = None + + def interpret_arg(self, node): + translator = Translator(self) + return translator.visit(node) + + def verbosity(self, level): + return self.options.verbosity >= level + + +class Main(parser.Main): + + def get_optparser(self): + '''Custom options.''' + + optparser = parser.Main.get_optparser(self) + optparser.add_option("-q", "--quiet", action="store_const", const=0, dest="verbosity", help="no messages") + optparser.add_option("-v", "--verbose", action="count", dest="verbosity", default=0, help="increase verbosity level") + optparser.add_option("-c", "--call", action="store", type="int", dest="call", default=0xffffffff, help="dump on this call") + optparser.add_option("-d", "--draw", action="store", type="int", dest="draw", default=0xffffffff, help="dump on this draw") + return optparser + + def process_arg(self, stream, options): + parser = Interpreter(stream, options) + parser.parse() + + +if __name__ == '__main__': + Main().main() diff --git a/lib/mesa/src/gallium/tools/trace/format.py b/lib/mesa/src/gallium/tools/trace/format.py new file mode 100755 index 000000000..e50f61299 --- /dev/null +++ b/lib/mesa/src/gallium/tools/trace/format.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +########################################################################## +# +# Copyright 2008 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, sub license, 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 (including the +# next paragraph) 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 NON-INFRINGEMENT. +# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. +# +########################################################################## + + +import sys + + +class Formatter: + '''Plain formatter''' + + def __init__(self, stream): + self.stream = stream + + def text(self, text): + self.stream.write(text) + + def newline(self): + self.text('\n') + + def function(self, name): + self.text(name) + + def variable(self, name): + self.text(name) + + def literal(self, value): + self.text(str(value)) + + def address(self, addr): + self.text(str(addr)) + + +class AnsiFormatter(Formatter): + '''Formatter for plain-text files which outputs ANSI escape codes. See + http://en.wikipedia.org/wiki/ANSI_escape_code for more information + concerning ANSI escape codes. + ''' + + _csi = '\33[' + + _normal = '0m' + _bold = '1m' + _italic = '3m' + _red = '31m' + _green = '32m' + _blue = '34m' + + def _escape(self, code): + self.text(self._csi + code) + + def function(self, name): + self._escape(self._bold) + Formatter.function(self, name) + self._escape(self._normal) + + def variable(self, name): + self._escape(self._italic) + Formatter.variable(self, name) + self._escape(self._normal) + + def literal(self, value): + self._escape(self._blue) + Formatter.literal(self, value) + self._escape(self._normal) + + def address(self, value): + self._escape(self._green) + Formatter.address(self, value) + self._escape(self._normal) + + +class WindowsConsoleFormatter(Formatter): + '''Formatter for the Windows Console. See + http://code.activestate.com/recipes/496901/ for more information. + ''' + + STD_INPUT_HANDLE = -10 + STD_OUTPUT_HANDLE = -11 + STD_ERROR_HANDLE = -12 + + FOREGROUND_BLUE = 0x01 + FOREGROUND_GREEN = 0x02 + FOREGROUND_RED = 0x04 + FOREGROUND_INTENSITY = 0x08 + BACKGROUND_BLUE = 0x10 + BACKGROUND_GREEN = 0x20 + BACKGROUND_RED = 0x40 + BACKGROUND_INTENSITY = 0x80 + + _normal = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED + _bold = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY + _italic = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED + _red = FOREGROUND_RED | FOREGROUND_INTENSITY + _green = FOREGROUND_GREEN | FOREGROUND_INTENSITY + _blue = FOREGROUND_BLUE | FOREGROUND_INTENSITY + + def __init__(self, stream): + Formatter.__init__(self, stream) + + if stream is sys.stdin: + nStdHandle = self.STD_INPUT_HANDLE + elif stream is sys.stdout: + nStdHandle = self.STD_OUTPUT_HANDLE + elif stream is sys.stderr: + nStdHandle = self.STD_ERROR_HANDLE + else: + nStdHandle = None + + if nStdHandle: + import ctypes + self.handle = ctypes.windll.kernel32.GetStdHandle(nStdHandle) + else: + self.handle = None + + def _attribute(self, attr): + if self.handle: + import ctypes + ctypes.windll.kernel32.SetConsoleTextAttribute(self.handle, attr) + + def function(self, name): + self._attribute(self._bold) + Formatter.function(self, name) + self._attribute(self._normal) + + def variable(self, name): + self._attribute(self._italic) + Formatter.variable(self, name) + self._attribute(self._normal) + + def literal(self, value): + self._attribute(self._blue) + Formatter.literal(self, value) + self._attribute(self._normal) + + def address(self, value): + self._attribute(self._green) + Formatter.address(self, value) + self._attribute(self._normal) + + +def DefaultFormatter(stream): + if sys.platform in ('linux2', 'cygwin'): + return AnsiFormatter(stream) + elif sys.platform in ('win32',): + return WindowsConsoleFormatter(stream) + else: + return Formatter(stream) + diff --git a/lib/mesa/src/gallium/tools/trace/model.py b/lib/mesa/src/gallium/tools/trace/model.py new file mode 100755 index 000000000..fb56701bf --- /dev/null +++ b/lib/mesa/src/gallium/tools/trace/model.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python +########################################################################## +# +# Copyright 2008 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, sub license, 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 (including the +# next paragraph) 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 NON-INFRINGEMENT. +# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. +# +########################################################################## + + +'''Trace data model.''' + + +import sys +import string +import binascii + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +import format + + +class Node: + + def visit(self, visitor): + raise NotImplementedError + + def __str__(self): + stream = StringIO() + formatter = format.DefaultFormatter(stream) + pretty_printer = PrettyPrinter(formatter) + self.visit(pretty_printer) + return stream.getvalue() + + +class Literal(Node): + + def __init__(self, value): + self.value = value + + def visit(self, visitor): + visitor.visit_literal(self) + + +class Blob(Node): + + def __init__(self, value): + self._rawValue = None + self._hexValue = value + + def getValue(self): + if self._rawValue is None: + self._rawValue = binascii.a2b_hex(self._hexValue) + self._hexValue = None + return self._rawValue + + def visit(self, visitor): + visitor.visit_blob(self) + + +class NamedConstant(Node): + + def __init__(self, name): + self.name = name + + def visit(self, visitor): + visitor.visit_named_constant(self) + + +class Array(Node): + + def __init__(self, elements): + self.elements = elements + + def visit(self, visitor): + visitor.visit_array(self) + + +class Struct(Node): + + def __init__(self, name, members): + self.name = name + self.members = members + + def visit(self, visitor): + visitor.visit_struct(self) + + +class Pointer(Node): + + def __init__(self, address): + self.address = address + + def visit(self, visitor): + visitor.visit_pointer(self) + + +class Call: + + def __init__(self, no, klass, method, args, ret, time): + self.no = no + self.klass = klass + self.method = method + self.args = args + self.ret = ret + self.time = time + + def visit(self, visitor): + visitor.visit_call(self) + + +class Trace: + + def __init__(self, calls): + self.calls = calls + + def visit(self, visitor): + visitor.visit_trace(self) + + +class Visitor: + + def visit_literal(self, node): + raise NotImplementedError + + def visit_blob(self, node): + raise NotImplementedError + + def visit_named_constant(self, node): + raise NotImplementedError + + def visit_array(self, node): + raise NotImplementedError + + def visit_struct(self, node): + raise NotImplementedError + + def visit_pointer(self, node): + raise NotImplementedError + + def visit_call(self, node): + raise NotImplementedError + + def visit_trace(self, node): + raise NotImplementedError + + +class PrettyPrinter: + + def __init__(self, formatter): + self.formatter = formatter + + def visit_literal(self, node): + if node.value is None: + self.formatter.literal('NULL') + return + + if isinstance(node.value, basestring): + self.formatter.literal('"' + node.value + '"') + return + + self.formatter.literal(repr(node.value)) + + def visit_blob(self, node): + self.formatter.address('blob()') + + def visit_named_constant(self, node): + self.formatter.literal(node.name) + + def visit_array(self, node): + self.formatter.text('{') + sep = '' + for value in node.elements: + self.formatter.text(sep) + value.visit(self) + sep = ', ' + self.formatter.text('}') + + def visit_struct(self, node): + self.formatter.text('{') + sep = '' + for name, value in node.members: + self.formatter.text(sep) + self.formatter.variable(name) + self.formatter.text(' = ') + value.visit(self) + sep = ', ' + self.formatter.text('}') + + def visit_pointer(self, node): + self.formatter.address(node.address) + + def visit_call(self, node): + self.formatter.text('%s ' % node.no) + if node.klass is not None: + self.formatter.function(node.klass + '::' + node.method) + else: + self.formatter.function(node.method) + self.formatter.text('(') + sep = '' + for name, value in node.args: + self.formatter.text(sep) + self.formatter.variable(name) + self.formatter.text(' = ') + value.visit(self) + sep = ', ' + self.formatter.text(')') + if node.ret is not None: + self.formatter.text(' = ') + node.ret.visit(self) + if node.time is not None: + self.formatter.text(' // time ') + node.time.visit(self) + + def visit_trace(self, node): + for call in node.calls: + call.visit(self) + self.formatter.newline() + diff --git a/lib/mesa/src/gallium/tools/trace/parse.py b/lib/mesa/src/gallium/tools/trace/parse.py new file mode 100755 index 000000000..25482c889 --- /dev/null +++ b/lib/mesa/src/gallium/tools/trace/parse.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python +########################################################################## +# +# Copyright 2008 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, sub license, 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 (including the +# next paragraph) 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 NON-INFRINGEMENT. +# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS 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. +# +########################################################################## + + +import sys +import xml.parsers.expat +import optparse + +from model import * + + +ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4) + + +class XmlToken: + + def __init__(self, type, name_or_data, attrs = None, line = None, column = None): + assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF) + self.type = type + self.name_or_data = name_or_data + self.attrs = attrs + self.line = line + self.column = column + + def __str__(self): + if self.type == ELEMENT_START: + return '<' + self.name_or_data + ' ...>' + if self.type == ELEMENT_END: + return '</' + self.name_or_data + '>' + if self.type == CHARACTER_DATA: + return self.name_or_data + if self.type == EOF: + return 'end of file' + assert 0 + + +class XmlTokenizer: + """Expat based XML tokenizer.""" + + def __init__(self, fp, skip_ws = True): + self.fp = fp + self.tokens = [] + self.index = 0 + self.final = False + self.skip_ws = skip_ws + + self.character_pos = 0, 0 + self.character_data = '' + + self.parser = xml.parsers.expat.ParserCreate() + self.parser.StartElementHandler = self.handle_element_start + self.parser.EndElementHandler = self.handle_element_end + self.parser.CharacterDataHandler = self.handle_character_data + + def handle_element_start(self, name, attributes): + self.finish_character_data() + line, column = self.pos() + token = XmlToken(ELEMENT_START, name, attributes, line, column) + self.tokens.append(token) + + def handle_element_end(self, name): + self.finish_character_data() + line, column = self.pos() + token = XmlToken(ELEMENT_END, name, None, line, column) + self.tokens.append(token) + + def handle_character_data(self, data): + if not self.character_data: + self.character_pos = self.pos() + self.character_data += data + + def finish_character_data(self): + if self.character_data: + if not self.skip_ws or not self.character_data.isspace(): + line, column = self.character_pos + token = XmlToken(CHARACTER_DATA, self.character_data, None, line, column) + self.tokens.append(token) + self.character_data = '' + + def next(self): + size = 16*1024 + while self.index >= len(self.tokens) and not self.final: + self.tokens = [] + self.index = 0 + data = self.fp.read(size) + self.final = len(data) < size + data = data.rstrip('\0') + try: + self.parser.Parse(data, self.final) + except xml.parsers.expat.ExpatError, e: + #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS: + if e.code == 3: + pass + else: + raise e + if self.index >= len(self.tokens): + line, column = self.pos() + token = XmlToken(EOF, None, None, line, column) + else: + token = self.tokens[self.index] + self.index += 1 + return token + + def pos(self): + return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber + + +class TokenMismatch(Exception): + + def __init__(self, expected, found): + self.expected = expected + self.found = found + + def __str__(self): + return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found)) + + + +class XmlParser: + """Base XML document parser.""" + + def __init__(self, fp): + self.tokenizer = XmlTokenizer(fp) + self.consume() + + def consume(self): + self.token = self.tokenizer.next() + + def match_element_start(self, name): + return self.token.type == ELEMENT_START and self.token.name_or_data == name + + def match_element_end(self, name): + return self.token.type == ELEMENT_END and self.token.name_or_data == name + + def element_start(self, name): + while self.token.type == CHARACTER_DATA: + self.consume() + if self.token.type != ELEMENT_START: + raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token) + if self.token.name_or_data != name: + raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token) + attrs = self.token.attrs + self.consume() + return attrs + + def element_end(self, name): + while self.token.type == CHARACTER_DATA: + self.consume() + if self.token.type != ELEMENT_END: + raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token) + if self.token.name_or_data != name: + raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token) + self.consume() + + def character_data(self, strip = True): + data = '' + while self.token.type == CHARACTER_DATA: + data += self.token.name_or_data + self.consume() + if strip: + data = data.strip() + return data + + +class TraceParser(XmlParser): + + def __init__(self, fp): + XmlParser.__init__(self, fp) + self.last_call_no = 0 + + def parse(self): + self.element_start('trace') + while self.token.type not in (ELEMENT_END, EOF): + call = self.parse_call() + self.handle_call(call) + if self.token.type != EOF: + self.element_end('trace') + + def parse_call(self): + attrs = self.element_start('call') + try: + no = int(attrs['no']) + except KeyError: + self.last_call_no += 1 + no = self.last_call_no + else: + self.last_call_no = no + klass = attrs['class'] + method = attrs['method'] + args = [] + ret = None + time = None + while self.token.type == ELEMENT_START: + if self.token.name_or_data == 'arg': + arg = self.parse_arg() + args.append(arg) + elif self.token.name_or_data == 'ret': + ret = self.parse_ret() + elif self.token.name_or_data == 'call': + # ignore nested function calls + self.parse_call() + elif self.token.name_or_data == 'time': + time = self.parse_time() + else: + raise TokenMismatch("<arg ...> or <ret ...>", self.token) + self.element_end('call') + + return Call(no, klass, method, args, ret, time) + + def parse_arg(self): + attrs = self.element_start('arg') + name = attrs['name'] + value = self.parse_value() + self.element_end('arg') + + return name, value + + def parse_ret(self): + attrs = self.element_start('ret') + value = self.parse_value() + self.element_end('ret') + + return value + + def parse_time(self): + attrs = self.element_start('time') + time = self.parse_value(); + self.element_end('time') + return time + + def parse_value(self): + expected_tokens = ('null', 'bool', 'int', 'uint', 'float', 'string', 'enum', 'array', 'struct', 'ptr', 'bytes') + if self.token.type == ELEMENT_START: + if self.token.name_or_data in expected_tokens: + method = getattr(self, 'parse_' + self.token.name_or_data) + return method() + raise TokenMismatch(" or " .join(expected_tokens), self.token) + + def parse_null(self): + self.element_start('null') + self.element_end('null') + return Literal(None) + + def parse_bool(self): + self.element_start('bool') + value = int(self.character_data()) + self.element_end('bool') + return Literal(value) + + def parse_int(self): + self.element_start('int') + value = int(self.character_data()) + self.element_end('int') + return Literal(value) + + def parse_uint(self): + self.element_start('uint') + value = int(self.character_data()) + self.element_end('uint') + return Literal(value) + + def parse_float(self): + self.element_start('float') + value = float(self.character_data()) + self.element_end('float') + return Literal(value) + + def parse_enum(self): + self.element_start('enum') + name = self.character_data() + self.element_end('enum') + return NamedConstant(name) + + def parse_string(self): + self.element_start('string') + value = self.character_data() + self.element_end('string') + return Literal(value) + + def parse_bytes(self): + self.element_start('bytes') + value = self.character_data() + self.element_end('bytes') + return Blob(value) + + def parse_array(self): + self.element_start('array') + elems = [] + while self.token.type != ELEMENT_END: + elems.append(self.parse_elem()) + self.element_end('array') + return Array(elems) + + def parse_elem(self): + self.element_start('elem') + value = self.parse_value() + self.element_end('elem') + return value + + def parse_struct(self): + attrs = self.element_start('struct') + name = attrs['name'] + members = [] + while self.token.type != ELEMENT_END: + members.append(self.parse_member()) + self.element_end('struct') + return Struct(name, members) + + def parse_member(self): + attrs = self.element_start('member') + name = attrs['name'] + value = self.parse_value() + self.element_end('member') + + return name, value + + def parse_ptr(self): + self.element_start('ptr') + address = self.character_data() + self.element_end('ptr') + + return Pointer(address) + + def handle_call(self, call): + pass + + +class TraceDumper(TraceParser): + + def __init__(self, fp, outStream = sys.stdout): + TraceParser.__init__(self, fp) + self.formatter = format.DefaultFormatter(outStream) + self.pretty_printer = PrettyPrinter(self.formatter) + + def handle_call(self, call): + call.visit(self.pretty_printer) + self.formatter.newline() + + +class Main: + '''Common main class for all retrace command line utilities.''' + + def __init__(self): + pass + + def main(self): + optparser = self.get_optparser() + (options, args) = optparser.parse_args(sys.argv[1:]) + + if not args: + optparser.error('insufficient number of arguments') + + for arg in args: + if arg.endswith('.gz'): + from gzip import GzipFile + stream = GzipFile(arg, 'rt') + elif arg.endswith('.bz2'): + from bz2 import BZ2File + stream = BZ2File(arg, 'rU') + else: + stream = open(arg, 'rt') + self.process_arg(stream, options) + + def get_optparser(self): + optparser = optparse.OptionParser( + usage="\n\t%prog [options] TRACE [...]") + return optparser + + def process_arg(self, stream, options): + parser = TraceDumper(stream) + parser.parse() + + +if __name__ == '__main__': + Main().main() diff --git a/lib/mesa/src/gallium/tools/trace/tracediff.sh b/lib/mesa/src/gallium/tools/trace/tracediff.sh new file mode 100755 index 000000000..c7827c0ff --- /dev/null +++ b/lib/mesa/src/gallium/tools/trace/tracediff.sh @@ -0,0 +1,66 @@ +#!/bin/bash +########################################################################## +# +# Copyright 2011 Jose Fonseca +# 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. +# +##########################################################################/ + +set -e + +TRACEDUMP=${TRACEDUMP:-`dirname "$0"`/dump.py} + +stripdump () { + python $TRACEDUMP "$1" \ + | sed \ + -e 's@ // time .*@@' \ + -e 's/\x1b\[[0-9]\{1,2\}\(;[0-9]\{1,2\}\)\{0,2\}m//g' \ + -e '/pipe_screen::is_format_supported/d' \ + -e '/pipe_screen::get_\(shader_\)\?paramf\?/d' \ + -e 's/\r$//g' \ + -e 's/^[0-9]\+ //' \ + -e 's/pipe = \w\+/pipe/g' \ + -e 's/screen = \w\+/screen/g' \ + -e 's/, /,\n\t/g' \ + -e 's/) = /)\n\t= /' \ + > "$2" + echo \ + -e 's/\<0x[0-9a-fA-F]\+\>/xxx/g' \ + > /dev/null +} + +FIFODIR=`mktemp -d` +FIFO1="$FIFODIR/1" +FIFO2="$FIFODIR/2" + +mkfifo "$FIFO1" +mkfifo "$FIFO2" + +stripdump "$1" "$FIFO1" & +stripdump "$2" "$FIFO2" & + +sdiff \ + --width=`tput cols` \ + --speed-large-files \ + "$FIFO1" "$FIFO2" \ +| less + +rm -rf "$FIFODIR" |