From cc4ad695caa0546c7a9bcb6719f5efaf90db5608 Mon Sep 17 00:00:00 2001 From: José Fonseca Date: Thu, 20 Jun 2013 19:40:06 +0100 Subject: tools/trace: Tool to dump gallium state at any draw call. Based from the code from the good old python state tracker. Extremely handy to diagnose regressions in state trackers. Reviewed-by: Brian Paul --- src/gallium/tools/trace/dump_state.py | 633 ++++++++++++++++++++++++++++++++++ src/gallium/tools/trace/parse.py | 4 +- 2 files changed, 635 insertions(+), 2 deletions(-) create mode 100755 src/gallium/tools/trace/dump_state.py (limited to 'src/gallium') diff --git a/src/gallium/tools/trace/dump_state.py b/src/gallium/tools/trace/dump_state.py new file mode 100755 index 00000000000..cad23bd1517 --- /dev/null +++ b/src/gallium/tools/trace/dump_state.py @@ -0,0 +1,633 @@ +#!/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 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 + + +def serialize(obj): + '''JSON serializer function for non-standard Python objects.''' + + # Don't dump full blobs, but merely a description of their size and + # CRC32 hash. + if isinstance(obj, bytearray): + crc32 = binascii.crc32(obj) + if crc32 < 0: + crc32 += 0x100000000 + return 'blob(size=%u,crc32=0x%08x)' % (len(obj), crc32) + + # 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 + + +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) + return resource + + def resource_destroy(self, resource): + self.interpreter.unregister_object(resource) + + def fence_finish(self, fence, flags): + 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._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_vertex_sampler_states(self, num_states, states): + self._state.vertex_sampler = states + + def bind_geometry_sampler_states(self, num_states, states): + self._state.geometry_sampler = states + + def bind_fragment_sampler_states(self, num_states, states): + self._state.fragment_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 + 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 + 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 = state + + def bind_gs_state(self, state): + self._state.gs = state + + def bind_fs_state(self, state): + self._state.fs = 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 set_constant_buffer(self, shader, index, constant_buffer): + # TODO + pass + + 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_fragment_sampler_views(self, num, views): + self._state.fragment_sampler_views = views + + def set_geometry_sampler_views(self, num, views): + self._state.geometry_sampler_views = views + + def set_vertex_sampler_views(self, num, views): + self._state.vertex_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 + + 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 + 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_UINT': 'I', + 'PIPE_FORMAT_R32_FLOAT': 'f', + 'PIPE_FORMAT_R32G32_FLOAT': '2f', + 'PIPE_FORMAT_R32G32B32_FLOAT': '3f', + 'PIPE_FORMAT_R32G32B32A32_FLOAT': '4f', + '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 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 + count - 1 + self._merge_vertices(min_index, max_index - min_index + 1) + + self._dump_state() + + def _dump_state(self): + '''Dump our state to JSON and terminate.''' + + json.dump( + obj = self._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: + # TODO + assert 0 + 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.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 + + def flush(self, flags): + pass + + 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): + surf_tmpl.resource = resource + return surf_tmpl + + def surface_destroy(self, surface): + self.interpreter.unregister_object(surface) + + def create_query(self, query_type): + return query_type + + def destroy_query(self, query): + pass + + +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): + return self.objects[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/src/gallium/tools/trace/parse.py b/src/gallium/tools/trace/parse.py index d8dce2628ab..6a0d2fedb9d 100755 --- a/src/gallium/tools/trace/parse.py +++ b/src/gallium/tools/trace/parse.py @@ -352,9 +352,9 @@ class TraceParser(XmlParser): class TraceDumper(TraceParser): - def __init__(self, fp): + def __init__(self, fp, outStream = sys.stdout): TraceParser.__init__(self, fp) - self.formatter = format.DefaultFormatter(sys.stdout) + self.formatter = format.DefaultFormatter(outStream) self.pretty_printer = PrettyPrinter(self.formatter) def handle_call(self, call): -- cgit v1.2.3