1 /*
2 * Cogl
3 *
4 * A Low Level GPU Graphics and Utilities API
5 *
6 * Copyright (C) 2009 Intel Corporation.
7 *
8 * Permission is hereby granted, free of charge, to any person
9 * obtaining a copy of this software and associated documentation
10 * files (the "Software"), to deal in the Software without
11 * restriction, including without limitation the rights to use, copy,
12 * modify, merge, publish, distribute, sublicense, and/or sell copies
13 * of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be
17 * included in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
23 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
24 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 * SOFTWARE.
27 *
28 *
29 *
30 * Authors:
31 * Robert Bragg <robert@linux.intel.com>
32 */
33
34 #include "cogl-config.h"
35
36 #include <stdlib.h>
37 #include <string.h>
38
39 #include <glib.h>
40
41 #include "cogl-context-private.h"
42 #include "cogl-debug.h"
43 #include "cogl-blend-string.h"
44
45 typedef enum _ParserState
46 {
47 PARSER_STATE_EXPECT_DEST_CHANNELS,
48 PARSER_STATE_SCRAPING_DEST_CHANNELS,
49 PARSER_STATE_EXPECT_FUNCTION_NAME,
50 PARSER_STATE_SCRAPING_FUNCTION_NAME,
51 PARSER_STATE_EXPECT_ARG_START,
52 PARSER_STATE_EXPECT_STATEMENT_END
53 } ParserState;
54
55 typedef enum _ParserArgState
56 {
57 PARSER_ARG_STATE_START,
58 PARSER_ARG_STATE_EXPECT_MINUS,
59 PARSER_ARG_STATE_EXPECT_COLOR_SRC_NAME,
60 PARSER_ARG_STATE_SCRAPING_COLOR_SRC_NAME,
61 PARSER_ARG_STATE_MAYBE_COLOR_MASK,
62 PARSER_ARG_STATE_SCRAPING_MASK,
63 PARSER_ARG_STATE_MAYBE_MULT,
64 PARSER_ARG_STATE_EXPECT_OPEN_PAREN,
65 PARSER_ARG_STATE_EXPECT_FACTOR,
66 PARSER_ARG_STATE_MAYBE_SRC_ALPHA_SATURATE,
67 PARSER_ARG_STATE_MAYBE_MINUS,
68 PARSER_ARG_STATE_EXPECT_CLOSE_PAREN,
69 PARSER_ARG_STATE_EXPECT_END
70 } ParserArgState;
71
72
73 #define DEFINE_COLOR_SOURCE(NAME, NAME_LEN) \
74 {/*.type = */COGL_BLEND_STRING_COLOR_SOURCE_ ## NAME, \
75 /*.name = */#NAME, \
76 /*.name_len = */NAME_LEN}
77
78 static CoglBlendStringColorSourceInfo blending_color_sources[] = {
79 DEFINE_COLOR_SOURCE (SRC_COLOR, 9),
80 DEFINE_COLOR_SOURCE (DST_COLOR, 9),
81 DEFINE_COLOR_SOURCE (CONSTANT, 8)
82 };
83
84 static CoglBlendStringColorSourceInfo tex_combine_color_sources[] = {
85 DEFINE_COLOR_SOURCE (TEXTURE, 7),
86 /* DEFINE_COLOR_SOURCE (TEXTURE_N, *) - handled manually */
87 DEFINE_COLOR_SOURCE (PRIMARY, 7),
88 DEFINE_COLOR_SOURCE (CONSTANT, 8),
89 DEFINE_COLOR_SOURCE (PREVIOUS, 8)
90 };
91
92 static CoglBlendStringColorSourceInfo tex_combine_texture_n_color_source = {
93 /*.type = */COGL_BLEND_STRING_COLOR_SOURCE_TEXTURE_N,
94 /*.name = */"TEXTURE_N",
95 /*.name_len = */0
96 };
97
98 #undef DEFINE_COLOR_SOURCE
99
100 #define DEFINE_FUNCTION(NAME, NAME_LEN, ARGC) \
101 { /*.type = */COGL_BLEND_STRING_FUNCTION_ ## NAME, \
102 /*.name = */#NAME, \
103 /*.name_len = */NAME_LEN, \
104 /*.argc = */ARGC }
105
106 /* NB: These must be sorted so any name that's a subset of another
107 * comes later than the longer name. */
108 static CoglBlendStringFunctionInfo tex_combine_functions[] = {
109 DEFINE_FUNCTION (REPLACE, 7, 1),
110 DEFINE_FUNCTION (MODULATE, 8, 2),
111 DEFINE_FUNCTION (ADD_SIGNED, 10, 2),
112 DEFINE_FUNCTION (ADD, 3, 2),
113 DEFINE_FUNCTION (INTERPOLATE, 11, 3),
114 DEFINE_FUNCTION (SUBTRACT, 8, 2),
115 DEFINE_FUNCTION (DOT3_RGBA, 9, 2),
116 DEFINE_FUNCTION (DOT3_RGB, 8, 2)
117 };
118
119 static CoglBlendStringFunctionInfo blend_functions[] = {
120 DEFINE_FUNCTION (ADD, 3, 2)
121 };
122
123 #undef DEFINE_FUNCTION
124
125 uint32_t
cogl_blend_string_error_quark(void)126 cogl_blend_string_error_quark (void)
127 {
128 return g_quark_from_static_string ("cogl-blend-string-error-quark");
129 }
130
131 void
_cogl_blend_string_split_rgba_statement(CoglBlendStringStatement * statement,CoglBlendStringStatement * rgb,CoglBlendStringStatement * a)132 _cogl_blend_string_split_rgba_statement (CoglBlendStringStatement *statement,
133 CoglBlendStringStatement *rgb,
134 CoglBlendStringStatement *a)
135 {
136 int i;
137
138 memcpy (rgb, statement, sizeof (CoglBlendStringStatement));
139 memcpy (a, statement, sizeof (CoglBlendStringStatement));
140
141 rgb->mask = COGL_BLEND_STRING_CHANNEL_MASK_RGB;
142 a->mask = COGL_BLEND_STRING_CHANNEL_MASK_ALPHA;
143
144 for (i = 0; i < statement->function->argc; i++)
145 {
146 CoglBlendStringArgument *arg = &statement->args[i];
147 CoglBlendStringArgument *rgb_arg = &rgb->args[i];
148 CoglBlendStringArgument *a_arg = &a->args[i];
149
150 if (arg->source.mask == COGL_BLEND_STRING_CHANNEL_MASK_RGBA)
151 {
152 rgb_arg->source.mask = COGL_BLEND_STRING_CHANNEL_MASK_RGB;
153 a_arg->source.mask = COGL_BLEND_STRING_CHANNEL_MASK_ALPHA;
154 }
155
156 if (arg->factor.is_color &&
157 arg->factor.source.mask == COGL_BLEND_STRING_CHANNEL_MASK_RGBA)
158 {
159 rgb_arg->factor.source.mask = COGL_BLEND_STRING_CHANNEL_MASK_RGB;
160 a_arg->factor.source.mask = COGL_BLEND_STRING_CHANNEL_MASK_ALPHA;
161 }
162 }
163 }
164
165 static gboolean
validate_tex_combine_statements(CoglBlendStringStatement * statements,int n_statements,GError ** error)166 validate_tex_combine_statements (CoglBlendStringStatement *statements,
167 int n_statements,
168 GError **error)
169 {
170 int i, j;
171 const char *error_string;
172 CoglBlendStringError detail = COGL_BLEND_STRING_ERROR_INVALID_ERROR;
173
174 for (i = 0; i < n_statements; i++)
175 {
176 for (j = 0; j < statements[i].function->argc; j++)
177 {
178 CoglBlendStringArgument *arg = &statements[i].args[j];
179 if (arg->source.is_zero)
180 {
181 error_string = "You can't use the constant '0' as a texture "
182 "combine argument";
183 goto error;
184 }
185 if (!arg->factor.is_one)
186 {
187 error_string = "Argument factors are only relevant to blending "
188 "not texture combining";
189 goto error;
190 }
191 }
192 }
193
194 return TRUE;
195
196 error:
197 g_set_error (error, COGL_BLEND_STRING_ERROR, detail,
198 "Invalid texture combine string: %s", error_string);
199
200 if (COGL_DEBUG_ENABLED (COGL_DEBUG_BLEND_STRINGS))
201 {
202 g_debug ("Invalid texture combine string: %s",
203 error_string);
204 }
205 return FALSE;
206 }
207
208 static gboolean
validate_blend_statements(CoglBlendStringStatement * statements,int n_statements,GError ** error)209 validate_blend_statements (CoglBlendStringStatement *statements,
210 int n_statements,
211 GError **error)
212 {
213 int i, j;
214 const char *error_string;
215 CoglBlendStringError detail = COGL_BLEND_STRING_ERROR_INVALID_ERROR;
216
217 _COGL_GET_CONTEXT (ctx, 0);
218
219 for (i = 0; i < n_statements; i++)
220 for (j = 0; j < statements[i].function->argc; j++)
221 {
222 CoglBlendStringArgument *arg = &statements[i].args[j];
223
224 if (arg->source.is_zero)
225 continue;
226
227 if ((j == 0 &&
228 arg->source.info->type !=
229 COGL_BLEND_STRING_COLOR_SOURCE_SRC_COLOR)
230 || (j == 1 &&
231 arg->source.info->type !=
232 COGL_BLEND_STRING_COLOR_SOURCE_DST_COLOR))
233 {
234 error_string = "For blending you must always use SRC_COLOR "
235 "for arg0 and DST_COLOR for arg1";
236 goto error;
237 }
238 }
239
240 return TRUE;
241
242 error:
243 g_set_error (error, COGL_BLEND_STRING_ERROR, detail,
244 "Invalid blend string: %s", error_string);
245 return FALSE;
246 }
247
248 static gboolean
validate_statements_for_context(CoglBlendStringStatement * statements,int n_statements,CoglBlendStringContext context,GError ** error)249 validate_statements_for_context (CoglBlendStringStatement *statements,
250 int n_statements,
251 CoglBlendStringContext context,
252 GError **error)
253 {
254 const char *error_string;
255
256 if (n_statements == 1)
257 {
258 if (statements[0].mask == COGL_BLEND_STRING_CHANNEL_MASK_ALPHA)
259 {
260 error_string = "You need to also give a blend statement for the RGB"
261 "channels";
262 goto error;
263 }
264 else if (statements[0].mask == COGL_BLEND_STRING_CHANNEL_MASK_RGB)
265 {
266 error_string = "You need to also give a blend statement for the "
267 "Alpha channel";
268 goto error;
269 }
270 }
271
272 if (context == COGL_BLEND_STRING_CONTEXT_BLENDING)
273 return validate_blend_statements (statements, n_statements, error);
274 else
275 return validate_tex_combine_statements (statements, n_statements, error);
276
277 error:
278 g_set_error (error, COGL_BLEND_STRING_ERROR,
279 COGL_BLEND_STRING_ERROR_INVALID_ERROR,
280 "Invalid %s string: %s",
281 context == COGL_BLEND_STRING_CONTEXT_BLENDING ?
282 "blend" : "texture combine",
283 error_string);
284
285 if (COGL_DEBUG_ENABLED (COGL_DEBUG_BLEND_STRINGS))
286 {
287 g_debug ("Invalid %s string: %s",
288 context == COGL_BLEND_STRING_CONTEXT_BLENDING ?
289 "blend" : "texture combine",
290 error_string);
291 }
292
293 return FALSE;
294 }
295
296 static void
print_argument(CoglBlendStringArgument * arg)297 print_argument (CoglBlendStringArgument *arg)
298 {
299 const char *mask_names[] = {
300 "RGB",
301 "A",
302 "RGBA"
303 };
304
305 g_print (" Arg:\n");
306 g_print (" is zero = %s\n", arg->source.is_zero ? "yes" : "no");
307 if (!arg->source.is_zero)
308 {
309 g_print (" color source = %s\n", arg->source.info->name);
310 g_print (" one minus = %s\n", arg->source.one_minus ? "yes" : "no");
311 g_print (" mask = %s\n", mask_names[arg->source.mask]);
312 g_print (" texture = %d\n", arg->source.texture);
313 g_print ("\n");
314 g_print (" factor is_one = %s\n", arg->factor.is_one ? "yes" : "no");
315 g_print (" factor is_src_alpha_saturate = %s\n",
316 arg->factor.is_src_alpha_saturate ? "yes" : "no");
317 g_print (" factor is_color = %s\n", arg->factor.is_color ? "yes" : "no");
318 if (arg->factor.is_color)
319 {
320 g_print (" factor color:is zero = %s\n",
321 arg->factor.source.is_zero ? "yes" : "no");
322 g_print (" factor color:color source = %s\n",
323 arg->factor.source.info->name);
324 g_print (" factor color:one minus = %s\n",
325 arg->factor.source.one_minus ? "yes" : "no");
326 g_print (" factor color:mask = %s\n",
327 mask_names[arg->factor.source.mask]);
328 g_print (" factor color:texture = %d\n",
329 arg->factor.source.texture);
330 }
331 }
332 }
333
334 static void
print_statement(int num,CoglBlendStringStatement * statement)335 print_statement (int num, CoglBlendStringStatement *statement)
336 {
337 const char *mask_names[] = {
338 "RGB",
339 "A",
340 "RGBA"
341 };
342 int i;
343 g_print ("Statement %d:\n", num);
344 g_print (" Destination channel mask = %s\n",
345 mask_names[statement->mask]);
346 g_print (" Function = %s\n", statement->function->name);
347 for (i = 0; i < statement->function->argc; i++)
348 print_argument (&statement->args[i]);
349 }
350
351 static const CoglBlendStringFunctionInfo *
get_function_info(const char * mark,const char * p,CoglBlendStringContext context)352 get_function_info (const char *mark,
353 const char *p,
354 CoglBlendStringContext context)
355 {
356 size_t len = p - mark;
357 CoglBlendStringFunctionInfo *functions;
358 size_t array_len;
359 int i;
360
361 if (context == COGL_BLEND_STRING_CONTEXT_BLENDING)
362 {
363 functions = blend_functions;
364 array_len = G_N_ELEMENTS (blend_functions);
365 }
366 else
367 {
368 functions = tex_combine_functions;
369 array_len = G_N_ELEMENTS (tex_combine_functions);
370 }
371
372 for (i = 0; i < array_len; i++)
373 {
374 if (len >= functions[i].name_len
375 && strncmp (mark, functions[i].name, functions[i].name_len) == 0)
376 return &functions[i];
377 }
378 return NULL;
379 }
380
381 static const CoglBlendStringColorSourceInfo *
get_color_src_info(const char * mark,const char * p,CoglBlendStringContext context)382 get_color_src_info (const char *mark,
383 const char *p,
384 CoglBlendStringContext context)
385 {
386 size_t len = p - mark;
387 CoglBlendStringColorSourceInfo *sources;
388 size_t array_len;
389 int i;
390
391 if (context == COGL_BLEND_STRING_CONTEXT_BLENDING)
392 {
393 sources = blending_color_sources;
394 array_len = G_N_ELEMENTS (blending_color_sources);
395 }
396 else
397 {
398 sources = tex_combine_color_sources;
399 array_len = G_N_ELEMENTS (tex_combine_color_sources);
400 }
401
402 if (len >= 8 &&
403 strncmp (mark, "TEXTURE_", 8) == 0 &&
404 g_ascii_isdigit (mark[8]))
405 {
406 return &tex_combine_texture_n_color_source;
407 }
408
409 for (i = 0; i < array_len; i++)
410 {
411 if (len >= sources[i].name_len
412 && strncmp (mark, sources[i].name, sources[i].name_len) == 0)
413 return &sources[i];
414 }
415
416 return NULL;
417 }
418
419 static gboolean
is_symbol_char(const char c)420 is_symbol_char (const char c)
421 {
422 return (g_ascii_isalpha (c) || c == '_') ? TRUE : FALSE;
423 }
424
425 static gboolean
is_alphanum_char(const char c)426 is_alphanum_char (const char c)
427 {
428 return (g_ascii_isalnum (c) || c == '_') ? TRUE : FALSE;
429 }
430
431 static gboolean
parse_argument(const char * string,const char ** ret_p,const CoglBlendStringStatement * statement,int current_arg,CoglBlendStringArgument * arg,CoglBlendStringContext context,GError ** error)432 parse_argument (const char *string, /* original user string */
433 const char **ret_p, /* start of argument IN:OUT */
434 const CoglBlendStringStatement *statement,
435 int current_arg,
436 CoglBlendStringArgument *arg, /* OUT */
437 CoglBlendStringContext context,
438 GError **error)
439 {
440 const char *p = *ret_p;
441 const char *mark = NULL;
442 const char *error_string = NULL;
443 ParserArgState state = PARSER_ARG_STATE_START;
444 gboolean parsing_factor = FALSE;
445 gboolean implicit_factor_brace = FALSE;
446
447 arg->source.is_zero = FALSE;
448 arg->source.info = NULL;
449 arg->source.texture = 0;
450 arg->source.one_minus = FALSE;
451 arg->source.mask = statement->mask;
452
453 arg->factor.is_one = FALSE;
454 arg->factor.is_color = FALSE;
455 arg->factor.is_src_alpha_saturate = FALSE;
456
457 arg->factor.source.is_zero = FALSE;
458 arg->factor.source.info = NULL;
459 arg->factor.source.texture = 0;
460 arg->factor.source.one_minus = FALSE;
461 arg->factor.source.mask = statement->mask;
462
463 do
464 {
465 if (g_ascii_isspace (*p))
466 continue;
467
468 if (*p == '\0')
469 {
470 error_string = "Unexpected end of string while parsing argument";
471 goto error;
472 }
473
474 switch (state)
475 {
476 case PARSER_ARG_STATE_START:
477 if (*p == '1')
478 state = PARSER_ARG_STATE_EXPECT_MINUS;
479 else if (*p == '0')
480 {
481 arg->source.is_zero = TRUE;
482 state = PARSER_ARG_STATE_EXPECT_END;
483 }
484 else
485 {
486 p--; /* backtrack */
487 state = PARSER_ARG_STATE_EXPECT_COLOR_SRC_NAME;
488 }
489 continue;
490
491 case PARSER_ARG_STATE_EXPECT_MINUS:
492 if (*p != '-')
493 {
494 error_string = "expected a '-' following the 1";
495 goto error;
496 }
497 arg->source.one_minus = TRUE;
498 state = PARSER_ARG_STATE_EXPECT_COLOR_SRC_NAME;
499 continue;
500
501 case PARSER_ARG_STATE_EXPECT_COLOR_SRC_NAME:
502 if (!is_symbol_char (*p))
503 {
504 error_string = "expected a color source name";
505 goto error;
506 }
507 state = PARSER_ARG_STATE_SCRAPING_COLOR_SRC_NAME;
508 mark = p;
509 if (parsing_factor)
510 arg->factor.is_color = TRUE;
511
512 G_GNUC_FALLTHROUGH;
513 case PARSER_ARG_STATE_SCRAPING_COLOR_SRC_NAME:
514 if (!is_symbol_char (*p))
515 {
516 CoglBlendStringColorSource *source =
517 parsing_factor ? &arg->factor.source : &arg->source;
518 source->info = get_color_src_info (mark, p, context);
519 if (!source->info)
520 {
521 error_string = "Unknown color source name";
522 goto error;
523 }
524 if (source->info->type ==
525 COGL_BLEND_STRING_COLOR_SOURCE_TEXTURE_N)
526 {
527 char *endp;
528 source->texture =
529 strtoul (&mark[strlen ("TEXTURE_")], &endp, 10);
530 if (mark == endp)
531 {
532 error_string = "invalid texture number given with "
533 "TEXTURE_N color source";
534 goto error;
535 }
536 p = endp;
537 }
538 state = PARSER_ARG_STATE_MAYBE_COLOR_MASK;
539 }
540 else
541 continue;
542
543 G_GNUC_FALLTHROUGH;
544 case PARSER_ARG_STATE_MAYBE_COLOR_MASK:
545 if (*p != '[')
546 {
547 p--; /* backtrack */
548 if (!parsing_factor)
549 state = PARSER_ARG_STATE_MAYBE_MULT;
550 else
551 state = PARSER_ARG_STATE_EXPECT_END;
552 continue;
553 }
554 state = PARSER_ARG_STATE_SCRAPING_MASK;
555 mark = p;
556
557 G_GNUC_FALLTHROUGH;
558 case PARSER_ARG_STATE_SCRAPING_MASK:
559 if (*p == ']')
560 {
561 size_t len = p - mark;
562 CoglBlendStringColorSource *source =
563 parsing_factor ? &arg->factor.source : &arg->source;
564
565 if (len == 5 && strncmp (mark, "[RGBA", len) == 0)
566 {
567 if (statement->mask != COGL_BLEND_STRING_CHANNEL_MASK_RGBA)
568 {
569 error_string = "You can't use an RGBA color mask if the "
570 "statement hasn't also got an RGBA= mask";
571 goto error;
572 }
573 source->mask = COGL_BLEND_STRING_CHANNEL_MASK_RGBA;
574 }
575 else if (len == 4 && strncmp (mark, "[RGB", len) == 0)
576 source->mask = COGL_BLEND_STRING_CHANNEL_MASK_RGB;
577 else if (len == 2 && strncmp (mark, "[A", len) == 0)
578 source->mask = COGL_BLEND_STRING_CHANNEL_MASK_ALPHA;
579 else
580 {
581 error_string = "Expected a channel mask of [RGBA]"
582 "[RGB] or [A]";
583 goto error;
584 }
585 if (parsing_factor)
586 state = PARSER_ARG_STATE_EXPECT_CLOSE_PAREN;
587 else
588 state = PARSER_ARG_STATE_MAYBE_MULT;
589 }
590 continue;
591
592 case PARSER_ARG_STATE_EXPECT_OPEN_PAREN:
593 if (*p != '(')
594 {
595 if (is_alphanum_char (*p))
596 {
597 p--; /* compensate for implicit brace and ensure this
598 * char gets considered part of the blend factor */
599 implicit_factor_brace = TRUE;
600 }
601 else
602 {
603 error_string = "Expected '(' around blend factor or alpha "
604 "numeric character for blend factor name";
605 goto error;
606 }
607 }
608 else
609 implicit_factor_brace = FALSE;
610 parsing_factor = TRUE;
611 state = PARSER_ARG_STATE_EXPECT_FACTOR;
612 continue;
613
614 case PARSER_ARG_STATE_EXPECT_FACTOR:
615 if (*p == '1')
616 state = PARSER_ARG_STATE_MAYBE_MINUS;
617 else if (*p == '0')
618 {
619 arg->source.is_zero = TRUE;
620 state = PARSER_ARG_STATE_EXPECT_CLOSE_PAREN;
621 }
622 else
623 {
624 state = PARSER_ARG_STATE_MAYBE_SRC_ALPHA_SATURATE;
625 mark = p;
626 }
627 continue;
628
629 case PARSER_ARG_STATE_MAYBE_SRC_ALPHA_SATURATE:
630 if (!is_symbol_char (*p))
631 {
632 size_t len = p - mark;
633 if (len >= strlen ("SRC_ALPHA_SATURATE") &&
634 strncmp (mark, "SRC_ALPHA_SATURATE", len) == 0)
635 {
636 arg->factor.is_src_alpha_saturate = TRUE;
637 state = PARSER_ARG_STATE_EXPECT_CLOSE_PAREN;
638 }
639 else
640 {
641 state = PARSER_ARG_STATE_EXPECT_COLOR_SRC_NAME;
642 p = mark - 1; /* backtrack */
643 }
644 }
645 continue;
646
647 case PARSER_ARG_STATE_MAYBE_MINUS:
648 if (*p == '-')
649 {
650 if (implicit_factor_brace)
651 {
652 error_string = "Expected ( ) braces around blend factor with "
653 "a subtraction";
654 goto error;
655 }
656 arg->factor.source.one_minus = TRUE;
657 state = PARSER_ARG_STATE_EXPECT_COLOR_SRC_NAME;
658 }
659 else
660 {
661 arg->factor.is_one = TRUE;
662 state = PARSER_ARG_STATE_EXPECT_CLOSE_PAREN;
663 }
664 continue;
665
666 case PARSER_ARG_STATE_EXPECT_CLOSE_PAREN:
667 if (implicit_factor_brace)
668 {
669 p--;
670 state = PARSER_ARG_STATE_EXPECT_END;
671 continue;
672 }
673 if (*p != ')')
674 {
675 error_string = "Expected closing parenthesis after blend factor";
676 goto error;
677 }
678 state = PARSER_ARG_STATE_EXPECT_END;
679 continue;
680
681 case PARSER_ARG_STATE_MAYBE_MULT:
682 if (*p == '*')
683 {
684 state = PARSER_ARG_STATE_EXPECT_OPEN_PAREN;
685 continue;
686 }
687 arg->factor.is_one = TRUE;
688 state = PARSER_ARG_STATE_EXPECT_END;
689
690 G_GNUC_FALLTHROUGH;
691 case PARSER_ARG_STATE_EXPECT_END:
692 if (*p != ',' && *p != ')')
693 {
694 error_string = "expected , or )";
695 goto error;
696 }
697
698 *ret_p = p - 1;
699 return TRUE;
700 }
701 }
702 while (p++);
703
704 error:
705 {
706 int offset = p - string;
707 g_set_error (error,
708 COGL_BLEND_STRING_ERROR,
709 COGL_BLEND_STRING_ERROR_ARGUMENT_PARSE_ERROR,
710 "Syntax error for argument %d at offset %d: %s",
711 current_arg,
712 offset,
713 error_string);
714
715 if (COGL_DEBUG_ENABLED (COGL_DEBUG_BLEND_STRINGS))
716 {
717 g_debug ("Syntax error for argument %d at offset %d: %s",
718 current_arg, offset, error_string);
719 }
720 return FALSE;
721 }
722 }
723
724 int
_cogl_blend_string_compile(const char * string,CoglBlendStringContext context,CoglBlendStringStatement * statements,GError ** error)725 _cogl_blend_string_compile (const char *string,
726 CoglBlendStringContext context,
727 CoglBlendStringStatement *statements,
728 GError **error)
729 {
730 const char *p = string;
731 const char *mark = NULL;
732 const char *error_string;
733 ParserState state = PARSER_STATE_EXPECT_DEST_CHANNELS;
734 CoglBlendStringStatement *statement = statements;
735 int current_statement = 0;
736 int current_arg = 0;
737 int remaining_argc = 0;
738
739 #if 0
740 COGL_DEBUG_SET_FLAG (COGL_DEBUG_BLEND_STRINGS);
741 #endif
742
743 if (COGL_DEBUG_ENABLED (COGL_DEBUG_BLEND_STRINGS))
744 {
745 COGL_NOTE (BLEND_STRINGS, "Compiling %s string:\n%s\n",
746 context == COGL_BLEND_STRING_CONTEXT_BLENDING ?
747 "blend" : "texture combine",
748 string);
749 }
750
751 do
752 {
753 if (g_ascii_isspace (*p))
754 continue;
755
756 if (*p == '\0')
757 {
758 switch (state)
759 {
760 case PARSER_STATE_EXPECT_DEST_CHANNELS:
761 if (current_statement != 0)
762 goto finished;
763 error_string = "Empty statement";
764 goto error;
765 case PARSER_STATE_SCRAPING_DEST_CHANNELS:
766 error_string = "Expected an '=' following the destination "
767 "channel mask";
768 goto error;
769 case PARSER_STATE_EXPECT_FUNCTION_NAME:
770 error_string = "Expected a function name";
771 goto error;
772 case PARSER_STATE_SCRAPING_FUNCTION_NAME:
773 error_string = "Expected parenthesis after the function name";
774 goto error;
775 case PARSER_STATE_EXPECT_ARG_START:
776 error_string = "Expected to find the start of an argument";
777 goto error;
778 case PARSER_STATE_EXPECT_STATEMENT_END:
779 error_string = "Expected closing parenthesis for statement";
780 goto error;
781 }
782 }
783
784 switch (state)
785 {
786 case PARSER_STATE_EXPECT_DEST_CHANNELS:
787 mark = p;
788 state = PARSER_STATE_SCRAPING_DEST_CHANNELS;
789
790 G_GNUC_FALLTHROUGH;
791 case PARSER_STATE_SCRAPING_DEST_CHANNELS:
792 if (*p != '=')
793 continue;
794 if (strncmp (mark, "RGBA", 4) == 0)
795 statement->mask = COGL_BLEND_STRING_CHANNEL_MASK_RGBA;
796 else if (strncmp (mark, "RGB", 3) == 0)
797 statement->mask = COGL_BLEND_STRING_CHANNEL_MASK_RGB;
798 else if (strncmp (mark, "A", 1) == 0)
799 statement->mask = COGL_BLEND_STRING_CHANNEL_MASK_ALPHA;
800 else
801 {
802 error_string = "Unknown destination channel mask; "
803 "expected RGBA=, RGB= or A=";
804 goto error;
805 }
806 state = PARSER_STATE_EXPECT_FUNCTION_NAME;
807 continue;
808
809 case PARSER_STATE_EXPECT_FUNCTION_NAME:
810 mark = p;
811 state = PARSER_STATE_SCRAPING_FUNCTION_NAME;
812
813 G_GNUC_FALLTHROUGH;
814 case PARSER_STATE_SCRAPING_FUNCTION_NAME:
815 if (*p != '(')
816 {
817 if (!is_alphanum_char (*p))
818 {
819 error_string = "non alpha numeric character in function"
820 "name";
821 goto error;
822 }
823 continue;
824 }
825 statement->function = get_function_info (mark, p, context);
826 if (!statement->function)
827 {
828 error_string = "Unknown function name";
829 goto error;
830 }
831 remaining_argc = statement->function->argc;
832 current_arg = 0;
833 state = PARSER_STATE_EXPECT_ARG_START;
834
835 G_GNUC_FALLTHROUGH;
836 case PARSER_STATE_EXPECT_ARG_START:
837 if (*p != '(' && *p != ',')
838 continue;
839 if (remaining_argc)
840 {
841 p++; /* parse_argument expects to see the first char of the arg */
842 if (!parse_argument (string, &p, statement,
843 current_arg, &statement->args[current_arg],
844 context, error))
845 return 0;
846 current_arg++;
847 remaining_argc--;
848 }
849 if (!remaining_argc)
850 state = PARSER_STATE_EXPECT_STATEMENT_END;
851 continue;
852
853 case PARSER_STATE_EXPECT_STATEMENT_END:
854 if (*p != ')')
855 {
856 error_string = "Expected end of statement";
857 goto error;
858 }
859 state = PARSER_STATE_EXPECT_DEST_CHANNELS;
860 if (current_statement++ == 1)
861 goto finished;
862 statement = &statements[current_statement];
863 }
864 }
865 while (p++);
866
867 finished:
868
869 if (COGL_DEBUG_ENABLED (COGL_DEBUG_BLEND_STRINGS))
870 {
871 if (current_statement > 0)
872 print_statement (0, &statements[0]);
873 if (current_statement > 1)
874 print_statement (1, &statements[1]);
875 }
876
877 if (!validate_statements_for_context (statements,
878 current_statement,
879 context,
880 error))
881 return 0;
882
883 return current_statement;
884
885 error:
886 {
887 int offset = p - string;
888 g_set_error (error,
889 COGL_BLEND_STRING_ERROR,
890 COGL_BLEND_STRING_ERROR_PARSE_ERROR,
891 "Syntax error at offset %d: %s",
892 offset,
893 error_string);
894
895 if (COGL_DEBUG_ENABLED (COGL_DEBUG_BLEND_STRINGS))
896 {
897 g_debug ("Syntax error at offset %d: %s",
898 offset, error_string);
899 }
900 return 0;
901 }
902 }
903
904 /*
905 * INTERNAL TESTING CODE ...
906 */
907
908 struct _TestString
909 {
910 const char *string;
911 CoglBlendStringContext context;
912 };
913
914 /* FIXME: this should probably be moved to a unit test */
915 int
916 _cogl_blend_string_test (void);
917
918 int
_cogl_blend_string_test(void)919 _cogl_blend_string_test (void)
920 {
921 struct _TestString strings[] = {
922 {" A = MODULATE ( TEXTURE[RGB], PREVIOUS[A], PREVIOUS[A] ) ",
923 COGL_BLEND_STRING_CONTEXT_TEXTURE_COMBINE },
924 {" RGB = MODULATE ( TEXTURE[RGB], PREVIOUS[A] ) ",
925 COGL_BLEND_STRING_CONTEXT_TEXTURE_COMBINE },
926 {"A=ADD(TEXTURE[A],PREVIOUS[RGB])",
927 COGL_BLEND_STRING_CONTEXT_TEXTURE_COMBINE },
928 {"A=ADD(TEXTURE[A],PREVIOUS[RGB])",
929 COGL_BLEND_STRING_CONTEXT_TEXTURE_COMBINE },
930
931 {"RGBA = ADD(SRC_COLOR*(SRC_COLOR[A]), DST_COLOR*(1-SRC_COLOR[A]))",
932 COGL_BLEND_STRING_CONTEXT_BLENDING },
933 {"RGB = ADD(SRC_COLOR, DST_COLOR*(0))",
934 COGL_BLEND_STRING_CONTEXT_BLENDING },
935 {"RGB = ADD(SRC_COLOR, 0)",
936 COGL_BLEND_STRING_CONTEXT_BLENDING },
937 {"RGB = ADD()",
938 COGL_BLEND_STRING_CONTEXT_BLENDING },
939 {"RGB = ADD(SRC_COLOR, 0, DST_COLOR)",
940 COGL_BLEND_STRING_CONTEXT_BLENDING },
941 {NULL}
942 };
943 int i;
944
945 GError *error = NULL;
946 for (i = 0; strings[i].string; i++)
947 {
948 CoglBlendStringStatement statements[2];
949 int count = _cogl_blend_string_compile (strings[i].string,
950 strings[i].context,
951 statements,
952 &error);
953 if (!count)
954 {
955 g_print ("Failed to parse string:\n%s\n%s\n",
956 strings[i].string,
957 error->message);
958 g_error_free (error);
959 error = NULL;
960 continue;
961 }
962 g_print ("Original:\n");
963 g_print ("%s\n", strings[i].string);
964 if (count > 0)
965 print_statement (0, &statements[0]);
966 if (count > 1)
967 print_statement (1, &statements[1]);
968 }
969
970 return 0;
971 }
972
973