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