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