3 /**************************************************************************
5 * Copyright 2009-2010 VMware, Inc.
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sub license, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
16 * The above copyright notice and this permission notice (including the
17 * next paragraph) shall be included in all copies or substantial portions
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
23 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR
24 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 **************************************************************************/
32 * Pixel format packing and unpacking functions.
34 * @author Jose Fonseca <jfonseca@vmware.com>
39 from __future__
import division
, print_function
43 from u_format_parse
import *
46 if sys
.version_info
< (3, 0):
47 integer_types
= (int, long)
50 integer_types
= (int, )
53 def inv_swizzles(swizzles
):
54 '''Return an array[4] of inverse swizzle terms'''
55 '''Only pick the first matching value to avoid l8 getting blue and i8 getting alpha'''
56 inv_swizzle
= [None]*4
59 if swizzle
< 4 and inv_swizzle
[swizzle
] == None:
60 inv_swizzle
[swizzle
] = i
63 def print_channels(format
, func
):
64 if format
.nr_channels() <= 1:
65 func(format
.le_channels
, format
.le_swizzles
)
67 print('#ifdef PIPE_ARCH_BIG_ENDIAN')
68 func(format
.be_channels
, format
.be_swizzles
)
70 func(format
.le_channels
, format
.le_swizzles
)
73 def generate_format_type(format
):
74 '''Generate a structure that describes the format.'''
76 assert format
.layout
== PLAIN
78 def generate_bitfields(channels
, swizzles
):
79 for channel
in channels
:
80 if channel
.type == VOID
:
82 print(' unsigned %s:%u;' % (channel
.name
, channel
.size
))
83 elif channel
.type == UNSIGNED
:
84 print(' unsigned %s:%u;' % (channel
.name
, channel
.size
))
85 elif channel
.type in (SIGNED
, FIXED
):
86 print(' int %s:%u;' % (channel
.name
, channel
.size
))
87 elif channel
.type == FLOAT
:
88 if channel
.size
== 64:
89 print(' double %s;' % (channel
.name
))
90 elif channel
.size
== 32:
91 print(' float %s;' % (channel
.name
))
93 print(' unsigned %s:%u;' % (channel
.name
, channel
.size
))
97 def generate_full_fields(channels
, swizzles
):
98 for channel
in channels
:
99 assert channel
.size
% 8 == 0 and is_pot(channel
.size
)
100 if channel
.type == VOID
:
102 print(' uint%u_t %s;' % (channel
.size
, channel
.name
))
103 elif channel
.type == UNSIGNED
:
104 print(' uint%u_t %s;' % (channel
.size
, channel
.name
))
105 elif channel
.type in (SIGNED
, FIXED
):
106 print(' int%u_t %s;' % (channel
.size
, channel
.name
))
107 elif channel
.type == FLOAT
:
108 if channel
.size
== 64:
109 print(' double %s;' % (channel
.name
))
110 elif channel
.size
== 32:
111 print(' float %s;' % (channel
.name
))
112 elif channel
.size
== 16:
113 print(' uint16_t %s;' % (channel
.name
))
119 print('union util_format_%s {' % format
.short_name())
121 if format
.block_size() in (8, 16, 32, 64):
122 print(' uint%u_t value;' % (format
.block_size(),))
124 use_bitfields
= False
125 for channel
in format
.le_channels
:
126 if channel
.size
% 8 or not is_pot(channel
.size
):
131 print_channels(format
, generate_bitfields
)
133 print_channels(format
, generate_full_fields
)
139 def is_format_supported(format
):
140 '''Determines whether we actually have the plumbing necessary to generate the
141 to read/write to/from this format.'''
143 # FIXME: Ideally we would support any format combination here.
145 if format
.layout
!= PLAIN
:
149 channel
= format
.le_channels
[i
]
150 if channel
.type not in (VOID
, UNSIGNED
, SIGNED
, FLOAT
, FIXED
):
152 if channel
.type == FLOAT
and channel
.size
not in (16, 32, 64):
157 def native_type(format
):
158 '''Get the native appropriate for a format.'''
160 if format
.name
== 'PIPE_FORMAT_R11G11B10_FLOAT':
162 if format
.name
== 'PIPE_FORMAT_R9G9B9E5_FLOAT':
165 if format
.layout
== PLAIN
:
166 if not format
.is_array():
167 # For arithmetic pixel formats return the integer type that matches the whole pixel
168 return 'uint%u_t' % format
.block_size()
170 # For array pixel formats return the integer type that matches the color channel
171 channel
= format
.array_element()
172 if channel
.type in (UNSIGNED
, VOID
):
173 return 'uint%u_t' % channel
.size
174 elif channel
.type in (SIGNED
, FIXED
):
175 return 'int%u_t' % channel
.size
176 elif channel
.type == FLOAT
:
177 if channel
.size
== 16:
179 elif channel
.size
== 32:
181 elif channel
.size
== 64:
191 def intermediate_native_type(bits
, sign
):
192 '''Find a native type adequate to hold intermediate results of the request bit size.'''
194 bytes
= 4 # don't use anything smaller than 32bits
195 while bytes
* 8 < bits
:
200 return 'int%u_t' % bits
202 return 'uint%u_t' % bits
205 def get_one_shift(type):
206 '''Get the number of the bit that matches unity for this type.'''
207 if type.type == 'FLOAT':
211 if type.type == UNSIGNED
:
213 if type.type == SIGNED
:
215 if type.type == FIXED
:
220 def truncate_mantissa(x
, bits
):
221 '''Truncate an integer so it can be represented exactly with a floating
224 assert isinstance(x
, integer_types
)
231 # We can represent integers up to mantissa + 1 bits exactly
232 mask
= (1 << (bits
+ 1)) - 1
234 # Slide the mask until the MSB matches
236 while (x
>> shift
) & ~mask
:
244 def value_to_native(type, value
):
245 '''Get the value of unity for this type.'''
246 if type.type == FLOAT
:
248 and isinstance(value
, integer_types
):
249 return truncate_mantissa(value
, 23)
251 if type.type == FIXED
:
252 return int(value
* (1 << (type.size
// 2)))
255 if type.type == UNSIGNED
:
256 return int(value
* ((1 << type.size
) - 1))
257 if type.type == SIGNED
:
258 return int(value
* ((1 << (type.size
- 1)) - 1))
262 def native_to_constant(type, value
):
263 '''Get the value of unity for this type.'''
264 if type.type == FLOAT
:
266 return "%.1ff" % float(value
)
268 return "%.1f" % float(value
)
270 return str(int(value
))
274 '''Get the value of unity for this type.'''
275 return value_to_native(type, 1)
278 def clamp_expr(src_channel
, dst_channel
, dst_native_type
, value
):
279 '''Generate the expression to clamp the value in the source type to the
280 destination type range.'''
282 if src_channel
== dst_channel
:
285 src_min
= src_channel
.min()
286 src_max
= src_channel
.max()
287 dst_min
= dst_channel
.min()
288 dst_max
= dst_channel
.max()
290 # Translate the destination range to the src native value
291 dst_min_native
= native_to_constant(src_channel
, value_to_native(src_channel
, dst_min
))
292 dst_max_native
= native_to_constant(src_channel
, value_to_native(src_channel
, dst_max
))
294 if src_min
< dst_min
and src_max
> dst_max
:
295 return 'CLAMP(%s, %s, %s)' % (value
, dst_min_native
, dst_max_native
)
297 if src_max
> dst_max
:
298 return 'MIN2(%s, %s)' % (value
, dst_max_native
)
300 if src_min
< dst_min
:
301 return 'MAX2(%s, %s)' % (value
, dst_min_native
)
306 def conversion_expr(src_channel
,
307 dst_channel
, dst_native_type
,
310 src_colorspace
= RGB
,
311 dst_colorspace
= RGB
):
312 '''Generate the expression to convert a value between two types.'''
314 if src_colorspace
!= dst_colorspace
:
315 if src_colorspace
== SRGB
:
316 assert src_channel
.type == UNSIGNED
317 assert src_channel
.norm
318 assert src_channel
.size
<= 8
319 assert src_channel
.size
>= 4
320 assert dst_colorspace
== RGB
321 if src_channel
.size
< 8:
322 value
= '%s << %x | %s >> %x' % (value
, 8 - src_channel
.size
, value
, 2 * src_channel
.size
- 8)
323 if dst_channel
.type == FLOAT
:
324 return 'util_format_srgb_8unorm_to_linear_float(%s)' % value
326 assert dst_channel
.type == UNSIGNED
327 assert dst_channel
.norm
328 assert dst_channel
.size
== 8
329 return 'util_format_srgb_to_linear_8unorm(%s)' % value
330 elif dst_colorspace
== SRGB
:
331 assert dst_channel
.type == UNSIGNED
332 assert dst_channel
.norm
333 assert dst_channel
.size
<= 8
334 assert src_colorspace
== RGB
335 if src_channel
.type == FLOAT
:
336 value
= 'util_format_linear_float_to_srgb_8unorm(%s)' % value
338 assert src_channel
.type == UNSIGNED
339 assert src_channel
.norm
340 assert src_channel
.size
== 8
341 value
= 'util_format_linear_to_srgb_8unorm(%s)' % value
342 # XXX rounding is all wrong.
343 if dst_channel
.size
< 8:
344 return '%s >> %x' % (value
, 8 - dst_channel
.size
)
347 elif src_colorspace
== ZS
:
349 elif dst_colorspace
== ZS
:
354 if src_channel
== dst_channel
:
357 src_type
= src_channel
.type
358 src_size
= src_channel
.size
359 src_norm
= src_channel
.norm
360 src_pure
= src_channel
.pure
362 # Promote half to float
363 if src_type
== FLOAT
and src_size
== 16:
364 value
= 'util_half_to_float(%s)' % value
367 # Special case for float <-> ubytes for more accurate results
368 # Done before clamping since these functions already take care of that
369 if src_type
== UNSIGNED
and src_norm
and src_size
== 8 and dst_channel
.type == FLOAT
and dst_channel
.size
== 32:
370 return 'ubyte_to_float(%s)' % value
371 if src_type
== FLOAT
and src_size
== 32 and dst_channel
.type == UNSIGNED
and dst_channel
.norm
and dst_channel
.size
== 8:
372 return 'float_to_ubyte(%s)' % value
375 if dst_channel
.type != FLOAT
or src_type
!= FLOAT
:
376 value
= clamp_expr(src_channel
, dst_channel
, dst_native_type
, value
)
378 if src_type
in (SIGNED
, UNSIGNED
) and dst_channel
.type in (SIGNED
, UNSIGNED
):
379 if not src_norm
and not dst_channel
.norm
:
380 # neither is normalized -- just cast
381 return '(%s)%s' % (dst_native_type
, value
)
383 src_one
= get_one(src_channel
)
384 dst_one
= get_one(dst_channel
)
386 if src_one
> dst_one
and src_norm
and dst_channel
.norm
:
387 # We can just bitshift
388 src_shift
= get_one_shift(src_channel
)
389 dst_shift
= get_one_shift(dst_channel
)
390 value
= '(%s >> %s)' % (value
, src_shift
- dst_shift
)
392 # We need to rescale using an intermediate type big enough to hold the multiplication of both
393 tmp_native_type
= intermediate_native_type(src_size
+ dst_channel
.size
, src_channel
.sign
and dst_channel
.sign
)
394 value
= '((%s)%s)' % (tmp_native_type
, value
)
395 value
= '(%s * 0x%x / 0x%x)' % (value
, dst_one
, src_one
)
396 value
= '(%s)%s' % (dst_native_type
, value
)
399 # Promote to either float or double
400 if src_type
!= FLOAT
:
401 if src_norm
or src_type
== FIXED
:
402 one
= get_one(src_channel
)
404 value
= '(%s * (1.0f/0x%x))' % (value
, one
)
405 if dst_channel
.size
<= 32:
406 value
= '(float)%s' % value
409 # bigger than single precision mantissa, use double
410 value
= '(%s * (1.0/0x%x))' % (value
, one
)
414 if src_size
<= 23 or dst_channel
.size
<= 32:
415 value
= '(float)%s' % value
418 # bigger than single precision mantissa, use double
419 value
= '(double)%s' % value
423 # Convert double or float to non-float
424 if dst_channel
.type != FLOAT
:
425 if dst_channel
.norm
or dst_channel
.type == FIXED
:
426 dst_one
= get_one(dst_channel
)
427 if dst_channel
.size
<= 23:
428 value
= 'util_iround(%s * 0x%x)' % (value
, dst_one
)
430 # bigger than single precision mantissa, use double
431 value
= '(%s * (double)0x%x)' % (value
, dst_one
)
432 value
= '(%s)%s' % (dst_native_type
, value
)
434 # Cast double to float when converting to either half or float
435 if dst_channel
.size
<= 32 and src_size
> 32:
436 value
= '(float)%s' % value
439 if dst_channel
.size
== 16:
440 value
= 'util_float_to_half(%s)' % value
441 elif dst_channel
.size
== 64 and src_size
< 64:
442 value
= '(double)%s' % value
447 def generate_unpack_kernel(format
, dst_channel
, dst_native_type
):
449 if not is_format_supported(format
):
452 assert format
.layout
== PLAIN
454 src_native_type
= native_type(format
)
456 def unpack_from_bitmask(channels
, swizzles
):
457 depth
= format
.block_size()
458 print(' uint%u_t value = *(const uint%u_t *)src;' % (depth
, depth
))
460 # Declare the intermediate variables
461 for i
in range(format
.nr_channels()):
462 src_channel
= channels
[i
]
463 if src_channel
.type == UNSIGNED
:
464 print(' uint%u_t %s;' % (depth
, src_channel
.name
))
465 elif src_channel
.type == SIGNED
:
466 print(' int%u_t %s;' % (depth
, src_channel
.name
))
468 # Compute the intermediate unshifted values
469 for i
in range(format
.nr_channels()):
470 src_channel
= channels
[i
]
472 shift
= src_channel
.shift
473 if src_channel
.type == UNSIGNED
:
475 value
= '%s >> %u' % (value
, shift
)
476 if shift
+ src_channel
.size
< depth
:
477 value
= '(%s) & 0x%x' % (value
, (1 << src_channel
.size
) - 1)
478 elif src_channel
.type == SIGNED
:
479 if shift
+ src_channel
.size
< depth
:
481 lshift
= depth
- (shift
+ src_channel
.size
)
482 value
= '%s << %u' % (value
, lshift
)
484 value
= '(int%u_t)(%s) ' % (depth
, value
)
485 if src_channel
.size
< depth
:
487 rshift
= depth
- src_channel
.size
488 value
= '(%s) >> %u' % (value
, rshift
)
492 if value
is not None:
493 print(' %s = %s;' % (src_channel
.name
, value
))
495 # Convert, swizzle, and store final values
497 swizzle
= swizzles
[i
]
499 src_channel
= channels
[swizzle
]
500 src_colorspace
= format
.colorspace
501 if src_colorspace
== SRGB
and i
== 3:
502 # Alpha channel is linear
504 value
= src_channel
.name
505 value
= conversion_expr(src_channel
,
506 dst_channel
, dst_native_type
,
508 src_colorspace
= src_colorspace
)
509 elif swizzle
== SWIZZLE_0
:
511 elif swizzle
== SWIZZLE_1
:
512 value
= get_one(dst_channel
)
513 elif swizzle
== SWIZZLE_NONE
:
517 print(' dst[%u] = %s; /* %s */' % (i
, value
, 'rgba'[i
]))
519 def unpack_from_union(channels
, swizzles
):
520 print(' union util_format_%s pixel;' % format
.short_name())
521 print(' memcpy(&pixel, src, sizeof pixel);')
524 swizzle
= swizzles
[i
]
526 src_channel
= channels
[swizzle
]
527 src_colorspace
= format
.colorspace
528 if src_colorspace
== SRGB
and i
== 3:
529 # Alpha channel is linear
531 value
= 'pixel.chan.%s' % src_channel
.name
532 value
= conversion_expr(src_channel
,
533 dst_channel
, dst_native_type
,
535 src_colorspace
= src_colorspace
)
536 elif swizzle
== SWIZZLE_0
:
538 elif swizzle
== SWIZZLE_1
:
539 value
= get_one(dst_channel
)
540 elif swizzle
== SWIZZLE_NONE
:
544 print(' dst[%u] = %s; /* %s */' % (i
, value
, 'rgba'[i
]))
546 if format
.is_bitmask():
547 print_channels(format
, unpack_from_bitmask
)
549 print_channels(format
, unpack_from_union
)
552 def generate_pack_kernel(format
, src_channel
, src_native_type
):
554 if not is_format_supported(format
):
557 dst_native_type
= native_type(format
)
559 assert format
.layout
== PLAIN
561 def pack_into_bitmask(channels
, swizzles
):
562 inv_swizzle
= inv_swizzles(swizzles
)
564 depth
= format
.block_size()
565 print(' uint%u_t value = 0;' % depth
)
568 dst_channel
= channels
[i
]
569 shift
= dst_channel
.shift
570 if inv_swizzle
[i
] is not None:
571 value
='src[%u]' % inv_swizzle
[i
]
572 dst_colorspace
= format
.colorspace
573 if dst_colorspace
== SRGB
and inv_swizzle
[i
] == 3:
574 # Alpha channel is linear
576 value
= conversion_expr(src_channel
,
577 dst_channel
, dst_native_type
,
579 dst_colorspace
= dst_colorspace
)
580 if dst_channel
.type in (UNSIGNED
, SIGNED
):
581 if shift
+ dst_channel
.size
< depth
:
582 value
= '(%s) & 0x%x' % (value
, (1 << dst_channel
.size
) - 1)
584 value
= '(%s) << %u' % (value
, shift
)
585 if dst_channel
.type == SIGNED
:
587 value
= '(uint%u_t)(%s) ' % (depth
, value
)
590 if value
is not None:
591 print(' value |= %s;' % (value
))
593 print(' *(uint%u_t *)dst = value;' % depth
)
595 def pack_into_union(channels
, swizzles
):
596 inv_swizzle
= inv_swizzles(swizzles
)
598 print(' union util_format_%s pixel;' % format
.short_name())
601 dst_channel
= channels
[i
]
602 width
= dst_channel
.size
603 if inv_swizzle
[i
] is None:
605 dst_colorspace
= format
.colorspace
606 if dst_colorspace
== SRGB
and inv_swizzle
[i
] == 3:
607 # Alpha channel is linear
609 value
='src[%u]' % inv_swizzle
[i
]
610 value
= conversion_expr(src_channel
,
611 dst_channel
, dst_native_type
,
613 dst_colorspace
= dst_colorspace
)
614 print(' pixel.chan.%s = %s;' % (dst_channel
.name
, value
))
616 print(' memcpy(dst, &pixel, sizeof pixel);')
618 if format
.is_bitmask():
619 print_channels(format
, pack_into_bitmask
)
621 print_channels(format
, pack_into_union
)
624 def generate_format_unpack(format
, dst_channel
, dst_native_type
, dst_suffix
):
625 '''Generate the function to unpack pixels from a particular format'''
627 name
= format
.short_name()
629 print('static inline void')
630 print('util_format_%s_unpack_%s(%s *dst_row, unsigned dst_stride, const uint8_t *src_row, unsigned src_stride, unsigned width, unsigned height)' % (name
, dst_suffix
, dst_native_type
))
633 if is_format_supported(format
):
634 print(' unsigned x, y;')
635 print(' for(y = 0; y < height; y += %u) {' % (format
.block_height
,))
636 print(' %s *dst = dst_row;' % (dst_native_type
))
637 print(' const uint8_t *src = src_row;')
638 print(' for(x = 0; x < width; x += %u) {' % (format
.block_width
,))
640 generate_unpack_kernel(format
, dst_channel
, dst_native_type
)
642 print(' src += %u;' % (format
.block_size() / 8,))
645 print(' src_row += src_stride;')
646 print(' dst_row += dst_stride/sizeof(*dst_row);')
653 def generate_format_pack(format
, src_channel
, src_native_type
, src_suffix
):
654 '''Generate the function to pack pixels to a particular format'''
656 name
= format
.short_name()
658 print('static inline void')
659 print('util_format_%s_pack_%s(uint8_t *dst_row, unsigned dst_stride, const %s *src_row, unsigned src_stride, unsigned width, unsigned height)' % (name
, src_suffix
, src_native_type
))
662 if is_format_supported(format
):
663 print(' unsigned x, y;')
664 print(' for(y = 0; y < height; y += %u) {' % (format
.block_height
,))
665 print(' const %s *src = src_row;' % (src_native_type
))
666 print(' uint8_t *dst = dst_row;')
667 print(' for(x = 0; x < width; x += %u) {' % (format
.block_width
,))
669 generate_pack_kernel(format
, src_channel
, src_native_type
)
672 print(' dst += %u;' % (format
.block_size() / 8,))
674 print(' dst_row += dst_stride;')
675 print(' src_row += src_stride/sizeof(*src_row);')
682 def generate_format_fetch(format
, dst_channel
, dst_native_type
, dst_suffix
):
683 '''Generate the function to unpack pixels from a particular format'''
685 name
= format
.short_name()
687 print('static inline void')
688 print('util_format_%s_fetch_%s(%s *dst, const uint8_t *src, UNUSED unsigned i, UNUSED unsigned j)' % (name
, dst_suffix
, dst_native_type
))
691 if is_format_supported(format
):
692 generate_unpack_kernel(format
, dst_channel
, dst_native_type
)
698 def is_format_hand_written(format
):
699 return format
.layout
in ('s3tc', 'rgtc', 'etc', 'bptc', 'astc', 'subsampled', 'other') or format
.colorspace
== ZS
702 def generate(formats
):
704 print('#include "pipe/p_compiler.h"')
705 print('#include "u_math.h"')
706 print('#include "u_half.h"')
707 print('#include "u_format.h"')
708 print('#include "u_format_other.h"')
709 print('#include "util/format_srgb.h"')
710 print('#include "u_format_yuv.h"')
711 print('#include "u_format_zs.h"')
714 for format
in formats
:
715 if not is_format_hand_written(format
):
717 if is_format_supported(format
):
718 generate_format_type(format
)
720 if format
.is_pure_unsigned():
721 native_type
= 'unsigned'
723 channel
= Channel(UNSIGNED
, False, True, 32)
725 generate_format_unpack(format
, channel
, native_type
, suffix
)
726 generate_format_pack(format
, channel
, native_type
, suffix
)
727 generate_format_fetch(format
, channel
, native_type
, suffix
)
729 channel
= Channel(SIGNED
, False, True, 32)
732 generate_format_unpack(format
, channel
, native_type
, suffix
)
733 generate_format_pack(format
, channel
, native_type
, suffix
)
734 elif format
.is_pure_signed():
737 channel
= Channel(SIGNED
, False, True, 32)
739 generate_format_unpack(format
, channel
, native_type
, suffix
)
740 generate_format_pack(format
, channel
, native_type
, suffix
)
741 generate_format_fetch(format
, channel
, native_type
, suffix
)
743 native_type
= 'unsigned'
745 channel
= Channel(UNSIGNED
, False, True, 32)
746 generate_format_unpack(format
, channel
, native_type
, suffix
)
747 generate_format_pack(format
, channel
, native_type
, suffix
)
749 channel
= Channel(FLOAT
, False, False, 32)
750 native_type
= 'float'
751 suffix
= 'rgba_float'
753 generate_format_unpack(format
, channel
, native_type
, suffix
)
754 generate_format_pack(format
, channel
, native_type
, suffix
)
755 generate_format_fetch(format
, channel
, native_type
, suffix
)
757 channel
= Channel(UNSIGNED
, True, False, 8)
758 native_type
= 'uint8_t'
759 suffix
= 'rgba_8unorm'
761 generate_format_unpack(format
, channel
, native_type
, suffix
)
762 generate_format_pack(format
, channel
, native_type
, suffix
)