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