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