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