#!/usr/bin/env python ########################################################################## # # Copyright 2008 Tungsten Graphics, Inc., Cedar Park, Texas. # All Rights Reserved. # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sub license, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice (including the # next paragraph) shall be included in all copies or substantial portions # of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. # IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS BE LIABLE FOR # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # ########################################################################## import sys import struct import gallium import model import parse as parser try: from struct import unpack_from except ImportError: def unpack_from(fmt, buf, offset=0): size = struct.calcsize(fmt) return struct.unpack(fmt, buf[offset:offset + size]) def make_image(surface, x=None, y=None, w=None, h=None): if x is None: x = 0 if y is None: y = 0 if w is None: w = surface.width - x if h is None: h = surface.height - y data = surface.get_tile_rgba8(x, y, surface.width, surface.height) import Image outimage = Image.fromstring('RGBA', (w, h), data, "raw", 'RGBA', 0, 1) return outimage def save_image(filename, surface, x=None, y=None, w=None, h=None): outimage = make_image(surface, x, y, w, h) outimage.save(filename, "PNG") def show_image(surface, title, x=None, y=None, w=None, h=None): outimage = make_image(surface, x, y, w, h) import Tkinter as tk from PIL import Image, ImageTk root = tk.Tk() root.title(title) image1 = ImageTk.PhotoImage(outimage) w = image1.width() h = image1.height() x = 100 y = 100 root.geometry("%dx%d+%d+%d" % (w, h, x, y)) panel1 = tk.Label(root, image=image1) panel1.pack(side='top', fill='both', expand='yes') panel1.image = image1 root.mainloop() class Struct: """C-like struct""" # A basic Python class can pass as a C-like structure pass struct_factories = { "pipe_blend_color": gallium.BlendColor, "pipe_blend_state": gallium.Blend, #"pipe_clip_state": gallium.Clip, #"pipe_buffer": gallium.Buffer, "pipe_depth_state": gallium.Depth, "pipe_stencil_state": gallium.Stencil, "pipe_alpha_state": gallium.Alpha, "pipe_depth_stencil_alpha_state": gallium.DepthStencilAlpha, #"pipe_framebuffer_state": gallium.Framebuffer, "pipe_poly_stipple": gallium.PolyStipple, "pipe_rasterizer_state": gallium.Rasterizer, "pipe_sampler_state": gallium.Sampler, "pipe_scissor_state": gallium.Scissor, #"pipe_shader_state": gallium.Shader, #"pipe_vertex_buffer": gallium.VertexBuffer, "pipe_vertex_element": gallium.VertexElement, "pipe_viewport_state": gallium.Viewport, #"pipe_texture": gallium.Texture, } member_array_factories = { #"pipe_rasterizer_state": {"sprite_coord_mode": gallium.ByteArray}, "pipe_poly_stipple": {"stipple": gallium.UnsignedArray}, "pipe_viewport_state": {"scale": gallium.FloatArray, "translate": gallium.FloatArray}, #"pipe_clip_state": {"ucp": gallium.FloatArray}, "pipe_depth_stencil_alpha_state": {"stencil": gallium.StencilArray}, "pipe_blend_color": {"color": gallium.FloatArray}, "pipe_sampler_state": {"border_color": gallium.FloatArray}, } class Translator(model.Visitor): """Translate model arguments into regular Python objects""" def __init__(self, interpreter): self.interpreter = interpreter self.result = None def visit(self, node): self.result = None node.visit(self) return self.result def visit_literal(self, node): self.result = node.value def visit_named_constant(self, node): # lookup the named constant in the gallium module self.result = getattr(gallium, node.name) def visit_array(self, node): array = [] for element in node.elements: array.append(self.visit(element)) self.result = array def visit_struct(self, node): struct_factory = struct_factories.get(node.name, Struct) struct = struct_factory() for member_name, member_node in node.members: member_value = self.visit(member_node) try: array_factory = member_array_factories[node.name][member_name] except KeyError: pass else: assert isinstance(member_value, list) array = array_factory(len(member_value)) for i in range(len(member_value)): array[i] = member_value[i] member_value = array #print node.name, member_name, member_value assert isinstance(struct, Struct) or hasattr(struct, member_name) setattr(struct, member_name, member_value) self.result = struct def visit_pointer(self, node): self.result = self.interpreter.lookup_object(node.address) class Object: def __init__(self, interpreter, real): self.interpreter = interpreter self.real = real class Global(Object): def __init__(self, interpreter, real): self.interpreter = interpreter self.real = real def pipe_winsys_create(self): return Winsys(self.interpreter, gallium.Device()) def pipe_screen_create(self, winsys=None): if winsys is None: real = gallium.Device() else: real = winsys.real return Screen(self.interpreter, real) def pipe_context_create(self, screen): context = screen.real.context_create() return Context(self.interpreter, context) class Winsys(Object): def __init__(self, interpreter, real): self.interpreter = interpreter self.real = real def get_name(self): pass def user_buffer_create(self, data, size): # We don't really care to distinguish between user and regular buffers buffer = self.real.buffer_create(size, 4, gallium.PIPE_BUFFER_USAGE_CPU_READ | gallium.PIPE_BUFFER_USAGE_CPU_WRITE ) assert size == len(data) buffer.write(data) return buffer def buffer_create(self, alignment, usage, size): return self.real.buffer_create(size, alignment, usage) def buffer_destroy(self, buffer): pass def buffer_write(self, buffer, data, size): assert size == len(data) buffer.write(data) def fence_finish(self, fence, flags): pass def fence_reference(self, dst, src): pass def flush_frontbuffer(self, surface): pass def surface_alloc(self): return None def surface_release(self, surface): pass class Transfer: def __init__(self, surface, x, y, w, h): self.surface = surface self.x = x self.y = y self.w = w self.h = h class Screen(Object): def destroy(self): pass def get_name(self): pass def get_vendor(self): pass def get_param(self, param): pass def get_paramf(self, param): pass def context_create(self): context = self.real.context_create() return Context(self.interpreter, context) def is_format_supported(self, format, target, tex_usage, geom_flags): return self.real.is_format_supported(format, target, tex_usage, geom_flags) def texture_create(self, templat): return self.real.texture_create( format = templat.format, width = templat.width, height = templat.height, depth = templat.depth, last_level = templat.last_level, target = templat.target, tex_usage = templat.tex_usage, ) def texture_destroy(self, texture): self.interpreter.unregister_object(texture) def texture_release(self, surface): pass def get_tex_surface(self, texture, face, level, zslice, usage): if texture is None: return None return texture.get_surface(face, level, zslice) def tex_surface_destroy(self, surface): self.interpreter.unregister_object(surface) def tex_surface_release(self, surface): pass def surface_write(self, surface, data, stride, size): if surface is None: return # assert surface.nblocksy * stride == size surface.put_tile_raw(0, 0, surface.width, surface.height, data, stride) def get_tex_transfer(self, texture, face, level, zslice, usage, x, y, w, h): if texture is None: return None transfer = Transfer(texture.get_surface(face, level, zslice), x, y, w, h) if transfer and usage & gallium.PIPE_TRANSFER_READ: if self.interpreter.options.all: self.interpreter.present(transfer.surface, 'transf_read', x, y, w, h) return transfer def tex_transfer_destroy(self, transfer): self.interpreter.unregister_object(transfer) def transfer_write(self, transfer, stride, data, size): if transfer is None: return transfer.surface.put_tile_raw(transfer.x, transfer.y, transfer.w, transfer.h, data, stride) if self.interpreter.options.all: self.interpreter.present(transfer.surface, 'transf_write', transfer.x, transfer.y, transfer.w, transfer.h) def user_buffer_create(self, data, size): # We don't really care to distinguish between user and regular buffers buffer = self.real.buffer_create(size, 4, gallium.PIPE_BUFFER_USAGE_CPU_READ | gallium.PIPE_BUFFER_USAGE_CPU_WRITE ) assert size == len(data) buffer.write(data) return buffer def buffer_create(self, alignment, usage, size): return self.real.buffer_create(size, alignment, usage) def buffer_destroy(self, buffer): pass def buffer_write(self, buffer, data, size, offset=0): assert size == len(data) buffer.write(data) def fence_finish(self, fence, flags): pass def fence_reference(self, dst, src): pass def flush_frontbuffer(self, surface): pass class Context(Object): def __init__(self, interpreter, real): Object.__init__(self, interpreter, real) self.cbufs = [] self.zsbuf = None self.vbufs = [] self.velems = [] self.dirty = False def destroy(self): pass def create_blend_state(self, state): if isinstance(state, str): state = gallium.Blend(state) sys.stdout.write('\t%s\n' % state) return state def bind_blend_state(self, state): if state is not None: self.real.set_blend(state) def delete_blend_state(self, state): pass def create_sampler_state(self, state): return state def delete_sampler_state(self, state): pass def bind_vertex_sampler_states(self, num_states, states): for i in range(num_states): self.real.set_vertex_sampler(i, states[i]) def bind_fragment_sampler_states(self, num_states, states): for i in range(num_states): self.real.set_fragment_sampler(i, states[i]) def create_rasterizer_state(self, state): return state def bind_rasterizer_state(self, state): if state is not None: self.real.set_rasterizer(state) def delete_rasterizer_state(self, state): pass def create_depth_stencil_alpha_state(self, state): return state def bind_depth_stencil_alpha_state(self, state): if state is not None: self.real.set_depth_stencil_alpha(state) def delete_depth_stencil_alpha_state(self, state): pass def create_fs_state(self, state): tokens = str(state.tokens) shader = gallium.Shader(tokens) return shader create_vs_state = create_fs_state def bind_fs_state(self, state): self.real.set_fragment_shader(state) def bind_vs_state(self, state): self.real.set_vertex_shader(state) def delete_fs_state(self, state): pass delete_vs_state = delete_fs_state def set_blend_color(self, state): self.real.set_blend_color(state) def set_stencil_ref(self, state): self.real.set_stencil_ref(state) def set_clip_state(self, state): _state = gallium.Clip() _state.nr = state.nr if state.nr: # FIXME ucp = gallium.FloatArray(gallium.PIPE_MAX_CLIP_PLANES*4) for i in range(len(state.ucp)): for j in range(len(state.ucp[i])): ucp[i*4 + j] = state.ucp[i][j] _state.ucp = ucp self.real.set_clip(_state) def dump_constant_buffer(self, buffer): if not self.interpreter.verbosity(2): return data = buffer.read() format = '4f' index = 0 for offset in range(0, len(data), struct.calcsize(format)): x, y, z, w = unpack_from(format, data, offset) sys.stdout.write('\tCONST[%2u] = {%10.4f, %10.4f, %10.4f, %10.4f}\n' % (index, x, y, z, w)) index += 1 sys.stdout.flush() def set_constant_buffer(self, shader, index, buffer): if buffer is not None: self.real.set_constant_buffer(shader, index, buffer) self.dump_constant_buffer(buffer) def set_framebuffer_state(self, state): _state = gallium.Framebuffer() _state.width = state.width _state.height = state.height _state.nr_cbufs = state.nr_cbufs for i in range(len(state.cbufs)): _state.set_cbuf(i, state.cbufs[i]) _state.set_zsbuf(state.zsbuf) self.real.set_framebuffer(_state) self.cbufs = state.cbufs self.zsbuf = state.zsbuf def set_polygon_stipple(self, state): self.real.set_polygon_stipple(state) def set_scissor_state(self, state): self.real.set_scissor(state) def set_viewport_state(self, state): self.real.set_viewport(state) def set_fragment_sampler_textures(self, num_textures, textures): for i in range(num_textures): self.real.set_fragment_sampler_texture(i, textures[i]) def set_vertex_sampler_textures(self, num_textures, textures): for i in range(num_textures): self.real.set_vertex_sampler_texture(i, textures[i]) def set_vertex_buffers(self, num_buffers, buffers): self.vbufs = buffers[0:num_buffers] for i in range(num_buffers): vbuf = buffers[i] self.real.set_vertex_buffer( i, stride = vbuf.stride, max_index = vbuf.max_index, buffer_offset = vbuf.buffer_offset, buffer = vbuf.buffer, ) def set_vertex_elements(self, num_elements, elements): self.velems = elements[0:num_elements] for i in range(num_elements): self.real.set_vertex_element(i, elements[i]) self.real.set_vertex_elements(num_elements) def dump_vertices(self, start, count): if not self.interpreter.verbosity(2): return for index in range(start, start + count): if index >= start + 16: sys.stdout.write('\t...\n') break sys.stdout.write('\t{\n') for velem in self.velems: vbuf = self.vbufs[velem.vertex_buffer_index] offset = vbuf.buffer_offset + velem.src_offset + vbuf.stride*index format = { gallium.PIPE_FORMAT_R32_FLOAT: 'f', gallium.PIPE_FORMAT_R32G32_FLOAT: '2f', gallium.PIPE_FORMAT_R32G32B32_FLOAT: '3f', gallium.PIPE_FORMAT_R32G32B32A32_FLOAT: '4f', gallium.PIPE_FORMAT_A8R8G8B8_UNORM: '4B', gallium.PIPE_FORMAT_R8G8B8A8_UNORM: '4B', gallium.PIPE_FORMAT_R16G16B16_SNORM: '3h', }[velem.src_format] data = vbuf.buffer.read() values = unpack_from(format, data, offset) sys.stdout.write('\t\t{' + ', '.join(map(str, values)) + '},\n') assert len(values) == velem.nr_components sys.stdout.write('\t},\n') sys.stdout.flush() def dump_indices(self, ibuf, isize, start, count): if not self.interpreter.verbosity(2): return format = { 1: 'B', 2: 'H', 4: 'I', }[isize] assert struct.calcsize(format) == isize data = ibuf.read() maxindex, minindex = 0, 0xffffffff sys.stdout.write('\t{\n') for i in range(start, start + count): if i >= start + 16 and not self.interpreter.verbosity(3): sys.stdout.write('\t...\n') break offset = i*isize index, = unpack_from(format, data, offset) sys.stdout.write('\t\t%u,\n' % index) minindex = min(minindex, index) maxindex = max(maxindex, index) sys.stdout.write('\t},\n') sys.stdout.flush() return minindex, maxindex def draw_arrays(self, mode, start, count): self.dump_vertices(start, count) self.real.draw_arrays(mode, start, count) self._set_dirty() def draw_elements(self, indexBuffer, indexSize, mode, start, count): if self.interpreter.verbosity(2): minindex, maxindex = self.dump_indices(indexBuffer, indexSize, start, count) self.dump_vertices(minindex, maxindex - minindex) self.real.draw_elements(indexBuffer, indexSize, mode, start, count) self._set_dirty() def draw_range_elements(self, indexBuffer, indexSize, minIndex, maxIndex, mode, start, count): if self.interpreter.verbosity(2): minindex, maxindex = self.dump_indices(indexBuffer, indexSize, start, count) minindex = min(minindex, minIndex) maxindex = min(maxindex, maxIndex) self.dump_vertices(minindex, maxindex - minindex) self.real.draw_range_elements(indexBuffer, indexSize, minIndex, maxIndex, mode, start, count) self._set_dirty() def surface_copy(self, dest, destx, desty, src, srcx, srcy, width, height): if dest is not None and src is not None: if self.interpreter.options.all: self.interpreter.present(src, 'surface_copy_src', srcx, srcy, width, height) self.real.surface_copy(dest, destx, desty, src, srcx, srcy, width, height) if dest in self.cbufs: self._set_dirty() flags = gallium.PIPE_FLUSH_FRAME else: flags = 0 self.flush(flags) if self.interpreter.options.all: self.interpreter.present(dest, 'surface_copy_dest', destx, desty, width, height) def is_texture_referenced(self, texture, face, level): #return self.real.is_texture_referenced(format, texture, face, level) pass def is_buffer_referenced(self, buf): #return self.real.is_buffer_referenced(format, buf) pass def _set_dirty(self): if self.interpreter.options.step: self._present() else: self.dirty = True def flush(self, flags): self.real.flush(flags) if self.dirty: if flags & gallium.PIPE_FLUSH_FRAME: self._present() self.dirty = False return None def clear(self, buffers, rgba, depth, stencil): _rgba = gallium.FloatArray(4) for i in range(4): _rgba[i] = rgba[i] self.real.clear(buffers, _rgba, depth, stencil) def _present(self): self.real.flush() if self.cbufs and self.cbufs[0]: self.interpreter.present(self.cbufs[0], "cbuf") if self.zsbuf: if self.interpreter.options.all: self.interpreter.present(self.zsbuf, "zsbuf") class Interpreter(parser.TraceDumper): ignore_calls = set(( ('pipe_screen', 'is_format_supported'), ('pipe_screen', 'get_param'), ('pipe_screen', 'get_paramf'), )) def __init__(self, stream, options): parser.TraceDumper.__init__(self, stream) self.options = options self.objects = {} self.result = None self.globl = Global(self, None) self.call_no = None def register_object(self, address, object): self.objects[address] = object def unregister_object(self, object): # FIXME: pass def lookup_object(self, address): return self.objects[address] def interpret(self, trace): for call in trace.calls: self.interpret_call(call) def handle_call(self, call): if self.options.stop and call.no > self.options.stop: sys.exit(0) if (call.klass, call.method) in self.ignore_calls: return self.call_no = call.no if self.verbosity(1): parser.TraceDumper.handle_call(self, call) sys.stdout.flush() args = [(str(name), self.interpret_arg(arg)) for name, arg in call.args] if call.klass: name, obj = args[0] args = args[1:] else: obj = self.globl method = getattr(obj, call.method) ret = method(**dict(args)) if call.ret and isinstance(call.ret, model.Pointer): if ret is None: sys.stderr.write('warning: NULL returned\n') self.register_object(call.ret.address, ret) self.call_no = None def interpret_arg(self, node): translator = Translator(self) return translator.visit(node) def verbosity(self, level): return self.options.verbosity >= level def present(self, surface, description, x=None, y=None, w=None, h=None): if self.call_no < self.options.start: return if self.options.images: filename = '%04u_%s.png' % (self.call_no, description) save_image(filename, surface, x, y, w, h) else: title = '%u. %s' % (self.call_no, description) show_image(surface, title, x, y, w, h) class Main(parser.Main): def get_optparser(self): optparser = parser.Main.get_optparser(self) optparser.add_option("-q", "--quiet", action="store_const", const=0, dest="verbosity", help="no messages") optparser.add_option("-v", "--verbose", action="count", dest="verbosity", default=1, help="increase verbosity level") optparser.add_option("-i", "--images", action="store_true", dest="images", default=False, help="save images instead of showing them") optparser.add_option("-a", "--all", action="store_true", dest="all", default=False, help="show depth, stencil, and transfers") optparser.add_option("-s", "--step", action="store_true", dest="step", default=False, help="step trhough every draw") optparser.add_option("-f", "--from", action="store", type="int", dest="start", default=0, help="from call no") optparser.add_option("-t", "--to", action="store", type="int", dest="stop", default=0, help="until call no") return optparser def process_arg(self, stream, options): parser = Interpreter(stream, options) parser.parse() if __name__ == '__main__': Main().main()