python/retrace: Application capable of replaying gallium traces.

At the moment it is capable of replaying trivial/tri kind of apps.

See README for status.
This commit is contained in:
José Fonseca
2008-08-14 11:00:22 +01:00
parent ffaa4e816f
commit 468c9775cb
4 changed files with 919 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
This is
To use it follow the instructions in src/gallium/drivers/trace/README and
src/gallium/state_trackers/python/README, and then do
python src/gallium/state_trackers/python/samples/retrace/interpreter.py filename.trace
This is still work in progress:
- not everything is captured/replayed
- surface/textures contents
- any tiny error will result in a crash
--
Jose Fonseca <jrfonseca@tungstengraphics.com>

View File

@@ -0,0 +1,418 @@
#!/usr/bin/env python
#############################################################################
#
# Copyright 2008 Tungsten Graphics, Inc.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import sys
import gallium
import model
from parser import TraceParser
def make_image(surface):
pixels = gallium.FloatArray(surface.height*surface.width*4)
surface.get_tile_rgba(0, 0, surface.width, surface.height, pixels)
import Image
outimage = Image.new(
mode='RGB',
size=(surface.width, surface.height),
color=(0,0,0))
outpixels = outimage.load()
for y in range(0, surface.height):
for x in range(0, surface.width):
offset = (y*surface.width + x)*4
r, g, b, a = [int(pixels[offset + ch]*255) for ch in range(4)]
outpixels[x, y] = r, g, b
return outimage
def save_image(filename, surface):
outimage = make_image(surface)
outimage.save(filename, "PNG")
def show_image(surface):
outimage = make_image(surface)
import Tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
root.title('background image')
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_constant_buffer": gallium.ConstantBuffer,
"pipe_depth_state": gallium.Depth,
"pipe_stencil_state": gallium.Stencil,
"pipe_alpha_state": gallium.Alpha,
"pipe_depth_stencil_alpha_state": gallium.DepthStencilAlpha,
"pipe_format_block": gallium.FormatBlock,
#"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},
}
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):
return Screen(self.interpreter, winsys.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 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):
buffer.write(data, size)
buffer.write(data, size)
class Screen(Object):
def get_name(self):
pass
def get_vendor(self):
pass
def get_param(self, param):
pass
def get_paramf(self, param):
pass
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, template):
return self.real.texture_create(
format = template.format,
width = template.width[0],
height = template.height[0],
depth = template.depth[0],
last_level = template.last_level,
target = template.target,
tex_usage = template.tex_usage,
)
def texture_release(self, texture):
self.interpreter.unregister_object(texture)
def get_tex_surface(self, texture, face, level, zslice, usage):
return texture.get_surface(face, level, zslice, usage)
def tex_surface_release(self, surface):
self.interpreter.unregister_object(surface)
def surface_map(self, surface, flags):
return None
def surface_unmap(self, surface):
pass
class Context(Object):
def __init__(self, interpreter, real):
Object.__init__(self, interpreter, real)
self.cbufs = []
self.zsbuf = None
def create_blend_state(self, state):
return state
def bind_blend_state(self, state):
self.real.set_blend(state)
def create_sampler_state(self, state):
return state
def bind_sampler_states(self, n, states):
for i in range(n):
self.real.set_sampler(i, states[i])
def create_rasterizer_state(self, state):
return state
def bind_rasterizer_state(self, state):
self.real.set_rasterizer(state)
def create_depth_stencil_alpha_state(self, state):
return state
def bind_depth_stencil_alpha_state(self, state):
self.real.set_depth_stencil_alpha(state)
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 set_blend_color(self, state):
self.real.set_blend_color(state)
def set_clip_state(self, state):
self.real.set_clip(state)
def set_constant_buffer(self, shader, index, state):
self.real.set_constant_buffer(shader, index, state.buffer)
def set_framebuffer_state(self, state):
_state = gallium.Framebuffer()
_state.width = state.width
_state.height = state.height
_state.num_cbufs = state.num_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_sampler_textures(self, n, textures):
for i in range(n):
self.real.set_sampler_textures(textures[i])
def set_vertex_buffers(self, n, vbufs):
for i in range(n):
vbuf = vbufs[i]
self.real.set_vertex_buffer(
i,
pitch = vbuf.pitch,
max_index = vbuf.max_index,
buffer_offset = vbuf.buffer_offset,
buffer = vbuf.buffer,
)
def set_vertex_elements(self, n, elements):
for i in range(n):
self.real.set_vertex_element(i, elements[i])
self.real.set_vertex_elements(n)
def set_edgeflags(self, bitfield):
# FIXME
pass
def draw_arrays(self, mode, start, count):
self.real.draw_arrays(mode, start, count)
self._update()
def flush(self, flags, fence):
self.real.flush(flags)
def clear(self, surface, value):
self.real.surface_clear(surface, value)
def _update(self):
self.real.flush()
if self.cbufs and self.cbufs[0]:
show_image(self.cbufs[0])
class Interpreter:
def __init__(self):
self.objects = {}
self.result = None
self.globl = Global(self, 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 interpret_call(self, call):
sys.stderr.write("%s\n" % call)
args = [self.interpret_arg(arg) for name, arg in call.args]
if call.klass:
obj = args[0]
args = args[1:]
else:
obj = self.globl
method = getattr(obj, call.method)
ret = method(*args)
if call.ret and isinstance(call.ret, model.Pointer):
self.register_object(call.ret.address, ret)
def interpret_arg(self, node):
translator = Translator(self)
return translator.visit(node)
def main():
for arg in sys.argv[1:]:
parser = TraceParser(open(arg, 'rt'))
trace = parser.parse()
interpreter = Interpreter()
interpreter.interpret(trace)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,151 @@
#!/usr/bin/env python
#############################################################################
#
# Copyright 2008 Tungsten Graphics, Inc.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
'''Trace data model.'''
class Node:
def visit(self, visitor):
raise NotImplementedError
class Literal(Node):
def __init__(self, value):
self.value = value
def visit(self, visitor):
visitor.visit_literal(self)
def __str__(self):
if isinstance(self.value, str) and len(self.value) > 32:
return '...'
else:
return repr(self.value)
class NamedConstant(Node):
def __init__(self, name):
self.name = name
def visit(self, visitor):
visitor.visit_named_constant(self)
def __str__(self):
return self.name
class Array(Node):
def __init__(self, elements):
self.elements = elements
def visit(self, visitor):
visitor.visit_array(self)
def __str__(self):
return '{' + ', '.join([str(value) for value in self.elements]) + '}'
class Struct(Node):
def __init__(self, name, members):
self.name = name
self.members = members
def visit(self, visitor):
visitor.visit_struct(self)
def __str__(self):
return '{' + ', '.join([name + ' = ' + str(value) for name, value in self.members]) + '}'
class Pointer(Node):
def __init__(self, address):
self.address = address
def visit(self, visitor):
visitor.visit_pointer(self)
def __str__(self):
return hex(self.address)
class Call:
def __init__(self, klass, method, args, ret):
self.klass = klass
self.method = method
self.args = args
self.ret = ret
def visit(self, visitor):
visitor.visit_call(self)
def __str__(self):
s = self.method
if self.klass:
s = self.klass + '::' + s
s += '(' + ', '.join([name + ' = ' + str(value) for name, value in self.args]) + ')'
if self.ret is not None:
s += ' = ' + str(self.ret)
return s
class Trace:
def __init__(self, calls):
self.calls = calls
def visit(self, visitor):
visitor.visit_trace(self)
def __str__(self):
return '\n'.join([str(call) for call in self.calls])
class Visitor:
def visit_literal(self, node):
raise NotImplementedError
def visit_named_constant(self, node):
raise NotImplementedError
def visit_array(self, node):
raise NotImplementedError
def visit_struct(self, node):
raise NotImplementedError
def visit_pointer(self, node):
raise NotImplementedError
def visit_call(self, node):
raise NotImplementedError
def visit_trace(self, node):
raise NotImplementedError

View File

@@ -0,0 +1,332 @@
#!/usr/bin/env python
#############################################################################
#
# Copyright 2008 Tungsten Graphics, Inc.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#############################################################################
import sys
import xml.parsers.expat
import binascii
from model import *
ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
class XmlToken:
def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF)
self.type = type
self.name_or_data = name_or_data
self.attrs = attrs
self.line = line
self.column = column
def __str__(self):
if self.type == ELEMENT_START:
return '<' + self.name_or_data + ' ...>'
if self.type == ELEMENT_END:
return '</' + self.name_or_data + '>'
if self.type == CHARACTER_DATA:
return self.name_or_data
if self.type == EOF:
return 'end of file'
assert 0
class XmlTokenizer:
"""Expat based XML tokenizer."""
def __init__(self, fp, skip_ws = True):
self.fp = fp
self.tokens = []
self.index = 0
self.final = False
self.skip_ws = skip_ws
self.character_pos = 0, 0
self.character_data = ''
self.parser = xml.parsers.expat.ParserCreate()
self.parser.StartElementHandler = self.handle_element_start
self.parser.EndElementHandler = self.handle_element_end
self.parser.CharacterDataHandler = self.handle_character_data
def handle_element_start(self, name, attributes):
self.finish_character_data()
line, column = self.pos()
token = XmlToken(ELEMENT_START, name, attributes, line, column)
self.tokens.append(token)
def handle_element_end(self, name):
self.finish_character_data()
line, column = self.pos()
token = XmlToken(ELEMENT_END, name, None, line, column)
self.tokens.append(token)
def handle_character_data(self, data):
if not self.character_data:
self.character_pos = self.pos()
self.character_data += data
def finish_character_data(self):
if self.character_data:
if not self.skip_ws or not self.character_data.isspace():
line, column = self.character_pos
token = XmlToken(CHARACTER_DATA, self.character_data, None, line, column)
self.tokens.append(token)
self.character_data = ''
def next(self):
size = 16*1024
while self.index >= len(self.tokens) and not self.final:
self.tokens = []
self.index = 0
data = self.fp.read(size)
self.final = len(data) < size
try:
self.parser.Parse(data, self.final)
except xml.parsers.expat.ExpatError, e:
#if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
if e.code == 3:
pass
else:
raise e
if self.index >= len(self.tokens):
line, column = self.pos()
token = XmlToken(EOF, None, None, line, column)
else:
token = self.tokens[self.index]
self.index += 1
return token
def pos(self):
return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
class TokenMismatch(Exception):
def __init__(self, expected, found):
self.expected = expected
self.found = found
def __str__(self):
return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
class XmlParser:
"""Base XML document parser."""
def __init__(self, fp):
self.tokenizer = XmlTokenizer(fp)
self.consume()
def consume(self):
self.token = self.tokenizer.next()
def match_element_start(self, name):
return self.token.type == ELEMENT_START and self.token.name_or_data == name
def match_element_end(self, name):
return self.token.type == ELEMENT_END and self.token.name_or_data == name
def element_start(self, name):
while self.token.type == CHARACTER_DATA:
self.consume()
if self.token.type != ELEMENT_START:
raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
if self.token.name_or_data != name:
raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
attrs = self.token.attrs
self.consume()
return attrs
def element_end(self, name):
while self.token.type == CHARACTER_DATA:
self.consume()
if self.token.type != ELEMENT_END:
raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
if self.token.name_or_data != name:
raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
self.consume()
def character_data(self, strip = True):
data = ''
while self.token.type == CHARACTER_DATA:
data += self.token.name_or_data
self.consume()
if strip:
data = data.strip()
return data
class TraceParser(XmlParser):
def parse(self):
self.element_start('trace')
calls = []
while self.token.type not in (ELEMENT_END, EOF):
calls.append(self.parse_call())
if self.token.type != EOF:
self.element_end('trace')
return Trace(calls)
def parse_call(self):
attrs = self.element_start('call')
klass = attrs['class']
method = attrs['method']
args = []
ret = None
while self.token.type == ELEMENT_START:
if self.token.name_or_data == 'arg':
arg = self.parse_arg()
args.append(arg)
elif self.token.name_or_data == 'ret':
ret = self.parse_ret()
elif self.token.name_or_data == 'call':
# ignore nested function calls
self.parse_call()
else:
raise TokenMismatch("<arg ...> or <ret ...>", self.token)
self.element_end('call')
return Call(klass, method, args, ret)
def parse_arg(self):
attrs = self.element_start('arg')
name = attrs['name']
value = self.parse_value()
self.element_end('arg')
return name, value
def parse_ret(self):
attrs = self.element_start('ret')
value = self.parse_value()
self.element_end('ret')
return value
def parse_value(self):
expected_tokens = ('null', 'bool', 'int', 'uint', 'float', 'string', 'enum', 'array', 'struct', 'ptr', 'bytes')
if self.token.type == ELEMENT_START:
if self.token.name_or_data in expected_tokens:
method = getattr(self, 'parse_' + self.token.name_or_data)
return method()
raise TokenMismatch(" or " .join(expected_tokens), self.token)
def parse_null(self):
self.element_start('null')
self.element_end('null')
return Literal(None)
def parse_bool(self):
self.element_start('bool')
value = int(self.character_data())
self.element_end('bool')
return Literal(value)
def parse_int(self):
self.element_start('int')
value = int(self.character_data())
self.element_end('int')
return Literal(value)
def parse_uint(self):
self.element_start('uint')
value = int(self.character_data())
self.element_end('uint')
return Literal(value)
def parse_float(self):
self.element_start('float')
value = float(self.character_data())
self.element_end('float')
return Literal(value)
def parse_enum(self):
self.element_start('enum')
name = self.character_data()
self.element_end('enum')
return NamedConstant(name)
def parse_string(self):
self.element_start('string')
value = self.character_data()
self.element_end('string')
return Literal(value)
def parse_bytes(self):
self.element_start('bytes')
value = binascii.a2b_hex(self.character_data())
self.element_end('bytes')
return Literal(value)
def parse_array(self):
self.element_start('array')
elems = []
while self.token.type != ELEMENT_END:
elems.append(self.parse_elem())
self.element_end('array')
return Array(elems)
def parse_elem(self):
self.element_start('elem')
value = self.parse_value()
self.element_end('elem')
return value
def parse_struct(self):
attrs = self.element_start('struct')
name = attrs['name']
members = []
while self.token.type != ELEMENT_END:
members.append(self.parse_member())
self.element_end('struct')
return Struct(name, members)
def parse_member(self):
attrs = self.element_start('member')
name = attrs['name']
value = self.parse_value()
self.element_end('member')
return name, value
def parse_ptr(self):
self.element_start('ptr')
address = self.character_data()
self.element_end('ptr')
address = int(address, 16)
return Pointer(address)
def main():
for arg in sys.argv[1:]:
parser = TraceParser(open(arg, 'rt'))
trace = parser.parse()
print trace
if __name__ == '__main__':
main()