progs/gallium: Move into src/gallium/tests

Two reasons:
- progs will eventually have its own repository
- it is just to easy to forget updating the
  code for interface changes when it is outside of src
diff --git a/src/gallium/SConscript b/src/gallium/SConscript
index b8c04f7..0353b05 100644
--- a/src/gallium/SConscript
+++ b/src/gallium/SConscript
@@ -22,3 +22,6 @@
 SConscript('winsys/SConscript')
 
 SConscript('targets/SConscript')
+
+SConscript('tests/unit/SConscript')
+#SConscript('tests/raw/SConscript')
diff --git a/src/gallium/tests/python/retrace/README b/src/gallium/tests/python/retrace/README
new file mode 100644
index 0000000..822cd11
--- /dev/null
+++ b/src/gallium/tests/python/retrace/README
@@ -0,0 +1,17 @@
+This is an application written in python to replay the traces captured by the
+ trace pipe driver. 
+
+
+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>
diff --git a/src/gallium/tests/python/retrace/format.py b/src/gallium/tests/python/retrace/format.py
new file mode 100755
index 0000000..a4285bf
--- /dev/null
+++ b/src/gallium/tests/python/retrace/format.py
@@ -0,0 +1,173 @@
+#!/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
+
+
+class Formatter:
+    '''Plain formatter'''
+
+    def __init__(self, stream):
+        self.stream = stream
+
+    def text(self, text):
+        self.stream.write(text)
+
+    def newline(self):
+        self.text('\n')
+
+    def function(self, name):
+        self.text(name)
+
+    def variable(self, name):
+        self.text(name)
+
+    def literal(self, value):
+        self.text(str(value))
+
+    def address(self, addr):
+        self.text(str(addr))
+
+
+class AnsiFormatter(Formatter):
+    '''Formatter for plain-text files which outputs ANSI escape codes. See
+    http://en.wikipedia.org/wiki/ANSI_escape_code for more information
+    concerning ANSI escape codes.
+    '''
+
+    _csi = '\33['
+
+    _normal = '0m'
+    _bold = '1m'
+    _italic = '3m'
+    _red = '31m'
+    _green = '32m'
+    _blue = '34m'
+
+    def _escape(self, code):
+        self.text(self._csi + code)
+
+    def function(self, name):
+        self._escape(self._bold)
+        Formatter.function(self, name)
+        self._escape(self._normal)
+
+    def variable(self, name):
+        self._escape(self._italic)
+        Formatter.variable(self, name)
+        self._escape(self._normal)
+
+    def literal(self, value):
+        self._escape(self._blue)
+        Formatter.literal(self, value)
+        self._escape(self._normal)
+
+    def address(self, value):
+        self._escape(self._green)
+        Formatter.address(self, value)
+        self._escape(self._normal)
+
+
+class WindowsConsoleFormatter(Formatter):
+    '''Formatter for the Windows Console. See 
+    http://code.activestate.com/recipes/496901/ for more information.
+    '''
+
+    STD_INPUT_HANDLE  = -10
+    STD_OUTPUT_HANDLE = -11
+    STD_ERROR_HANDLE  = -12
+
+    FOREGROUND_BLUE      = 0x01
+    FOREGROUND_GREEN     = 0x02
+    FOREGROUND_RED       = 0x04
+    FOREGROUND_INTENSITY = 0x08
+    BACKGROUND_BLUE      = 0x10
+    BACKGROUND_GREEN     = 0x20
+    BACKGROUND_RED       = 0x40
+    BACKGROUND_INTENSITY = 0x80
+
+    _normal = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
+    _bold = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY
+    _italic = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
+    _red = FOREGROUND_RED | FOREGROUND_INTENSITY
+    _green = FOREGROUND_GREEN | FOREGROUND_INTENSITY
+    _blue = FOREGROUND_BLUE | FOREGROUND_INTENSITY
+
+    def __init__(self, stream):
+        Formatter.__init__(self, stream)
+
+        if stream is sys.stdin:
+            nStdHandle = self.STD_INPUT_HANDLE
+        elif stream is sys.stdout:
+            nStdHandle = self.STD_OUTPUT_HANDLE
+        elif stream is sys.stderr:
+            nStdHandle = self.STD_ERROR_HANDLE
+        else:
+            nStdHandle = None
+
+        if nStdHandle:
+            import ctypes
+            self.handle = ctypes.windll.kernel32.GetStdHandle(nStdHandle)
+        else:
+            self.handle = None
+
+    def _attribute(self, attr):
+        if self.handle:
+            import ctypes
+            ctypes.windll.kernel32.SetConsoleTextAttribute(self.handle, attr)
+
+    def function(self, name):
+        self._attribute(self._bold)
+        Formatter.function(self, name)
+        self._attribute(self._normal)
+
+    def variable(self, name):
+        self._attribute(self._italic)
+        Formatter.variable(self, name)
+        self._attribute(self._normal)
+
+    def literal(self, value):
+        self._attribute(self._blue)
+        Formatter.literal(self, value)
+        self._attribute(self._normal)
+
+    def address(self, value):
+        self._attribute(self._green)
+        Formatter.address(self, value)
+        self._attribute(self._normal)
+
+
+def DefaultFormatter(stream):
+    if sys.platform in ('linux2', 'cygwin'):
+        return AnsiFormatter(stream)
+    elif sys.platform in ('win32',):
+        return WindowsConsoleFormatter(stream)
+    else:
+        return Formatter(stream)
+
diff --git a/src/gallium/tests/python/retrace/interpreter.py b/src/gallium/tests/python/retrace/interpreter.py
new file mode 100755
index 0000000..60253cf
--- /dev/null
+++ b/src/gallium/tests/python/retrace/interpreter.py
@@ -0,0 +1,734 @@
+#!/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,
+    'pipe_subresource': gallium.pipe_subresource,
+    'pipe_box': gallium.pipe_box,
+}
+
+
+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_screen_create(self):
+        real = gallium.Device()
+        return Screen(self.interpreter, real)
+    
+    def pipe_context_create(self, screen):
+        context = screen.real.context_create()
+        return Context(self.interpreter, context)
+
+    
+class Transfer:
+
+    def __init__(self, resource, usage, subresource, box):
+        self.resource = resource
+        self.usage = usage
+        self.subresource = subresource
+        self.box = box
+
+
+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, bind, geom_flags):
+        return self.real.is_format_supported(format, target, bind, geom_flags)
+    
+    def resource_create(self, templat):
+        return self.real.resource_create(
+            format = templat.format,
+            width = templat.width,
+            height = templat.height,
+            depth = templat.depth,
+            last_level = templat.last_level,
+            target = templat.target,
+            bind = templat.bind,
+        )
+
+    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 user_buffer_create(self, data, size, bind):
+        # We don't really care to distinguish between user and regular buffers
+        buffer = self.real.buffer_create(size, bind)
+        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 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 create_sampler_view(self, texture, templ):
+        return self.real.create_sampler_view(texture,
+                       format = templ.format,
+                       first_level = templ.first_level,
+                       last_level = templ.last_level,
+                       swizzle_r = templ.swizzle_r,
+                       swizzle_g = templ.swizzle_r,
+                       swizzle_b = templ.swizzle_g,
+                       swizzle_a = templ.swizzle_a)
+
+    def set_fragment_sampler_views(self, num, views):
+        for i in range(num):
+            self.real.set_fragment_sampler_view(i, views[i])
+
+    def set_vertex_sampler_views(self, num, views):
+        for i in range(num):
+            self.real.set_vertex_sampler_view(i, views[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 create_vertex_elements_state(self, num_elements, elements):
+        return elements[0:num_elements]
+
+    def bind_vertex_elements_state(self, state):
+        elements = state
+        num_elements = len(elements)
+        self.velems = elements
+        for i in range(num_elements):
+            self.real.set_vertex_element(i, elements[i])
+        self.real.set_vertex_elements(num_elements)
+
+    def delete_vertex_elements_state(self, state):
+        pass
+
+    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')
+            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 buffer_write(self, buffer, data, size, offset=0):
+        assert size == len(data)
+        self.buffer_write(buffer, data)
+
+    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_transfer(self, texture, sr, usage, box):
+        if texture is None:
+            return None
+        transfer = Transfer(texture, sr, usage, box)
+        if transfer and usage & gallium.PIPE_TRANSFER_READ:
+            if self.interpreter.options.all:
+                surface = texture.get_surface(sr.face, sr.level, box.z)
+                self.interpreter.present(transfer.surface, 'transf_read', box.x, box.y, box.w, box.h)
+        return transfer
+    
+    def tex_transfer_destroy(self, transfer):
+        self.interpreter.unregister_object(transfer)
+
+    def transfer_write(self, transfer, data, size):
+        if transfer is None:
+            return
+        self.real.transfer_inline_write(resource, sr, usage, box, data, stride, slice_stride)
+        self.transfertransfer.surface.put_tile_raw(transfer.x, transfer.y, transfer.w, transfer.h, data, transfer.stride)
+        if self.interpreter.options.all:
+            box = transfer.box
+            surface = transfer.resource.get_surface(sr.face, sr.level, box.z)
+            self.interpreter.present(transfer.surface, 'transf_write', box.x, box.y, box.w, box.h)
+
+    def transfer_inline_write(self, resource, sr, usage, box, stride, slice_stride, data):
+        self.real.transfer_inline_write(resource, sr, usage, box, data, stride, slice_stride)
+
+    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()
diff --git a/src/gallium/tests/python/retrace/model.py b/src/gallium/tests/python/retrace/model.py
new file mode 100755
index 0000000..d4a079f
--- /dev/null
+++ b/src/gallium/tests/python/retrace/model.py
@@ -0,0 +1,213 @@
+#!/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.
+# 
+##########################################################################
+
+
+'''Trace data model.'''
+
+
+import sys
+import string
+import format
+
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+
+
+class Node:
+    
+    def visit(self, visitor):
+        raise NotImplementedError
+
+    def __str__(self):
+        stream = StringIO()
+        formatter = format.DefaultFormatter(stream)
+        pretty_printer = PrettyPrinter(formatter)
+        self.visit(pretty_printer)
+        return stream.getvalue()
+
+
+class Literal(Node):
+    
+    def __init__(self, value):
+        self.value = value
+
+    def visit(self, visitor):
+        visitor.visit_literal(self)
+
+
+class NamedConstant(Node):
+    
+    def __init__(self, name):
+        self.name = name
+
+    def visit(self, visitor):
+        visitor.visit_named_constant(self)
+    
+
+class Array(Node):
+    
+    def __init__(self, elements):
+        self.elements = elements
+
+    def visit(self, visitor):
+        visitor.visit_array(self)
+
+
+class Struct(Node):
+    
+    def __init__(self, name, members):
+        self.name = name
+        self.members = members        
+
+    def visit(self, visitor):
+        visitor.visit_struct(self)
+
+        
+class Pointer(Node):
+    
+    def __init__(self, address):
+        self.address = address
+
+    def visit(self, visitor):
+        visitor.visit_pointer(self)
+
+
+class Call:
+    
+    def __init__(self, no, klass, method, args, ret):
+        self.no = no
+        self.klass = klass
+        self.method = method
+        self.args = args
+        self.ret = ret
+        
+    def visit(self, visitor):
+        visitor.visit_call(self)
+
+
+class Trace:
+    
+    def __init__(self, calls):
+        self.calls = calls
+        
+    def visit(self, visitor):
+        visitor.visit_trace(self)
+    
+    
+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
+
+
+class PrettyPrinter:
+
+    def __init__(self, formatter):
+        self.formatter = formatter
+    
+    def visit_literal(self, node):
+        if isinstance(node.value, basestring):
+            if len(node.value) >= 4096 or node.value.strip(string.printable):
+                self.formatter.text('...')
+                return
+
+            self.formatter.literal('"' + node.value + '"')
+            return
+
+        self.formatter.literal(repr(node.value))
+    
+    def visit_named_constant(self, node):
+        self.formatter.literal(node.name)
+    
+    def visit_array(self, node):
+        self.formatter.text('{')
+        sep = ''
+        for value in node.elements:
+            self.formatter.text(sep)
+            value.visit(self) 
+            sep = ', '
+        self.formatter.text('}')
+    
+    def visit_struct(self, node):
+        self.formatter.text('{')
+        sep = ''
+        for name, value in node.members:
+            self.formatter.text(sep)
+            self.formatter.variable(name)
+            self.formatter.text(' = ')
+            value.visit(self) 
+            sep = ', '
+        self.formatter.text('}')
+    
+    def visit_pointer(self, node):
+        self.formatter.address(node.address)
+    
+    def visit_call(self, node):
+        self.formatter.text('%s ' % node.no)
+        if node.klass is not None:
+            self.formatter.function(node.klass + '::' + node.method)
+        else:
+            self.formatter.function(node.method)
+        self.formatter.text('(')
+        sep = ''
+        for name, value in node.args:
+            self.formatter.text(sep)
+            self.formatter.variable(name)
+            self.formatter.text(' = ')
+            value.visit(self) 
+            sep = ', '
+        self.formatter.text(')')
+        if node.ret is not None:
+            self.formatter.text(' = ')
+            node.ret.visit(self)
+    
+    def visit_trace(self, node):
+        for call in node.calls:
+            call.visit(self)
+            self.formatter.newline()
+
diff --git a/src/gallium/tests/python/retrace/parse.py b/src/gallium/tests/python/retrace/parse.py
new file mode 100755
index 0000000..b08d368
--- /dev/null
+++ b/src/gallium/tests/python/retrace/parse.py
@@ -0,0 +1,392 @@
+#!/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 xml.parsers.expat
+import binascii
+import optparse
+
+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
+            data = data.rstrip('\0')
+            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 __init__(self, fp):
+        XmlParser.__init__(self, fp)
+        self.last_call_no = 0
+    
+    def parse(self):
+        self.element_start('trace')
+        while self.token.type not in (ELEMENT_END, EOF):
+            call = self.parse_call()
+            self.handle_call(call)
+        if self.token.type != EOF:
+            self.element_end('trace')
+
+    def parse_call(self):
+        attrs = self.element_start('call')
+        try:
+            no = int(attrs['no'])
+        except KeyError:
+            self.last_call_no += 1
+            no = self.last_call_no
+        else:
+            self.last_call_no = no
+        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(no, 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')
+
+        return Pointer(address)
+
+    def handle_call(self, call):
+        pass
+    
+    
+class TraceDumper(TraceParser):
+    
+    def __init__(self, fp):
+        TraceParser.__init__(self, fp)
+        self.formatter = format.DefaultFormatter(sys.stdout)
+        self.pretty_printer = PrettyPrinter(self.formatter)
+
+    def handle_call(self, call):
+        call.visit(self.pretty_printer)
+        self.formatter.newline()
+        
+
+class Main:
+    '''Common main class for all retrace command line utilities.''' 
+
+    def __init__(self):
+        pass
+
+    def main(self):
+        optparser = self.get_optparser()
+        (options, args) = optparser.parse_args(sys.argv[1:])
+    
+        if args:
+            for arg in args:
+                if arg.endswith('.gz'):
+                    from gzip import GzipFile
+                    stream = GzipFile(arg, 'rt')
+                elif arg.endswith('.bz2'):
+                    from bz2 import BZ2File
+                    stream = BZ2File(arg, 'rU')
+                else:
+                    stream = open(arg, 'rt')
+                self.process_arg(stream, options)
+        else:
+            self.process_arg(stream, options)
+
+    def get_optparser(self):
+        optparser = optparse.OptionParser(
+            usage="\n\t%prog [options] [traces] ...")
+        return optparser
+
+    def process_arg(self, stream, options):
+        parser = TraceDumper(stream)
+        parser.parse()
+
+
+if __name__ == '__main__':
+    Main().main()
diff --git a/src/gallium/tests/python/retrace/parser.py b/src/gallium/tests/python/retrace/parser.py
new file mode 100755
index 0000000..bd47c9a
--- /dev/null
+++ b/src/gallium/tests/python/retrace/parser.py
@@ -0,0 +1,34 @@
+#!/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.
+# 
+##########################################################################
+
+
+from parse import *
+
+
+if __name__ == '__main__':
+    Main().main()
diff --git a/src/gallium/tests/python/samples/gs.py b/src/gallium/tests/python/samples/gs.py
new file mode 100644
index 0000000..936c0b3
--- /dev/null
+++ b/src/gallium/tests/python/samples/gs.py
@@ -0,0 +1,254 @@
+#!/usr/bin/env python
+##########################################################################
+#
+# Copyright 2009 VMware
+# 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.
+#
+##########################################################################
+
+
+from gallium import *
+
+
+def make_image(surface):
+    data = surface.get_tile_rgba8(0, 0, surface.width, surface.height)
+
+    import Image
+    outimage = Image.fromstring('RGBA', (surface.width, surface.height), data, "raw", 'RGBA', 0, 1)
+    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()
+
+
+def test(dev):
+    ctx = dev.context_create()
+
+    width = 255
+    height = 255
+    minz = 0.0
+    maxz = 1.0
+
+    # disabled blending/masking
+    blend = Blend()
+    blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE
+    blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE
+    blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO
+    blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO
+    blend.rt[0].colormask = PIPE_MASK_RGBA
+    ctx.set_blend(blend)
+
+    # depth/stencil/alpha
+    depth_stencil_alpha = DepthStencilAlpha()
+    depth_stencil_alpha.depth.enabled = 1
+    depth_stencil_alpha.depth.writemask = 1
+    depth_stencil_alpha.depth.func = PIPE_FUNC_LESS
+    ctx.set_depth_stencil_alpha(depth_stencil_alpha)
+
+    # rasterizer
+    rasterizer = Rasterizer()
+    rasterizer.front_winding = PIPE_WINDING_CW
+    rasterizer.cull_mode = PIPE_WINDING_NONE
+    rasterizer.scissor = 1
+    ctx.set_rasterizer(rasterizer)
+
+    # viewport
+    viewport = Viewport()
+    scale = FloatArray(4)
+    scale[0] = width / 2.0
+    scale[1] = -height / 2.0
+    scale[2] = (maxz - minz) / 2.0
+    scale[3] = 1.0
+    viewport.scale = scale
+    translate = FloatArray(4)
+    translate[0] = width / 2.0
+    translate[1] = height / 2.0
+    translate[2] = (maxz - minz) / 2.0
+    translate[3] = 0.0
+    viewport.translate = translate
+    ctx.set_viewport(viewport)
+
+    # samplers
+    sampler = Sampler()
+    sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE
+    sampler.min_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+    sampler.mag_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+    sampler.normalized_coords = 1
+    ctx.set_sampler(0, sampler)
+
+    # scissor
+    scissor = Scissor()
+    scissor.minx = 0
+    scissor.miny = 0
+    scissor.maxx = width
+    scissor.maxy = height
+    ctx.set_scissor(scissor)
+
+    clip = Clip()
+    clip.nr = 0
+    ctx.set_clip(clip)
+
+    # framebuffer
+    cbuf = dev.resource_create(
+        PIPE_FORMAT_B8G8R8X8_UNORM,
+        width, height,
+        bind=PIPE_BIND_RENDER_TARGET,
+    ).get_surface()
+    zbuf = dev.resource_create(
+        PIPE_FORMAT_Z32_UNORM,
+        width, height,
+        bind=PIPE_BIND_DEPTH_STENCIL,
+    ).get_surface()
+    fb = Framebuffer()
+    fb.width = width
+    fb.height = height
+    fb.nr_cbufs = 1
+    fb.set_cbuf(0, cbuf)
+    fb.set_zsbuf(zbuf)
+    ctx.set_framebuffer(fb)
+    rgba = FloatArray(4);
+    rgba[0] = 0.0
+    rgba[1] = 0.0
+    rgba[2] = 0.0
+    rgba[3] = 0.0
+    ctx.clear(PIPE_CLEAR_COLOR | PIPE_CLEAR_DEPTHSTENCIL, rgba, 1.0, 0xff)
+
+    # vertex shader
+    vs = Shader('''
+        VERT
+        DCL IN[0], POSITION, CONSTANT
+        DCL IN[1], COLOR, CONSTANT
+        DCL OUT[0], POSITION, CONSTANT
+        DCL OUT[1], COLOR, CONSTANT
+        0:MOV OUT[0], IN[0]
+        1:MOV OUT[1], IN[1]
+        2:END
+    ''')
+    ctx.set_vertex_shader(vs)
+
+    gs = Shader('''
+        GEOM
+        PROPERTY GS_INPUT_PRIMITIVE TRIANGLES
+        PROPERTY GS_OUTPUT_PRIMITIVE TRIANGLE_STRIP
+        DCL IN[][0], POSITION, CONSTANT
+        DCL IN[][1], COLOR, CONSTANT
+        DCL OUT[0], POSITION, CONSTANT
+        DCL OUT[1], COLOR, CONSTANT
+        0:MOV OUT[0], IN[0][0]
+        1:MOV OUT[1], IN[0][1]
+        2:EMIT
+        3:MOV OUT[0], IN[1][0]
+        4:MOV OUT[1], IN[1][1]
+        5:EMIT
+        6:MOV OUT[0], IN[2][0]
+        7:MOV OUT[1], IN[2][1]
+        8:EMIT
+        9:ENDPRIM
+        10:END
+    ''')
+    ctx.set_geometry_shader(gs)
+
+    # fragment shader
+    fs = Shader('''
+        FRAG
+        DCL IN[0], COLOR, LINEAR
+        DCL OUT[0], COLOR, CONSTANT
+        0:MOV OUT[0], IN[0]
+        1:END
+    ''')
+    ctx.set_fragment_shader(fs)
+
+    nverts = 3
+    nattrs = 2
+    verts = FloatArray(nverts * nattrs * 4)
+
+    verts[ 0] =   0.0 # x1
+    verts[ 1] =   0.8 # y1
+    verts[ 2] =   0.2 # z1
+    verts[ 3] =   1.0 # w1
+    verts[ 4] =   1.0 # r1
+    verts[ 5] =   0.0 # g1
+    verts[ 6] =   0.0 # b1
+    verts[ 7] =   1.0 # a1
+    verts[ 8] =  -0.8 # x2
+    verts[ 9] =  -0.8 # y2
+    verts[10] =   0.5 # z2
+    verts[11] =   1.0 # w2
+    verts[12] =   0.0 # r2
+    verts[13] =   1.0 # g2
+    verts[14] =   0.0 # b2
+    verts[15] =   1.0 # a2
+    verts[16] =   0.8 # x3
+    verts[17] =  -0.8 # y3
+    verts[18] =   0.8 # z3
+    verts[19] =   1.0 # w3
+    verts[20] =   0.0 # r3
+    verts[21] =   0.0 # g3
+    verts[22] =   1.0 # b3
+    verts[23] =   1.0 # a3
+
+    ctx.draw_vertices(PIPE_PRIM_TRIANGLES,
+                      nverts,
+                      nattrs,
+                      verts)
+
+    ctx.flush()
+
+    show_image(cbuf)
+    #show_image(zbuf)
+    #save_image('cbuf.png', cbuf)
+    #save_image('zbuf.png', zbuf)
+
+
+
+def main():
+    dev = Device()
+    test(dev)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/gallium/tests/python/samples/tri.py b/src/gallium/tests/python/samples/tri.py
new file mode 100644
index 0000000..fed929d
--- /dev/null
+++ b/src/gallium/tests/python/samples/tri.py
@@ -0,0 +1,233 @@
+#!/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.
+# 
+##########################################################################
+
+
+from gallium import *
+
+
+def make_image(ctx, surface):
+    data = ctx.surface_read_rgba8(surface, 0, 0, surface.width, surface.height)
+
+    import Image
+    outimage = Image.fromstring('RGBA', (surface.width, surface.height), data, "raw", 'RGBA', 0, 1)
+    return outimage
+
+def save_image(ctx, surface, filename):
+    outimage = make_image(ctx, surface)
+    outimage.save(filename, "PNG")
+
+def show_image(ctx, surface):
+    outimage = make_image(ctx, 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()
+
+
+def test(dev):
+    ctx = dev.context_create()
+
+    width = 255
+    height = 255
+    minz = 0.0
+    maxz = 1.0
+
+    # disabled blending/masking
+    blend = Blend()
+    blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE
+    blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE
+    blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO
+    blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO
+    blend.rt[0].colormask = PIPE_MASK_RGBA
+    ctx.set_blend(blend)
+
+    # depth/stencil/alpha
+    depth_stencil_alpha = DepthStencilAlpha()
+    depth_stencil_alpha.depth.enabled = 1
+    depth_stencil_alpha.depth.writemask = 1
+    depth_stencil_alpha.depth.func = PIPE_FUNC_LESS
+    ctx.set_depth_stencil_alpha(depth_stencil_alpha)
+
+    # rasterizer
+    rasterizer = Rasterizer()
+    rasterizer.front_winding = PIPE_WINDING_CW
+    rasterizer.cull_mode = PIPE_WINDING_NONE
+    rasterizer.scissor = 1
+    ctx.set_rasterizer(rasterizer)
+
+    # viewport
+    viewport = Viewport()
+    scale = FloatArray(4)
+    scale[0] = width / 2.0
+    scale[1] = -height / 2.0
+    scale[2] = (maxz - minz) / 2.0
+    scale[3] = 1.0
+    viewport.scale = scale
+    translate = FloatArray(4)
+    translate[0] = width / 2.0
+    translate[1] = height / 2.0
+    translate[2] = (maxz - minz) / 2.0
+    translate[3] = 0.0
+    viewport.translate = translate
+    ctx.set_viewport(viewport)
+
+    # samplers
+    sampler = Sampler()
+    sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE
+    sampler.min_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+    sampler.mag_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+    sampler.normalized_coords = 1
+    ctx.set_fragment_sampler(0, sampler)
+
+    # scissor
+    scissor = Scissor()
+    scissor.minx = 0
+    scissor.miny = 0
+    scissor.maxx = width
+    scissor.maxy = height
+    ctx.set_scissor(scissor)
+
+    # clip
+    clip = Clip()
+    clip.nr = 0
+    ctx.set_clip(clip)
+
+    # framebuffer
+    cbuf = dev.resource_create(
+        PIPE_FORMAT_B8G8R8X8_UNORM, 
+        width, height,
+        bind=PIPE_BIND_RENDER_TARGET,
+    ).get_surface()
+    zbuf = dev.resource_create(
+        PIPE_FORMAT_Z32_UNORM,
+        width, height,
+        bind=PIPE_BIND_DEPTH_STENCIL,
+    ).get_surface()
+    fb = Framebuffer()
+    fb.width = width
+    fb.height = height
+    fb.nr_cbufs = 1
+    fb.set_cbuf(0, cbuf)
+    fb.set_zsbuf(zbuf)
+    ctx.set_framebuffer(fb)
+    rgba = FloatArray(4);
+    rgba[0] = 0.0
+    rgba[1] = 0.0
+    rgba[2] = 0.0
+    rgba[3] = 0.0
+    ctx.clear(PIPE_CLEAR_COLOR | PIPE_CLEAR_DEPTHSTENCIL, rgba, 1.0, 0xff)
+    
+    # vertex shader
+    vs = Shader('''
+        VERT
+        DCL IN[0], POSITION, CONSTANT
+        DCL IN[1], COLOR, CONSTANT
+        DCL OUT[0], POSITION, CONSTANT
+        DCL OUT[1], COLOR, CONSTANT
+        0:MOV OUT[0], IN[0]
+        1:MOV OUT[1], IN[1]
+        2:END
+    ''')
+    ctx.set_vertex_shader(vs)
+
+    # fragment shader
+    fs = Shader('''
+        FRAG
+        DCL IN[0], COLOR, LINEAR
+        DCL OUT[0], COLOR, CONSTANT
+        0:MOV OUT[0], IN[0]
+        1:END
+    ''')
+    ctx.set_fragment_shader(fs)
+
+    nverts = 3
+    nattrs = 2
+    verts = FloatArray(nverts * nattrs * 4)
+
+    verts[ 0] =   0.0 # x1
+    verts[ 1] =   0.8 # y1
+    verts[ 2] =   0.2 # z1
+    verts[ 3] =   1.0 # w1
+    verts[ 4] =   1.0 # r1
+    verts[ 5] =   0.0 # g1
+    verts[ 6] =   0.0 # b1
+    verts[ 7] =   1.0 # a1
+    verts[ 8] =  -0.8 # x2
+    verts[ 9] =  -0.8 # y2
+    verts[10] =   0.5 # z2
+    verts[11] =   1.0 # w2
+    verts[12] =   0.0 # r2
+    verts[13] =   1.0 # g2
+    verts[14] =   0.0 # b2
+    verts[15] =   1.0 # a2
+    verts[16] =   0.8 # x3
+    verts[17] =  -0.8 # y3
+    verts[18] =   0.8 # z3
+    verts[19] =   1.0 # w3
+    verts[20] =   0.0 # r3
+    verts[21] =   0.0 # g3
+    verts[22] =   1.0 # b3
+    verts[23] =   1.0 # a3
+
+    ctx.draw_vertices(PIPE_PRIM_TRIANGLES,
+                      nverts, 
+                      nattrs, 
+                      verts)
+
+    ctx.flush()
+    
+    show_image(ctx, cbuf)
+    show_image(ctx, zbuf)
+    save_image(ctx, cbuf, 'cbuf.png')
+    save_image(ctx, zbuf, 'zbuf.png')
+
+
+
+def main():
+    dev = Device()
+    test(dev)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/gallium/tests/python/tests/.gitignore b/src/gallium/tests/python/tests/.gitignore
new file mode 100644
index 0000000..0dbbaee
--- /dev/null
+++ b/src/gallium/tests/python/tests/.gitignore
@@ -0,0 +1,3 @@
+*.txt
+*.tsv
+*.dot
diff --git a/src/gallium/tests/python/tests/base.py b/src/gallium/tests/python/tests/base.py
new file mode 100755
index 0000000..d8cf84d
--- /dev/null
+++ b/src/gallium/tests/python/tests/base.py
@@ -0,0 +1,399 @@
+#!/usr/bin/env python
+##########################################################################
+# 
+# Copyright 2009 VMware, Inc.
+# 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 VMWARE 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.
+# 
+##########################################################################
+
+
+"""Base classes for tests.
+
+Loosely inspired on Python's unittest module.
+"""
+
+
+import os.path
+import sys
+
+from gallium import *
+
+
+# Enumerate all pixel formats
+formats = {}
+for name, value in globals().items():
+    if name.startswith("PIPE_FORMAT_") and isinstance(value, int) and name not in ("PIPE_FORMAT_NONE", "PIPE_FORMAT_COUNT"):
+        formats[value] = name
+
+def make_image(width, height, rgba):
+    import Image
+    outimage = Image.new(
+        mode='RGB',
+        size=(width, height),
+        color=(0,0,0))
+    outpixels = outimage.load()
+    for y in range(0, height):
+        for x in range(0, width):
+            offset = (y*width + x)*4
+            r, g, b, a = [int(min(max(rgba[offset + ch], 0.0), 1.0)*255) for ch in range(4)]
+            outpixels[x, y] = r, g, b
+    return outimage
+
+def save_image(width, height, rgba, filename):
+    outimage = make_image(width, height, rgba)
+    outimage.save(filename, "PNG")
+
+def show_image(width, height, **rgbas):
+    import Tkinter as tk
+    from PIL import Image, ImageTk
+
+    root = tk.Tk()
+    
+    x = 64
+    y = 64
+    
+    labels = rgbas.keys()
+    labels.sort() 
+    for i in range(len(labels)):
+        label = labels[i]
+        outimage = make_image(width, height, rgbas[label])
+        
+        if i:
+            window = tk.Toplevel(root)
+        else:
+            window = root    
+        window.title(label)
+        image1 = ImageTk.PhotoImage(outimage)
+        w = image1.width()
+        h = image1.height()
+        window.geometry("%dx%d+%d+%d" % (w, h, x, y))
+        panel1 = tk.Label(window, image=image1)
+        panel1.pack(side='top', fill='both', expand='yes')
+        panel1.image = image1
+        x += w + 2
+    
+    root.mainloop()
+
+
+class TestFailure(Exception):
+
+    pass
+
+class TestSkip(Exception):
+    
+    pass
+
+
+class Test:
+
+    def __init__(self):
+        pass
+
+    def _run(self, result):
+        raise NotImplementedError
+    
+    def run(self):
+        result = TestResult()
+        self._run(result)
+        result.report()
+
+    def assert_rgba(self, ctx, surface, x, y, w, h, expected_rgba, pixel_tol=4.0/256, surface_tol=0.85):
+        total = h*w
+        different = ctx.surface_compare_rgba(surface, x, y, w, h, expected_rgba, tol=pixel_tol)
+        if different:
+            sys.stderr.write("%u out of %u pixels differ\n" % (different, total))
+
+        if float(total - different)/float(total) < surface_tol:
+            if 0:
+                rgba = FloatArray(h*w*4)
+                ctx.surface_read_rgba(surface, x, y, w, h, rgba)
+                show_image(w, h, Result=rgba, Expected=expected_rgba)
+                save_image(w, h, rgba, "result.png")
+                save_image(w, h, expected_rgba, "expected.png")
+            #sys.exit(0)
+            
+            raise TestFailure
+
+
+class TestCase(Test):
+    
+    tags = ()
+
+    def __init__(self, dev, **kargs):
+        Test.__init__(self)
+        self.dev = dev
+        self.__dict__.update(kargs)
+
+    def description(self):
+        descriptions = []
+        for tag in self.tags:
+            value = self.get(tag)
+            if value is not None and value != '':
+                descriptions.append(tag + '=' + str(value))
+        return ' '.join(descriptions)
+
+    def get(self, tag):
+        try:
+            method = getattr(self, '_get_' + tag)
+        except AttributeError:
+            return getattr(self, tag, None)
+        else:
+            return method()
+
+    def _get_target(self):
+        return {
+            PIPE_TEXTURE_1D: "1d", 
+            PIPE_TEXTURE_2D: "2d", 
+            PIPE_TEXTURE_3D: "3d", 
+            PIPE_TEXTURE_CUBE: "cube",
+        }[self.target]
+
+    def _get_format(self):
+        name = formats[self.format]
+        if name.startswith('PIPE_FORMAT_'):
+            name  = name[12:]
+        name = name.lower()
+        return name
+
+    def _get_face(self):
+        if self.target == PIPE_TEXTURE_CUBE:
+            return {
+                PIPE_TEX_FACE_POS_X: "+x",
+                PIPE_TEX_FACE_NEG_X: "-x",
+                PIPE_TEX_FACE_POS_Y: "+y",
+                PIPE_TEX_FACE_NEG_Y: "-y", 
+                PIPE_TEX_FACE_POS_Z: "+z", 
+                PIPE_TEX_FACE_NEG_Z: "-z",
+            }[self.face]
+        else:
+            return ''
+
+    def test(self):
+        raise NotImplementedError
+    
+    def _run(self, result):
+        result.test_start(self)
+        try:
+            self.test()
+        except KeyboardInterrupt:
+            raise
+        except TestSkip:
+            result.test_skipped(self)
+        except TestFailure:
+            result.test_failed(self)
+        else:
+            result.test_passed(self)
+
+
+class TestSuite(Test):
+    
+    def __init__(self, tests = None):
+        Test.__init__(self)
+        if tests is None:
+            self.tests = []
+        else:
+            self.tests = tests
+
+    def add_test(self, test):
+        self.tests.append(test) 
+    
+    def _run(self, result):
+        for test in self.tests:
+            test._run(result)
+
+
+class TestResult:
+    
+    def __init__(self):
+        self.tests = 0
+        self.passed = 0
+        self.skipped = 0
+        self.failed = 0
+
+        self.names = ['result']
+        self.types = ['pass skip fail']
+        self.rows = []
+    
+    def test_start(self, test):
+        sys.stdout.write("Running %s...\n" % test.description())
+        sys.stdout.flush()
+        self.tests += 1
+    
+    def test_passed(self, test):
+        sys.stdout.write("PASS\n")
+        sys.stdout.flush()
+        self.passed += 1
+        self.log_result(test, 'pass')
+            
+    def test_skipped(self, test):
+        sys.stdout.write("SKIP\n")
+        sys.stdout.flush()
+        self.skipped += 1
+        self.log_result(test, 'skip')
+        
+    def test_failed(self, test):
+        sys.stdout.write("FAIL\n")
+        sys.stdout.flush()
+        self.failed += 1
+        self.log_result(test, 'fail')
+
+    def log_result(self, test, result):
+        row = ['']*len(self.names)
+
+        # add result
+        assert self.names[0] == 'result'
+        assert result in ('pass', 'skip', 'fail')
+        row[0] = result
+
+        # add tags
+        for tag in test.tags:
+            value = test.get(tag)
+
+            # infer type
+            if value is None:
+                continue
+            elif isinstance(value, (int, float)):
+                value = str(value)
+                type = 'c' # continous
+            elif isinstance(value, basestring):
+                type = 'd' # discrete
+            else:
+                assert False
+                value = str(value)
+                type = 'd' # discrete
+
+            # insert value
+            try:
+                col = self.names.index(tag, 1)
+            except ValueError:
+                self.names.append(tag)
+                self.types.append(type)
+                row.append(value)
+            else:
+                row[col] = value
+                assert self.types[col] == type
+        
+        self.rows.append(row)
+
+    def report(self):
+        sys.stdout.write("%u tests, %u passed, %u skipped, %u failed\n\n" % (self.tests, self.passed, self.skipped, self.failed))
+        sys.stdout.flush()
+
+        name, ext = os.path.splitext(os.path.basename(sys.argv[0]))
+
+        tree = self.report_tree(name)
+        self.report_junit(name, stdout=tree)
+
+    def report_tree(self, name):
+        filename = name + '.tsv'
+        stream = file(filename, 'wt')
+
+        # header
+        stream.write('\t'.join(self.names) + '\n')
+        stream.write('\t'.join(self.types) + '\n')
+        stream.write('class\n')
+
+        # rows
+        for row in self.rows:
+            if row[0] == 'skip':
+                continue
+            row += ['']*(len(self.names) - len(row))
+            stream.write('\t'.join(row) + '\n')
+
+        stream.close()
+
+        # See http://www.ailab.si/orange/doc/ofb/c_otherclass.htm
+        try:
+            import orange
+            import orngTree
+        except ImportError:
+            sys.stderr.write('Install Orange from http://www.ailab.si/orange/ for a classification tree.\n')
+            return None
+
+        data = orange.ExampleTable(filename)
+
+        tree = orngTree.TreeLearner(data, sameMajorityPruning=1, mForPruning=2)
+
+        orngTree.printTxt(tree, maxDepth=4)
+
+        text_tree = orngTree.dumpTree(tree)
+
+        file(name + '.txt', 'wt').write(text_tree)
+
+        orngTree.printDot(tree, fileName=name+'.dot', nodeShape='ellipse', leafShape='box')
+
+        return text_tree
+    
+    def report_junit(self, name, stdout=None, stderr=None):
+        """Write test results in ANT's junit XML format, to use with Hudson CI.
+
+        See also:
+        - http://fisheye.hudson-ci.org/browse/Hudson/trunk/hudson/main/core/src/test/resources/hudson/tasks/junit
+        - http://www.junit.org/node/399
+        - http://wiki.apache.org/ant/Proposals/EnhancedTestReports
+        """
+
+        stream = file(name + '.xml', 'wt')
+
+        stream.write('<?xml version="1.0" encoding="UTF-8" ?>\n')
+        stream.write('<testsuite name="%s">\n' % self.escape_xml(name))
+        stream.write('  <properties>\n')
+        stream.write('  </properties>\n')
+
+        names = self.names[1:]
+
+        for row in self.rows:
+
+            test_name = ' '.join(['%s=%s' % pair for pair in zip(self.names[1:], row[1:])])
+
+            stream.write('  <testcase name="%s">\n' % (self.escape_xml(test_name)))
+
+            result = row[0]
+            if result == 'pass':
+                pass
+            elif result == 'skip':
+                stream.write('    <skipped/>\n')
+            else:
+                stream.write('    <failure/>\n')
+            
+            stream.write('  </testcase>\n')
+
+        if stdout:
+            stream.write('  <system-out>%s</system-out>\n' % self.escape_xml(stdout))
+        if stderr:
+            stream.write('  <system-err>%s</system-err>\n' % self.escape_xml(stderr))
+
+        stream.write('</testsuite>\n')
+
+        stream.close()
+
+    def escape_xml(self, s):
+        '''Escape a XML string.'''
+        s = s.replace('&', '&amp;')
+        s = s.replace('<', '&lt;')
+        s = s.replace('>', '&gt;')
+        s = s.replace('"', '&quot;')
+        s = s.replace("'", '&apos;')
+        return s
+
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/.gitignore b/src/gallium/tests/python/tests/regress/fragment-shader/.gitignore
new file mode 100644
index 0000000..e33609d
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/.gitignore
@@ -0,0 +1 @@
+*.png
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-abs.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-abs.sh
new file mode 100644
index 0000000..103d749
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-abs.sh
@@ -0,0 +1,13 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { -0.5, -0.4, -0.6, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+ABS OUT[0], TEMP[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-add.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-add.sh
new file mode 100644
index 0000000..bcb9420
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-add.sh
@@ -0,0 +1,8 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+ADD OUT[0], IN[0], IN[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-cb-1d.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-cb-1d.sh
new file mode 100644
index 0000000..85fb9ea
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-cb-1d.sh
@@ -0,0 +1,13 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+DCL CONST[1]
+DCL CONST[3]
+DCL TEMP[0..1]
+
+ADD TEMP[0], IN[0], CONST[1]
+RCP TEMP[1], CONST[3].xxxx
+MUL OUT[0], TEMP[0], TEMP[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-cb-2d.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-cb-2d.sh
new file mode 100644
index 0000000..f70a514
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-cb-2d.sh
@@ -0,0 +1,9 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+DCL CONST[1][1..2]
+
+MAD OUT[0], IN[0], CONST[1][2], CONST[1][1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-dp3.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-dp3.sh
new file mode 100644
index 0000000..b528197
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-dp3.sh
@@ -0,0 +1,8 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DP3 OUT[0], IN[0], IN[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-dp4.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-dp4.sh
new file mode 100644
index 0000000..d59df76
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-dp4.sh
@@ -0,0 +1,8 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DP4 OUT[0], IN[0].xyzx, IN[0].xyzx
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-dst.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-dst.sh
new file mode 100644
index 0000000..fbb20fa
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-dst.sh
@@ -0,0 +1,8 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DST OUT[0], IN[0], IN[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-ex2.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-ex2.sh
new file mode 100644
index 0000000..b511288
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-ex2.sh
@@ -0,0 +1,11 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+EX2 TEMP[0], IN[0].xxxx
+MUL OUT[0], TEMP[0], IN[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-flr.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-flr.sh
new file mode 100644
index 0000000..99a2f96
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-flr.sh
@@ -0,0 +1,15 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 2.5, 4.0,  2.0, 1.0 }
+IMM FLT32 { 0.4, 0.25, 0.5, 1.0 }
+
+MUL TEMP[0], IN[0], IMM[0]
+FLR TEMP[0], TEMP[0]
+MUL OUT[0], TEMP[0], IMM[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-frc.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-frc.sh
new file mode 100644
index 0000000..a54c262
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-frc.sh
@@ -0,0 +1,13 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 2.7, 3.1, 4.5, 1.0 }
+
+MUL TEMP[0], IN[0], IMM[0]
+FRC OUT[0], TEMP[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-lg2.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-lg2.sh
new file mode 100644
index 0000000..5f5b4be
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-lg2.sh
@@ -0,0 +1,15 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 1.0, 0.0, 0.0, 0.0 }
+IMM FLT32 { 0.5, 0.0, 0.0, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+LG2 TEMP[0].x, TEMP[0].xxxx
+ADD OUT[0], TEMP[0], IMM[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-lit.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-lit.sh
new file mode 100644
index 0000000..6323c47
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-lit.sh
@@ -0,0 +1,8 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+LIT OUT[0], IN[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-lrp.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-lrp.sh
new file mode 100644
index 0000000..740809d
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-lrp.sh
@@ -0,0 +1,11 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+ABS TEMP[0], IN[0]
+LRP OUT[0], TEMP[0], IN[0].xxxx, IN[0].yyyy
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-mad.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-mad.sh
new file mode 100644
index 0000000..413b9dc
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-mad.sh
@@ -0,0 +1,11 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+IMM FLT32 { 0.5, 0.4, 0.6, 1.0 }
+IMM FLT32 { 0.5, 0.4, 0.6, 0.0 }
+
+MAD OUT[0], IN[0], IMM[0], IMM[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-max.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-max.sh
new file mode 100644
index 0000000..b69f213
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-max.sh
@@ -0,0 +1,10 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+IMM FLT32 { 0.4, 0.4, 0.4, 0.0 }
+
+MAX OUT[0], IN[0], IMM[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-min.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-min.sh
new file mode 100644
index 0000000..df284f4
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-min.sh
@@ -0,0 +1,10 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+IMM FLT32 { 0.6, 0.6, 0.6, 1.0 }
+
+MIN OUT[0], IN[0], IMM[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-mov.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-mov.sh
new file mode 100644
index 0000000..64af72f
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-mov.sh
@@ -0,0 +1,8 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+MOV OUT[0], IN[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-mul.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-mul.sh
new file mode 100644
index 0000000..bdd0b00
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-mul.sh
@@ -0,0 +1,10 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+IMM FLT32 { 0.5, 0.6, 0.7, 1.0 }
+
+MUL OUT[0], IN[0], IMM[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-rcp.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-rcp.sh
new file mode 100644
index 0000000..f4b611b
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-rcp.sh
@@ -0,0 +1,15 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 1.0, 0.0, 0.0, 0.0 }
+IMM FLT32 { 1.5, 0.0, 0.0, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+RCP TEMP[0].x, TEMP[0].xxxx
+SUB OUT[0], TEMP[0], IMM[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-rsq.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-rsq.sh
new file mode 100644
index 0000000..d1e9b0b
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-rsq.sh
@@ -0,0 +1,15 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 1.0, 0.0, 0.0, 0.0 }
+IMM FLT32 { 1.5, 0.0, 0.0, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+RSQ TEMP[0].x, TEMP[0].xxxx
+SUB OUT[0], TEMP[0], IMM[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-sge.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-sge.sh
new file mode 100644
index 0000000..1f33fac
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-sge.sh
@@ -0,0 +1,13 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 0.6, 0.6, 0.6, 0.0 }
+
+SGE TEMP[0], IN[0], IMM[0]
+MUL OUT[0], IN[0], TEMP[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-slt.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-slt.sh
new file mode 100644
index 0000000..d58b788
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-slt.sh
@@ -0,0 +1,13 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 0.6, 0.6, 0.6, 0.0 }
+
+SLT TEMP[0], IN[0], IMM[0]
+MUL OUT[0], IN[0], TEMP[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-abs.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-abs.sh
new file mode 100644
index 0000000..ecd1924
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-abs.sh
@@ -0,0 +1,13 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { -0.3, -0.5, -0.4, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+MOV OUT[0], |TEMP[0]|
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-absneg.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-absneg.sh
new file mode 100644
index 0000000..c2d99dd
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-absneg.sh
@@ -0,0 +1,15 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { -0.2, -0.3, -0.4, 0.0 }
+IMM FLT32 { -1.0, -1.0, -1.0, -1.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+MOV TEMP[0], -|TEMP[0]|
+MUL OUT[0], TEMP[0], IMM[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-neg.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-neg.sh
new file mode 100644
index 0000000..a08ab6d
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-neg.sh
@@ -0,0 +1,11 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+DCL TEMP[0]
+
+SUB TEMP[0], IN[0], IN[0].yzxw
+MOV OUT[0], -TEMP[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-swz.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-swz.sh
new file mode 100644
index 0000000..6110647
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-srcmod-swz.sh
@@ -0,0 +1,8 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+MOV OUT[0], IN[0].yxzw
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-sub.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-sub.sh
new file mode 100644
index 0000000..673fca1
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-sub.sh
@@ -0,0 +1,8 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+SUB OUT[0], IN[0], IN[0].yzxw
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/frag-xpd.sh b/src/gallium/tests/python/tests/regress/fragment-shader/frag-xpd.sh
new file mode 100644
index 0000000..6ec8b11
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/frag-xpd.sh
@@ -0,0 +1,8 @@
+FRAG
+
+DCL IN[0], COLOR, LINEAR
+DCL OUT[0], COLOR
+
+XPD OUT[0], IN[0], IN[0].yzxw
+
+END
diff --git a/src/gallium/tests/python/tests/regress/fragment-shader/fragment-shader.py b/src/gallium/tests/python/tests/regress/fragment-shader/fragment-shader.py
new file mode 100644
index 0000000..ef65a9c
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/fragment-shader/fragment-shader.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python
+##########################################################################
+# 
+# Copyright 2009 VMware, Inc.
+# 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 VMWARE 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 struct
+
+from gallium import *
+
+def make_image(surface):
+    data = surface.get_tile_rgba8(0, 0, surface.width, surface.height)
+
+    import Image
+    outimage = Image.fromstring('RGBA', (surface.width, surface.height), data, "raw", 'RGBA', 0, 1)
+    return outimage
+
+def save_image(filename, surface):
+    outimage = make_image(surface)
+    outimage.save(filename, "PNG")
+
+def test(dev, name):
+    ctx = dev.context_create()
+
+    width = 320
+    height = 320
+    minz = 0.0
+    maxz = 1.0
+
+    # disabled blending/masking
+    blend = Blend()
+    blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE
+    blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE
+    blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO
+    blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO
+    blend.rt[0].colormask = PIPE_MASK_RGBA
+    ctx.set_blend(blend)
+
+    # depth/stencil/alpha
+    depth_stencil_alpha = DepthStencilAlpha()
+    depth_stencil_alpha.depth.enabled = 0
+    depth_stencil_alpha.depth.writemask = 1
+    depth_stencil_alpha.depth.func = PIPE_FUNC_LESS
+    ctx.set_depth_stencil_alpha(depth_stencil_alpha)
+
+    # rasterizer
+    rasterizer = Rasterizer()
+    rasterizer.front_winding = PIPE_WINDING_CW
+    rasterizer.cull_mode = PIPE_WINDING_NONE
+    rasterizer.scissor = 1
+    ctx.set_rasterizer(rasterizer)
+
+    # viewport
+    viewport = Viewport()
+    scale = FloatArray(4)
+    scale[0] = width / 2.0
+    scale[1] = -height / 2.0
+    scale[2] = (maxz - minz) / 2.0
+    scale[3] = 1.0
+    viewport.scale = scale
+    translate = FloatArray(4)
+    translate[0] = width / 2.0
+    translate[1] = height / 2.0
+    translate[2] = (maxz - minz) / 2.0
+    translate[3] = 0.0
+    viewport.translate = translate
+    ctx.set_viewport(viewport)
+
+    # samplers
+    sampler = Sampler()
+    sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE
+    sampler.min_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+    sampler.mag_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+    sampler.normalized_coords = 1
+    ctx.set_fragment_sampler(0, sampler)
+
+    # scissor
+    scissor = Scissor()
+    scissor.minx = 0
+    scissor.miny = 0
+    scissor.maxx = width
+    scissor.maxy = height
+    ctx.set_scissor(scissor)
+
+    clip = Clip()
+    clip.nr = 0
+    ctx.set_clip(clip)
+
+    # framebuffer
+    cbuf = dev.resource_create(
+        PIPE_FORMAT_B8G8R8X8_UNORM,
+        width, height,
+        bind=PIPE_BIND_RENDER_TARGET,
+    ).get_surface()
+    fb = Framebuffer()
+    fb.width = width
+    fb.height = height
+    fb.nr_cbufs = 1
+    fb.set_cbuf(0, cbuf)
+    ctx.set_framebuffer(fb)
+    rgba = FloatArray(4);
+    rgba[0] = 0.5
+    rgba[1] = 0.5
+    rgba[2] = 0.5
+    rgba[3] = 0.5
+    ctx.clear(PIPE_CLEAR_COLOR, rgba, 0.0, 0)
+
+    # vertex shader
+    vs = Shader('''
+        VERT
+        DCL IN[0], POSITION
+        DCL IN[1], COLOR
+        DCL OUT[0], POSITION
+        DCL OUT[1], COLOR
+        MOV OUT[0], IN[0]
+        MOV OUT[1], IN[1]
+        END
+    ''')
+    ctx.set_vertex_shader(vs)
+
+    # fragment shader
+    fs = Shader(file('frag-' + name + '.sh', 'rt').read())
+    ctx.set_fragment_shader(fs)
+
+    constbuf0 = dev.buffer_create(64,
+                                  (PIPE_BUFFER_USAGE_CONSTANT |
+                                   PIPE_BUFFER_USAGE_GPU_READ |
+                                   PIPE_BUFFER_USAGE_CPU_WRITE),
+                                  4 * 4 * 4)
+
+    cbdata = ''
+    cbdata += struct.pack('4f', 0.4, 0.0, 0.0, 1.0)
+    cbdata += struct.pack('4f', 1.0, 1.0, 1.0, 1.0)
+    cbdata += struct.pack('4f', 2.0, 2.0, 2.0, 2.0)
+    cbdata += struct.pack('4f', 4.0, 8.0, 16.0, 32.0)
+
+    constbuf0.write(cbdata, 0)
+
+    ctx.set_constant_buffer(PIPE_SHADER_FRAGMENT,
+                            0,
+                            constbuf0)
+
+    constbuf1 = dev.buffer_create(64,
+                                  (PIPE_BUFFER_USAGE_CONSTANT |
+                                   PIPE_BUFFER_USAGE_GPU_READ |
+                                   PIPE_BUFFER_USAGE_CPU_WRITE),
+                                  4 * 4 * 4)
+
+    cbdata = ''
+    cbdata += struct.pack('4f', 0.1, 0.1, 0.1, 0.1)
+    cbdata += struct.pack('4f', 0.25, 0.25, 0.25, 0.25)
+    cbdata += struct.pack('4f', 0.5, 0.5, 0.5, 0.5)
+    cbdata += struct.pack('4f', 0.75, 0.75, 0.75, 0.75)
+
+    constbuf1.write(cbdata, 0)
+
+    ctx.set_constant_buffer(PIPE_SHADER_FRAGMENT,
+                            1,
+                            constbuf1)
+
+    xy = [
+        -0.8, -0.8,
+         0.8, -0.8,
+         0.0,  0.8,
+    ]
+    color = [
+        1.0, 0.0, 0.0,
+        0.0, 1.0, 0.0,
+        0.0, 0.0, 1.0,
+    ]
+
+    nverts = 3
+    nattrs = 2
+    verts = FloatArray(nverts * nattrs * 4)
+
+    for i in range(0, nverts):
+        verts[i * nattrs * 4 + 0] = xy[i * 2 + 0] # x
+        verts[i * nattrs * 4 + 1] = xy[i * 2 + 1] # y
+        verts[i * nattrs * 4 + 2] = 0.5 # z
+        verts[i * nattrs * 4 + 3] = 1.0 # w
+        verts[i * nattrs * 4 + 4] = color[i * 3 + 0] # r
+        verts[i * nattrs * 4 + 5] = color[i * 3 + 1] # g
+        verts[i * nattrs * 4 + 6] = color[i * 3 + 2] # b
+        verts[i * nattrs * 4 + 7] = 1.0 # a
+
+    ctx.draw_vertices(PIPE_PRIM_TRIANGLES,
+                      nverts,
+                      nattrs,
+                      verts)
+
+    ctx.flush()
+
+    save_image('frag-' + name + '.png', cbuf)
+
+def main():
+    tests = [
+        'abs',
+        'add',
+        'cb-1d',
+        'cb-2d',
+        'dp3',
+        'dp4',
+        'dst',
+        'ex2',
+        'flr',
+        'frc',
+        'lg2',
+        'lit',
+        'lrp',
+        'mad',
+        'max',
+        'min',
+        'mov',
+        'mul',
+        'rcp',
+        'rsq',
+        'sge',
+        'slt',
+        'srcmod-abs',
+        'srcmod-absneg',
+        'srcmod-neg',
+        'srcmod-swz',
+        'sub',
+        'xpd',
+    ]
+
+    dev = Device()
+    for t in tests:
+        test(dev, t)
+
+if __name__ == '__main__':
+    main()
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/.gitignore b/src/gallium/tests/python/tests/regress/vertex-shader/.gitignore
new file mode 100644
index 0000000..e33609d
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/.gitignore
@@ -0,0 +1 @@
+*.png
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-abs.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-abs.sh
new file mode 100644
index 0000000..79c9ca6
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-abs.sh
@@ -0,0 +1,15 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+DCL TEMP[0]
+
+IMM FLT32 { 0.2, 0.2, 0.0, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+ABS OUT[0], TEMP[0]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-add.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-add.sh
new file mode 100644
index 0000000..ca97ad0
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-add.sh
@@ -0,0 +1,13 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+IMM FLT32 { 0.2, -0.1, 0.0, 0.0 }
+
+ADD OUT[0], IN[0], IMM[0]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-arl.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-arl.sh
new file mode 100644
index 0000000..321140e
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-arl.sh
@@ -0,0 +1,23 @@
+VERT
+
+DCL IN[0], POSITION
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+DCL ADDR[0]
+
+IMM FLT32 { 3.0, 1.0, 1.0, 1.0 }
+IMM FLT32 { 1.0, 0.0, 0.0, 1.0 }
+IMM FLT32 { 0.0, 1.0, 0.0, 1.0 }
+IMM FLT32 { 0.0, 0.0, 1.0, 1.0 }
+IMM FLT32 { 1.0, 1.0, 0.0, 1.0 }
+IMM FLT32 { 0.0, 1.0, 1.0, 1.0 }
+
+MOV OUT[0], IN[0]
+MUL TEMP[0], IN[0], IMM[0]
+ARL ADDR[0].x, TEMP[0]
+MOV OUT[1], IMM[ADDR[0].x + 3]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-arr.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-arr.sh
new file mode 100644
index 0000000..d60ea46
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-arr.sh
@@ -0,0 +1,23 @@
+VERT
+
+DCL IN[0], POSITION
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+DCL ADDR[0]
+
+IMM FLT32 { 3.0, 1.0, 1.0, 1.0 }
+IMM FLT32 { 1.0, 0.0, 0.0, 1.0 }
+IMM FLT32 { 0.0, 1.0, 0.0, 1.0 }
+IMM FLT32 { 0.0, 0.0, 1.0, 1.0 }
+IMM FLT32 { 1.0, 1.0, 0.0, 1.0 }
+IMM FLT32 { 0.0, 1.0, 1.0, 1.0 }
+
+MOV OUT[0], IN[0]
+MUL TEMP[0], IN[0], IMM[0]
+ARR ADDR[0].x, TEMP[0]
+MOV OUT[1], IMM[ADDR[0].x + 3]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-cb-1d.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-cb-1d.sh
new file mode 100644
index 0000000..b41fe5d
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-cb-1d.sh
@@ -0,0 +1,16 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+DCL CONST[1]
+DCL CONST[3]
+DCL TEMP[0..1]
+
+MOV OUT[0], IN[0]
+ADD TEMP[0], IN[1], CONST[1]
+RCP TEMP[1], CONST[3].xxxx
+MUL OUT[1], TEMP[0], TEMP[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-cb-2d.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-cb-2d.sh
new file mode 100644
index 0000000..45f5e6b
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-cb-2d.sh
@@ -0,0 +1,12 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+DCL CONST[1][1..2]
+
+MOV OUT[0], IN[0]
+MAD OUT[1], IN[1], CONST[1][2], CONST[1][1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-dp3.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-dp3.sh
new file mode 100644
index 0000000..caff622
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-dp3.sh
@@ -0,0 +1,16 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+DCL TEMP[0]
+
+IMM FLT32 { 0.0, 0.0, 1.0, 1.0 }
+
+DP3 TEMP[0].xy, IN[0], IN[0]
+MOV TEMP[0].zw, IMM[0]
+MUL OUT[0], IN[0], TEMP[0]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-dp4.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-dp4.sh
new file mode 100644
index 0000000..3dd2fd1
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-dp4.sh
@@ -0,0 +1,16 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+DCL TEMP[0]
+
+IMM FLT32 { 0.0, 0.0, 1.0, 1.0 }
+
+DP4 TEMP[0].xy, IN[0], IN[0]
+MOV TEMP[0].zw, IMM[0]
+MUL OUT[0], IN[0], TEMP[0]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-dst.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-dst.sh
new file mode 100644
index 0000000..da9cc18
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-dst.sh
@@ -0,0 +1,11 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+MOV OUT[0], IN[0]
+DST OUT[1], IN[1], IN[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-ex2.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-ex2.sh
new file mode 100644
index 0000000..4637227
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-ex2.sh
@@ -0,0 +1,18 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0..1]
+
+IMM FLT32 { 0.3, 0.3, 0.3, 1.0 }
+
+EX2 TEMP[0], IN[0]
+EX2 TEMP[1], IN[1].yyyy
+MUL TEMP[0], TEMP[0], IMM[0]
+MOV OUT[0], IN[0]
+MUL OUT[1], TEMP[0], TEMP[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-flr.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-flr.sh
new file mode 100644
index 0000000..aa80d6e
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-flr.sh
@@ -0,0 +1,23 @@
+VERT
+
+DCL IN[0], POSITION
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+DCL ADDR[0]
+
+IMM FLT32 { 3.0, 1.0, 1.0, 1.0 }
+IMM FLT32 { 1.0, 0.0, 0.0, 1.0 }
+IMM FLT32 { 0.0, 1.0, 0.0, 1.0 }
+IMM FLT32 { 0.0, 0.0, 1.0, 1.0 }
+IMM FLT32 { 1.0, 1.0, 0.0, 1.0 }
+IMM FLT32 { 0.0, 1.0, 1.0, 1.0 }
+
+MOV OUT[0], IN[0]
+MUL TEMP[0], IN[0], IMM[0]
+FLR ADDR[0].x, TEMP[0]
+MOV OUT[1], IMM[ADDR[0].x + 3]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-frc.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-frc.sh
new file mode 100644
index 0000000..64d1a49
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-frc.sh
@@ -0,0 +1,15 @@
+VERT
+
+DCL IN[0], POSITION
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 2.7, 3.1, 4.5, 1.0 }
+
+MUL TEMP[0], IN[0].xyxw, IMM[0]
+MOV OUT[0], IN[0]
+FRC OUT[1], TEMP[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-lg2.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-lg2.sh
new file mode 100644
index 0000000..5cf16fd
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-lg2.sh
@@ -0,0 +1,18 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 1.0, 0.0, 0.0, 0.0 }
+IMM FLT32 { 0.5, 0.0, 0.0, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+LG2 TEMP[0].x, TEMP[0].xxxx
+ADD OUT[0], TEMP[0], IMM[1]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-lit.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-lit.sh
new file mode 100644
index 0000000..a4a752d
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-lit.sh
@@ -0,0 +1,11 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+MOV OUT[0], IN[0]
+LIT OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-lrp.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-lrp.sh
new file mode 100644
index 0000000..4bb5f3e
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-lrp.sh
@@ -0,0 +1,14 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+ABS TEMP[0], IN[0]
+MOV OUT[0], IN[0]
+LRP OUT[1], TEMP[0], IN[1].xxxx, IN[1].yyyy
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-mad.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-mad.sh
new file mode 100644
index 0000000..daaa941
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-mad.sh
@@ -0,0 +1,14 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+IMM FLT32 { 0.5, 1.0, 1.0, 1.0 }
+IMM FLT32 { 0.5, 0.0, 0.0, 0.0 }
+
+MAD OUT[0], IN[0], IMM[0], IMM[1]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-max.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-max.sh
new file mode 100644
index 0000000..af279ec
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-max.sh
@@ -0,0 +1,13 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+IMM FLT32 { 0.5, 0.5, 0.5, 0.0 }
+
+MOV OUT[0], IN[0]
+MAX OUT[1], IN[1], IMM[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-min.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-min.sh
new file mode 100644
index 0000000..46d886c
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-min.sh
@@ -0,0 +1,13 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+IMM FLT32 { 0.5, 0.5, 0.5, 0.0 }
+
+MOV OUT[0], IN[0]
+MIN OUT[1], IN[1], IMM[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-mov.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-mov.sh
new file mode 100644
index 0000000..0ef9163
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-mov.sh
@@ -0,0 +1,11 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+MOV OUT[0], IN[0]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-mul.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-mul.sh
new file mode 100644
index 0000000..d34f6cd
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-mul.sh
@@ -0,0 +1,13 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+IMM FLT32 { 0.6, 0.6, 1.0, 1.0 }
+
+MUL OUT[0], IN[0], IMM[0]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-rcp.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-rcp.sh
new file mode 100644
index 0000000..cfb3ec3
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-rcp.sh
@@ -0,0 +1,18 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 1.0, 0.0, 0.0, 0.0 }
+IMM FLT32 { 1.5, 0.0, 0.0, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+RCP TEMP[0].x, TEMP[0].xxxx
+SUB OUT[0], TEMP[0], IMM[1]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-rsq.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-rsq.sh
new file mode 100644
index 0000000..faf1e6e
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-rsq.sh
@@ -0,0 +1,18 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 1.0, 0.0, 0.0, 0.0 }
+IMM FLT32 { 1.5, 0.0, 0.0, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+RSQ TEMP[0].x, TEMP[0].xxxx
+SUB OUT[0], TEMP[0], IMM[1]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-sge.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-sge.sh
new file mode 100644
index 0000000..6de1d07
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-sge.sh
@@ -0,0 +1,16 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { -0.1, -0.1, 1.0, 0.0 }
+
+SGE TEMP[0], IN[0], IMM[0]
+MOV OUT[0], IN[0]
+MUL OUT[1], IN[1], TEMP[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-slt.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-slt.sh
new file mode 100644
index 0000000..9a52422
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-slt.sh
@@ -0,0 +1,16 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+DCL TEMP[0]
+
+IMM FLT32 { 0.6, 0.6, 0.0, 0.0 }
+
+SLT TEMP[0], IN[0], IMM[0]
+MOV OUT[0], IN[0]
+MUL OUT[1], IN[1], TEMP[0]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-abs.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-abs.sh
new file mode 100644
index 0000000..dc87ce4
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-abs.sh
@@ -0,0 +1,15 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+DCL TEMP[0]
+
+IMM FLT32 { 0.1, 0.1, 0.0, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+MOV OUT[0], |TEMP[0]|
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-absneg.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-absneg.sh
new file mode 100644
index 0000000..d82eb08
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-absneg.sh
@@ -0,0 +1,16 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+DCL TEMP[0]
+
+IMM FLT32 { -0.2, -0.2, 0.0, 0.0 }
+
+ADD TEMP[0], IN[0], IMM[0]
+MOV OUT[0].xy, -|TEMP[0]|
+MOV OUT[0].zw, IN[0]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-neg.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-neg.sh
new file mode 100644
index 0000000..e39bebc
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-neg.sh
@@ -0,0 +1,12 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+MOV OUT[0].xy, -IN[0]
+MOV OUT[0].zw, IN[0]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-swz.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-swz.sh
new file mode 100644
index 0000000..6f20552
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-srcmod-swz.sh
@@ -0,0 +1,11 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+MOV OUT[0], IN[0].yxzw
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-sub.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-sub.sh
new file mode 100644
index 0000000..0f9678b
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-sub.sh
@@ -0,0 +1,13 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+IMM FLT32 { 0.1, 0.1, 0.0, 0.0 }
+
+SUB OUT[0], IN[0], IMM[0]
+MOV OUT[1], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vert-xpd.sh b/src/gallium/tests/python/tests/regress/vertex-shader/vert-xpd.sh
new file mode 100644
index 0000000..39d42ae
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vert-xpd.sh
@@ -0,0 +1,11 @@
+VERT
+
+DCL IN[0], POSITION
+DCL IN[1], COLOR
+DCL OUT[0], POSITION
+DCL OUT[1], COLOR
+
+MOV OUT[0], IN[0]
+XPD OUT[1], IN[0], IN[1]
+
+END
diff --git a/src/gallium/tests/python/tests/regress/vertex-shader/vertex-shader.py b/src/gallium/tests/python/tests/regress/vertex-shader/vertex-shader.py
new file mode 100644
index 0000000..05e40db
--- /dev/null
+++ b/src/gallium/tests/python/tests/regress/vertex-shader/vertex-shader.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+##########################################################################
+# 
+# Copyright 2009 VMware, Inc.
+# 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 VMWARE 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 struct
+
+from gallium import *
+
+def make_image(surface):
+    data = surface.get_tile_rgba8(0, 0, surface.width, surface.height)
+
+    import Image
+    outimage = Image.fromstring('RGBA', (surface.width, surface.height), data, "raw", 'RGBA', 0, 1)
+    return outimage
+
+def save_image(filename, surface):
+    outimage = make_image(surface)
+    outimage.save(filename, "PNG")
+
+def test(dev, name):
+    ctx = dev.context_create()
+
+    width = 320
+    height = 320
+    minz = 0.0
+    maxz = 1.0
+
+    # disabled blending/masking
+    blend = Blend()
+    blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE
+    blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE
+    blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO
+    blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO
+    blend.rt[0].colormask = PIPE_MASK_RGBA
+    ctx.set_blend(blend)
+
+    # depth/stencil/alpha
+    depth_stencil_alpha = DepthStencilAlpha()
+    depth_stencil_alpha.depth.enabled = 0
+    depth_stencil_alpha.depth.writemask = 1
+    depth_stencil_alpha.depth.func = PIPE_FUNC_LESS
+    ctx.set_depth_stencil_alpha(depth_stencil_alpha)
+
+    # rasterizer
+    rasterizer = Rasterizer()
+    rasterizer.front_winding = PIPE_WINDING_CW
+    rasterizer.cull_mode = PIPE_WINDING_NONE
+    rasterizer.scissor = 1
+    ctx.set_rasterizer(rasterizer)
+
+    # viewport
+    viewport = Viewport()
+    scale = FloatArray(4)
+    scale[0] = width / 2.0
+    scale[1] = -height / 2.0
+    scale[2] = (maxz - minz) / 2.0
+    scale[3] = 1.0
+    viewport.scale = scale
+    translate = FloatArray(4)
+    translate[0] = width / 2.0
+    translate[1] = height / 2.0
+    translate[2] = (maxz - minz) / 2.0
+    translate[3] = 0.0
+    viewport.translate = translate
+    ctx.set_viewport(viewport)
+
+    # samplers
+    sampler = Sampler()
+    sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+    sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE
+    sampler.min_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+    sampler.mag_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+    sampler.normalized_coords = 1
+    ctx.set_fragment_sampler(0, sampler)
+
+    # scissor
+    scissor = Scissor()
+    scissor.minx = 0
+    scissor.miny = 0
+    scissor.maxx = width
+    scissor.maxy = height
+    ctx.set_scissor(scissor)
+
+    clip = Clip()
+    clip.nr = 0
+    ctx.set_clip(clip)
+
+    # framebuffer
+    cbuf = dev.resource_create(
+        PIPE_FORMAT_B8G8R8X8_UNORM,
+        width, height,
+        bind=PIPE_BIND_RENDER_TARGET,
+    ).get_surface()
+    fb = Framebuffer()
+    fb.width = width
+    fb.height = height
+    fb.nr_cbufs = 1
+    fb.set_cbuf(0, cbuf)
+    ctx.set_framebuffer(fb)
+    rgba = FloatArray(4);
+    rgba[0] = 0.5
+    rgba[1] = 0.5
+    rgba[2] = 0.5
+    rgba[3] = 0.5
+    ctx.clear(PIPE_CLEAR_COLOR, rgba, 0.0, 0)
+
+    # vertex shader
+    vs = Shader(file('vert-' + name + '.sh', 'rt').read())
+    ctx.set_vertex_shader(vs)
+
+    # fragment shader
+    fs = Shader('''
+        FRAG
+        DCL IN[0], COLOR, LINEAR
+        DCL OUT[0], COLOR, CONSTANT
+        0:MOV OUT[0], IN[0]
+        1:END
+    ''')
+    ctx.set_fragment_shader(fs)
+
+    constbuf0 = dev.buffer_create(64,
+                                  (PIPE_BUFFER_USAGE_CONSTANT |
+                                   PIPE_BUFFER_USAGE_GPU_READ |
+                                   PIPE_BUFFER_USAGE_CPU_WRITE),
+                                  4 * 4 * 4)
+
+    cbdata = ''
+    cbdata += struct.pack('4f', 0.4, 0.0, 0.0, 1.0)
+    cbdata += struct.pack('4f', 1.0, 1.0, 1.0, 1.0)
+    cbdata += struct.pack('4f', 2.0, 2.0, 2.0, 2.0)
+    cbdata += struct.pack('4f', 4.0, 8.0, 16.0, 32.0)
+
+    constbuf0.write(cbdata, 0)
+
+    ctx.set_constant_buffer(PIPE_SHADER_VERTEX,
+                            0,
+                            constbuf0)
+
+    constbuf1 = dev.buffer_create(64,
+                                  (PIPE_BUFFER_USAGE_CONSTANT |
+                                   PIPE_BUFFER_USAGE_GPU_READ |
+                                   PIPE_BUFFER_USAGE_CPU_WRITE),
+                                  4 * 4 * 4)
+
+    cbdata = ''
+    cbdata += struct.pack('4f', 0.1, 0.1, 0.1, 0.1)
+    cbdata += struct.pack('4f', 0.25, 0.25, 0.25, 0.25)
+    cbdata += struct.pack('4f', 0.5, 0.5, 0.5, 0.5)
+    cbdata += struct.pack('4f', 0.75, 0.75, 0.75, 0.75)
+
+    constbuf1.write(cbdata, 0)
+
+    ctx.set_constant_buffer(PIPE_SHADER_VERTEX,
+                            1,
+                            constbuf1)
+
+    xy = [
+         0.0,  0.8,
+        -0.2,  0.4,
+         0.2,  0.4,
+        -0.4,  0.0,
+         0.0,  0.0,
+         0.4,  0.0,
+        -0.6, -0.4,
+        -0.2, -0.4,
+         0.2, -0.4,
+         0.6, -0.4,
+        -0.8, -0.8,
+        -0.4, -0.8,
+         0.0, -0.8,
+         0.4, -0.8,
+         0.8, -0.8,
+    ]
+    color = [
+        1.0, 0.0, 0.0,
+        0.0, 1.0, 0.0,
+        0.0, 0.0, 1.0,
+    ]
+    tri = [
+         1,  2,  0,
+         3,  4,  1,
+         4,  2,  1,
+         4,  5,  2,
+         6,  7,  3,
+         7,  4,  3,
+         7,  8,  4,
+         8,  5,  4,
+         8,  9,  5,
+        10, 11,  6,
+        11,  7,  6,
+        11, 12,  7,
+        12,  8,  7,
+        12, 13,  8,
+        13,  9,  8,
+        13, 14,  9,
+    ]
+
+    nverts = 16 * 3
+    nattrs = 2
+    verts = FloatArray(nverts * nattrs * 4)
+
+    for i in range(0, nverts):
+        verts[i * nattrs * 4 + 0] = xy[tri[i] * 2 + 0] # x
+        verts[i * nattrs * 4 + 1] = xy[tri[i] * 2 + 1] # y
+        verts[i * nattrs * 4 + 2] = 0.5 # z
+        verts[i * nattrs * 4 + 3] = 1.0 # w
+        verts[i * nattrs * 4 + 4] = color[(i % 3) * 3 + 0] # r
+        verts[i * nattrs * 4 + 5] = color[(i % 3) * 3 + 1] # g
+        verts[i * nattrs * 4 + 6] = color[(i % 3) * 3 + 2] # b
+        verts[i * nattrs * 4 + 7] = 1.0 # a
+
+    ctx.draw_vertices(PIPE_PRIM_TRIANGLES,
+                      nverts,
+                      nattrs,
+                      verts)
+
+    ctx.flush()
+
+    save_image('vert-' + name + '.png', cbuf)
+
+def main():
+    tests = [
+        'abs',
+        'add',
+        'arl',
+        'arr',
+        'cb-1d',
+        'cb-2d',
+        'dp3',
+        'dp4',
+        'dst',
+        'ex2',
+        'flr',
+        'frc',
+        'lg2',
+        'lit',
+        'lrp',
+        'mad',
+        'max',
+        'min',
+        'mov',
+        'mul',
+        'rcp',
+        'rsq',
+        'sge',
+        'slt',
+        'srcmod-abs',
+        'srcmod-absneg',
+        'srcmod-neg',
+        'srcmod-swz',
+        'sub',
+        'xpd',
+    ]
+
+    dev = Device()
+    for t in tests:
+        test(dev, t)
+
+if __name__ == '__main__':
+    main()
diff --git a/src/gallium/tests/python/tests/surface_copy.py b/src/gallium/tests/python/tests/surface_copy.py
new file mode 100755
index 0000000..8d84164
--- /dev/null
+++ b/src/gallium/tests/python/tests/surface_copy.py
@@ -0,0 +1,200 @@
+#!/usr/bin/env python
+##########################################################################
+# 
+# Copyright 2009 VMware, Inc.
+# 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 VMWARE 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 os
+import random
+
+from gallium import *
+from base import *
+
+
+def lods(*dims):
+    size = max(dims)
+    lods = 0
+    while size:
+        lods += 1
+        size >>= 1
+    return lods
+
+
+class TextureTest(TestCase):
+    
+    tags = (
+        'target',
+        'format',
+        'width',
+        'height',
+        'depth',
+        'last_level',
+        'face',
+        'level',
+        'zslice',
+    )
+
+    def test(self):
+        dev = self.dev
+        ctx = self.ctx
+        
+        target = self.target
+        format = self.format
+        width = self.width
+        height = self.height
+        depth = self.depth
+        last_level = self.last_level
+        face = self.face
+        level = self.level
+        zslice = self.zslice
+        
+        bind = PIPE_BIND_SAMPLER_VIEW
+        geom_flags = 0
+        if not dev.is_format_supported(format, target, bind, geom_flags):
+            raise TestSkip
+
+        if not dev.is_format_supported(format, target, bind, geom_flags):
+            raise TestSkip
+
+        #  textures
+        dst_texture = dev.resource_create(
+            target = target,
+            format = format, 
+            width = width, 
+            height = height,
+            depth = depth, 
+            last_level = last_level,
+            bind = bind,
+        )
+
+        dst_surface = dst_texture.get_surface(face = face, level = level, zslice = zslice)
+        
+        src_texture = dev.resource_create(
+            target = target,
+            format = format, 
+            width = dst_surface.width, 
+            height = dst_surface.height,
+            depth = 1, 
+            last_level = 0,
+            bind = PIPE_BIND_SAMPLER_VIEW,
+        )
+
+        src_surface = src_texture.get_surface()
+        
+        w = dst_surface.width
+        h = dst_surface.height
+
+        stride = util_format_get_stride(format, w)
+        size = util_format_get_nblocksy(format, h) * stride
+        src_raw = os.urandom(size)
+
+        ctx.surface_write_raw(src_surface, 0, 0, w, h, src_raw, stride)
+
+        ctx.surface_copy(dst_surface, 0, 0, 
+                         src_surface, 0, 0, w, h)
+    
+        dst_raw = ctx.surface_read_raw(dst_surface, 0, 0, w, h)
+
+        if dst_raw != src_raw:
+            raise TestFailure
+
+
+def main():
+    dev = Device()
+    ctx = dev.context_create()
+    suite = TestSuite()
+    
+    targets = [
+        PIPE_TEXTURE_2D,
+        PIPE_TEXTURE_CUBE,
+        PIPE_TEXTURE_3D,
+    ]
+    
+    sizes = [64, 32, 16, 8, 4, 2, 1]
+    #sizes = [1020, 508, 252, 62, 30, 14, 6, 3]
+    #sizes = [64]
+    #sizes = [63]
+    
+    faces = [
+        PIPE_TEX_FACE_POS_X,
+        PIPE_TEX_FACE_NEG_X,
+        PIPE_TEX_FACE_POS_Y,
+        PIPE_TEX_FACE_NEG_Y, 
+        PIPE_TEX_FACE_POS_Z, 
+        PIPE_TEX_FACE_NEG_Z,
+    ]
+
+    try:
+        n = int(sys.argv[1])
+    except:
+        n = 10000
+    
+    for i in range(n):
+        format = random.choice(formats.keys())
+        if not util_format_is_depth_or_stencil(format):
+            is_depth_or_stencil = util_format_is_depth_or_stencil(format)
+
+            if is_depth_or_stencil:
+                target = PIPE_TEXTURE_2D
+            else:
+                target = random.choice(targets)
+            
+            size = random.choice(sizes)
+
+            if target == PIPE_TEXTURE_3D:
+                depth = size
+            else:
+                depth = 1
+
+            if target == PIPE_TEXTURE_CUBE:
+                face = random.choice(faces)
+            else:
+                face = PIPE_TEX_FACE_POS_X
+
+            levels = lods(size)
+            last_level = random.randint(0, levels - 1)
+            level = random.randint(0, last_level)
+            zslice = random.randint(0, max(depth >> level, 1) - 1)
+
+            test = TextureTest(
+                dev = dev,
+                ctx = ctx,
+                target = target,
+                format = format, 
+                width = size,
+                height = size,
+                depth = depth,
+                last_level = last_level,
+                face = face,
+                level = level,
+                zslice = zslice,
+            )
+            suite.add_test(test)
+    suite.run()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/gallium/tests/python/tests/texture_blit.py b/src/gallium/tests/python/tests/texture_blit.py
new file mode 100755
index 0000000..77f006e
--- /dev/null
+++ b/src/gallium/tests/python/tests/texture_blit.py
@@ -0,0 +1,636 @@
+#!/usr/bin/env python
+##########################################################################
+# 
+# Copyright 2009 VMware, Inc.
+# 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 VMWARE 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 random
+
+from gallium import *
+from base import *
+
+
+def lods(*dims):
+    size = max(dims)
+    lods = 0
+    while size:
+        lods += 1
+        size >>= 1
+    return lods
+
+
+def minify(dims, level = 1):
+    return [max(dim>>level, 1) for dim in dims]
+
+
+def tex_coords(texture, face, level, zslice):
+    st = [ 
+        [0.0, 0.0], 
+        [1.0, 0.0], 
+        [1.0, 1.0], 
+        [0.0, 1.0],
+    ] 
+    
+    if texture.target == PIPE_TEXTURE_2D:
+        return [[s, t, 0.0] for s, t in st]
+    elif texture.target == PIPE_TEXTURE_3D:
+        depth = texture.get_depth(level)
+        if depth > 1:
+            r = float(zslice)/float(depth - 1)
+        else:
+            r = 0.0
+        return [[s, t, r] for s, t in st]
+    elif texture.target == PIPE_TEXTURE_CUBE:
+        result = []
+        for s, t in st:
+            # See http://developer.nvidia.com/object/cube_map_ogl_tutorial.html
+            sc = 2.0*s - 1.0
+            tc = 2.0*t - 1.0
+            if face == PIPE_TEX_FACE_POS_X:
+                rx = 1.0
+                ry = -tc
+                rz = -sc
+            if face == PIPE_TEX_FACE_NEG_X:
+                rx = -1.0
+                ry = -tc
+                rz = sc
+            if face == PIPE_TEX_FACE_POS_Y:
+                rx = sc
+                ry = 1.0
+                rz = tc
+            if face == PIPE_TEX_FACE_NEG_Y:
+                rx = sc
+                ry = -1.0
+                rz = -tc
+            if face == PIPE_TEX_FACE_POS_Z:
+                rx = sc
+                ry = -tc
+                rz = 1.0
+            if face == PIPE_TEX_FACE_NEG_Z:
+                rx = -sc
+                ry = -tc
+                rz = -1.0
+            result.append([rx, ry, rz])
+        return result
+
+def is_pot(n):
+    return n & (n - 1) == 0
+      
+                
+class TextureColorSampleTest(TestCase):
+    
+    tags = (
+        'target',
+        'format',
+        'width',
+        'height',
+        'depth',
+        'last_level',
+        'face',
+        'level',
+        'zslice',
+    )
+
+    def test(self):
+        dev = self.dev
+        ctx = self.ctx
+        
+        target = self.target
+        format = self.format
+        width = self.width
+        height = self.height
+        depth = self.depth
+        last_level = self.last_level
+        face = self.face
+        level = self.level
+        zslice = self.zslice
+        minz = 0.0
+        maxz = 1.0
+        
+        bind = PIPE_BIND_SAMPLER_VIEW
+        geom_flags = 0
+        if width != height:
+            geom_flags |= PIPE_TEXTURE_GEOM_NON_SQUARE
+        if not is_pot(width) or not is_pot(height) or not is_pot(depth):
+            geom_flags |= PIPE_TEXTURE_GEOM_NON_POWER_OF_TWO
+        
+        if not dev.is_format_supported(format, target, bind, geom_flags):
+            raise TestSkip
+        
+        # disabled blending/masking
+        blend = Blend()
+        blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE
+        blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE
+        blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO
+        blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO
+        blend.rt[0].colormask = PIPE_MASK_RGBA
+        ctx.set_blend(blend)
+    
+        # no-op depth/stencil/alpha
+        depth_stencil_alpha = DepthStencilAlpha()
+        ctx.set_depth_stencil_alpha(depth_stencil_alpha)
+    
+        # rasterizer
+        rasterizer = Rasterizer()
+        rasterizer.front_winding = PIPE_WINDING_CW
+        rasterizer.cull_mode = PIPE_WINDING_NONE
+        ctx.set_rasterizer(rasterizer)
+    
+        # samplers
+        sampler = Sampler()
+        sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+        sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+        sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+        sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NEAREST
+        sampler.min_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+        sampler.mag_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+        sampler.normalized_coords = 1
+        sampler.min_lod = 0
+        sampler.max_lod = PIPE_MAX_TEXTURE_LEVELS - 1
+        ctx.set_fragment_sampler(0, sampler)
+    
+        #  texture 
+        texture = dev.resource_create(
+            target = target,
+            format = format, 
+            width = width, 
+            height = height,
+            depth = depth, 
+            last_level = last_level,
+            bind = bind,
+        )
+        
+        expected_rgba = FloatArray(height*width*4) 
+        surface = texture.get_surface(
+            face = face,
+            level = level,
+            zslice = zslice,
+        )
+        
+        ctx.surface_sample_rgba(surface, expected_rgba, True)
+        
+        ctx.set_fragment_sampler_texture(0, texture)
+
+        # viewport
+        viewport = Viewport()
+        scale = FloatArray(4)
+        scale[0] = width
+        scale[1] = height
+        scale[2] = (maxz - minz) / 2.0
+        scale[3] = 1.0
+        viewport.scale = scale
+        translate = FloatArray(4)
+        translate[0] = 0.0
+        translate[1] = 0.0
+        translate[2] = (maxz - minz) / 2.0
+        translate[3] = 0.0
+        viewport.translate = translate
+        ctx.set_viewport(viewport)
+
+        # scissor
+        scissor = Scissor()
+        scissor.minx = 0
+        scissor.miny = 0
+        scissor.maxx = width
+        scissor.maxy = height
+        ctx.set_scissor(scissor)
+
+        # clip
+        clip = Clip()
+        clip.nr = 0
+        ctx.set_clip(clip)
+
+        #  framebuffer 
+        cbuf_tex = dev.resource_create(
+            PIPE_FORMAT_B8G8R8A8_UNORM, 
+            width, 
+            height,
+            bind = PIPE_BIND_RENDER_TARGET,
+        )
+
+        cbuf = cbuf_tex.get_surface()
+        fb = Framebuffer()
+        fb.width = width
+        fb.height = height
+        fb.nr_cbufs = 1
+        fb.set_cbuf(0, cbuf)
+        ctx.set_framebuffer(fb)
+        rgba = FloatArray(4);
+        rgba[0] = 0.5
+        rgba[1] = 0.5
+        rgba[2] = 0.5
+        rgba[3] = 0.5
+        ctx.clear(PIPE_CLEAR_COLOR, rgba, 0.0, 0)
+        del fb
+    
+        # vertex shader
+        vs = Shader('''
+            VERT
+            DCL IN[0], POSITION, CONSTANT
+            DCL IN[1], GENERIC, CONSTANT
+            DCL OUT[0], POSITION, CONSTANT
+            DCL OUT[1], GENERIC, CONSTANT
+            0:MOV OUT[0], IN[0]
+            1:MOV OUT[1], IN[1]
+            2:END
+        ''')
+        #vs.dump()
+        ctx.set_vertex_shader(vs)
+    
+        # fragment shader
+        op = {
+            PIPE_TEXTURE_1D: "1D", 
+            PIPE_TEXTURE_2D: "2D", 
+            PIPE_TEXTURE_3D: "3D", 
+            PIPE_TEXTURE_CUBE: "CUBE",
+        }[target]
+        fs = Shader('''
+            FRAG
+            DCL IN[0], GENERIC[0], LINEAR
+            DCL OUT[0], COLOR, CONSTANT
+            DCL SAMP[0], CONSTANT
+            0:TEX OUT[0], IN[0], SAMP[0], %s
+            1:END
+        ''' % op)
+        #fs.dump()
+        ctx.set_fragment_shader(fs)
+
+        nverts = 4
+        nattrs = 2
+        verts = FloatArray(nverts * nattrs * 4)
+    
+        x = 0
+        y = 0
+        w, h = minify((width, height), level)
+    
+        pos = [
+            [x, y],
+            [x+w, y],
+            [x+w, y+h],
+            [x, y+h],
+        ]
+    
+        tex = tex_coords(texture, face, level, zslice)
+    
+        for i in range(0, 4):
+            j = 8*i
+            verts[j + 0] = pos[i][0]/float(width) # x
+            verts[j + 1] = pos[i][1]/float(height) # y
+            verts[j + 2] = 0.0 # z
+            verts[j + 3] = 1.0 # w
+            verts[j + 4] = tex[i][0] # s
+            verts[j + 5] = tex[i][1] # r
+            verts[j + 6] = tex[i][2] # q
+            verts[j + 7] = 1.0
+    
+        ctx.draw_vertices(PIPE_PRIM_TRIANGLE_FAN,
+                          nverts, 
+                          nattrs, 
+                          verts)
+    
+        ctx.flush()
+    
+        cbuf = cbuf_tex.get_surface()
+        
+        self.assert_rgba(ctx, cbuf, x, y, w, h, expected_rgba, 4.0/256, 0.85)
+        
+
+class TextureDepthSampleTest(TestCase):
+    
+    tags = (
+        'target',
+        'format',
+        'width',
+        'height',
+        'depth',
+        'last_level',
+        'face',
+        'level',
+        'zslice',
+    )
+
+    def test(self):
+        dev = self.dev
+        ctx = self.ctx
+        
+        target = self.target
+        format = self.format
+        width = self.width
+        height = self.height
+        depth = self.depth
+        last_level = self.last_level
+        face = self.face
+        level = self.level
+        zslice = self.zslice
+        minz = 0.0
+        maxz = 1.0
+        
+        bind = PIPE_BIND_SAMPLER_VIEW
+        geom_flags = 0
+        if width != height:
+            geom_flags |= PIPE_TEXTURE_GEOM_NON_SQUARE
+        if not is_pot(width) or not is_pot(height) or not is_pot(depth):
+            geom_flags |= PIPE_TEXTURE_GEOM_NON_POWER_OF_TWO
+        
+        if not dev.is_format_supported(format, target, bind, geom_flags):
+            raise TestSkip
+        
+        # disabled blending/masking
+        blend = Blend()
+        blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE
+        blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE
+        blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO
+        blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO
+        blend.rt[0].colormask = PIPE_MASK_RGBA
+        ctx.set_blend(blend)
+    
+        # depth/stencil/alpha
+        depth_stencil_alpha = DepthStencilAlpha()
+        depth_stencil_alpha.depth.enabled = 1
+        depth_stencil_alpha.depth.writemask = 1
+        depth_stencil_alpha.depth.func = PIPE_FUNC_LESS
+        ctx.set_depth_stencil_alpha(depth_stencil_alpha)
+    
+        # rasterizer
+        rasterizer = Rasterizer()
+        rasterizer.front_winding = PIPE_WINDING_CW
+        rasterizer.cull_mode = PIPE_WINDING_NONE
+        ctx.set_rasterizer(rasterizer)
+    
+        # viewport
+        viewport = Viewport()
+        scale = FloatArray(4)
+        scale[0] = width
+        scale[1] = height
+        scale[2] = (maxz - minz) / 2.0
+        scale[3] = 1.0
+        viewport.scale = scale
+        translate = FloatArray(4)
+        translate[0] = 0.0
+        translate[1] = 0.0
+        translate[2] = (maxz - minz) / 2.0
+        translate[3] = 0.0
+        viewport.translate = translate
+        ctx.set_viewport(viewport)
+
+        # samplers
+        sampler = Sampler()
+        sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+        sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+        sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+        sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NEAREST
+        sampler.min_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+        sampler.mag_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+        sampler.normalized_coords = 1
+        sampler.min_lod = 0
+        sampler.max_lod = PIPE_MAX_TEXTURE_LEVELS - 1
+        ctx.set_fragment_sampler(0, sampler)
+    
+        #  texture 
+        texture = dev.resource_create(
+            target = target,
+            format = format, 
+            width = width, 
+            height = height,
+            depth = depth, 
+            last_level = last_level,
+            bind = bind,
+        )
+        
+        expected_rgba = FloatArray(height*width*4) 
+        surface = texture.get_surface(
+            face = face,
+            level = level,
+            zslice = zslice,
+        )
+
+        ctx.surface_sample_rgba(surface, expected_rgba, True)
+        
+        ctx.set_fragment_sampler_texture(0, texture)
+
+        # scissor
+        scissor = Scissor()
+        scissor.minx = 0
+        scissor.miny = 0
+        scissor.maxx = width
+        scissor.maxy = height
+        ctx.set_scissor(scissor)
+
+        # clip
+        clip = Clip()
+        clip.nr = 0
+        ctx.set_clip(clip)
+
+        #  framebuffer 
+        cbuf_tex = dev.resource_create(
+            PIPE_FORMAT_B8G8R8A8_UNORM, 
+            width, 
+            height,
+            bind = PIPE_BIND_RENDER_TARGET,
+        )
+
+        zsbuf_tex = dev.resource_create(
+            PIPE_FORMAT_X8Z24_UNORM, 
+            width, 
+            height,
+            bind = PIPE_BIND_RENDER_TARGET,
+        )
+
+        cbuf = cbuf_tex.get_surface()
+        zsbuf = zsbuf_tex.get_surface()
+        fb = Framebuffer()
+        fb.width = width
+        fb.height = height
+        fb.nr_cbufs = 1
+        fb.set_cbuf(0, cbuf)
+        fb.set_zsbuf(zsbuf)
+        ctx.set_framebuffer(fb)
+        rgba = FloatArray(4);
+        rgba[0] = 0.5
+        rgba[1] = 0.5
+        rgba[2] = 0.5
+        rgba[3] = 0.5
+        ctx.clear(PIPE_CLEAR_DEPTHSTENCIL, rgba, 1.0, 0)
+        del fb
+    
+        # vertex shader
+        vs = Shader('''
+            VERT
+            DCL IN[0], POSITION, CONSTANT
+            DCL IN[1], GENERIC, CONSTANT
+            DCL OUT[0], POSITION, CONSTANT
+            DCL OUT[1], GENERIC, CONSTANT
+            0:MOV OUT[0], IN[0]
+            1:MOV OUT[1], IN[1]
+            2:END
+        ''')
+        #vs.dump()
+        ctx.set_vertex_shader(vs)
+    
+        # fragment shader
+        op = {
+            PIPE_TEXTURE_1D: "1D", 
+            PIPE_TEXTURE_2D: "2D", 
+            PIPE_TEXTURE_3D: "3D", 
+            PIPE_TEXTURE_CUBE: "CUBE",
+        }[target]
+        fs = Shader('''
+            FRAG
+            DCL IN[0], GENERIC[0], LINEAR
+            DCL SAMP[0], CONSTANT
+            DCL OUT[0].z, POSITION
+            0:TEX OUT[0].z, IN[0], SAMP[0], %s
+            1:END
+        ''' % op)
+        #fs.dump()
+        ctx.set_fragment_shader(fs)
+
+        nverts = 4
+        nattrs = 2
+        verts = FloatArray(nverts * nattrs * 4)
+    
+        x = 0
+        y = 0
+        w, h = minify((width, height), level)
+    
+        pos = [
+            [x, y],
+            [x+w, y],
+            [x+w, y+h],
+            [x, y+h],
+        ]
+    
+        tex = tex_coords(texture, face, level, zslice)
+    
+        for i in range(0, 4):
+            j = 8*i
+            verts[j + 0] = pos[i][0]/float(width) # x
+            verts[j + 1] = pos[i][1]/float(height) # y
+            verts[j + 2] = 0.0 # z
+            verts[j + 3] = 1.0 # w
+            verts[j + 4] = tex[i][0] # s
+            verts[j + 5] = tex[i][1] # r
+            verts[j + 6] = tex[i][2] # q
+            verts[j + 7] = 1.0
+    
+        ctx.draw_vertices(PIPE_PRIM_TRIANGLE_FAN,
+                          nverts, 
+                          nattrs, 
+                          verts)
+    
+        ctx.flush()
+    
+        zsbuf = zsbuf_tex.get_surface()
+        
+        self.assert_rgba(ctx, zsbuf, x, y, w, h, expected_rgba, 4.0/256, 0.85)
+        
+
+
+
+def main():
+    random.seed(0xdead3eef)
+
+    dev = Device()
+    ctx = dev.context_create()
+    suite = TestSuite()
+    
+    targets = [
+        PIPE_TEXTURE_2D,
+        PIPE_TEXTURE_CUBE,
+        PIPE_TEXTURE_3D,
+    ]
+    
+    #sizes = [64, 32, 16, 8, 4, 2, 1]
+    #sizes = [1020, 508, 252, 62, 30, 14, 6, 3]
+    sizes = [64]
+    #sizes = [63]
+    
+    faces = [
+        PIPE_TEX_FACE_POS_X,
+        PIPE_TEX_FACE_NEG_X,
+        PIPE_TEX_FACE_POS_Y,
+        PIPE_TEX_FACE_NEG_Y, 
+        PIPE_TEX_FACE_POS_Z, 
+        PIPE_TEX_FACE_NEG_Z,
+    ]
+
+    try:
+        n = int(sys.argv[1])
+    except:
+        n = 10000
+    
+    for i in range(n):
+        format = random.choice(formats.keys())
+        if not util_format_is_depth_or_stencil(format):
+            is_depth_or_stencil = util_format_is_depth_or_stencil(format)
+
+            if is_depth_or_stencil:
+                target = PIPE_TEXTURE_2D
+            else:
+                target = random.choice(targets)
+            
+            size = random.choice(sizes)
+
+            if target == PIPE_TEXTURE_3D:
+                depth = size
+            else:
+                depth = 1
+
+            if target == PIPE_TEXTURE_CUBE:
+                face = random.choice(faces)
+            else:
+                face = PIPE_TEX_FACE_POS_X
+
+            levels = lods(size)
+            last_level = random.randint(0, levels - 1)
+            level = random.randint(0, last_level)
+            zslice = random.randint(0, max(depth >> level, 1) - 1)
+
+            if is_depth_or_stencil:
+                klass = TextureDepthSampleTest
+            else:
+                klass = TextureColorSampleTest
+
+            test = klass(
+                dev = dev,
+                ctx = ctx,
+                target = target,
+                format = format, 
+                width = size,
+                height = size,
+                depth = depth,
+                last_level = last_level,
+                face = face,
+                level = level,
+                zslice = zslice,
+            )
+            suite.add_test(test)
+    suite.run()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/gallium/tests/python/tests/texture_render.py b/src/gallium/tests/python/tests/texture_render.py
new file mode 100755
index 0000000..23f3d2a
--- /dev/null
+++ b/src/gallium/tests/python/tests/texture_render.py
@@ -0,0 +1,320 @@
+#!/usr/bin/env python
+##########################################################################
+# 
+# Copyright 2009 VMware, Inc.
+# 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 VMWARE 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.
+# 
+##########################################################################
+
+
+from gallium import *
+from base import *
+
+
+def lods(*dims):
+    size = max(dims)
+    lods = 0
+    while size:
+        lods += 1
+        size >>= 1
+    return lods
+
+
+class TextureTest(TestCase):
+    
+    tags = (
+        'target',
+        'format',
+        'width',
+        'height',
+        'depth',
+        'last_level',
+        'face',
+        'level',
+        'zslice',
+    )
+
+    def test(self):
+        dev = self.dev
+        
+        target = self.target
+        format = self.format
+        width = self.width
+        height = self.height
+        depth = self.depth
+        last_level = self.last_level
+        face = self.face
+        level = self.level
+        zslice = self.zslice
+        
+        #  textures
+        dst_texture = dev.resource_create(
+            target = target,
+            format = format, 
+            width = width, 
+            height = height,
+            depth = depth, 
+            last_level = last_level,
+            bind = PIPE_BIND_RENDER_TARGET,
+        )
+        if dst_texture is None:
+            raise TestSkip
+
+        dst_surface = dst_texture.get_surface(face = face, level = level, zslice = zslice)
+        
+        ref_texture = dev.resource_create(
+            target = target,
+            format = format, 
+            width = dst_surface.width, 
+            height = dst_surface.height,
+            depth = 1, 
+            last_level = 0,
+            bind = PIPE_BIND_SAMPLER_VIEW,
+        )
+
+        ref_surface = ref_texture.get_surface()
+        
+        src_texture = dev.resource_create(
+            target = target,
+            format = PIPE_FORMAT_B8G8R8A8_UNORM, 
+            width = dst_surface.width, 
+            height = dst_surface.height,
+            depth = 1, 
+            last_level = 0,
+            bind = PIPE_BIND_SAMPLER_VIEW,
+        )
+
+        src_surface = src_texture.get_surface()
+        
+        expected_rgba = FloatArray(height*width*4) 
+        ref_surface.sample_rgba(expected_rgba)
+
+        src_surface.put_tile_rgba(0, 0, src_surface.width, src_surface.height, expected_rgba)
+        
+        ctx = self.dev.context_create()
+    
+        # disabled blending/masking
+        blend = Blend()
+        blend.rt[0].rgb_src_factor = PIPE_BLENDFACTOR_ONE
+        blend.rt[0].alpha_src_factor = PIPE_BLENDFACTOR_ONE
+        blend.rt[0].rgb_dst_factor = PIPE_BLENDFACTOR_ZERO
+        blend.rt[0].alpha_dst_factor = PIPE_BLENDFACTOR_ZERO
+        blend.rt[0].colormask = PIPE_MASK_RGBA
+        ctx.set_blend(blend)
+    
+        # no-op depth/stencil/alpha
+        depth_stencil_alpha = DepthStencilAlpha()
+        ctx.set_depth_stencil_alpha(depth_stencil_alpha)
+    
+        # rasterizer
+        rasterizer = Rasterizer()
+        rasterizer.front_winding = PIPE_WINDING_CW
+        rasterizer.cull_mode = PIPE_WINDING_NONE
+        rasterizer.bypass_vs_clip_and_viewport = 1
+        ctx.set_rasterizer(rasterizer)
+    
+        # samplers
+        sampler = Sampler()
+        sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+        sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+        sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE
+        sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NEAREST
+        sampler.min_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+        sampler.mag_img_filter = PIPE_TEX_MIPFILTER_NEAREST
+        sampler.normalized_coords = 1
+        sampler.min_lod = 0
+        sampler.max_lod = PIPE_MAX_TEXTURE_LEVELS - 1
+        ctx.set_fragment_sampler(0, sampler)
+        ctx.set_fragment_sampler_texture(0, src_texture)
+
+        #  framebuffer 
+        cbuf_tex = dev.resource_create(
+            PIPE_FORMAT_B8G8R8A8_UNORM, 
+            width, 
+            height,
+            bind = PIPE_BIND_RENDER_TARGET,
+        )
+
+        fb = Framebuffer()
+        fb.width = dst_surface.width
+        fb.height = dst_surface.height
+        fb.nr_cbufs = 1
+        fb.set_cbuf(0, dst_surface)
+        ctx.set_framebuffer(fb)
+        rgba = FloatArray(4);
+        rgba[0] = 0.0
+        rgba[1] = 0.0
+        rgba[2] = 0.0
+        rgba[3] = 0.0
+        ctx.clear(PIPE_CLEAR_COLOR, rgba, 0.0, 0)
+        del fb
+    
+        # vertex shader
+        vs = Shader('''
+            VERT
+            DCL IN[0], POSITION, CONSTANT
+            DCL IN[1], GENERIC, CONSTANT
+            DCL OUT[0], POSITION, CONSTANT
+            DCL OUT[1], GENERIC, CONSTANT
+            0:MOV OUT[0], IN[0]
+            1:MOV OUT[1], IN[1]
+            2:END
+        ''')
+        #vs.dump()
+        ctx.set_vertex_shader(vs)
+    
+        # fragment shader
+        fs = Shader('''
+            FRAG
+            DCL IN[0], GENERIC[0], LINEAR
+            DCL OUT[0], COLOR, CONSTANT
+            DCL SAMP[0], CONSTANT
+            0:TEX OUT[0], IN[0], SAMP[0], 2D
+            1:END
+        ''')
+        #fs.dump()
+        ctx.set_fragment_shader(fs)
+
+        nverts = 4
+        nattrs = 2
+        verts = FloatArray(nverts * nattrs * 4)
+    
+        x = 0
+        y = 0
+        w = dst_surface.width
+        h = dst_surface.height
+    
+        pos = [
+            [x, y],
+            [x+w, y],
+            [x+w, y+h],
+            [x, y+h],
+        ]
+    
+        tex = [
+            [0.0, 0.0], 
+            [1.0, 0.0], 
+            [1.0, 1.0], 
+            [0.0, 1.0],
+        ]
+    
+        for i in range(0, 4):
+            j = 8*i
+            verts[j + 0] = pos[i][0] # x
+            verts[j + 1] = pos[i][1] # y
+            verts[j + 2] = 0.0 # z
+            verts[j + 3] = 1.0 # w
+            verts[j + 4] = tex[i][0] # s
+            verts[j + 5] = tex[i][1] # r
+            verts[j + 6] = 0.0
+            verts[j + 7] = 1.0
+    
+        ctx.draw_vertices(PIPE_PRIM_TRIANGLE_FAN,
+                          nverts, 
+                          nattrs, 
+                          verts)
+    
+        ctx.flush()
+    
+        self.assert_rgba(dst_surface, x, y, w, h, expected_rgba, 4.0/256, 0.85)
+        
+
+
+def main():
+    dev = Device()
+    suite = TestSuite()
+    
+    targets = [
+        PIPE_TEXTURE_2D,
+        PIPE_TEXTURE_CUBE,
+        #PIPE_TEXTURE_3D,
+    ]
+    
+    formats = [
+        PIPE_FORMAT_B8G8R8A8_UNORM,
+        PIPE_FORMAT_B8G8R8X8_UNORM,
+        #PIPE_FORMAT_B8G8R8A8_SRGB,
+        PIPE_FORMAT_B5G6R5_UNORM,
+        PIPE_FORMAT_B5G5R5A1_UNORM,
+        PIPE_FORMAT_B4G4R4A4_UNORM,
+        #PIPE_FORMAT_Z32_UNORM,
+        #PIPE_FORMAT_S8_USCALED_Z24_UNORM,
+        #PIPE_FORMAT_X8Z24_UNORM,
+        #PIPE_FORMAT_Z16_UNORM,
+        #PIPE_FORMAT_S8_USCALED,
+        PIPE_FORMAT_A8_UNORM,
+        PIPE_FORMAT_L8_UNORM,
+        #PIPE_FORMAT_DXT1_RGB,
+        #PIPE_FORMAT_DXT1_RGBA,
+        #PIPE_FORMAT_DXT3_RGBA,
+        #PIPE_FORMAT_DXT5_RGBA,
+    ]
+    
+    sizes = [64, 32, 16, 8, 4, 2, 1]
+    #sizes = [1020, 508, 252, 62, 30, 14, 6, 3]
+    #sizes = [64]
+    #sizes = [63]
+    
+    faces = [
+        PIPE_TEX_FACE_POS_X,
+        PIPE_TEX_FACE_NEG_X,
+        PIPE_TEX_FACE_POS_Y,
+        PIPE_TEX_FACE_NEG_Y, 
+        PIPE_TEX_FACE_POS_Z, 
+        PIPE_TEX_FACE_NEG_Z,
+    ]
+
+    for target in targets:
+        for format in formats:
+            for size in sizes:
+                if target == PIPE_TEXTURE_3D:
+                    depth = size
+                else:
+                    depth = 1
+                for face in faces:
+                    if target != PIPE_TEXTURE_CUBE and face:
+                        continue
+                    levels = lods(size)
+                    for last_level in range(levels):
+                        for level in range(0, last_level + 1):
+                            zslice = 0
+                            while zslice < depth >> level:
+                                test = TextureTest(
+                                    dev = dev,
+                                    target = target,
+                                    format = format, 
+                                    width = size,
+                                    height = size,
+                                    depth = depth,
+                                    last_level = last_level,
+                                    face = face,
+                                    level = level,
+                                    zslice = zslice,
+                                )
+                                suite.add_test(test)
+                                zslice = (zslice + 1)*2 - 1
+    suite.run()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/gallium/tests/python/tests/texture_transfer.py b/src/gallium/tests/python/tests/texture_transfer.py
new file mode 100755
index 0000000..4aa3d6c
--- /dev/null
+++ b/src/gallium/tests/python/tests/texture_transfer.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+##########################################################################
+# 
+# Copyright 2009 VMware, Inc.
+# 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 VMWARE 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 os
+import random
+
+from gallium import *
+from base import *
+
+
+def lods(*dims):
+    size = max(dims)
+    lods = 0
+    while size:
+        lods += 1
+        size >>= 1
+    return lods
+
+
+class TextureTest(TestCase):
+    
+    tags = (
+        'target',
+        'format',
+        'width',
+        'height',
+        'depth',
+        'last_level',
+        'face',
+        'level',
+        'zslice',
+    )
+
+    def test(self):
+        dev = self.dev
+        ctx = self.ctx
+        
+        target = self.target
+        format = self.format
+        width = self.width
+        height = self.height
+        depth = self.depth
+        last_level = self.last_level
+        face = self.face
+        level = self.level
+        zslice = self.zslice
+        
+        bind = PIPE_BIND_SAMPLER_VIEW
+        geom_flags = 0
+        if not dev.is_format_supported(format, target, bind, geom_flags):
+            raise TestSkip
+        
+        #  textures
+        texture = dev.resource_create(
+            target = target,
+            format = format, 
+            width = width, 
+            height = height,
+            depth = depth, 
+            last_level = last_level,
+            bind = bind,
+        )
+        
+        surface = texture.get_surface(face, level, zslice)
+        
+        stride = util_format_get_stride(format, surface.width)
+        size = util_format_get_nblocksy(format, surface.height) * stride
+
+        in_raw = os.urandom(size)
+
+        ctx.surface_write_raw(surface, 0, 0, surface.width, surface.height, in_raw, stride)
+
+        out_raw = ctx.surface_read_raw(surface, 0, 0, surface.width, surface.height)
+
+        if in_raw != out_raw:
+            raise TestFailure
+
+
+def main():
+    dev = Device()
+    ctx = dev.context_create()
+    suite = TestSuite()
+    
+    targets = [
+        PIPE_TEXTURE_2D,
+        PIPE_TEXTURE_CUBE,
+        PIPE_TEXTURE_3D,
+    ]
+    
+    sizes = [64, 32, 16, 8, 4, 2, 1]
+    #sizes = [1020, 508, 252, 62, 30, 14, 6, 3]
+    #sizes = [64]
+    #sizes = [63]
+    
+    faces = [
+        PIPE_TEX_FACE_POS_X,
+        PIPE_TEX_FACE_NEG_X,
+        PIPE_TEX_FACE_POS_Y,
+        PIPE_TEX_FACE_NEG_Y, 
+        PIPE_TEX_FACE_POS_Z, 
+        PIPE_TEX_FACE_NEG_Z,
+    ]
+
+    try:
+        n = int(sys.argv[1])
+    except:
+        n = 10000
+    
+    for i in range(n):
+        format = random.choice(formats.keys())
+        if not util_format_is_depth_or_stencil(format):
+            is_depth_or_stencil = util_format_is_depth_or_stencil(format)
+
+            if is_depth_or_stencil:
+                target = PIPE_TEXTURE_2D
+            else:
+                target = random.choice(targets)
+            
+            size = random.choice(sizes)
+
+            if target == PIPE_TEXTURE_3D:
+                depth = size
+            else:
+                depth = 1
+
+            if target == PIPE_TEXTURE_CUBE:
+                face = random.choice(faces)
+            else:
+                face = PIPE_TEX_FACE_POS_X
+
+            levels = lods(size)
+            last_level = random.randint(0, levels - 1)
+            level = random.randint(0, last_level)
+            zslice = random.randint(0, max(depth >> level, 1) - 1)
+
+            test = TextureTest(
+                dev = dev,
+                ctx = ctx,
+                target = target,
+                format = format, 
+                width = size,
+                height = size,
+                depth = depth,
+                last_level = last_level,
+                face = face,
+                level = level,
+                zslice = zslice,
+            )
+            suite.add_test(test)
+    suite.run()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/src/gallium/tests/python/tests/tree.py b/src/gallium/tests/python/tests/tree.py
new file mode 100755
index 0000000..0c1bcda
--- /dev/null
+++ b/src/gallium/tests/python/tests/tree.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# 
+# See also:
+#  http://www.ailab.si/orange/doc/ofb/c_otherclass.htm
+
+import os.path
+import sys
+
+import orange
+import orngTree
+
+for arg in sys.argv[1:]:
+    name, ext = os.path.splitext(arg)
+
+    data = orange.ExampleTable(arg)
+
+    tree = orngTree.TreeLearner(data, sameMajorityPruning=1, mForPruning=2)
+
+    orngTree.printTxt(tree)
+
+    file(name+'.txt', 'wt').write(orngTree.dumpTree(tree) + '\n')
+
+    orngTree.printDot(tree, fileName=name+'.dot', nodeShape='ellipse', leafShape='box')
diff --git a/src/gallium/tests/raw/SConscript b/src/gallium/tests/raw/SConscript
new file mode 100644
index 0000000..073b979
--- /dev/null
+++ b/src/gallium/tests/raw/SConscript
@@ -0,0 +1,17 @@
+Import('*')
+
+env = env.Clone()
+
+env.Prepend(LIBPATH = [graw.dir])
+env.Prepend(LIBS = [graw.name])
+
+progs = [
+    'clear'
+]
+
+for prog in progs:
+    env.Program(
+        target = prog,
+        source = prog + '.c',
+    )
+
diff --git a/src/gallium/tests/raw/clear.c b/src/gallium/tests/raw/clear.c
new file mode 100644
index 0000000..706e3be
--- /dev/null
+++ b/src/gallium/tests/raw/clear.c
@@ -0,0 +1,95 @@
+/* Display a cleared blue window.  This demo has no dependencies on
+ * any utility code, just the graw interface and gallium.
+ */
+
+#include "state_tracker/graw.h"
+#include "pipe/p_screen.h"
+#include "pipe/p_context.h"
+#include "pipe/p_state.h"
+#include "pipe/p_defines.h"
+#include <unistd.h>             /* for sleep() */
+
+#include "util/u_debug.h"       /* debug_dump_surface_bmp() */
+
+enum pipe_format formats[] = {
+   PIPE_FORMAT_R8G8B8A8_UNORM,
+   PIPE_FORMAT_B8G8R8A8_UNORM,
+   PIPE_FORMAT_NONE
+};
+
+static const int WIDTH = 300;
+static const int HEIGHT = 300;
+
+int main( int argc, char *argv[] )
+{
+   struct pipe_screen *screen;
+   struct pipe_context *pipe;
+   struct pipe_surface *surf;
+   struct pipe_framebuffer_state fb;
+   struct pipe_texture *tex, templat;
+   void *window = NULL;
+   float clear_color[4] = {1,0,1,1};
+   int i;
+
+   screen = graw_init();
+   if (screen == NULL)
+      exit(1);
+
+   for (i = 0; 
+        window == NULL && formats[i] != PIPE_FORMAT_NONE;
+        i++) {
+      
+      window = graw_create_window(0,0,300,300, formats[i]);
+   }
+   
+   if (window == NULL)
+      exit(2);
+   
+   pipe = screen->context_create(screen, NULL);
+   if (pipe == NULL)
+      exit(3);
+
+   templat.target = PIPE_TEXTURE_2D;
+   templat.format = formats[i];
+   templat.width0 = WIDTH;
+   templat.height0 = HEIGHT;
+   templat.depth0 = 1;
+   templat.last_level = 0;
+   templat.nr_samples = 1;
+   templat.bind = (PIPE_BIND_RENDER_TARGET |
+                        PIPE_BIND_DISPLAY_TARGET);
+   
+   tex = screen->resource_create(screen,
+                                &templat);
+   if (tex == NULL)
+      exit(4);
+
+   surf = screen->get_tex_surface(screen, tex, 0, 0, 0,
+                                  PIPE_BIND_RENDER_TARGET |
+                                  PIPE_BIND_DISPLAY_TARGET);
+   if (surf == NULL)
+      exit(5);
+
+   memset(&fb, 0, sizeof fb);
+   fb.nr_cbufs = 1;
+   fb.width = WIDTH;
+   fb.height = HEIGHT;
+   fb.cbufs[0] = surf;
+
+   pipe->set_framebuffer_state(pipe, &fb);
+   pipe->clear(pipe, PIPE_CLEAR_COLOR, clear_color, 0, 0);
+   pipe->flush(pipe, PIPE_FLUSH_RENDER_CACHE, NULL);
+
+   /* At the moment, libgraw includes/makes available all the symbols
+    * from gallium/auxiliary, including these debug helpers.  Will
+    * eventually want to bless some of these paths, and lock the
+    * others down so they aren't accessible from test programs.
+    */
+   if (0)
+      debug_dump_surface_bmp(pipe, "result.bmp", surf);
+
+   screen->flush_frontbuffer(screen, surf, window);
+
+   sleep(100);
+   return 0;
+}
diff --git a/src/gallium/tests/trivial/.gitignore b/src/gallium/tests/trivial/.gitignore
new file mode 100644
index 0000000..af6cded
--- /dev/null
+++ b/src/gallium/tests/trivial/.gitignore
@@ -0,0 +1,3 @@
+tri
+quad-tex
+result.bmp
diff --git a/src/gallium/tests/trivial/Makefile b/src/gallium/tests/trivial/Makefile
new file mode 100644
index 0000000..2b8af1a
--- /dev/null
+++ b/src/gallium/tests/trivial/Makefile
@@ -0,0 +1,44 @@
+# progs/gallium/simple/Makefile
+
+TOP = ../../..
+include $(TOP)/configs/current
+
+INCLUDES = \
+	-I. \
+	-I$(TOP)/src/gallium/include \
+	-I$(TOP)/src/gallium/auxiliary \
+	-I$(TOP)/src/gallium/drivers \
+	-I$(TOP)/src/gallium/winsys \
+	$(PROG_INCLUDES)
+
+LINKS = \
+	$(TOP)/src/gallium/drivers/trace/libtrace.a \
+	$(TOP)/src/gallium/winsys/sw/null/libws_null.a \
+	$(TOP)/src/gallium/drivers/softpipe/libsoftpipe.a \
+	$(GALLIUM_AUXILIARIES) \
+	$(PROG_LINKS)
+
+SOURCES = \
+	tri.c \
+	quad-tex.c
+
+OBJECTS = $(SOURCES:.c=.o)
+
+PROGS = $(OBJECTS:.o=)
+
+##### TARGETS #####
+
+default: $(PROGS)
+
+clean:
+	-rm -f $(PROGS)
+	-rm -f *.o
+	-rm -f result.bmp
+
+##### RULES #####
+
+$(OBJECTS): %.o: %.c
+	$(CC) -c $(INCLUDES) $(CFLAGS) $(DEFINES) $(PROG_DEFINES) $< -o $@
+
+$(PROGS): %: %.o
+	$(CC) $(LDFLAGS) $< $(LINKS) -lm -lpthread -o $@
diff --git a/src/gallium/tests/trivial/quad-tex.c b/src/gallium/tests/trivial/quad-tex.c
new file mode 100644
index 0000000..7ad157c
--- /dev/null
+++ b/src/gallium/tests/trivial/quad-tex.c
@@ -0,0 +1,346 @@
+/**************************************************************************
+ *
+ * Copyright © 2010 Jakob Bornecrantz
+ *
+ * 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, sublicense,
+ * 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 NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS 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.
+ *
+ **************************************************************************/
+
+
+#define USE_TRACE 0
+#define WIDTH 300
+#define HEIGHT 300
+#define NEAR 30
+#define FAR 1000
+#define FLIP 0
+
+/* pipe_*_state structs */
+#include "pipe/p_state.h"
+/* pipe_context */
+#include "pipe/p_context.h"
+/* pipe_screen */
+#include "pipe/p_screen.h"
+/* PIPE_* */
+#include "pipe/p_defines.h"
+/* TGSI_SEMANTIC_{POSITION|GENERIC} */
+#include "pipe/p_shader_tokens.h"
+/* pipe_buffer_* helpers */
+#include "util/u_inlines.h"
+
+/* constant state object helper */
+#include "cso_cache/cso_context.h"
+
+/* u_sampler_view_default_template */
+#include "util/u_sampler.h"
+/* debug_dump_surface_bmp */
+#include "util/u_debug.h"
+/* util_draw_vertex_buffer helper */
+#include "util/u_draw_quad.h"
+/* FREE & CALLOC_STRUCT */
+#include "util/u_memory.h"
+/* util_make_[fragment|vertex]_passthrough_shader */
+#include "util/u_simple_shaders.h"
+
+/* softpipe software driver */
+#include "softpipe/sp_public.h"
+
+/* null software winsys */
+#include "sw/null/null_sw_winsys.h"
+
+/* traceing support see src/gallium/drivers/trace/README for more info. */
+#if USE_TRACE
+#include "trace/tr_screen.h"
+#include "trace/tr_context.h"
+#endif
+
+struct program
+{
+	struct pipe_screen *screen;
+	struct pipe_context *pipe;
+	struct cso_context *cso;
+
+	struct pipe_blend_state blend;
+	struct pipe_depth_stencil_alpha_state depthstencil;
+	struct pipe_rasterizer_state rasterizer;
+	struct pipe_sampler_state sampler;
+	struct pipe_viewport_state viewport;
+	struct pipe_framebuffer_state framebuffer;
+	struct pipe_vertex_element velem[2];
+
+	void *vs;
+	void *fs;
+
+	float clear_color[4];
+
+	struct pipe_buffer *vbuf;
+	struct pipe_texture *target;
+	struct pipe_texture *tex;
+	struct pipe_sampler_view *view;
+};
+
+static void init_prog(struct program *p)
+{
+	/* create the software rasterizer */
+	p->screen = softpipe_create_screen(null_sw_create());
+#if USE_TRACE
+	p->screen = trace_screen_create(p->screen);
+#endif
+	p->pipe = p->screen->context_create(p->screen, NULL);
+	p->cso = cso_create_context(p->pipe);
+
+	/* set clear color */
+	p->clear_color[0] = 0.3;
+	p->clear_color[1] = 0.1;
+	p->clear_color[2] = 0.3;
+	p->clear_color[3] = 1.0;
+
+	/* vertex buffer */
+	{
+		float vertices[4][2][4] = {
+			{
+				{ 0.9f, 0.9f, 0.0f, 1.0f },
+				{ 1.0f, 1.0f, 0.0f, 1.0f }
+			},
+			{
+				{ -0.9f, 0.9f, 0.0f, 1.0f },
+				{  0.0f, 1.0f, 0.0f, 1.0f }
+			},
+			{
+				{ -0.9f, -0.9f, 0.0f, 1.0f },
+				{  0.0f,  0.0f, 1.0f, 1.0f }
+			},
+			{
+				{ 0.9f, -0.9f, 0.0f, 1.0f },
+				{ 1.0f,  0.0f, 1.0f, 1.0f }
+			}
+		};
+
+		p->vbuf = pipe_buffer_create(p->screen, 16, PIPE_BUFFER_USAGE_VERTEX, sizeof(vertices));
+		pipe_buffer_write(p->screen, p->vbuf, 0, sizeof(vertices), vertices);
+	}
+
+	/* render target texture */
+	{
+		struct pipe_texture tmplt;
+		memset(&tmplt, 0, sizeof(tmplt));
+		tmplt.target = PIPE_TEXTURE_2D;
+		tmplt.format = PIPE_FORMAT_B8G8R8A8_UNORM; /* All drivers support this */
+		tmplt.width0 = WIDTH;
+		tmplt.height0 = HEIGHT;
+		tmplt.depth0 = 1;
+		tmplt.last_level = 0;
+		tmplt.bind = PIPE_BIND_RENDER_TARGET;
+
+		p->target = p->screen->resource_create(p->screen, &tmplt);
+	}
+
+	/* sampler texture */
+	{
+		uint32_t *ptr;
+		struct pipe_transfer *t;
+		struct pipe_texture t_tmplt;
+		struct pipe_sampler_view v_tmplt;
+
+		memset(&t_tmplt, 0, sizeof(t_tmplt));
+		t_tmplt.target = PIPE_TEXTURE_2D;
+		t_tmplt.format = PIPE_FORMAT_B8G8R8A8_UNORM; /* All drivers support this */
+		t_tmplt.width0 = 2;
+		t_tmplt.height0 = 2;
+		t_tmplt.depth0 = 1;
+		t_tmplt.last_level = 0;
+		t_tmplt.bind = PIPE_BIND_RENDER_TARGET;
+
+		p->tex = p->screen->resource_create(p->screen, &t_tmplt);
+
+		t = p->pipe->get_tex_transfer(p->pipe, p->tex,
+		                              0, 0, 0, /* face, level, zslice */
+		                              PIPE_TRANSFER_WRITE,
+		                              0, 0, 2, 2); /* x, y, width, height */
+
+		ptr = p->pipe->transfer_map(p->pipe, t);
+		ptr[0] = 0xffff0000;
+		ptr[1] = 0xff0000ff;
+		ptr[2] = 0xff00ff00;
+		ptr[3] = 0xffffff00;
+		p->pipe->transfer_unmap(p->pipe, t);
+
+		p->pipe->tex_transfer_destroy(p->pipe, t);
+
+		u_sampler_view_default_template(&v_tmplt, p->tex, p->tex->format);
+
+		p->view = p->pipe->create_sampler_view(p->pipe, p->tex, &v_tmplt);
+	}
+
+	/* disabled blending/masking */
+	memset(&p->blend, 0, sizeof(p->blend));
+	p->blend.rt[0].colormask = PIPE_MASK_RGBA;
+
+	/* no-op depth/stencil/alpha */
+	memset(&p->depthstencil, 0, sizeof(p->depthstencil));
+
+	/* rasterizer */
+	memset(&p->rasterizer, 0, sizeof(p->rasterizer));
+	p->rasterizer.front_winding = PIPE_WINDING_CW;
+	p->rasterizer.cull_mode = PIPE_WINDING_NONE;
+	p->rasterizer.gl_rasterization_rules = 1;
+
+	/* sampler */
+	memset(&p->sampler, 0, sizeof(p->sampler));
+	p->sampler.wrap_s = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
+	p->sampler.wrap_t = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
+	p->sampler.wrap_r = PIPE_TEX_WRAP_CLAMP_TO_EDGE;
+	p->sampler.min_mip_filter = PIPE_TEX_MIPFILTER_NONE;
+	p->sampler.min_img_filter = PIPE_TEX_MIPFILTER_LINEAR;
+	p->sampler.mag_img_filter = PIPE_TEX_MIPFILTER_LINEAR;
+	p->sampler.normalized_coords = 1;
+
+	/* drawing destination */
+	memset(&p->framebuffer, 0, sizeof(p->framebuffer));
+	p->framebuffer.width = WIDTH;
+	p->framebuffer.height = HEIGHT;
+	p->framebuffer.nr_cbufs = 1;
+	p->framebuffer.cbufs[0] = p->screen->get_tex_surface(p->screen, p->target, 0, 0, 0, PIPE_BUFFER_USAGE_GPU_WRITE);
+
+	/* viewport, depth isn't really needed */
+	{
+		float x = 0;
+		float y = 0;
+		float z = FAR;
+		float half_width = (float)WIDTH / 2.0f;
+		float half_height = (float)HEIGHT / 2.0f;
+		float half_depth = ((float)FAR - (float)NEAR) / 2.0f;
+		float scale, bias;
+
+		if (FLIP) {
+			scale = -1.0f;
+			bias = (float)HEIGHT;
+		} else {
+			scale = 1.0f;
+			bias = 0.0f;
+		}
+
+		p->viewport.scale[0] = half_width;
+		p->viewport.scale[1] = half_height * scale;
+		p->viewport.scale[2] = half_depth;
+		p->viewport.scale[3] = 1.0f;
+
+		p->viewport.translate[0] = half_width + x;
+		p->viewport.translate[1] = (half_height + y) * scale + bias;
+		p->viewport.translate[2] = half_depth + z;
+		p->viewport.translate[3] = 0.0f;
+	}
+
+	/* vertex elements state */
+	memset(p->velem, 0, sizeof(p->velem));
+	p->velem[0].src_offset = 0 * 4 * sizeof(float); /* offset 0, first element */
+	p->velem[0].instance_divisor = 0;
+	p->velem[0].vertex_buffer_index = 0;
+	p->velem[0].src_format = PIPE_FORMAT_R32G32B32A32_FLOAT;
+
+	p->velem[1].src_offset = 1 * 4 * sizeof(float); /* offset 16, second element */
+	p->velem[1].instance_divisor = 0;
+	p->velem[1].vertex_buffer_index = 0;
+	p->velem[1].src_format = PIPE_FORMAT_R32G32B32A32_FLOAT;
+
+	/* vertex shader */
+	{
+		const uint semantic_names[] = { TGSI_SEMANTIC_POSITION,
+		                                TGSI_SEMANTIC_GENERIC };
+		const uint semantic_indexes[] = { 0, 0 };
+		p->vs = util_make_vertex_passthrough_shader(p->pipe, 2, semantic_names, semantic_indexes);
+	}
+
+	/* fragment shader */
+	p->fs = util_make_fragment_tex_shader(p->pipe, TGSI_TEXTURE_2D);
+}
+
+static void close_prog(struct program *p)
+{
+	/* unset bound textures as well */
+	cso_set_fragment_sampler_views(p->cso, 0, NULL);
+
+	/* unset all state */
+	cso_release_all(p->cso);
+
+	p->pipe->delete_vs_state(p->pipe, p->vs);
+	p->pipe->delete_fs_state(p->pipe, p->fs);
+
+	pipe_surface_reference(&p->framebuffer.cbufs[0], NULL);
+	pipe_sampler_view_reference(&p->view, NULL);
+	pipe_texture_reference(&p->target, NULL);
+	pipe_texture_reference(&p->tex, NULL);
+	pipe_buffer_reference(&p->vbuf, NULL);
+
+	cso_destroy_context(p->cso);
+	p->pipe->destroy(p->pipe);
+	p->screen->destroy(p->screen);
+
+	FREE(p);
+}
+
+static void draw(struct program *p)
+{
+	/* set the render target */
+	cso_set_framebuffer(p->cso, &p->framebuffer);
+
+	/* clear the render target */
+	p->pipe->clear(p->pipe, PIPE_CLEAR_COLOR, p->clear_color, 0, 0);
+
+	/* set misc state we care about */
+	cso_set_blend(p->cso, &p->blend);
+	cso_set_depth_stencil_alpha(p->cso, &p->depthstencil);
+	cso_set_rasterizer(p->cso, &p->rasterizer);
+	cso_set_viewport(p->cso, &p->viewport);
+
+	/* sampler */
+	cso_single_sampler(p->cso, 0, &p->sampler);
+	cso_single_sampler_done(p->cso);
+
+	/* texture sampler view */
+	cso_set_fragment_sampler_views(p->cso, 1, &p->view);
+
+	/* shaders */
+	cso_set_fragment_shader_handle(p->cso, p->fs);
+	cso_set_vertex_shader_handle(p->cso, p->vs);
+
+	/* vertex element data */
+	cso_set_vertex_elements(p->cso, 2, p->velem);
+
+	util_draw_vertex_buffer(p->pipe,
+	                        p->vbuf, 0,
+	                        PIPE_PRIM_QUADS,
+	                        4,  /* verts */
+	                        2); /* attribs/vert */
+
+	p->pipe->flush(p->pipe, PIPE_FLUSH_RENDER_CACHE, NULL);
+
+	debug_dump_surface_bmp(p->pipe, "result.bmp", p->framebuffer.cbufs[0]);
+}
+
+int main(int argc, char** argv)
+{
+	struct program *p = CALLOC_STRUCT(program);
+
+	init_prog(p);
+	draw(p);
+	close_prog(p);
+
+	return 0;
+}
diff --git a/src/gallium/tests/trivial/tri.c b/src/gallium/tests/trivial/tri.c
new file mode 100644
index 0000000..6286aef
--- /dev/null
+++ b/src/gallium/tests/trivial/tri.c
@@ -0,0 +1,278 @@
+/**************************************************************************
+ *
+ * Copyright © 2010 Jakob Bornecrantz
+ *
+ * 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, sublicense,
+ * 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 NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS 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.
+ *
+ **************************************************************************/
+
+
+#define USE_TRACE 0
+#define WIDTH 300
+#define HEIGHT 300
+#define NEAR 30
+#define FAR 1000
+#define FLIP 0
+
+/* pipe_*_state structs */
+#include "pipe/p_state.h"
+/* pipe_context */
+#include "pipe/p_context.h"
+/* pipe_screen */
+#include "pipe/p_screen.h"
+/* PIPE_* */
+#include "pipe/p_defines.h"
+/* TGSI_SEMANTIC_{POSITION|GENERIC} */
+#include "pipe/p_shader_tokens.h"
+/* pipe_buffer_* helpers */
+#include "util/u_inlines.h"
+
+/* constant state object helper */
+#include "cso_cache/cso_context.h"
+
+/* debug_dump_surface_bmp */
+#include "util/u_debug.h"
+/* util_draw_vertex_buffer helper */
+#include "util/u_draw_quad.h"
+/* FREE & CALLOC_STRUCT */
+#include "util/u_memory.h"
+/* util_make_[fragment|vertex]_passthrough_shader */
+#include "util/u_simple_shaders.h"
+
+/* softpipe software driver */
+#include "softpipe/sp_public.h"
+
+/* null software winsys */
+#include "sw/null/null_sw_winsys.h"
+
+/* traceing support see src/gallium/drivers/trace/README for more info. */
+#if USE_TRACE
+#include "trace/tr_screen.h"
+#include "trace/tr_context.h"
+#endif
+
+struct program
+{
+	struct pipe_screen *screen;
+	struct pipe_context *pipe;
+	struct cso_context *cso;
+
+	struct pipe_blend_state blend;
+	struct pipe_depth_stencil_alpha_state depthstencil;
+	struct pipe_rasterizer_state rasterizer;
+	struct pipe_viewport_state viewport;
+	struct pipe_framebuffer_state framebuffer;
+	struct pipe_vertex_element velem[2];
+
+	void *vs;
+	void *fs;
+
+	float clear_color[4];
+
+	struct pipe_buffer *vbuf;
+	struct pipe_texture *target;
+};
+
+static void init_prog(struct program *p)
+{
+	/* create the software rasterizer */
+	p->screen = softpipe_create_screen(null_sw_create());
+#if USE_TRACE
+	p->screen = trace_screen_create(p->screen);
+#endif
+	p->pipe = p->screen->context_create(p->screen, NULL);
+	p->cso = cso_create_context(p->pipe);
+
+	/* set clear color */
+	p->clear_color[0] = 0.3;
+	p->clear_color[1] = 0.1;
+	p->clear_color[2] = 0.3;
+	p->clear_color[3] = 1.0;
+
+	/* vertex buffer */
+	{
+		float vertices[4][2][4] = {
+			{
+				{ 0.0f, -0.9f, 0.0f, 1.0f },
+				{ 1.0f, 0.0f, 0.0f, 1.0f }
+			},
+			{
+				{ -0.9f, 0.9f, 0.0f, 1.0f },
+				{ 0.0f, 1.0f, 0.0f, 1.0f }
+			},
+			{
+				{ 0.9f, 0.9f, 0.0f, 1.0f },
+				{ 0.0f, 0.0f, 1.0f, 1.0f }
+			}
+		};
+
+		p->vbuf = pipe_buffer_create(p->screen, 16, PIPE_BUFFER_USAGE_VERTEX, sizeof(vertices));
+		pipe_buffer_write(p->screen, p->vbuf, 0, sizeof(vertices), vertices);
+	}
+
+	/* render target texture */
+	{
+		struct pipe_texture tmplt;
+		memset(&tmplt, 0, sizeof(tmplt));
+		tmplt.target = PIPE_TEXTURE_2D;
+		tmplt.format = PIPE_FORMAT_B8G8R8A8_UNORM; /* All drivers support this */
+		tmplt.width0 = WIDTH;
+		tmplt.height0 = HEIGHT;
+		tmplt.depth0 = 1;
+		tmplt.last_level = 0;
+		tmplt.bind = PIPE_BIND_RENDER_TARGET;
+
+		p->target = p->screen->resource_create(p->screen, &tmplt);
+	}
+
+	/* disabled blending/masking */
+	memset(&p->blend, 0, sizeof(p->blend));
+	p->blend.rt[0].colormask = PIPE_MASK_RGBA;
+
+	/* no-op depth/stencil/alpha */
+	memset(&p->depthstencil, 0, sizeof(p->depthstencil));
+
+	/* rasterizer */
+	memset(&p->rasterizer, 0, sizeof(p->rasterizer));
+	p->rasterizer.front_winding = PIPE_WINDING_CW;
+	p->rasterizer.cull_mode = PIPE_WINDING_NONE;
+	p->rasterizer.gl_rasterization_rules = 1;
+
+	/* drawing destination */
+	memset(&p->framebuffer, 0, sizeof(p->framebuffer));
+	p->framebuffer.width = WIDTH;
+	p->framebuffer.height = HEIGHT;
+	p->framebuffer.nr_cbufs = 1;
+	p->framebuffer.cbufs[0] = p->screen->get_tex_surface(p->screen, p->target, 0, 0, 0, PIPE_BUFFER_USAGE_GPU_WRITE);
+
+	/* viewport, depth isn't really needed */
+	{
+		float x = 0;
+		float y = 0;
+		float z = FAR;
+		float half_width = (float)WIDTH / 2.0f;
+		float half_height = (float)HEIGHT / 2.0f;
+		float half_depth = ((float)FAR - (float)NEAR) / 2.0f;
+		float scale, bias;
+
+		if (FLIP) {
+			scale = -1.0f;
+			bias = (float)HEIGHT;
+		} else {
+			scale = 1.0f;
+			bias = 0.0f;
+		}
+
+		p->viewport.scale[0] = half_width;
+		p->viewport.scale[1] = half_height * scale;
+		p->viewport.scale[2] = half_depth;
+		p->viewport.scale[3] = 1.0f;
+
+		p->viewport.translate[0] = half_width + x;
+		p->viewport.translate[1] = (half_height + y) * scale + bias;
+		p->viewport.translate[2] = half_depth + z;
+		p->viewport.translate[3] = 0.0f;
+	}
+
+	/* vertex elements state */
+	memset(p->velem, 0, sizeof(p->velem));
+	p->velem[0].src_offset = 0 * 4 * sizeof(float); /* offset 0, first element */
+	p->velem[0].instance_divisor = 0;
+	p->velem[0].vertex_buffer_index = 0;
+	p->velem[0].src_format = PIPE_FORMAT_R32G32B32A32_FLOAT;
+
+	p->velem[1].src_offset = 1 * 4 * sizeof(float); /* offset 16, second element */
+	p->velem[1].instance_divisor = 0;
+	p->velem[1].vertex_buffer_index = 0;
+	p->velem[1].src_format = PIPE_FORMAT_R32G32B32A32_FLOAT;
+
+	/* vertex shader */
+	{
+			const uint semantic_names[] = { TGSI_SEMANTIC_POSITION,
+							TGSI_SEMANTIC_COLOR };
+			const uint semantic_indexes[] = { 0, 0 };
+			p->vs = util_make_vertex_passthrough_shader(p->pipe, 2, semantic_names, semantic_indexes);
+	}
+
+	/* fragment shader */
+	p->fs = util_make_fragment_passthrough_shader(p->pipe);
+}
+
+static void close_prog(struct program *p)
+{
+	/* unset all state */
+	cso_release_all(p->cso);
+
+	p->pipe->delete_vs_state(p->pipe, p->vs);
+	p->pipe->delete_fs_state(p->pipe, p->fs);
+
+	pipe_surface_reference(&p->framebuffer.cbufs[0], NULL);
+	pipe_texture_reference(&p->target, NULL);
+	pipe_buffer_reference(&p->vbuf, NULL);
+
+	cso_destroy_context(p->cso);
+	p->pipe->destroy(p->pipe);
+	p->screen->destroy(p->screen);
+
+	FREE(p);
+}
+
+static void draw(struct program *p)
+{
+	/* set the render target */
+	cso_set_framebuffer(p->cso, &p->framebuffer);
+
+	/* clear the render target */
+	p->pipe->clear(p->pipe, PIPE_CLEAR_COLOR, p->clear_color, 0, 0);
+
+	/* set misc state we care about */
+	cso_set_blend(p->cso, &p->blend);
+	cso_set_depth_stencil_alpha(p->cso, &p->depthstencil);
+	cso_set_rasterizer(p->cso, &p->rasterizer);
+	cso_set_viewport(p->cso, &p->viewport);
+
+	/* shaders */
+	cso_set_fragment_shader_handle(p->cso, p->fs);
+	cso_set_vertex_shader_handle(p->cso, p->vs);
+
+	/* vertex element data */
+	cso_set_vertex_elements(p->cso, 2, p->velem);
+
+	util_draw_vertex_buffer(p->pipe,
+	                        p->vbuf, 0,
+	                        PIPE_PRIM_TRIANGLES,
+	                        3,  /* verts */
+	                        2); /* attribs/vert */
+
+	p->pipe->flush(p->pipe, PIPE_FLUSH_RENDER_CACHE, NULL);
+
+	debug_dump_surface_bmp(p->pipe, "result.bmp", p->framebuffer.cbufs[0]);
+}
+
+int main(int argc, char** argv)
+{
+	struct program *p = CALLOC_STRUCT(program);
+
+	init_prog(p);
+	draw(p);
+	close_prog(p);
+
+	return 0;
+}
diff --git a/src/gallium/tests/unit/Makefile b/src/gallium/tests/unit/Makefile
new file mode 100644
index 0000000..f3dbd76
--- /dev/null
+++ b/src/gallium/tests/unit/Makefile
@@ -0,0 +1,44 @@
+# progs/gallium/simple/Makefile
+
+TOP = ../../..
+include $(TOP)/configs/current
+
+INCLUDES = \
+	-I. \
+	-I$(TOP)/src/gallium/include \
+	-I$(TOP)/src/gallium/auxiliary \
+	-I$(TOP)/src/gallium/drivers \
+	-I$(TOP)/src/gallium/winsys \
+	$(PROG_INCLUDES)
+
+LINKS = \
+	$(TOP)/src/gallium/drivers/trace/libtrace.a \
+	$(TOP)/src/gallium/winsys/sw/null/libws_null.a \
+	$(TOP)/src/gallium/drivers/softpipe/libsoftpipe.a \
+	$(GALLIUM_AUXILIARIES) \
+	$(PROG_LINKS)
+
+SOURCES = \
+	u_format_test.c \
+	u_half_test.c
+
+OBJECTS = $(SOURCES:.c=.o)
+
+PROGS = $(OBJECTS:.o=)
+
+##### TARGETS #####
+
+default: $(PROGS)
+
+clean:
+	-rm -f $(PROGS)
+	-rm -f *.o
+	-rm -f result.bmp
+
+##### RULES #####
+
+$(OBJECTS): %.o: %.c
+	$(CC) -c $(INCLUDES) $(CFLAGS) $(DEFINES) $(PROG_DEFINES) $< -o $@
+
+$(PROGS): %: %.o
+	$(CC) $(LDFLAGS) $< $(LINKS) -lm -lpthread -ldl -o $@
diff --git a/src/gallium/tests/unit/SConscript b/src/gallium/tests/unit/SConscript
new file mode 100644
index 0000000..0db3bb6
--- /dev/null
+++ b/src/gallium/tests/unit/SConscript
@@ -0,0 +1,23 @@
+Import('*')
+
+env = env.Clone()
+
+env.Prepend(LIBS = [gallium])
+
+progs = [
+    'u_format_test',
+    'u_half_test'
+]
+
+for prog in progs:
+    prog = env.Program(
+        target = prog,
+        source = prog + '.c',
+    )
+    
+    env.InstallProgram(prog)
+
+    # http://www.scons.org/wiki/UnitTests
+    test_alias = env.Alias('unit', [prog], prog[0].abspath)
+    AlwaysBuild(test_alias)
+
diff --git a/src/gallium/tests/unit/u_format_test.c b/src/gallium/tests/unit/u_format_test.c
new file mode 100644
index 0000000..cfde6af
--- /dev/null
+++ b/src/gallium/tests/unit/u_format_test.c
@@ -0,0 +1,708 @@
+/**************************************************************************
+ *
+ * Copyright 2009-2010 VMware, Inc.
+ * 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 VMWARE 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.
+ *
+ **************************************************************************/
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <float.h>
+
+#include "util/u_half.h"
+#include "util/u_format.h"
+#include "util/u_format_tests.h"
+#include "util/u_format_s3tc.h"
+
+
+static boolean
+compare_float(float x, float y)
+{
+   float error = y - x;
+
+   if (error < 0.0f)
+      error = -error;
+
+   if (error > FLT_EPSILON) {
+      return FALSE;
+   }
+
+   return TRUE;
+}
+
+
+static void
+print_packed(const struct util_format_description *format_desc,
+             const char *prefix,
+             const uint8_t *packed,
+             const char *suffix)
+{
+   unsigned i;
+   const char *sep = "";
+
+   printf("%s", prefix);
+   for (i = 0; i < format_desc->block.bits/8; ++i) {
+      printf("%s%02x", sep, packed[i]);
+      sep = " ";
+   }
+   printf("%s", suffix);
+}
+
+
+static void
+print_unpacked_rgba_doubl(const struct util_format_description *format_desc,
+                     const char *prefix,
+                     const double unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH][4],
+                     const char *suffix)
+{
+   unsigned i, j;
+   const char *sep = "";
+
+   printf("%s", prefix);
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         printf("%s{%f, %f, %f, %f}", sep, unpacked[i][j][0], unpacked[i][j][1], unpacked[i][j][2], unpacked[i][j][3]);
+         sep = ", ";
+      }
+      sep = ",\n";
+   }
+   printf("%s", suffix);
+}
+
+
+static void
+print_unpacked_rgba_float(const struct util_format_description *format_desc,
+                     const char *prefix,
+                     float unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH][4],
+                     const char *suffix)
+{
+   unsigned i, j;
+   const char *sep = "";
+
+   printf("%s", prefix);
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         printf("%s{%f, %f, %f, %f}", sep, unpacked[i][j][0], unpacked[i][j][1], unpacked[i][j][2], unpacked[i][j][3]);
+         sep = ", ";
+      }
+      sep = ",\n";
+   }
+   printf("%s", suffix);
+}
+
+
+static void
+print_unpacked_rgba_8unorm(const struct util_format_description *format_desc,
+                      const char *prefix,
+                      uint8_t unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH][4],
+                      const char *suffix)
+{
+   unsigned i, j;
+   const char *sep = "";
+
+   printf("%s", prefix);
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         printf("%s{0x%02x, 0x%02x, 0x%02x, 0x%02x}", sep, unpacked[i][j][0], unpacked[i][j][1], unpacked[i][j][2], unpacked[i][j][3]);
+         sep = ", ";
+      }
+   }
+   printf("%s", suffix);
+}
+
+
+static void
+print_unpacked_z_float(const struct util_format_description *format_desc,
+                       const char *prefix,
+                       float unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH],
+                       const char *suffix)
+{
+   unsigned i, j;
+   const char *sep = "";
+
+   printf("%s", prefix);
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         printf("%s%f", sep, unpacked[i][j]);
+         sep = ", ";
+      }
+      sep = ",\n";
+   }
+   printf("%s", suffix);
+}
+
+
+static void
+print_unpacked_z_32unorm(const struct util_format_description *format_desc,
+                         const char *prefix,
+                         uint32_t unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH],
+                         const char *suffix)
+{
+   unsigned i, j;
+   const char *sep = "";
+
+   printf("%s", prefix);
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         printf("%s0x%08x", sep, unpacked[i][j]);
+         sep = ", ";
+      }
+   }
+   printf("%s", suffix);
+}
+
+
+static void
+print_unpacked_s_8uscaled(const struct util_format_description *format_desc,
+                          const char *prefix,
+                          uint8_t unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH],
+                          const char *suffix)
+{
+   unsigned i, j;
+   const char *sep = "";
+
+   printf("%s", prefix);
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         printf("%s0x%02x", sep, unpacked[i][j]);
+         sep = ", ";
+      }
+   }
+   printf("%s", suffix);
+}
+
+
+static boolean
+test_format_fetch_rgba_float(const struct util_format_description *format_desc,
+                             const struct util_format_test_case *test)
+{
+   float unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH][4] = { { { 0 } } };
+   unsigned i, j, k;
+   boolean success;
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         format_desc->fetch_rgba_float(unpacked[i][j], test->packed, j, i);
+         for (k = 0; k < 4; ++k) {
+            if (!compare_float(test->unpacked[i][j][k], unpacked[i][j][k])) {
+               success = FALSE;
+            }
+         }
+      }
+   }
+
+   if (!success) {
+      print_unpacked_rgba_float(format_desc, "FAILED: ", unpacked, " obtained\n");
+      print_unpacked_rgba_doubl(format_desc, "        ", test->unpacked, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+test_format_unpack_rgba_float(const struct util_format_description *format_desc,
+                              const struct util_format_test_case *test)
+{
+   float unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH][4] = { { { 0 } } };
+   unsigned i, j, k;
+   boolean success;
+
+   format_desc->unpack_rgba_float(&unpacked[0][0][0], sizeof unpacked[0],
+                             test->packed, 0,
+                             format_desc->block.width, format_desc->block.height);
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         for (k = 0; k < 4; ++k) {
+            if (!compare_float(test->unpacked[i][j][k], unpacked[i][j][k])) {
+               success = FALSE;
+            }
+         }
+      }
+   }
+
+   if (!success) {
+      print_unpacked_rgba_float(format_desc, "FAILED: ", unpacked, " obtained\n");
+      print_unpacked_rgba_doubl(format_desc, "        ", test->unpacked, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+test_format_pack_rgba_float(const struct util_format_description *format_desc,
+                            const struct util_format_test_case *test)
+{
+   float unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH][4];
+   uint8_t packed[UTIL_FORMAT_MAX_PACKED_BYTES];
+   unsigned i, j, k;
+   boolean success;
+
+   if (test->format == PIPE_FORMAT_DXT1_RGBA) {
+      /*
+       * Skip S3TC as packed representation is not canonical.
+       *
+       * TODO: Do a round trip conversion.
+       */
+      return TRUE;
+   }
+
+   memset(packed, 0, sizeof packed);
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         for (k = 0; k < 4; ++k) {
+            unpacked[i][j][k] = (float) test->unpacked[i][j][k];
+         }
+      }
+   }
+
+   format_desc->pack_rgba_float(packed, 0,
+                           &unpacked[0][0][0], sizeof unpacked[0],
+                           format_desc->block.width, format_desc->block.height);
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.bits/8; ++i)
+      if ((test->packed[i] & test->mask[i]) != (packed[i] & test->mask[i]))
+         success = FALSE;
+
+   if (!success) {
+      print_packed(format_desc, "FAILED: ", packed, " obtained\n");
+      print_packed(format_desc, "        ", test->packed, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+convert_float_to_8unorm(uint8_t *dst, const double *src)
+{
+   unsigned i;
+   boolean accurate = TRUE;
+
+   for (i = 0; i < UTIL_FORMAT_MAX_UNPACKED_HEIGHT*UTIL_FORMAT_MAX_UNPACKED_WIDTH*4; ++i) {
+      if (src[i] < 0.0) {
+         accurate = FALSE;
+         dst[i] = 0;
+      }
+      else if (src[i] > 1.0) {
+         accurate = FALSE;
+         dst[i] = 255;
+      }
+      else {
+         dst[i] = src[i] * 255.0;
+      }
+   }
+
+   return accurate;
+}
+
+
+static boolean
+test_format_unpack_rgba_8unorm(const struct util_format_description *format_desc,
+                               const struct util_format_test_case *test)
+{
+   uint8_t unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH][4] = { { { 0 } } };
+   uint8_t expected[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH][4] = { { { 0 } } };
+   unsigned i, j, k;
+   boolean success;
+
+   format_desc->unpack_rgba_8unorm(&unpacked[0][0][0], sizeof unpacked[0],
+                              test->packed, 0,
+                              format_desc->block.width, format_desc->block.height);
+
+   convert_float_to_8unorm(&expected[0][0][0], &test->unpacked[0][0][0]);
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         for (k = 0; k < 4; ++k) {
+            if (expected[i][j][k] != unpacked[i][j][k]) {
+               success = FALSE;
+            }
+         }
+      }
+   }
+
+   if (!success) {
+      print_unpacked_rgba_8unorm(format_desc, "FAILED: ", unpacked, " obtained\n");
+      print_unpacked_rgba_8unorm(format_desc, "        ", expected, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+test_format_pack_rgba_8unorm(const struct util_format_description *format_desc,
+                             const struct util_format_test_case *test)
+{
+   uint8_t unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH][4];
+   uint8_t packed[UTIL_FORMAT_MAX_PACKED_BYTES];
+   unsigned i;
+   boolean success;
+
+   if (test->format == PIPE_FORMAT_DXT1_RGBA) {
+      /*
+       * Skip S3TC as packed representation is not canonical.
+       *
+       * TODO: Do a round trip conversion.
+       */
+      return TRUE;
+   }
+
+   if (!convert_float_to_8unorm(&unpacked[0][0][0], &test->unpacked[0][0][0])) {
+      /*
+       * Skip test cases which cannot be represented by four unorm bytes.
+       */
+      return TRUE;
+   }
+
+   memset(packed, 0, sizeof packed);
+
+   format_desc->pack_rgba_8unorm(packed, 0,
+                            &unpacked[0][0][0], sizeof unpacked[0],
+                            format_desc->block.width, format_desc->block.height);
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.bits/8; ++i)
+      if ((test->packed[i] & test->mask[i]) != (packed[i] & test->mask[i]))
+         success = FALSE;
+
+   if (!success) {
+      print_packed(format_desc, "FAILED: ", packed, " obtained\n");
+      print_packed(format_desc, "        ", test->packed, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+test_format_unpack_z_float(const struct util_format_description *format_desc,
+                              const struct util_format_test_case *test)
+{
+   float unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH] = { { 0 } };
+   unsigned i, j;
+   boolean success;
+
+   format_desc->unpack_z_float(&unpacked[0][0], sizeof unpacked[0],
+                               test->packed, 0,
+                               format_desc->block.width, format_desc->block.height);
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         if (!compare_float(test->unpacked[i][j][0], unpacked[i][j])) {
+            success = FALSE;
+         }
+      }
+   }
+
+   if (!success) {
+      print_unpacked_z_float(format_desc, "FAILED: ", unpacked, " obtained\n");
+      print_unpacked_rgba_doubl(format_desc, "        ", test->unpacked, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+test_format_pack_z_float(const struct util_format_description *format_desc,
+                            const struct util_format_test_case *test)
+{
+   float unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH];
+   uint8_t packed[UTIL_FORMAT_MAX_PACKED_BYTES];
+   unsigned i, j;
+   boolean success;
+
+   memset(packed, 0, sizeof packed);
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         unpacked[i][j] = (float) test->unpacked[i][j][0];
+         if (test->unpacked[i][j][1]) {
+            return TRUE;
+         }
+      }
+   }
+
+   format_desc->pack_z_float(packed, 0,
+                             &unpacked[0][0], sizeof unpacked[0],
+                             format_desc->block.width, format_desc->block.height);
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.bits/8; ++i)
+      if ((test->packed[i] & test->mask[i]) != (packed[i] & test->mask[i]))
+         success = FALSE;
+
+   if (!success) {
+      print_packed(format_desc, "FAILED: ", packed, " obtained\n");
+      print_packed(format_desc, "        ", test->packed, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+test_format_unpack_z_32unorm(const struct util_format_description *format_desc,
+                               const struct util_format_test_case *test)
+{
+   uint32_t unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH] = { { 0 } };
+   uint32_t expected[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH] = { { 0 } };
+   unsigned i, j;
+   boolean success;
+
+   format_desc->unpack_z_32unorm(&unpacked[0][0], sizeof unpacked[0],
+                                 test->packed, 0,
+                                 format_desc->block.width, format_desc->block.height);
+
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         expected[i][j] = test->unpacked[i][j][0] * 0xffffffff;
+      }
+   }
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         if (expected[i][j] != unpacked[i][j]) {
+            success = FALSE;
+         }
+      }
+   }
+
+   if (!success) {
+      print_unpacked_z_32unorm(format_desc, "FAILED: ", unpacked, " obtained\n");
+      print_unpacked_z_32unorm(format_desc, "        ", expected, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+test_format_pack_z_32unorm(const struct util_format_description *format_desc,
+                             const struct util_format_test_case *test)
+{
+   uint32_t unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH];
+   uint8_t packed[UTIL_FORMAT_MAX_PACKED_BYTES];
+   unsigned i, j;
+   boolean success;
+
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         unpacked[i][j] = test->unpacked[i][j][0] * 0xffffffff;
+         if (test->unpacked[i][j][1]) {
+            return TRUE;
+         }
+      }
+   }
+
+   memset(packed, 0, sizeof packed);
+
+   format_desc->pack_z_32unorm(packed, 0,
+                               &unpacked[0][0], sizeof unpacked[0],
+                               format_desc->block.width, format_desc->block.height);
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.bits/8; ++i)
+      if ((test->packed[i] & test->mask[i]) != (packed[i] & test->mask[i]))
+         success = FALSE;
+
+   if (!success) {
+      print_packed(format_desc, "FAILED: ", packed, " obtained\n");
+      print_packed(format_desc, "        ", test->packed, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+test_format_unpack_s_8uscaled(const struct util_format_description *format_desc,
+                               const struct util_format_test_case *test)
+{
+   uint8_t unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH] = { { 0 } };
+   uint8_t expected[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH] = { { 0 } };
+   unsigned i, j;
+   boolean success;
+
+   format_desc->unpack_s_8uscaled(&unpacked[0][0], sizeof unpacked[0],
+                                  test->packed, 0,
+                                  format_desc->block.width, format_desc->block.height);
+
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         expected[i][j] = test->unpacked[i][j][1];
+      }
+   }
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         if (expected[i][j] != unpacked[i][j]) {
+            success = FALSE;
+         }
+      }
+   }
+
+   if (!success) {
+      print_unpacked_s_8uscaled(format_desc, "FAILED: ", unpacked, " obtained\n");
+      print_unpacked_s_8uscaled(format_desc, "        ", expected, " expected\n");
+   }
+
+   return success;
+}
+
+
+static boolean
+test_format_pack_s_8uscaled(const struct util_format_description *format_desc,
+                             const struct util_format_test_case *test)
+{
+   uint8_t unpacked[UTIL_FORMAT_MAX_UNPACKED_HEIGHT][UTIL_FORMAT_MAX_UNPACKED_WIDTH];
+   uint8_t packed[UTIL_FORMAT_MAX_PACKED_BYTES];
+   unsigned i, j;
+   boolean success;
+
+   for (i = 0; i < format_desc->block.height; ++i) {
+      for (j = 0; j < format_desc->block.width; ++j) {
+         unpacked[i][j] = test->unpacked[i][j][1];
+         if (test->unpacked[i][j][0]) {
+            return TRUE;
+         }
+      }
+   }
+
+   memset(packed, 0, sizeof packed);
+
+   format_desc->pack_s_8uscaled(packed, 0,
+                                &unpacked[0][0], sizeof unpacked[0],
+                                format_desc->block.width, format_desc->block.height);
+
+   success = TRUE;
+   for (i = 0; i < format_desc->block.bits/8; ++i)
+      if ((test->packed[i] & test->mask[i]) != (packed[i] & test->mask[i]))
+         success = FALSE;
+
+   if (!success) {
+      print_packed(format_desc, "FAILED: ", packed, " obtained\n");
+      print_packed(format_desc, "        ", test->packed, " expected\n");
+   }
+
+   return success;
+}
+
+
+typedef boolean
+(*test_func_t)(const struct util_format_description *format_desc,
+               const struct util_format_test_case *test);
+
+
+static boolean
+test_one_func(const struct util_format_description *format_desc,
+              test_func_t func,
+              const char *suffix)
+{
+   unsigned i;
+   bool success = TRUE;
+
+   printf("Testing util_format_%s_%s ...\n",
+          format_desc->short_name, suffix);
+
+   for (i = 0; i < util_format_nr_test_cases; ++i) {
+      const struct util_format_test_case *test = &util_format_test_cases[i];
+
+      if (test->format == format_desc->format) {
+         if (!func(format_desc, &util_format_test_cases[i])) {
+           success = FALSE;
+         }
+      }
+   }
+
+   return success;
+}
+
+
+static boolean
+test_all(void)
+{
+   enum pipe_format format;
+   bool success = TRUE;
+
+   for (format = 1; format < PIPE_FORMAT_COUNT; ++format) {
+      const struct util_format_description *format_desc;
+
+      format_desc = util_format_description(format);
+      if (!format_desc) {
+         continue;
+      }
+
+      if (format_desc->layout == UTIL_FORMAT_LAYOUT_S3TC &&
+          !util_format_s3tc_enabled) {
+         continue;
+      }
+
+#     define TEST_ONE_FUNC(name) \
+      if (format_desc->name) { \
+         if (!test_one_func(format_desc, &test_format_##name, #name)) { \
+           success = FALSE; \
+         } \
+      }
+
+      TEST_ONE_FUNC(fetch_rgba_float);
+      TEST_ONE_FUNC(pack_rgba_float);
+      TEST_ONE_FUNC(unpack_rgba_float);
+      TEST_ONE_FUNC(pack_rgba_8unorm);
+      TEST_ONE_FUNC(unpack_rgba_8unorm);
+
+      TEST_ONE_FUNC(unpack_z_32unorm);
+      TEST_ONE_FUNC(pack_z_32unorm);
+      TEST_ONE_FUNC(unpack_z_float);
+      TEST_ONE_FUNC(pack_z_float);
+      TEST_ONE_FUNC(unpack_s_8uscaled);
+      TEST_ONE_FUNC(pack_s_8uscaled);
+
+#     undef TEST_ONE_FUNC
+   }
+
+   return success;
+}
+
+
+int main(int argc, char **argv)
+{
+   boolean success;
+
+   util_format_s3tc_init();
+
+   success = test_all();
+
+   return success ? 0 : 1;
+}
diff --git a/src/gallium/tests/unit/u_half_test.c b/src/gallium/tests/unit/u_half_test.c
new file mode 100644
index 0000000..00bda7f
--- /dev/null
+++ b/src/gallium/tests/unit/u_half_test.c
@@ -0,0 +1,32 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <float.h>
+
+#include "util/u_math.h"
+#include "util/u_half.h"
+
+int
+main(int argc, char **argv)
+{
+   unsigned i;
+   unsigned roundtrip_fails = 0;
+   for(i = 0; i < 1 << 16; ++i)
+   {
+      uint16_t h = (uint16_t) i;
+      union fi f;
+      uint16_t rh;
+      f.ui = util_half_to_floatui(h);
+      rh = util_floatui_to_half(f.ui);
+      if(h != rh)
+      {
+	 printf("Roundtrip failed: %x -> %x = %f -> %x\n", h, f.ui, f.f, rh);
+	 ++roundtrip_fails;
+      }
+   }
+
+   if(roundtrip_fails)
+      printf("Failure! %u/65536 half floats failed a conversion to float and back.\n", roundtrip_fails);
+   else
+      printf("Success!\n");
+   return 0;
+}