1#!/usr/local/bin/python3.8
2##########################################################################
3#
4# Copyright 2008-2013, VMware, Inc.
5# All Rights Reserved.
6#
7# Permission is hereby granted, free of charge, to any person obtaining a
8# copy of this software and associated documentation files (the
9# "Software"), to deal in the Software without restriction, including
10# without limitation the rights to use, copy, modify, merge, publish,
11# distribute, sub license, and/or sell copies of the Software, and to
12# permit persons to whom the Software is furnished to do so, subject to
13# the following conditions:
14#
15# The above copyright notice and this permission notice (including the
16# next paragraph) shall be included in all copies or substantial portions
17# of the Software.
18#
19# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
23# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26#
27##########################################################################
28
29
30import sys
31import struct
32import json
33import binascii
34import re
35import copy
36import argparse
37
38import model
39import format
40import parse as parser
41
42
43try:
44    from struct import unpack_from
45except ImportError:
46    def unpack_from(fmt, buf, offset=0):
47        size = struct.calcsize(fmt)
48        return struct.unpack(fmt, buf[offset:offset + size])
49
50#
51# Some constants
52#
53PIPE_BUFFER = 0
54PIPE_SHADER_VERTEX   = 0
55PIPE_SHADER_FRAGMENT = 1
56PIPE_SHADER_GEOMETRY = 2
57PIPE_SHADER_COMPUTE  = 3
58PIPE_SHADER_TYPES    = 4
59
60
61def serialize(obj):
62    '''JSON serializer function for non-standard Python objects.'''
63    if isinstance(obj, bytearray) or isinstance(obj, bytes):
64        # TODO: Decide on a single way of dumping blobs
65        if False:
66            # Don't dump full blobs, but merely a description of their size and
67            # CRC32 hash.
68            crc32 = binascii.crc32(obj)
69            if crc32 < 0:
70                crc32 += 0x100000000
71            return 'blob(size=%u,crc32=0x%08x)' % (len(obj), crc32)
72        if True:
73            # Dump blobs as an array of 16byte hexadecimals
74            res = []
75            for i in range(0, len(obj), 16):
76                res.append(obj[i : i + 16].hex())
77            return res
78        # Dump blobs as a single hexadecimal string
79        return obj.hex()
80
81    # If the object has a __json__ method, use it.
82    try:
83        method = obj.__json__
84    except AttributeError:
85        raise TypeError(obj)
86    else:
87        return method()
88
89
90class Struct:
91    """C-like struct.
92
93    Python doesn't have C structs, but do its dynamic nature, any object is
94    pretty close.
95    """
96
97    def __json__(self):
98        '''Convert the structure to a standard Python dict, so it can be
99        serialized.'''
100
101        obj = {}
102        for name, value in self.__dict__.items():
103            if not name.startswith('_'):
104                obj[name] = value
105        return obj
106
107    def __repr__(self):
108        return repr(self.__json__())
109
110
111class Translator(model.Visitor):
112    """Translate model arguments into regular Python objects"""
113
114    def __init__(self, interpreter):
115        self.interpreter = interpreter
116        self.result = None
117
118    def visit(self, node):
119        self.result = None
120        node.visit(self)
121        return self.result
122
123    def visit_literal(self, node):
124        self.result = node.value
125
126    def visit_blob(self, node):
127        self.result = node
128
129    def visit_named_constant(self, node):
130        self.result = node.name
131
132    def visit_array(self, node):
133        array = []
134        for element in node.elements:
135            array.append(self.visit(element))
136        self.result = array
137
138    def visit_struct(self, node):
139        struct = Struct()
140        for member_name, member_node in node.members:
141            member_name = member_name.replace('.', '_')
142            member_value = self.visit(member_node)
143            setattr(struct, member_name, member_value)
144        self.result = struct
145
146    def visit_pointer(self, node):
147        self.result = self.interpreter.lookup_object(node.address)
148
149
150class Dispatcher:
151    '''Base class for classes whose methods can dispatch Gallium calls.'''
152
153    def __init__(self, interpreter):
154        self.interpreter = interpreter
155
156
157class Global(Dispatcher):
158    '''Global name space.
159
160    For calls that are not associated with objects, i.e, functions and not
161    methods.
162    '''
163
164    def pipe_screen_create(self):
165        return Screen(self.interpreter)
166
167    def pipe_context_create(self, screen):
168        return screen.context_create()
169
170
171class Transfer:
172    '''pipe_transfer'''
173
174    def __init__(self, resource, usage, subresource, box):
175        self.resource = resource
176        self.usage = usage
177        self.subresource = subresource
178        self.box = box
179
180
181class Screen(Dispatcher):
182    '''pipe_screen'''
183
184    def __init__(self, interpreter):
185        Dispatcher.__init__(self, interpreter)
186
187    def destroy(self):
188        pass
189
190    def context_create(self, priv=None, flags=0):
191        return Context(self.interpreter)
192
193    def is_format_supported(self, format, target, sample_count, bind, geom_flags):
194        pass
195
196    def resource_create(self, templat):
197        resource = templat
198        # Normalize state to avoid spurious differences
199        if resource.nr_samples == 0:
200            resource.nr_samples = 1
201        if resource.target == PIPE_BUFFER:
202            # We will keep track of buffer contents
203            resource.data = bytearray(resource.width)
204            # Ignore format
205            del resource.format
206        return resource
207
208    def resource_destroy(self, resource):
209        self.interpreter.unregister_object(resource)
210
211    def fence_finish(self, fence, timeout=None, ctx=None):
212        pass
213
214    def fence_signalled(self, fence):
215        pass
216
217    def fence_reference(self, dst, src):
218        pass
219
220    def flush_frontbuffer(self, resource):
221        pass
222
223
224class Context(Dispatcher):
225    '''pipe_context'''
226
227    # Internal methods variable should be prefixed with '_'
228
229    def __init__(self, interpreter):
230        Dispatcher.__init__(self, interpreter)
231
232        # Setup initial state
233        self._state = Struct()
234        self._state.scissors = []
235        self._state.viewports = []
236        self._state.vertex_buffers = []
237        self._state.vertex_elements = []
238        self._state.vs = Struct()
239        self._state.gs = Struct()
240        self._state.fs = Struct()
241        self._state.vs.shader = None
242        self._state.gs.shader = None
243        self._state.fs.shader = None
244        self._state.vs.sampler = []
245        self._state.gs.sampler = []
246        self._state.fs.sampler = []
247        self._state.vs.sampler_views = []
248        self._state.gs.sampler_views = []
249        self._state.fs.sampler_views = []
250        self._state.vs.constant_buffer = []
251        self._state.gs.constant_buffer = []
252        self._state.fs.constant_buffer = []
253        self._state.render_condition_condition = 0
254        self._state.render_condition_mode = 0
255
256        self._draw_no = 0
257
258    def destroy(self):
259        pass
260
261    def create_blend_state(self, state):
262        # Normalize state to avoid spurious differences
263        if not state.logicop_enable:
264            del state.logicop_func
265        if not state.rt[0].blend_enable:
266            del state.rt[0].rgb_src_factor
267            del state.rt[0].rgb_dst_factor
268            del state.rt[0].rgb_func
269            del state.rt[0].alpha_src_factor
270            del state.rt[0].alpha_dst_factor
271            del state.rt[0].alpha_func
272        return state
273
274    def bind_blend_state(self, state):
275        # Normalize state
276        self._state.blend = state
277
278    def delete_blend_state(self, state):
279        pass
280
281    def create_sampler_state(self, state):
282        return state
283
284    def delete_sampler_state(self, state):
285        pass
286
287    def bind_sampler_states(self, shader, start, num_states, states):
288        # FIXME: Handle non-zero start
289        assert start == 0
290        self._get_stage_state(shader).sampler = states
291
292    def bind_vertex_sampler_states(self, num_states, states):
293        # XXX: deprecated method
294        self._state.vs.sampler = states
295
296    def bind_geometry_sampler_states(self, num_states, states):
297        # XXX: deprecated method
298        self._state.gs.sampler = states
299
300    def bind_fragment_sampler_states(self, num_states, states):
301        # XXX: deprecated method
302        self._state.fs.sampler = states
303
304    def create_rasterizer_state(self, state):
305        return state
306
307    def bind_rasterizer_state(self, state):
308        self._state.rasterizer = state
309
310    def delete_rasterizer_state(self, state):
311        pass
312
313    def create_depth_stencil_alpha_state(self, state):
314        # Normalize state to avoid spurious differences
315        if not state.alpha.enabled:
316            del state.alpha.func
317            del state.alpha.ref_value
318        for i in range(2):
319            if not state.stencil[i].enabled:
320                del state.stencil[i].func
321        return state
322
323    def bind_depth_stencil_alpha_state(self, state):
324        self._state.depth_stencil_alpha = state
325
326    def delete_depth_stencil_alpha_state(self, state):
327        pass
328
329    _tokenLabelRE = re.compile('^\s*\d+: ', re.MULTILINE)
330
331    def _create_shader_state(self, state):
332        # Strip the labels from the tokens
333        if state.tokens is not None:
334            state.tokens = self._tokenLabelRE.sub('', state.tokens)
335        return state
336
337    create_vs_state = _create_shader_state
338    create_gs_state = _create_shader_state
339    create_fs_state = _create_shader_state
340
341    def bind_vs_state(self, state):
342        self._state.vs.shader = state
343
344    def bind_gs_state(self, state):
345        self._state.gs.shader = state
346
347    def bind_fs_state(self, state):
348        self._state.fs.shader = state
349
350    def _delete_shader_state(self, state):
351        return state
352
353    delete_vs_state = _delete_shader_state
354    delete_gs_state = _delete_shader_state
355    delete_fs_state = _delete_shader_state
356
357    def set_blend_color(self, state):
358        self._state.blend_color = state
359
360    def set_stencil_ref(self, state):
361        self._state.stencil_ref = state
362
363    def set_clip_state(self, state):
364        self._state.clip = state
365
366    def _dump_constant_buffer(self, buffer):
367        if not self.interpreter.verbosity(2):
368            return
369
370        data = self.real.buffer_read(buffer)
371        format = '4f'
372        index = 0
373        for offset in range(0, len(data), struct.calcsize(format)):
374            x, y, z, w = unpack_from(format, data, offset)
375            sys.stdout.write('\tCONST[%2u] = {%10.4f, %10.4f, %10.4f, %10.4f}\n' % (index, x, y, z, w))
376            index += 1
377        sys.stdout.flush()
378
379    def _get_stage_state(self, shader):
380        if shader == PIPE_SHADER_VERTEX:
381            return self._state.vs
382        if shader == PIPE_SHADER_GEOMETRY:
383            return self._state.gs
384        if shader == PIPE_SHADER_FRAGMENT:
385            return self._state.fs
386        assert False
387
388    def set_constant_buffer(self, shader, index, constant_buffer):
389        self._update(self._get_stage_state(shader).constant_buffer, index, 1, [constant_buffer])
390
391    def set_framebuffer_state(self, state):
392        self._state.fb = state
393
394    def set_polygon_stipple(self, state):
395        self._state.polygon_stipple = state
396
397    def _update(self, array, start_slot, num_slots, states):
398        if not isinstance(states, list):
399            # XXX: trace is not serializing multiple scissors/viewports properly yet
400            num_slots = 1
401            states = [states]
402        while len(array) < start_slot + num_slots:
403            array.append(None)
404        for i in range(num_slots):
405            array[start_slot + i] = states[i]
406
407    def set_scissor_states(self, start_slot, num_scissors, states):
408        self._update(self._state.scissors, start_slot, num_scissors, states)
409
410    def set_viewport_states(self, start_slot, num_viewports, states):
411        self._update(self._state.viewports, start_slot, num_viewports, states)
412
413    def create_sampler_view(self, resource, templ):
414        templ.resource = resource
415        return templ
416
417    def sampler_view_destroy(self, view):
418        pass
419
420    def set_sampler_views(self, shader, start, num, views):
421        # FIXME: Handle non-zero start
422        assert start == 0
423        self._get_stage_state(shader).sampler_views = views
424
425    def set_fragment_sampler_views(self, num, views):
426        # XXX: deprecated
427        self._state.fs.sampler_views = views
428
429    def set_geometry_sampler_views(self, num, views):
430        # XXX: deprecated
431        self._state.gs.sampler_views = views
432
433    def set_vertex_sampler_views(self, num, views):
434        # XXX: deprecated
435        self._state.vs.sampler_views = views
436
437    def set_vertex_buffers(self, start_slot, num_buffers, buffers):
438        self._update(self._state.vertex_buffers, start_slot, num_buffers, buffers)
439
440    def create_vertex_elements_state(self, num_elements, elements):
441        return elements[0:num_elements]
442
443    def bind_vertex_elements_state(self, state):
444        self._state.vertex_elements = state
445
446    def delete_vertex_elements_state(self, state):
447        pass
448
449    def set_index_buffer(self, ib):
450        self._state.index_buffer = ib
451
452    # Don't dump more than this number of indices/vertices
453    MAX_ELEMENTS = 16
454
455    def _merge_indices(self, info):
456        '''Merge the vertices into our state.'''
457
458        index_size = self._state.index_buffer.index_size
459
460        format = {
461            1: 'B',
462            2: 'H',
463            4: 'I',
464        }[index_size]
465
466        assert struct.calcsize(format) == index_size
467
468        if self._state.index_buffer.buffer is None:
469            # Could happen with index in user memory
470            return 0, 0
471
472        data = self._state.index_buffer.buffer.data
473        max_index, min_index = 0, 0xffffffff
474
475        count = min(info.count, self.MAX_ELEMENTS)
476        indices = []
477        for i in range(info.start, info.start + count):
478            offset = self._state.index_buffer.offset + i*index_size
479            if offset + index_size > len(data):
480                index = 0
481            else:
482                index, = unpack_from(format, data, offset)
483            indices.append(index)
484            min_index = min(min_index, index)
485            max_index = max(max_index, index)
486
487        self._state.indices = indices
488
489        return min_index + info.index_bias, max_index + info.index_bias
490
491    def _merge_vertices(self, start, count):
492        '''Merge the vertices into our state.'''
493
494        count = min(count, self.MAX_ELEMENTS)
495        vertices = []
496        for index in range(start, start + count):
497            if index >= start + 16:
498                sys.stdout.write('\t...\n')
499                break
500            vertex = []
501            for velem in self._state.vertex_elements:
502                vbuf = self._state.vertex_buffers[velem.vertex_buffer_index]
503                resource = vbuf.buffer_resource
504                if resource is None:
505                    continue
506
507                data = resource.data
508
509                offset = vbuf.buffer_offset + velem.src_offset + vbuf.stride*index
510                format = {
511                    'PIPE_FORMAT_R32_FLOAT': 'f',
512                    'PIPE_FORMAT_R32G32_FLOAT': '2f',
513                    'PIPE_FORMAT_R32G32B32_FLOAT': '3f',
514                    'PIPE_FORMAT_R32G32B32A32_FLOAT': '4f',
515                    'PIPE_FORMAT_R32_UINT': 'I',
516                    'PIPE_FORMAT_R32G32_UINT': '2I',
517                    'PIPE_FORMAT_R32G32B32_UINT': '3I',
518                    'PIPE_FORMAT_R32G32B32A32_UINT': '4I',
519                    'PIPE_FORMAT_R8_UINT': 'B',
520                    'PIPE_FORMAT_R8G8_UINT': '2B',
521                    'PIPE_FORMAT_R8G8B8_UINT': '3B',
522                    'PIPE_FORMAT_R8G8B8A8_UINT': '4B',
523                    'PIPE_FORMAT_A8R8G8B8_UNORM': '4B',
524                    'PIPE_FORMAT_R8G8B8A8_UNORM': '4B',
525                    'PIPE_FORMAT_B8G8R8A8_UNORM': '4B',
526                    'PIPE_FORMAT_R16G16B16_SNORM': '3h',
527                }[velem.src_format]
528
529                data = resource.data
530                attribute = unpack_from(format, data, offset)
531                vertex.append(attribute)
532
533            vertices.append(vertex)
534
535        self._state.vertices = vertices
536
537    def render_condition(self, query, condition = 0, mode = 0):
538        self._state.render_condition_query = query
539        self._state.render_condition_condition = condition
540        self._state.render_condition_mode = mode
541
542    def set_stream_output_targets(self, num_targets, tgs, offsets):
543        self._state.so_targets = tgs
544        self._state.offsets = offsets
545
546    def draw_vbo(self, info):
547        self._draw_no += 1
548
549        if self.interpreter.call_no < self.interpreter.options.call and \
550            self._draw_no < self.interpreter.options.draw:
551                return
552
553        # Merge the all draw state
554
555        self._state.draw = info
556
557        if info.index_size != 0:
558            min_index, max_index = self._merge_indices(info)
559        else:
560            min_index = info.start
561            max_index = info.start + info.count - 1
562        self._merge_vertices(min_index, max_index - min_index + 1)
563
564        self._dump_state()
565
566    _dclRE = re.compile('^DCL\s+(IN|OUT|SAMP|SVIEW)\[([0-9]+)\].*$', re.MULTILINE)
567
568    def _normalize_stage_state(self, stage):
569
570        registers = {}
571
572        if stage.shader is not None and stage.shader.tokens is not None:
573            for mo in self._dclRE.finditer(stage.shader.tokens):
574                file_ = mo.group(1)
575                index = mo.group(2)
576                register = registers.setdefault(file_, set())
577                register.add(int(index))
578
579        if 'SAMP' in registers and 'SVIEW' not in registers:
580            registers['SVIEW'] = registers['SAMP']
581
582        mapping = [
583            #("CONST", "constant_buffer"),
584            ("SAMP", "sampler"),
585            ("SVIEW", "sampler_views"),
586        ]
587
588        for fileName, attrName in mapping:
589            register = registers.setdefault(fileName, set())
590            attr = getattr(stage, attrName)
591            for index in range(len(attr)):
592                if index not in register:
593                    attr[index] = None
594            while attr and attr[-1] is None:
595                attr.pop()
596
597    def _dump_state(self):
598        '''Dump our state to JSON and terminate.'''
599
600        state = copy.deepcopy(self._state)
601
602        self._normalize_stage_state(state.vs)
603        self._normalize_stage_state(state.gs)
604        self._normalize_stage_state(state.fs)
605
606        json.dump(
607            obj = state,
608            fp = sys.stdout,
609            default = serialize,
610            sort_keys = True,
611            indent = 4,
612            separators = (',', ': ')
613        )
614
615        sys.exit(0)
616
617    def resource_copy_region(self, dst, dst_level, dstx, dsty, dstz, src, src_level, src_box):
618        if dst.target == PIPE_BUFFER or src.target == PIPE_BUFFER:
619            assert dst.target == PIPE_BUFFER and src.target == PIPE_BUFFER
620            assert dst_level == 0
621            assert dsty == 0
622            assert dstz == 0
623            assert src_level == 0
624            assert src_box.y == 0
625            assert src_box.z == 0
626            assert src_box.height == 1
627            assert src_box.depth == 1
628            dst.data[dstx : dstx + src_box.width] = src.data[src_box.x : src_box.x + src_box.width]
629        pass
630
631    def is_resource_referenced(self, texture, face, level):
632        pass
633
634    def get_transfer(self, texture, sr, usage, box):
635        if texture is None:
636            return None
637        transfer = Transfer(texture, sr, usage, box)
638        return transfer
639
640    def tex_transfer_destroy(self, transfer):
641        self.interpreter.unregister_object(transfer)
642
643    def buffer_subdata(self, resource, usage, data, box=None, offset=None, size=None, level=None, stride=None, layer_stride=None):
644        if box is not None:
645            # XXX trace_context_transfer_unmap generates brokens buffer_subdata
646            assert offset is None
647            assert size is None
648            assert level == 0
649            offset = box.x
650            size = box.width
651            box = None
652
653        if resource is not None and resource.target == PIPE_BUFFER:
654            data = data.getValue()
655            assert len(data) >= size
656            assert offset + size <= len(resource.data)
657            resource.data[offset : offset + size] = data[:size]
658
659    def texture_subdata(self, resource, level, usage, box, data, stride, layer_stride):
660        pass
661
662    def transfer_inline_write(self, resource, level, usage, box, stride, layer_stride, data):
663        if resource is not None and resource.target == PIPE_BUFFER:
664            data = data.getValue()
665            assert len(data) >= box.width
666            assert box.x + box.width <= len(resource.data)
667            resource.data[box.x : box.x + box.width] = data[:box.width]
668
669    def flush(self, flags):
670        # Return a fake fence
671        return self.interpreter.call_no
672
673    def clear(self, buffers, color, depth, stencil, scissor_state=None):
674        pass
675
676    def clear_render_target(self, dst, rgba, dstx, dsty, width, height):
677        pass
678
679    def clear_depth_stencil(self, dst, clear_flags, depth, stencil, dstx, dsty, width, height):
680        pass
681
682    def create_surface(self, resource, surf_tmpl):
683        assert resource is not None
684        surf_tmpl.resource = resource
685        return surf_tmpl
686
687    def surface_destroy(self, surface):
688        self.interpreter.unregister_object(surface)
689
690    def create_query(self, query_type, index):
691        return query_type
692
693    def destroy_query(self, query):
694        pass
695
696    def begin_query(self, query):
697        pass
698
699    def end_query(self, query):
700        pass
701
702    def create_stream_output_target(self, res, buffer_offset, buffer_size):
703        so_target = Struct()
704        so_target.resource = res
705        so_target.offset = buffer_offset
706        so_target.size = buffer_size
707        return so_target
708
709
710class Interpreter(parser.SimpleTraceDumper):
711    '''Specialization of a trace parser that interprets the calls as it goes
712    along.'''
713
714    ignoredCalls = set((
715            ('pipe_screen', 'is_format_supported'),
716            ('pipe_screen', 'get_name'),
717            ('pipe_screen', 'get_vendor'),
718            ('pipe_screen', 'get_param'),
719            ('pipe_screen', 'get_paramf'),
720            ('pipe_screen', 'get_shader_param'),
721            ('pipe_screen', 'get_disk_shader_cache'),
722            ('pipe_context', 'clear_render_target'), # XXX workaround trace bugs
723            ('pipe_context', 'flush_resource'),
724    ))
725
726    def __init__(self, stream, options, formatter):
727        parser.SimpleTraceDumper.__init__(self, stream, options, formatter)
728        self.objects = {}
729        self.result = None
730        self.globl = Global(self)
731        self.call_no = None
732
733    def register_object(self, address, object):
734        self.objects[address] = object
735
736    def unregister_object(self, object):
737        # TODO
738        pass
739
740    def lookup_object(self, address):
741        try:
742            return self.objects[address]
743        except KeyError:
744            # Could happen, e.g., with user memory pointers
745            return address
746
747    def interpret(self, trace):
748        for call in trace.calls:
749            self.interpret_call(call)
750
751    def handle_call(self, call):
752        if (call.klass, call.method) in self.ignoredCalls:
753            return
754
755        self.call_no = call.no
756
757        if self.verbosity(1):
758            # Write the call to stderr (as stdout would corrupt the JSON output)
759            sys.stderr.flush()
760            sys.stdout.flush()
761            parser.TraceDumper.handle_call(self, call)
762            sys.stderr.flush()
763            sys.stdout.flush()
764
765        args = [(str(name), self.interpret_arg(arg)) for name, arg in call.args]
766
767        if call.klass:
768            name, obj = args[0]
769            args = args[1:]
770        else:
771            obj = self.globl
772
773        method = getattr(obj, call.method)
774        ret = method(**dict(args))
775
776        # Keep track of created pointer objects.
777        if call.ret and isinstance(call.ret, model.Pointer):
778            if ret is None:
779                sys.stderr.write('warning: NULL returned\n')
780            self.register_object(call.ret.address, ret)
781
782        self.call_no = None
783
784    def interpret_arg(self, node):
785        translator = Translator(self)
786        return translator.visit(node)
787
788    def verbosity(self, level):
789        return self.options.verbosity >= level
790
791
792class Main(parser.Main):
793
794    def get_optparser(self):
795        optparser = argparse.ArgumentParser(
796            description="Parse and dump Gallium trace(s) as JSON")
797
798        optparser.add_argument("filename", action="extend", nargs="+",
799            type=str, metavar="filename", help="Gallium trace filename (plain or .gz, .bz2)")
800
801        optparser.add_argument("-v", "--verbose", action="count", default=0, dest="verbosity", help="increase verbosity level")
802        optparser.add_argument("-q", "--quiet", action="store_const", const=0, dest="verbosity", help="no messages")
803        optparser.add_argument("-c", "--call", action="store", type=int, dest="call", default=0xffffffff, help="dump on this call")
804        optparser.add_argument("-d", "--draw", action="store", type=int, dest="draw", default=0xffffffff, help="dump on this draw")
805        return optparser
806
807    def process_arg(self, stream, options):
808        formatter = format.Formatter(sys.stderr)
809        parser = Interpreter(stream, options, formatter)
810        parser.parse()
811
812
813if __name__ == '__main__':
814    Main().main()
815