mesa: Add python to parse the formats CSV file
[mesa.git] / src / mesa / main / format_parser.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2009 VMware, Inc.
4 # Copyright 2014 Intel Corporation
5 # All Rights Reserved.
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a
8 # copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sub license, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
14 #
15 # The above copyright notice and this permission notice (including the
16 # next paragraph) shall be included in all copies or substantial portions
17 # of the Software.
18 #
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
22 # IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
23 # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
27 VOID = 'x'
28 UNSIGNED = 'u'
29 SIGNED = 's'
30 FLOAT = 'f'
31
32 ARRAY = 'array'
33 PACKED = 'packed'
34 OTHER = 'other'
35
36 RGB = 'rgb'
37 SRGB = 'srgb'
38 YUV = 'yuv'
39 ZS = 'zs'
40
41 def is_power_of_two(x):
42 return not bool(x & (x - 1))
43
44 VERY_LARGE = 99999999999999999999999
45
46 class Channel:
47 """Describes a color channel."""
48
49 def __init__(self, type, norm, size):
50 self.type = type
51 self.norm = norm
52 self.size = size
53 self.sign = type in (SIGNED, FLOAT)
54 self.name = None # Set when the channels are added to the format
55 self.shift = -1 # Set when the channels are added to the format
56 self.index = -1 # Set when the channels are added to the format
57
58 def __str__(self):
59 s = str(self.type)
60 if self.norm:
61 s += 'n'
62 s += str(self.size)
63 return s
64
65 def __eq__(self, other):
66 return self.type == other.type and self.norm == other.norm and self.size == other.size
67
68 def max(self):
69 """Returns the maximum representable number."""
70 if self.type == FLOAT:
71 return VERY_LARGE
72 if self.norm:
73 return 1
74 if self.type == UNSIGNED:
75 return (1 << self.size) - 1
76 if self.type == SIGNED:
77 return (1 << (self.size - 1)) - 1
78 assert False
79
80 def min(self):
81 """Returns the minimum representable number."""
82 if self.type == FLOAT:
83 return -VERY_LARGE
84 if self.type == UNSIGNED:
85 return 0
86 if self.norm:
87 return -1
88 if self.type == SIGNED:
89 return -(1 << (self.size - 1))
90 assert False
91
92 def one(self):
93 """Returns the value that represents 1.0f."""
94 if self.type == UNSIGNED:
95 return (1 << self.size) - 1
96 if self.type == SIGNED:
97 return (1 << (self.size - 1)) - 1
98 else:
99 return 1
100
101 def is_power_of_two(self):
102 """Returns true if the size of this channel is a power of two."""
103 return is_power_of_two(self.size)
104
105 class Swizzle:
106 """Describes a swizzle operation.
107
108 A Swizzle is a mapping from one set of channels in one format to the
109 channels in another. Each channel in the destination format is
110 associated with one of the following constants:
111
112 * SWIZZLE_X: The first channel in the source format
113 * SWIZZLE_Y: The second channel in the source format
114 * SWIZZLE_Z: The third channel in the source format
115 * SWIZZLE_W: The fourth channel in the source format
116 * SWIZZLE_ZERO: The numeric constant 0
117 * SWIZZLE_ONE: THe numeric constant 1
118 * SWIZZLE_NONE: No data available for this channel
119
120 Sometimes a Swizzle is represented by a 4-character string. In this
121 case, the source channels are represented by the characters "x", "y",
122 "z", and "w"; the numeric constants are represented as "0" and "1"; and
123 no mapping is represented by "_". For instance, the map from
124 luminance-alpha to rgba is given by "xxxy" because each of the three rgb
125 channels maps to the first luminance-alpha channel and the alpha channel
126 maps to second luminance-alpha channel. The mapping from bgr to rgba is
127 given by "zyx1" because the first three colors are reversed and alpha is
128 always 1.
129 """
130
131 __identity_str = 'xyzw01_'
132
133 SWIZZLE_X = 0
134 SWIZZLE_Y = 1
135 SWIZZLE_Z = 2
136 SWIZZLE_W = 3
137 SWIZZLE_ZERO = 4
138 SWIZZLE_ONE = 5
139 SWIZZLE_NONE = 6
140
141 def __init__(self, swizzle):
142 """Creates a Swizzle object from a string or array."""
143 if isinstance(swizzle, str):
144 swizzle = [Swizzle.__identity_str.index(c) for c in swizzle]
145 else:
146 swizzle = list(swizzle)
147 for s in swizzle:
148 assert isinstance(s, int) and 0 <= s and s <= Swizzle.SWIZZLE_NONE
149
150 assert len(swizzle) <= 4
151
152 self.__list = swizzle + [Swizzle.SWIZZLE_NONE] * (4 - len(swizzle))
153 assert len(self.__list) == 4
154
155 def __iter__(self):
156 """Returns an iterator that iterates over this Swizzle.
157
158 The values that the iterator produces are described by the SWIZZLE_*
159 constants.
160 """
161 return self.__list.__iter__()
162
163 def __str__(self):
164 """Returns a string representation of this Swizzle."""
165 return ''.join(Swizzle.__identity_str[i] for i in self.__list)
166
167 def __getitem__(self, idx):
168 """Returns the SWIZZLE_* constant for the given destination channel.
169
170 Valid values for the destination channel include any of the SWIZZLE_*
171 constants or any of the following single-character strings: "x", "y",
172 "z", "w", "r", "g", "b", "a", "z" "s".
173 """
174
175 if isinstance(idx, int):
176 assert idx >= Swizzle.SWIZZLE_X and idx <= Swizzle.SWIZZLE_NONE
177 if idx <= Swizzle.SWIZZLE_W:
178 return self.__list.__getitem__(idx)
179 else:
180 return idx
181 elif isinstance(idx, str):
182 if idx in 'xyzw':
183 idx = 'xyzw'.find(idx)
184 elif idx in 'rgba':
185 idx = 'rgba'.find(idx)
186 elif idx in 'zs':
187 idx = 'zs'.find(idx)
188 else:
189 assert False
190 return self.__list.__getitem__(idx)
191 else:
192 assert False
193
194 def __mul__(self, other):
195 """Returns the composition of this Swizzle with another Swizzle.
196
197 The resulting swizzle is such that, for any valid input to
198 __getitem__, (a * b)[i] = a[b[i]].
199 """
200 assert isinstance(other, Swizzle)
201 return Swizzle(self[x] for x in other)
202
203 def inverse(self):
204 """Returns a pseudo-inverse of this swizzle.
205
206 Since swizzling isn't necisaraly a bijection, a Swizzle can never
207 be truely inverted. However, the swizzle returned is *almost* the
208 inverse of this swizzle in the sense that, for each i in range(3),
209 a[a.inverse()[i]] is either i or SWIZZLE_NONE. If swizzle is just
210 a permutation with no channels added or removed, then this
211 function returns the actual inverse.
212
213 This "pseudo-inverse" idea can be demonstrated by mapping from
214 luminance-alpha to rgba that is given by "xxxy". To get from rgba
215 to lumanence-alpha, we use Swizzle("xxxy").inverse() or "xw__".
216 This maps the first component in the lumanence-alpha texture is
217 the red component of the rgba image and the second to the alpha
218 component, exactly as you would expect.
219 """
220 rev = [Swizzle.SWIZZLE_NONE] * 4
221 for i in xrange(4):
222 for j in xrange(4):
223 if self.__list[j] == i and rev[i] == Swizzle.SWIZZLE_NONE:
224 rev[i] = j
225 return Swizzle(rev)
226
227
228 class Format:
229 """Describes a pixel format."""
230
231 def __init__(self, name, layout, block_width, block_height, channels, swizzle, colorspace):
232 """Constructs a Format from some metadata and a list of channels.
233
234 The channel objects must be unique to this Format and should not be
235 re-used to construct another Format. This is because certain channel
236 information such as shift, offset, and the channel name are set when
237 the Format is created and are calculated based on the entire list of
238 channels.
239
240 Arguments:
241 name -- Name of the format such as 'MESA_FORMAT_A8R8G8B8'
242 layout -- One of 'array', 'packed' 'other', or a compressed layout
243 block_width -- The block width if the format is compressed, 1 otherwise
244 block_height -- The block height if the format is compressed, 1 otherwise
245 channels -- A list of Channel objects
246 swizzle -- A Swizzle from this format to rgba
247 colorspace -- one of 'rgb', 'srgb', 'yuv', or 'zs'
248 """
249 self.name = name
250 self.layout = layout
251 self.block_width = block_width
252 self.block_height = block_height
253 self.channels = channels
254 assert isinstance(swizzle, Swizzle)
255 self.swizzle = swizzle
256 self.name = name
257 assert colorspace in (RGB, SRGB, YUV, ZS)
258 self.colorspace = colorspace
259
260 # Name the channels
261 chan_names = ['']*4
262 if self.colorspace in (RGB, SRGB):
263 for (i, s) in enumerate(swizzle):
264 if s < 4:
265 chan_names[s] += 'rgba'[i]
266 elif colorspace == ZS:
267 for (i, s) in enumerate(swizzle):
268 if s < 4:
269 chan_names[s] += 'zs'[i]
270 else:
271 chan_names = ['x', 'y', 'z', 'w']
272
273 for c, name in zip(self.channels, chan_names):
274 assert c.name is None
275 if name == 'rgb':
276 c.name = 'l'
277 elif name == 'rgba':
278 c.name = 'i'
279 elif name == '':
280 c.name = 'x'
281 else:
282 c.name = name
283
284 # Set indices and offsets
285 if self.layout == PACKED:
286 shift = 0
287 for channel in self.channels:
288 assert channel.shift == -1
289 channel.shift = shift
290 shift += channel.size
291 for idx, channel in enumerate(self.channels):
292 assert channel.index == -1
293 channel.index = idx
294 else:
295 pass # Shift means nothing here
296
297 def __str__(self):
298 return self.name
299
300 def short_name(self):
301 """Returns a short name for a format.
302
303 The short name should be suitable to be used as suffix in function
304 names.
305 """
306
307 name = self.name
308 if name.startswith('MESA_FORMAT_'):
309 name = name[len('MESA_FORMAT_'):]
310 name = name.lower()
311 return name
312
313 def block_size(self):
314 """Returns the block size (in bits) of the format."""
315 size = 0
316 for channel in self.channels:
317 size += channel.size
318 return size
319
320 def num_channels(self):
321 """Returns the number of channels in the format."""
322 nr_channels = 0
323 for channel in self.channels:
324 if channel.size:
325 nr_channels += 1
326 return nr_channels
327
328 def array_element(self):
329 """Returns a non-void channel if this format is an array, otherwise None.
330
331 If the returned channel is not None, then this format can be
332 considered to be an array of num_channels() channels identical to the
333 returned channel.
334 """
335 if self.layout == ARRAY:
336 return self.channels[0]
337 elif self.layout == PACKED:
338 ref_channel = self.channels[0]
339 if ref_channel.type == VOID:
340 ref_channel = self.channels[1]
341 for channel in self.channels:
342 if channel.size == 0 or channel.type == VOID:
343 continue
344 if channel.size != ref_channel.size or channel.size % 8 != 0:
345 return None
346 if channel.type != ref_channel.type:
347 return None
348 if channel.norm != ref_channel.norm:
349 return None
350 return ref_channel
351 else:
352 return None
353
354 def is_array(self):
355 """Returns true if this format can be considered an array format.
356
357 This function will return true if self.layout == 'array'. However,
358 some formats, such as MESA_FORMAT_A8G8B8R8, can be considered as
359 array formats even though they are technically packed.
360 """
361 return self.array_element() != None
362
363 def is_compressed(self):
364 """Returns true if this is a compressed format."""
365 return self.block_width != 1 or self.block_height != 1
366
367 def is_int(self):
368 """Returns true if this format is an integer format.
369
370 See also: is_norm()
371 """
372 if self.layout not in (ARRAY, PACKED):
373 return False
374 for channel in self.channels:
375 if channel.type not in (VOID, UNSIGNED, SIGNED):
376 return False
377 return True
378
379 def is_float(self):
380 """Returns true if this format is an floating-point format."""
381 if self.layout not in (ARRAY, PACKED):
382 return False
383 for channel in self.channels:
384 if channel.type not in (VOID, FLOAT):
385 return False
386 return True
387
388 def channel_type(self):
389 """Returns the type of the channels in this format."""
390 _type = VOID
391 for c in self.channels:
392 if c.type == VOID:
393 continue
394 if _type == VOID:
395 _type = c.type
396 assert c.type == _type
397 return _type
398
399 def channel_size(self):
400 """Returns the size (in bits) of the channels in this format.
401
402 This function should only be called if all of the channels have the
403 same size. This is always the case if is_array() returns true.
404 """
405 size = None
406 for c in self.channels:
407 if c.type == VOID:
408 continue
409 if size is None:
410 size = c.size
411 assert c.size == size
412 return size
413
414 def max_channel_size(self):
415 """Returns the size of the largest channel."""
416 size = 0
417 for c in self.channels:
418 if c.type == VOID:
419 continue
420 size = max(size, c.size)
421 return size
422
423 def is_normalized(self):
424 """Returns true if this format is normalized.
425
426 While only integer formats can be normalized, not all integer formats
427 are normalized. Normalized integer formats are those where the
428 integer value is re-interpreted as a fixed point value in the range
429 [0, 1].
430 """
431 norm = None
432 for c in self.channels:
433 if c.type == VOID:
434 continue
435 if norm is None:
436 norm = c.norm
437 assert c.norm == norm
438 return norm
439
440 def has_channel(self, name):
441 """Returns true if this format has the given channel."""
442 if self.is_compressed():
443 # Compressed formats are a bit tricky because the list of channels
444 # contains a single channel of type void. Since we don't have any
445 # channel information there, we pull it from the swizzle.
446 if str(self.swizzle) == 'xxxx':
447 return name == 'i'
448 elif str(self.swizzle)[0:3] in ('xxx', 'yyy'):
449 if name == 'l':
450 return True
451 elif name == 'a':
452 return self.swizzle['a'] <= Swizzle.SWIZZLE_W
453 else:
454 return False
455 elif name in 'rgba':
456 return self.swizzle[name] <= Swizzle.SWIZZLE_W
457 else:
458 return False
459 else:
460 for channel in self.channels:
461 if channel.name == name:
462 return True
463 return False
464
465 def get_channel(self, name):
466 """Returns the channel with the given name if it exists."""
467 for channel in self.channels:
468 if channel.name == name:
469 return channel
470 return None
471
472 def _parse_channels(fields, layout, colorspace, swizzle):
473 channels = []
474 for field in fields:
475 if not field:
476 continue
477
478 type = field[0] if field[0] else 'x'
479
480 if field[1] == 'n':
481 norm = True
482 size = int(field[2:])
483 else:
484 norm = False
485 size = int(field[1:])
486
487 channel = Channel(type, norm, size)
488 channels.append(channel)
489
490 return channels
491
492 def parse(filename):
493 """Parse a format descrition in CSV format.
494
495 This function parses the given CSV file and returns an iterable of
496 channels."""
497
498 with open(filename) as stream:
499 for line in stream:
500 try:
501 comment = line.index('#')
502 except ValueError:
503 pass
504 else:
505 line = line[:comment]
506 line = line.strip()
507 if not line:
508 continue
509
510 fields = [field.strip() for field in line.split(',')]
511
512 name = fields[0]
513 layout = fields[1]
514 block_width = int(fields[2])
515 block_height = int(fields[3])
516 colorspace = fields[9]
517
518 swizzle = Swizzle(fields[8])
519 channels = _parse_channels(fields[4:8], layout, colorspace, swizzle)
520
521 yield Format(name, layout, block_width, block_height, channels, swizzle, colorspace)