xref: /aosp_15_r20/external/mesa3d/src/vulkan/screenshot-layer/screenshot_params.c (revision 6104692788411f58d303aa86923a9ff6ecaded22)
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[256] = {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 
266 static bool
parse_help(const char * str)267 parse_help(const char *str)
268 {
269    LOG(NO_PREFIX, "Layer params using VK_LAYER_MESA_SCREENSHOT_CONFIG=\n");
270 #define SCREENSHOT_PARAM_BOOL(name)                \
271    LOG(NO_PREFIX, "\t%s=0|1\n", #name);
272 #define SCREENSHOT_PARAM_CUSTOM(name)
273    SCREENSHOT_PARAMS
274 #undef SCREENSHOT_PARAM_BOOL
275 #undef SCREENSHOT_PARAM_CUSTOM
276    LOG(NO_PREFIX, "\tlog_type=info|debug (if no selection, no logs besides errors are given)\n");
277    LOG(NO_PREFIX, "\toutput_dir='/path/to/dir'\n");
278    LOG(NO_PREFIX, "\tframes=Individual frames, separated by '/', followed by " \
279                   "a range setup, separated by '-', <range start>-<range count>-<range interval>\n" \
280                   "\tFor example '1/5/7/15-4-5' = [1,5,7,15,20,25,30]\n" \
281                   "\tframes='all' will select all frames.");
282 
283    return true;
284 }
285 
286 static enum LogType
parse_log_type(const char * str)287 parse_log_type(const char *str)
288 {
289    if(!strcmp(str, "info")) {
290       return INFO;
291    } else if (!strcmp(str, "debug")) {
292       return DEBUG;
293    } else {
294       /* Required logs only */
295       return REQUIRED;
296    }
297 }
298 
299 /* TODO: Improve detection of proper directory path */
300 static const char *
parse_output_dir(const char * str)301 parse_output_dir(const char *str)
302 {
303    static char output_dir[256];
304    strcpy(output_dir, str);
305    uint32_t last_char_index = strlen(str)-1;
306    // Ensure we're in bounds and the last character is '/'
307    if (last_char_index > 0 &&
308        str[last_char_index] != '/' &&
309        last_char_index < 254) {
310       output_dir[last_char_index+1] = '/';
311    }
312    DIR *dir = opendir(output_dir);
313    assert(dir);
314    closedir(dir);
315 
316    return output_dir;
317 }
318 
is_delimiter(char c)319 static bool is_delimiter(char c)
320 {
321    return c == 0 || c == ',' || c == ':' || c == ';' || c == '=';
322 }
323 
324 static int
parse_string(const char * s,char * out_param,char * out_value)325 parse_string(const char *s, char *out_param, char *out_value)
326 {
327    int i = 0;
328 
329    for (; !is_delimiter(*s); s++, out_param++, i++)
330       *out_param = *s;
331 
332    *out_param = 0;
333 
334    if (*s == '=') {
335       s++;
336       i++;
337       for (; !is_delimiter(*s); s++, out_value++, i++)
338          *out_value = *s;
339    } else
340       *(out_value++) = '1';
341    *out_value = 0;
342 
343    if (*s && is_delimiter(*s)) {
344       s++;
345       i++;
346    }
347 
348    if (*s && !i) {
349       LOG(ERROR, "mesa-screenshot: syntax error: unexpected '%c' (%i) while "
350                  "parsing a string\n", *s, *s);
351    }
352    return i;
353 }
354 
355 const char *screenshot_param_names[] = {
356 #define SCREENSHOT_PARAM_BOOL(name) #name,
357 #define SCREENSHOT_PARAM_CUSTOM(name)
358    SCREENSHOT_PARAMS
359 #undef SCREENSHOT_PARAM_BOOL
360 #undef SCREENSHOT_PARAM_CUSTOM
361 };
362 
363 void
parse_screenshot_env(struct screenshot_params * params,const char * env)364 parse_screenshot_env(struct screenshot_params *params,
365                   const char *env)
366 {
367 
368    if (!env)
369       return;
370 
371    uint32_t num;
372    const char *itr = env;
373    char key[256], value[256];
374 
375    memset(params, 0, sizeof(*params));
376 
377    params->control    = "mesa_screenshot";
378    params->frames     = NULL;
379    params->output_dir = NULL;
380 
381    /* Loop once first until log options found (if they exist) */
382    while ((num = parse_string(itr, key, value)) != 0) {
383       itr += num;
384       if (!strcmp("log_type", key)) {
385          LOG_TYPE = parse_log_type(value);
386          break;
387       }
388    }
389    /* Reset the iterator */
390    itr = env;
391 
392    while ((num = parse_string(itr, key, value)) != 0) {
393       itr += num;
394       if (!strcmp("log_type", key)) {
395          /* Skip if matched again*/
396          continue;
397       }
398 #define SCREENSHOT_PARAM_BOOL(name)                                        \
399       if (!strcmp(#name, key)) {                                           \
400          params->enabled[SCREENSHOT_PARAM_ENABLED_##name] =                \
401             strtol(value, NULL, 0);                                        \
402          continue;                                                         \
403       }
404 #define SCREENSHOT_PARAM_CUSTOM(name)              \
405       if (!strcmp(#name, key)) {                   \
406          params->name = parse_##name(value);       \
407          continue;                                 \
408       }
409       SCREENSHOT_PARAMS
410 #undef SCREENSHOT_PARAM_BOOL
411 #undef SCREENSHOT_PARAM_CUSTOM
412       LOG(ERROR, "Unknown option '%s'\n", key);
413    }
414 }
415