summaryrefslogtreecommitdiffstats
path: root/src/mesa/main/format_parser.py
diff options
context:
space:
mode:
authorJason Ekstrand <[email protected]>2014-08-01 18:09:46 -0700
committerJason Ekstrand <[email protected]>2014-08-05 10:56:15 -0700
commitd4c780e052c9cc361bed5958b72b42d8151800c2 (patch)
treeedbf1c2b0059bbb9f15b2ff8da6fc1df044e643b /src/mesa/main/format_parser.py
parent056cc47e12db58b3946abb0eec5e5a194449dd2a (diff)
mesa: Add python to parse the formats CSV file
The basic concept for the format parser was taken from the format CSV parser in gallium/auxilliary/util. However, this one has been altered in a number of ways: * Removed big endian vs. little endian stuff (mesa doesn't need it) * Better documentation: Almost every method has a full docstring * An actual Swizzle class with methods for composition and inverses * Over-all cleaner (in my opinion) implementation and class interactions * A few bug fixes Signed-off-by: Jason Ekstrand <[email protected]> Reviewed-by: Brian Paul <[email protected]>
Diffstat (limited to 'src/mesa/main/format_parser.py')
-rwxr-xr-xsrc/mesa/main/format_parser.py521
1 files changed, 521 insertions, 0 deletions
diff --git a/src/mesa/main/format_parser.py b/src/mesa/main/format_parser.py
new file mode 100755
index 00000000000..5e45c74de3e
--- /dev/null
+++ b/src/mesa/main/format_parser.py
@@ -0,0 +1,521 @@
+#!/usr/bin/env python
+#
+# Copyright 2009 VMware, Inc.
+# Copyright 2014 Intel Corporation
+# 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.
+
+VOID = 'x'
+UNSIGNED = 'u'
+SIGNED = 's'
+FLOAT = 'f'
+
+ARRAY = 'array'
+PACKED = 'packed'
+OTHER = 'other'
+
+RGB = 'rgb'
+SRGB = 'srgb'
+YUV = 'yuv'
+ZS = 'zs'
+
+def is_power_of_two(x):
+ return not bool(x & (x - 1))
+
+VERY_LARGE = 99999999999999999999999
+
+class Channel:
+ """Describes a color channel."""
+
+ def __init__(self, type, norm, size):
+ self.type = type
+ self.norm = norm
+ self.size = size
+ self.sign = type in (SIGNED, FLOAT)
+ self.name = None # Set when the channels are added to the format
+ self.shift = -1 # Set when the channels are added to the format
+ self.index = -1 # Set when the channels are added to the format
+
+ def __str__(self):
+ s = str(self.type)
+ if self.norm:
+ s += 'n'
+ s += str(self.size)
+ return s
+
+ def __eq__(self, other):
+ return self.type == other.type and self.norm == other.norm and self.size == other.size
+
+ def max(self):
+ """Returns the maximum representable number."""
+ if self.type == FLOAT:
+ return VERY_LARGE
+ if self.norm:
+ return 1
+ if self.type == UNSIGNED:
+ return (1 << self.size) - 1
+ if self.type == SIGNED:
+ return (1 << (self.size - 1)) - 1
+ assert False
+
+ def min(self):
+ """Returns the minimum representable number."""
+ if self.type == FLOAT:
+ return -VERY_LARGE
+ if self.type == UNSIGNED:
+ return 0
+ if self.norm:
+ return -1
+ if self.type == SIGNED:
+ return -(1 << (self.size - 1))
+ assert False
+
+ def one(self):
+ """Returns the value that represents 1.0f."""
+ if self.type == UNSIGNED:
+ return (1 << self.size) - 1
+ if self.type == SIGNED:
+ return (1 << (self.size - 1)) - 1
+ else:
+ return 1
+
+ def is_power_of_two(self):
+ """Returns true if the size of this channel is a power of two."""
+ return is_power_of_two(self.size)
+
+class Swizzle:
+ """Describes a swizzle operation.
+
+ A Swizzle is a mapping from one set of channels in one format to the
+ channels in another. Each channel in the destination format is
+ associated with one of the following constants:
+
+ * SWIZZLE_X: The first channel in the source format
+ * SWIZZLE_Y: The second channel in the source format
+ * SWIZZLE_Z: The third channel in the source format
+ * SWIZZLE_W: The fourth channel in the source format
+ * SWIZZLE_ZERO: The numeric constant 0
+ * SWIZZLE_ONE: THe numeric constant 1
+ * SWIZZLE_NONE: No data available for this channel
+
+ Sometimes a Swizzle is represented by a 4-character string. In this
+ case, the source channels are represented by the characters "x", "y",
+ "z", and "w"; the numeric constants are represented as "0" and "1"; and
+ no mapping is represented by "_". For instance, the map from
+ luminance-alpha to rgba is given by "xxxy" because each of the three rgb
+ channels maps to the first luminance-alpha channel and the alpha channel
+ maps to second luminance-alpha channel. The mapping from bgr to rgba is
+ given by "zyx1" because the first three colors are reversed and alpha is
+ always 1.
+ """
+
+ __identity_str = 'xyzw01_'
+
+ SWIZZLE_X = 0
+ SWIZZLE_Y = 1
+ SWIZZLE_Z = 2
+ SWIZZLE_W = 3
+ SWIZZLE_ZERO = 4
+ SWIZZLE_ONE = 5
+ SWIZZLE_NONE = 6
+
+ def __init__(self, swizzle):
+ """Creates a Swizzle object from a string or array."""
+ if isinstance(swizzle, str):
+ swizzle = [Swizzle.__identity_str.index(c) for c in swizzle]
+ else:
+ swizzle = list(swizzle)
+ for s in swizzle:
+ assert isinstance(s, int) and 0 <= s and s <= Swizzle.SWIZZLE_NONE
+
+ assert len(swizzle) <= 4
+
+ self.__list = swizzle + [Swizzle.SWIZZLE_NONE] * (4 - len(swizzle))
+ assert len(self.__list) == 4
+
+ def __iter__(self):
+ """Returns an iterator that iterates over this Swizzle.
+
+ The values that the iterator produces are described by the SWIZZLE_*
+ constants.
+ """
+ return self.__list.__iter__()
+
+ def __str__(self):
+ """Returns a string representation of this Swizzle."""
+ return ''.join(Swizzle.__identity_str[i] for i in self.__list)
+
+ def __getitem__(self, idx):
+ """Returns the SWIZZLE_* constant for the given destination channel.
+
+ Valid values for the destination channel include any of the SWIZZLE_*
+ constants or any of the following single-character strings: "x", "y",
+ "z", "w", "r", "g", "b", "a", "z" "s".
+ """
+
+ if isinstance(idx, int):
+ assert idx >= Swizzle.SWIZZLE_X and idx <= Swizzle.SWIZZLE_NONE
+ if idx <= Swizzle.SWIZZLE_W:
+ return self.__list.__getitem__(idx)
+ else:
+ return idx
+ elif isinstance(idx, str):
+ if idx in 'xyzw':
+ idx = 'xyzw'.find(idx)
+ elif idx in 'rgba':
+ idx = 'rgba'.find(idx)
+ elif idx in 'zs':
+ idx = 'zs'.find(idx)
+ else:
+ assert False
+ return self.__list.__getitem__(idx)
+ else:
+ assert False
+
+ def __mul__(self, other):
+ """Returns the composition of this Swizzle with another Swizzle.
+
+ The resulting swizzle is such that, for any valid input to
+ __getitem__, (a * b)[i] = a[b[i]].
+ """
+ assert isinstance(other, Swizzle)
+ return Swizzle(self[x] for x in other)
+
+ def inverse(self):
+ """Returns a pseudo-inverse of this swizzle.
+
+ Since swizzling isn't necisaraly a bijection, a Swizzle can never
+ be truely inverted. However, the swizzle returned is *almost* the
+ inverse of this swizzle in the sense that, for each i in range(3),
+ a[a.inverse()[i]] is either i or SWIZZLE_NONE. If swizzle is just
+ a permutation with no channels added or removed, then this
+ function returns the actual inverse.
+
+ This "pseudo-inverse" idea can be demonstrated by mapping from
+ luminance-alpha to rgba that is given by "xxxy". To get from rgba
+ to lumanence-alpha, we use Swizzle("xxxy").inverse() or "xw__".
+ This maps the first component in the lumanence-alpha texture is
+ the red component of the rgba image and the second to the alpha
+ component, exactly as you would expect.
+ """
+ rev = [Swizzle.SWIZZLE_NONE] * 4
+ for i in xrange(4):
+ for j in xrange(4):
+ if self.__list[j] == i and rev[i] == Swizzle.SWIZZLE_NONE:
+ rev[i] = j
+ return Swizzle(rev)
+
+
+class Format:
+ """Describes a pixel format."""
+
+ def __init__(self, name, layout, block_width, block_height, channels, swizzle, colorspace):
+ """Constructs a Format from some metadata and a list of channels.
+
+ The channel objects must be unique to this Format and should not be
+ re-used to construct another Format. This is because certain channel
+ information such as shift, offset, and the channel name are set when
+ the Format is created and are calculated based on the entire list of
+ channels.
+
+ Arguments:
+ name -- Name of the format such as 'MESA_FORMAT_A8R8G8B8'
+ layout -- One of 'array', 'packed' 'other', or a compressed layout
+ block_width -- The block width if the format is compressed, 1 otherwise
+ block_height -- The block height if the format is compressed, 1 otherwise
+ channels -- A list of Channel objects
+ swizzle -- A Swizzle from this format to rgba
+ colorspace -- one of 'rgb', 'srgb', 'yuv', or 'zs'
+ """
+ self.name = name
+ self.layout = layout
+ self.block_width = block_width
+ self.block_height = block_height
+ self.channels = channels
+ assert isinstance(swizzle, Swizzle)
+ self.swizzle = swizzle
+ self.name = name
+ assert colorspace in (RGB, SRGB, YUV, ZS)
+ self.colorspace = colorspace
+
+ # Name the channels
+ chan_names = ['']*4
+ if self.colorspace in (RGB, SRGB):
+ for (i, s) in enumerate(swizzle):
+ if s < 4:
+ chan_names[s] += 'rgba'[i]
+ elif colorspace == ZS:
+ for (i, s) in enumerate(swizzle):
+ if s < 4:
+ chan_names[s] += 'zs'[i]
+ else:
+ chan_names = ['x', 'y', 'z', 'w']
+
+ for c, name in zip(self.channels, chan_names):
+ assert c.name is None
+ if name == 'rgb':
+ c.name = 'l'
+ elif name == 'rgba':
+ c.name = 'i'
+ elif name == '':
+ c.name = 'x'
+ else:
+ c.name = name
+
+ # Set indices and offsets
+ if self.layout == PACKED:
+ shift = 0
+ for channel in self.channels:
+ assert channel.shift == -1
+ channel.shift = shift
+ shift += channel.size
+ for idx, channel in enumerate(self.channels):
+ assert channel.index == -1
+ channel.index = idx
+ else:
+ pass # Shift means nothing here
+
+ def __str__(self):
+ return self.name
+
+ def short_name(self):
+ """Returns a short name for a format.
+
+ The short name should be suitable to be used as suffix in function
+ names.
+ """
+
+ name = self.name
+ if name.startswith('MESA_FORMAT_'):
+ name = name[len('MESA_FORMAT_'):]
+ name = name.lower()
+ return name
+
+ def block_size(self):
+ """Returns the block size (in bits) of the format."""
+ size = 0
+ for channel in self.channels:
+ size += channel.size
+ return size
+
+ def num_channels(self):
+ """Returns the number of channels in the format."""
+ nr_channels = 0
+ for channel in self.channels:
+ if channel.size:
+ nr_channels += 1
+ return nr_channels
+
+ def array_element(self):
+ """Returns a non-void channel if this format is an array, otherwise None.
+
+ If the returned channel is not None, then this format can be
+ considered to be an array of num_channels() channels identical to the
+ returned channel.
+ """
+ if self.layout == ARRAY:
+ return self.channels[0]
+ elif self.layout == PACKED:
+ ref_channel = self.channels[0]
+ if ref_channel.type == VOID:
+ ref_channel = self.channels[1]
+ for channel in self.channels:
+ if channel.size == 0 or channel.type == VOID:
+ continue
+ if channel.size != ref_channel.size or channel.size % 8 != 0:
+ return None
+ if channel.type != ref_channel.type:
+ return None
+ if channel.norm != ref_channel.norm:
+ return None
+ return ref_channel
+ else:
+ return None
+
+ def is_array(self):
+ """Returns true if this format can be considered an array format.
+
+ This function will return true if self.layout == 'array'. However,
+ some formats, such as MESA_FORMAT_A8G8B8R8, can be considered as
+ array formats even though they are technically packed.
+ """
+ return self.array_element() != None
+
+ def is_compressed(self):
+ """Returns true if this is a compressed format."""
+ return self.block_width != 1 or self.block_height != 1
+
+ def is_int(self):
+ """Returns true if this format is an integer format.
+
+ See also: is_norm()
+ """
+ if self.layout not in (ARRAY, PACKED):
+ return False
+ for channel in self.channels:
+ if channel.type not in (VOID, UNSIGNED, SIGNED):
+ return False
+ return True
+
+ def is_float(self):
+ """Returns true if this format is an floating-point format."""
+ if self.layout not in (ARRAY, PACKED):
+ return False
+ for channel in self.channels:
+ if channel.type not in (VOID, FLOAT):
+ return False
+ return True
+
+ def channel_type(self):
+ """Returns the type of the channels in this format."""
+ _type = VOID
+ for c in self.channels:
+ if c.type == VOID:
+ continue
+ if _type == VOID:
+ _type = c.type
+ assert c.type == _type
+ return _type
+
+ def channel_size(self):
+ """Returns the size (in bits) of the channels in this format.
+
+ This function should only be called if all of the channels have the
+ same size. This is always the case if is_array() returns true.
+ """
+ size = None
+ for c in self.channels:
+ if c.type == VOID:
+ continue
+ if size is None:
+ size = c.size
+ assert c.size == size
+ return size
+
+ def max_channel_size(self):
+ """Returns the size of the largest channel."""
+ size = 0
+ for c in self.channels:
+ if c.type == VOID:
+ continue
+ size = max(size, c.size)
+ return size
+
+ def is_normalized(self):
+ """Returns true if this format is normalized.
+
+ While only integer formats can be normalized, not all integer formats
+ are normalized. Normalized integer formats are those where the
+ integer value is re-interpreted as a fixed point value in the range
+ [0, 1].
+ """
+ norm = None
+ for c in self.channels:
+ if c.type == VOID:
+ continue
+ if norm is None:
+ norm = c.norm
+ assert c.norm == norm
+ return norm
+
+ def has_channel(self, name):
+ """Returns true if this format has the given channel."""
+ if self.is_compressed():
+ # Compressed formats are a bit tricky because the list of channels
+ # contains a single channel of type void. Since we don't have any
+ # channel information there, we pull it from the swizzle.
+ if str(self.swizzle) == 'xxxx':
+ return name == 'i'
+ elif str(self.swizzle)[0:3] in ('xxx', 'yyy'):
+ if name == 'l':
+ return True
+ elif name == 'a':
+ return self.swizzle['a'] <= Swizzle.SWIZZLE_W
+ else:
+ return False
+ elif name in 'rgba':
+ return self.swizzle[name] <= Swizzle.SWIZZLE_W
+ else:
+ return False
+ else:
+ for channel in self.channels:
+ if channel.name == name:
+ return True
+ return False
+
+ def get_channel(self, name):
+ """Returns the channel with the given name if it exists."""
+ for channel in self.channels:
+ if channel.name == name:
+ return channel
+ return None
+
+def _parse_channels(fields, layout, colorspace, swizzle):
+ channels = []
+ for field in fields:
+ if not field:
+ continue
+
+ type = field[0] if field[0] else 'x'
+
+ if field[1] == 'n':
+ norm = True
+ size = int(field[2:])
+ else:
+ norm = False
+ size = int(field[1:])
+
+ channel = Channel(type, norm, size)
+ channels.append(channel)
+
+ return channels
+
+def parse(filename):
+ """Parse a format descrition in CSV format.
+
+ This function parses the given CSV file and returns an iterable of
+ channels."""
+
+ with open(filename) as stream:
+ for line in stream:
+ try:
+ comment = line.index('#')
+ except ValueError:
+ pass
+ else:
+ line = line[:comment]
+ line = line.strip()
+ if not line:
+ continue
+
+ fields = [field.strip() for field in line.split(',')]
+
+ name = fields[0]
+ layout = fields[1]
+ block_width = int(fields[2])
+ block_height = int(fields[3])
+ colorspace = fields[9]
+
+ swizzle = Swizzle(fields[8])
+ channels = _parse_channels(fields[4:8], layout, colorspace, swizzle)
+
+ yield Format(name, layout, block_width, block_height, channels, swizzle, colorspace)