• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2024 Intel Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  */
23 
24 #include <assert.h>
25 #include <ctype.h>
26 #include <dirent.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdarg.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33 
34 #include "screenshot_params.h"
35 
36 #include "util/os_socket.h"
37 
38 enum LogType LOG_TYPE = REQUIRED;
39 
print_log_type(enum LogType log_type)40 static const char *print_log_type(enum LogType log_type) {
41    switch(log_type)
42    {
43       case(DEBUG):
44          return "DEBUG";
45       case(ERROR):
46          return "ERROR";
47       case(INFO):
48          return "INFO";
49       case(NO_PREFIX):
50          return "NO_PREFIX";
51       case(REQUIRED):
52          return "REQUIRED";
53       case(WARN):
54          return "WARN";
55       default:
56          /* Don't show log type*/
57          return "";
58    }
59 }
60 
LOG(enum LogType log_type,const char * format,...)61 void LOG(enum LogType log_type, const char *format, ...) {
62    FILE *file_type;
63    va_list args;
64    if (log_type == WARN || log_type == ERROR) {
65       file_type = stderr;
66    } else {
67       file_type = stdout;
68    }
69    if (log_type == DEBUG && LOG_TYPE != DEBUG) {
70       return;
71    } else if (log_type == INFO && (LOG_TYPE != INFO && LOG_TYPE != DEBUG)) {
72       return;
73    }
74    if (log_type != NO_PREFIX)
75       fprintf(file_type, "mesa-screenshot: %s: ", print_log_type(log_type));
76    va_start(args, format);
77    vfprintf(file_type, format, args);
78    va_end(args);
79 }
80 
81 static const char *
parse_control(const char * str)82 parse_control(const char *str)
83 {
84    static char control_str[64];
85    if (strlen(str) > 63) {
86       LOG(ERROR, "control string too long. Must be < 64 chars\n");
87       return NULL;
88    }
89    strcpy(control_str, str);
90 
91    return control_str;
92 }
93 
94 /* Inserts frame nodes in ascending order */
insert_frame(struct frame_list * list,uint32_t new_frame_num)95 static void insert_frame(struct frame_list *list, uint32_t new_frame_num)
96 {
97    struct frame_node *new_node, *curr, *next;
98    new_node = (struct frame_node*)malloc(sizeof(struct frame_node));
99    new_node->frame_num = new_frame_num;
100    new_node->next = NULL;
101    curr = list->head;
102 
103    /* Empty list */
104    if (list->head == NULL)
105       list->head = new_node;
106    /* Insert as new head of list */
107    else if (list->head->frame_num > new_frame_num) {
108       list->head = new_node;
109       new_node->next = curr;
110    /* Traverse list & insert frame number in correct, ascending location */
111    } else {
112       while (curr != NULL) {
113          if (curr->frame_num == new_frame_num) {
114             free(new_node);
115             return; // Avoid inserting duplicates
116          }
117          next = curr->next;
118          if (next) {
119             if (next->frame_num > new_frame_num) {
120                curr->next = new_node;
121                new_node->next = next;
122                break;
123             }
124          } else {
125             curr->next = new_node;
126             break;
127          }
128          curr = curr->next;
129       }
130    }
131    list->size++;
132 }
133 
remove_node(struct frame_list * list,struct frame_node * prev,struct frame_node * node)134 void remove_node(struct frame_list *list,
135                  struct frame_node *prev,
136                  struct frame_node *node) {
137    if (node) {
138       if (prev)
139          prev->next = node->next;
140       else {
141          list->head = node->next;
142       }
143       free(node);
144       list->size--;
145    } else
146       LOG(ERROR, "Encountered null node while removing from frame list\n");
147 }
148 
destroy_frame_list(struct frame_list * list)149 void destroy_frame_list(struct frame_list *list)
150 {
151    struct frame_node *curr, *prev;
152    if (!list || !list->head)
153       return;
154    else {
155       curr = list->head;
156       while (curr != NULL) {
157          prev = curr;
158          curr = curr->next;
159          free(prev);
160       }
161    }
162 }
163 
164 static unsigned
parse_unsigned(const char * str)165 parse_unsigned(const char *str)
166 {
167    return strtol(str, NULL, 0);
168 }
169 
is_frame_delimiter(char c)170 static bool is_frame_delimiter(char c)
171 {
172    return c == 0 ||  c == '/' || c == '-';
173 }
174 
175 static struct frame_list *
parse_frames(const char * str)176 parse_frames(const char *str)
177 {
178    int32_t range_start;
179    uint32_t range_counter, range_interval, range_end;
180    range_start = -1;
181    range_counter = 0;
182    uint32_t range_delimit_count = 0;
183    range_interval = 1;
184    char *prev_delim = NULL;
185    char str_buf[STANDARD_BUFFER_SIZE] = {0};
186    char *str_buf_ptr;
187    str_buf_ptr = str_buf;
188    struct frame_list *list = (struct frame_list*)malloc(sizeof(struct frame_list));
189    list->size = 0;
190    list->all_frames = false;
191 
192    if (!strcmp(str, "all")) {
193       /* Don't bother counting, we want all frames */
194       list->all_frames = true;
195    } else {
196       while (*str != 0) { // Still string left to parse
197          for (; !is_frame_delimiter(*str); str++, str_buf_ptr++) {
198             if (!isdigit(*str))
199             {
200                LOG(ERROR, "mesa-screenshot: syntax error: unexpected non-digit "
201                           "'%c' while parsing the frame numbers\n", *str);
202                destroy_frame_list(list);
203                return NULL;
204             }
205             *str_buf_ptr = *str;
206          }
207          if (strlen(str_buf) == 0) {
208             LOG(ERROR, "mesa-screenshot: syntax error: empty string given in frame range\n");
209             return NULL;
210          } else if (strlen(str_buf) > 0 && *str == '/') {
211             if (prev_delim && *prev_delim == '-') {
212                LOG(ERROR, "mesa-screenshot: syntax error: detected invalid individual " \
213                           "frame selection (/) after range selection (-)\n");
214                return NULL;
215             }
216             LOG(DEBUG, "Adding frame: %u\n", parse_unsigned(str_buf));
217             insert_frame(list, parse_unsigned(str_buf));
218          } else if (strlen(str_buf) > 0 && (*str == '-' || *str == 0 )) {
219             if (range_delimit_count < 1) {
220                LOG(DEBUG, "Range start set\n");
221                range_start = parse_unsigned(str_buf);
222                range_delimit_count++;
223             } else if(range_delimit_count < 2) {
224                LOG(DEBUG, "Range counter set\n");
225                range_counter = parse_unsigned(str_buf);
226                range_delimit_count++;
227             } else {
228                LOG(DEBUG, "Range interval set\n");
229                range_interval = parse_unsigned(str_buf);
230                break;
231             }
232             if (*str == 0) {
233                break;
234             }
235             prev_delim = (char *)str;
236          }
237          str++;
238          /* Reset buffer for next set of numbers */
239          memset(str_buf, '\0', sizeof(str_buf));
240          str_buf_ptr = str_buf;
241       }
242       range_end = range_start + (range_counter * range_interval);
243       if (range_start >= 0) {
244          int i = range_start;
245          do {
246             insert_frame(list, i);
247             i += range_interval;
248          } while (i < range_end);
249       }
250    }
251    LOG(INFO, "frame range: ");
252    if (list->all_frames) {
253       LOG(NO_PREFIX, "all");
254    } else {
255       for (struct frame_node *iter = list->head; iter != NULL; iter = iter->next) {
256          LOG(NO_PREFIX, "%u", iter->frame_num);
257          if(iter->next) {
258             LOG(NO_PREFIX, ", ");
259          }
260       }
261    }
262    LOG(NO_PREFIX, "\n");
263    return list;
264 }
265 
getRegionFromInput(const char * str)266 struct ImageRegion getRegionFromInput(const char *str) {
267    struct ImageRegion region;
268    region.useImageRegion = false;
269    region.startX = 0;
270    region.startY = 0;
271    region.endX = 1;
272    region.endY = 1;
273    /* Expected form is a tuple of four float entries, representing a percentage,
274       so need to attempt to convert the values to floating point type and ensure
275       the values are in the range 0.00 <= x <= 1.00.
276 
277       An example of proper input would be:
278       "0.20/0.20/0.75/0.60"
279    */
280    if (strlen(str) == 0) {
281       LOG(ERROR, "Region input was empty!\n");
282       return region;
283    }
284    errno = 0;
285    float dimensions[] = {0, 0, 1, 1};
286    char *dup = strdup(str);
287    char *token = strtok(dup, "/");
288    char *endptr;
289    int i;
290    for (i = 0; i < 4; i++, token = strtok(NULL, "/")) {
291       if (!token) {
292          LOG(ERROR, "Four region entries were not detected!\n");
293          break;
294       }
295       dimensions[i] = strtof(token, &endptr);
296       if (errno || endptr == token) {
297          LOG(ERROR, "Found non-float in region description: %s\n", token, errno);
298          break;
299       }
300       if (dimensions[i] < 0 || 1 < dimensions[i] ) {
301          LOG(ERROR, "Found invalid region value, region value must be between 0 and 1: %f\n", dimensions[i]);
302          break;
303       }
304    }
305    if (i == 4) {
306       if (dimensions[0] < dimensions[2] && dimensions[1] < dimensions[3]) {
307          region.startX = dimensions[0];
308          region.startY = dimensions[1];
309          region.endX = dimensions[2];
310          region.endY = dimensions[3];
311          region.useImageRegion = true;
312       } else {
313          LOG(ERROR, "Region end values need to be greater than region start values!\n");
314       }
315    }
316    free(dup);
317    return region;
318 }
319 
parse_region(const char * str)320 static struct ImageRegion parse_region(const char *str)
321 {
322    return getRegionFromInput(str);
323 }
324 
325 static bool
parse_help(const char * str)326 parse_help(const char *str)
327 {
328    LOG(NO_PREFIX, "Layer params using VK_LAYER_MESA_SCREENSHOT_CONFIG=\n");
329 #define SCREENSHOT_PARAM_BOOL(name)                \
330    LOG(NO_PREFIX, "\t%s=0|1\n", #name);
331 #define SCREENSHOT_PARAM_CUSTOM(name)
332    SCREENSHOT_PARAMS
333 #undef SCREENSHOT_PARAM_BOOL
334 #undef SCREENSHOT_PARAM_CUSTOM
335    LOG(NO_PREFIX, "\tlog_type=info|debug (if no selection, no logs besides errors are given)\n");
336    LOG(NO_PREFIX, "\toutput_dir='/path/to/dir'\n");
337    LOG(NO_PREFIX, "\tframes=Individual frames, separated by '/', followed by " \
338                   "a range setup, separated by '-', <range start>-<range count>-<range interval>\n" \
339                   "\tFor example '1/5/7/15-4-5' = [1,5,7,15,20,25,30]\n" \
340                   "\tframes='all' will select all frames.");
341 
342    return true;
343 }
344 
345 static enum LogType
parse_log_type(const char * str)346 parse_log_type(const char *str)
347 {
348    if(!strcmp(str, "info")) {
349       return INFO;
350    } else if (!strcmp(str, "debug")) {
351       return DEBUG;
352    } else {
353       /* Required logs only */
354       return REQUIRED;
355    }
356 }
357 
358 /* TODO: Improve detection of proper directory path */
359 static const char *
parse_output_dir(const char * str)360 parse_output_dir(const char *str)
361 {
362    static char output_dir[LARGE_BUFFER_SIZE];
363    strcpy(output_dir, str);
364    uint32_t last_char_index = strlen(str)-1;
365    // Ensure we're in bounds and the last character is '/'
366    if (last_char_index > 0 &&
367        str[last_char_index] != '/' &&
368        last_char_index < LARGE_BUFFER_SIZE-1) {
369       output_dir[last_char_index+1] = '/';
370    }
371    DIR *dir = opendir(output_dir);
372    assert(dir);
373    closedir(dir);
374 
375    return output_dir;
376 }
377 
is_delimiter(char c)378 static bool is_delimiter(char c)
379 {
380    return c == 0 || c == ',' || c == ':' || c == ';' || c == '=';
381 }
382 
383 static int
parse_string(const char * s,char * out_param,char * out_value)384 parse_string(const char *s, char *out_param, char *out_value)
385 {
386    int i = 0;
387 
388    for (; !is_delimiter(*s); s++, out_param++, i++)
389       *out_param = *s;
390 
391    *out_param = 0;
392 
393    if (*s == '=') {
394       s++;
395       i++;
396       for (; !is_delimiter(*s); s++, out_value++, i++)
397          *out_value = *s;
398    } else
399       *(out_value++) = '1';
400    *out_value = 0;
401 
402    if (*s && is_delimiter(*s)) {
403       s++;
404       i++;
405    }
406 
407    if (*s && !i) {
408       LOG(ERROR, "mesa-screenshot: syntax error: unexpected '%c' (%i) while "
409                  "parsing a string\n", *s, *s);
410    }
411    return i;
412 }
413 
414 const char *screenshot_param_names[] = {
415 #define SCREENSHOT_PARAM_BOOL(name) #name,
416 #define SCREENSHOT_PARAM_CUSTOM(name)
417    SCREENSHOT_PARAMS
418 #undef SCREENSHOT_PARAM_BOOL
419 #undef SCREENSHOT_PARAM_CUSTOM
420 };
421 
422 void
parse_screenshot_env(struct screenshot_params * params,const char * env)423 parse_screenshot_env(struct screenshot_params *params,
424                   const char *env)
425 {
426 
427    if (!env)
428       return;
429 
430    uint32_t num;
431    const char *itr = env;
432    char key[STANDARD_BUFFER_SIZE], value[LARGE_BUFFER_SIZE];
433 
434    memset(params, 0, sizeof(*params));
435 
436    params->control    = "mesa_screenshot";
437    params->frames     = NULL;
438    params->output_dir = NULL;
439    params->region.useImageRegion = false;
440 
441    /* Loop once first until log options found (if they exist) */
442    while ((num = parse_string(itr, key, value)) != 0) {
443       itr += num;
444       if (!strcmp("log_type", key)) {
445          LOG_TYPE = parse_log_type(value);
446          break;
447       }
448    }
449    /* Reset the iterator */
450    itr = env;
451 
452    while ((num = parse_string(itr, key, value)) != 0) {
453       itr += num;
454       if (!strcmp("log_type", key)) {
455          /* Skip if matched again*/
456          continue;
457       }
458 #define SCREENSHOT_PARAM_BOOL(name)                                        \
459       if (!strcmp(#name, key)) {                                           \
460          params->enabled[SCREENSHOT_PARAM_ENABLED_##name] =                \
461             strtol(value, NULL, 0);                                        \
462          continue;                                                         \
463       }
464 #define SCREENSHOT_PARAM_CUSTOM(name)              \
465       if (!strcmp(#name, key)) {                   \
466          params->name = parse_##name(value);       \
467          continue;                                 \
468       }
469       SCREENSHOT_PARAMS
470 #undef SCREENSHOT_PARAM_BOOL
471 #undef SCREENSHOT_PARAM_CUSTOM
472       LOG(ERROR, "Unknown option '%s'\n", key);
473    }
474 }
475