• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

saitoha / libsixel / 20466639304

23 Dec 2025 04:53PM UTC coverage: 51.46% (-6.3%) from 57.773%
20466639304

push

github

saitoha
build: fix windows find path in images meson build

14511 of 44933 branches covered (32.29%)

21089 of 40981 relevant lines covered (51.46%)

3915123.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.63
/src/decoder-parallel.c
1
/*
2
 * SPDX-License-Identifier: MIT
3
 *
4
 * Copyright (c) 2025 libsixel developers. See `AUTHORS`.
5
 */
6

7
#include "config.h"
8

9
#include <stdlib.h>
10
#include <string.h>
11
#if HAVE_ERRNO_H
12
# include <errno.h>
13
#endif  /* HAVE_ERRNO_H */
14
#if HAVE_LIMITS_H
15
# include <limits.h>
16
#endif /* HAVE_LIMITS_H */
17

18
#include <sixel.h>
19

20
#include "decoder-parallel.h"
21
#if SIXEL_ENABLE_THREADS
22
# include "logger.h"
23
# include "threading.h"
24
#endif
25
#include "compat_stub.h"
26

27
/*
28
 * The previous prescan-based parallel decoder has been removed.
29
 * The current implementation keeps the public entry points so that
30
 * callers can fall back to the single threaded decoder while we
31
 * prepare the new chunked worker pipeline.
32
 */
33

34
typedef struct sixel_decoder_thread_config {
35
    int env_checked;
36
    int env_valid;
37
    int env_threads;
38
    int override_active;
39
    int override_threads;
40
} sixel_decoder_thread_config_t;
41

42
#if SIXEL_ENABLE_THREADS
43
typedef struct sixel_decoder_worker_context {
44
    struct sixel_decoder_worker_chain *chain;
45
    unsigned char *input;
46
    unsigned char *anchor;
47
    int length;
48
    int payload_len;
49
    int start_offset;
50
    int end_offset;
51
    int index;
52
    int direct_mode;
53
    int const *palette;
54
    int palette_limit;
55
    int width;
56
    int height;
57
    int pixel_size;
58
    int depth;
59
    sixel_logger_t *logger;
60
    unsigned char *local_buffer;
61
    int local_capacity;
62
    int local_written;
63
    int result;
64
} sixel_decoder_worker_context_t;
65

66
typedef struct sixel_decoder_local_chunk {
67
    unsigned char *data;
68
    int start_row;
69
    int rows;
70
    struct sixel_decoder_local_chunk *next;
71
} sixel_decoder_local_chunk_t;
72

73
typedef struct sixel_local_buffer {
74
    sixel_decoder_local_chunk_t *head;
75
    sixel_decoder_local_chunk_t *tail;
76
    sixel_decoder_local_chunk_t *cursor;
77
    int width;
78
    int pixel_size;
79
} sixel_local_buffer_t;
80

81
typedef struct sixel_decoder_worker_chain {
82
    sixel_mutex_t mutex;
83
    sixel_cond_t cond;
84
    int *copy_offsets;
85
    int *copy_ready;
86
    int thread_count;
87
    unsigned char *global_buffer;
88
    int global_capacity;
89
    int pixel_size;
90
    int abort_requested;
91
} sixel_decoder_worker_chain_t;
92
#endif
93

94
static sixel_decoder_thread_config_t g_decoder_threads = {
95
    0,
96
    0,
97
    1,
98
    0,
99
    1
100
};
101

102
#if SIXEL_ENABLE_THREADS
103
static void
104
sixel_decoder_parallel_fill_spans(int payload_len,
105
                                  int threads,
106
                                  int *spans);
107

108
static int
109
sixel_decoder_parallel_skew_percent(void)
6✔
110
{
111
    char const *text;
6✔
112
    char *endptr;
6✔
113
    long value;
6✔
114

115
    /*
116
     * SIXEL_PARALLEL_SKEW lets operators bias span lengths by +/-20% so the
117
     * trailing workers take slightly more work.  The default keeps spans
118
     * balanced.
119
     */
120
    text = sixel_compat_getenv("SIXEL_PARALLEL_SKEW");
6✔
121
    if (text == NULL || text[0] == '\0') {
6!
122
        return 0;
123
    }
124

125
    errno = 0;
×
126
    value = strtol(text, &endptr, 10);
×
127
    if (errno != 0 || endptr == text || *endptr != '\0') {
×
128
        return 0;
129
    }
130

131
    if (value < -20) {
×
132
        value = -20;
133
    } else if (value > 20) {
×
134
        value = 20;
135
    }
136

137
    return (int)value;
×
138
}
139

140
static void
141
sixel_decoder_parallel_fill_spans(int payload_len,
6✔
142
                                  int threads,
143
                                  int *spans)
144
{
145
    int base_share;
6✔
146
    int skew_percent;
6✔
147
    double skew;
6✔
148
    int i;
6✔
149
    double center;
6✔
150
    int total;
6✔
151
    int remainder;
6✔
152

153
    base_share = payload_len / threads;
6✔
154
    skew_percent = sixel_decoder_parallel_skew_percent();
6✔
155
    skew = ((double)base_share * (double)skew_percent) / 100.0;
6✔
156
    total = 0;
6✔
157
    for (i = 0; i < threads; ++i) {
30✔
158
        center = (double)i - (double)(threads - 1) / 2.0;
24✔
159
        spans[i] = base_share + (int)(skew * center);
24✔
160
        if (spans[i] < 1) {
24!
161
            spans[i] = 1;
×
162
        }
163
        total += spans[i];
24✔
164
    }
165

166
    remainder = payload_len - total;
6✔
167
    while (remainder > 0) {
6!
168
        for (i = threads - 1; i >= 0 && remainder > 0; --i) {
×
169
            spans[i] += 1;
×
170
            --remainder;
×
171
        }
172
    }
173
    while (remainder < 0) {
6!
174
        for (i = threads - 1; i >= 0 && remainder < 0; --i) {
×
175
            if (spans[i] > 1) {
×
176
                spans[i] -= 1;
×
177
                ++remainder;
×
178
            }
179
        }
180
        if (remainder < 0) {
×
181
            break;
182
        }
183
    }
184
}
6✔
185

186
static int
187
sixel_decoder_parallel_pixel_size(int depth)
213✔
188
{
189
    int pixel_size;
213✔
190

191
    pixel_size = 4;
213✔
192
    if (depth == 1) {
213✔
193
        pixel_size = 1;
194
    } else if (depth == 2) {
12!
195
        pixel_size = 2;
×
196
    }
197

198
    return pixel_size;
213✔
199
}
200

201
static void
202
sixel_decoder_parallel_store_pixel(unsigned char *dst,
864✔
203
                                   int depth,
204
                                   int color_index,
205
                                   int const *palette)
206
{
207
    int color;
864✔
208

209
    if (depth == 1) {
864!
210
        dst[0] = (unsigned char)color_index;
×
211
        return;
×
212
    }
213

214
    if (depth == 2) {
864!
215
        unsigned short packed;
×
216

217
        packed = (unsigned short)color_index;
×
218
        memcpy(dst, &packed, sizeof(packed));
×
219
        return;
×
220
    }
221

222
    color = 0;
864✔
223
    if (palette != NULL &&
864!
224
            color_index >= 0 &&
864!
225
            color_index < SIXEL_PALETTE_MAX_DECODER) {
226
        color = palette[color_index];
864✔
227
    }
228
    dst[0] = (unsigned char)((color >> 16) & 0xff);
864✔
229
    dst[1] = (unsigned char)((color >> 8) & 0xff);
864✔
230
    dst[2] = (unsigned char)(color & 0xff);
864✔
231
    dst[3] = 255u;
864✔
232
}
1!
233

234
static void
235
sixel_local_buffer_init(sixel_local_buffer_t *buffer,
24✔
236
                        int width,
237
                        int pixel_size)
238
{
239
    buffer->head = NULL;
24✔
240
    buffer->tail = NULL;
24✔
241
    buffer->cursor = NULL;
24✔
242
    buffer->width = width;
24✔
243
    buffer->pixel_size = pixel_size;
24✔
244
}
245

246
static void
247
sixel_local_buffer_dispose(sixel_local_buffer_t *buffer)
24✔
248
{
249
    sixel_decoder_local_chunk_t *cursor;
24✔
250

251
    cursor = buffer->head;
24✔
252
    while (cursor != NULL) {
48✔
253
        sixel_decoder_local_chunk_t *tmp;
24✔
254

255
        free(cursor->data);
24✔
256
        tmp = cursor->next;
24✔
257
        free(cursor);
24✔
258
        cursor = tmp;
24✔
259
    }
260

261
    buffer->head = NULL;
24✔
262
    buffer->tail = NULL;
24✔
263
    buffer->cursor = NULL;
24✔
264
}
24✔
265

266
static sixel_decoder_local_chunk_t *
267
sixel_local_buffer_append(sixel_local_buffer_t *buffer,
24✔
268
                          int rows,
269
                          int start_row)
270
{
271
    sixel_decoder_local_chunk_t *chunk;
24✔
272
    size_t bytes;
24✔
273

274
    if (rows % 6 != 0) {
24!
275
        rows += 6 - (rows % 6);
×
276
    }
277

278
    chunk = (sixel_decoder_local_chunk_t *)calloc(
24✔
279
        1, sizeof(sixel_decoder_local_chunk_t));
280
    if (chunk == NULL) {
24!
281
        return NULL;
282
    }
283

284
    bytes = (size_t)(rows * buffer->width) * (size_t)buffer->pixel_size;
24✔
285
    chunk->data = (unsigned char *)calloc(bytes, 1);
24✔
286
    if (chunk->data == NULL) {
24!
287
        free(chunk);
×
288
        return NULL;
×
289
    }
290

291
    chunk->start_row = start_row;
24✔
292
    chunk->rows = rows;
24✔
293
    chunk->next = NULL;
24✔
294

295
    if (buffer->head == NULL) {
24!
296
        buffer->head = chunk;
24✔
297
    } else {
298
        buffer->tail->next = chunk;
×
299
    }
300
    buffer->tail = chunk;
24✔
301
    buffer->cursor = chunk;
24✔
302

303
    return chunk;
24✔
304
}
305

306
static sixel_decoder_local_chunk_t *
307
sixel_local_buffer_reserve_row(sixel_local_buffer_t *buffer,
288✔
308
                               int row)
309
{
310
    sixel_decoder_local_chunk_t *cursor;
288✔
311

312
    cursor = buffer->cursor;
288✔
313
    while (cursor != NULL && row >= cursor->start_row + cursor->rows) {
288!
314
        cursor = cursor->next;
×
315
    }
316
    if (cursor != NULL && row >= cursor->start_row) {
288!
317
        buffer->cursor = cursor;
288✔
318
        return cursor;
288✔
319
    }
320

321
    cursor = buffer->tail;
×
322
    if (cursor == NULL) {
×
323
        return NULL;
324
    }
325

326
    while (row >= cursor->start_row + cursor->rows) {
×
327
        sixel_decoder_local_chunk_t *next;
×
328
        int start_row;
×
329

330
        start_row = cursor->start_row + cursor->rows;
×
331
        next = sixel_local_buffer_append(buffer, cursor->rows, start_row);
×
332
        if (next == NULL) {
×
333
            return NULL;
334
        }
335
        cursor = next;
336
    }
×
337

338
    buffer->cursor = cursor;
×
339
    return cursor;
×
340
}
341

342
/*
343
 * Worker entry for the fast sixel parser.  Each thread jumps to the next
344
 * '-' marker from its assigned offset, then walks tokens until the first
345
 * '-' after its scheduled end offset.
346
 *
347
 * Supported tokens: palette switches ('#n'), carriage return ('$'), next
348
 * line ('-'), and raster data ('?' - '~').  Any raster attribute reset '"',
349
 * palette redefinition '#n;type;...', or CAN/SUB control code marks the
350
 * chunk as invalid and returns a failure status so the caller can fall back.
351
 */
352
static int
353
sixel_decoder_parallel_worker(void *arg)
24✔
354
{
355
    sixel_decoder_worker_context_t *context;
24✔
356
    sixel_decoder_worker_chain_t *chain;
24✔
357
    sixel_local_buffer_t local_buffer;
24✔
358
    sixel_decoder_local_chunk_t *chunk_cursor = NULL;
24✔
359
    unsigned char *anchor = NULL;
24✔
360
    unsigned char *scan = NULL;
24✔
361
    unsigned char *cursor = NULL;
24✔
362
    unsigned char *stop = NULL;
24✔
363
    unsigned char *limit = NULL;
24✔
364
    unsigned char *start = NULL;
24✔
365
    int capacity = 0;
24✔
366
    int written = 0;
24✔
367
    int assigned = 0;
24✔
368
    int bits = 0;
24✔
369
    int repeat = 1;
24✔
370
    int color_index = 0;
24✔
371
    int max_relative = (-1);
24✔
372
    int min_relative = (-1);
24✔
373
    int pos_x = 0;
24✔
374
    int pos_y = 0;
24✔
375
    int relative = 0;
24✔
376
    int row_base = 0;
24✔
377
    int r = 0;
24✔
378
    int row_offset = 0;
24✔
379
    int line_count = 0;
24✔
380
    int copy_offset = 0;
24✔
381
    int copy_span = 0;
24✔
382
    int next_offset = 0;
24✔
383
    int i;
24✔
384
    int fallback = 0;
24✔
385
    int status = (-1);
24✔
386
    sixel_logger_t *logger = NULL;
24✔
387
    int width = 0;
24✔
388
    int pixel_size = 0;
24✔
389
    int depth = 0;
24✔
390
    int height = 0;
24✔
391
    int chain_offset = 0;
24✔
392
    unsigned char ch;
24✔
393

394
    context = (sixel_decoder_worker_context_t *)arg;
24✔
395
    if (context == NULL) {
24!
396
        return (-1);
397
    }
398

399
    context->result = status;
24✔
400

401
    chain = context->chain;
24✔
402
    if (chain == NULL) {
24!
403
        context->result = status;
404
        return status;
405
    }
406

407
    logger = context->logger;
24✔
408
    anchor = context->anchor;
24✔
409
    if (context->input == NULL || context->length <= 0) {
24!
410
        context->result = status;
411
        return status;
412
    }
413

414
    width = context->width;
24✔
415
    if (width <= 0) {
24!
416
        context->result = status;
417
        return status;
418
    }
419

420
    pixel_size = context->pixel_size;
24✔
421
    depth = context->depth;
24✔
422
    height = context->height;
24✔
423
    if (height <= 0) {
24!
424
        context->result = status;
425
        return status;
426
    }
427
    sixel_local_buffer_init(&local_buffer, width, pixel_size);
24✔
428
    if (context->payload_len <= 0) {
24!
429
        context->result = status;
430
        return status;
431
    }
432

433
    start = context->input + context->start_offset;
24✔
434
    if (start < context->input ||
24!
435
            start >= context->input + context->length) {
24!
436
        context->result = status;
437
        return status;
438
    }
439

440
    assigned = context->end_offset - context->start_offset + 1;
24✔
441
    if (assigned <= 0) {
24!
442
        context->result = status;
443
        return status;
444
    }
445

446
    status = 0;
24✔
447

448
    if (logger != NULL) {
24!
449
        /*
450
         * Decode window for this worker. The key keeps decode spans grouped
451
         * per-thread in the timeline output.
452
         */
453
        sixel_logger_logf(logger,
×
454
                          "decode",
455
                          "decoder",
456
                          "start",
457
                          context->index,
458
                          context->index,
459
                          0,
460
                          0,
461
                          context->start_offset,
462
                          context->end_offset,
463
                          "worker %d decode span [%d,%d]",
464
                          context->index,
465
                          context->start_offset,
466
                          context->end_offset);
467
    }
468

469
    cursor = start;
24✔
470
    if (context->index > 0) {
24✔
471
        cursor = (unsigned char *)memchr(start,
18✔
472
                                         '-',
473
                                         (size_t)(context->length -
18✔
474
                                         context->start_offset));
18✔
475
        if (cursor != NULL &&
18!
476
                cursor + 1 < context->input + context->length) {
18!
477
            cursor += 1;
24✔
478
        } else {
479
            cursor = start;
480
        }
481
    }
482
    if (context->index > 0 && cursor == start) {
24!
483
        status = (-1);
×
484
        context->result = status;
×
485
        return status;
×
486
    }
487

488
    if (anchor != NULL && anchor < cursor) {
24!
489
        scan = anchor;
490
        while (scan < cursor) {
5,814✔
491
            if (*scan == '-') {
5,796✔
492
                line_count += 1;
18✔
493
                scan += 1;
18✔
494
                continue;
18✔
495
            }
496

497
            if (*scan == '#') {
5,778✔
498
                int value;
1,152✔
499
                unsigned char *p;
1,152✔
500

501
                value = 0;
1,152✔
502
                p = scan + 1;
1,152✔
503
                while (p < cursor && *p >= '0' && *p <= '9') {
3,276!
504
                    value = value * 10 + (*p - '0');
2,124✔
505
                    p += 1;
2,124✔
506
                }
507
                color_index = value;
1,152✔
508
                if (color_index < 0) {
1,152!
509
                    color_index = 0;
510
                }
511
                if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
1,152!
512
                    color_index = SIXEL_PALETTE_MAX_DECODER - 1;
513
                }
514
                if (p < cursor && *p == ';') {
1,152!
515
                    scan = p;
516
                    continue;
517
                }
518
                scan = p;
1,152✔
519
                continue;
1,152✔
520
            }
521

522
            scan += 1;
4,626✔
523
        }
524
    }
525
    row_offset = line_count * 6;
24✔
526

527
    capacity = (int)((double)chain->global_capacity *
24✔
528
        ((double)assigned / (double)context->payload_len));
24✔
529
    capacity = (int)((double)capacity * 1.10);
24✔
530
    if (capacity < 1) {
24!
531
        capacity = 1;
532
    }
533
    if (capacity < width * 6) {
24!
534
        capacity = width * 6;
535
    }
536
    if (capacity % (width * 6) != 0) {
24!
537
        capacity += (width * 6) - (capacity % (width * 6));
×
538
    }
539

540
    chunk_cursor = sixel_local_buffer_append(&local_buffer,
24✔
541
                                             capacity / width,
542
                                             0);
543
    if (chunk_cursor == NULL) {
24!
544
        status = (-1);
×
545
        context->result = status;
×
546
        return status;
×
547
    }
548
    capacity = chunk_cursor->rows * width;
24✔
549

550
    stop = context->input + context->end_offset;
24✔
551
    limit = context->input + context->length;
24✔
552
    while (cursor < limit) {
252!
553
        ch = *cursor;
252✔
554

555
        if (ch < 0x20) {
252!
556
            if (ch == 0x18 || ch == 0x1a) {
90!
557
                fallback = 1;
558
                status = (-1);
559
                break;
560
            }
561
            cursor += 1;
90✔
562
            if (ch == 0x1b && cursor < limit && *cursor == '\\') {
90!
563
                status = (0);
564
                break;
565
            }
566
            continue;
72✔
567
        }
568

569
        if (ch == '"') {
54!
570
            fallback = 1;
571
            status = (-1);
572
            break;
573
        }
574

575
        if (ch == '!') {
90✔
576
            int value;
54✔
577
            unsigned char *p;
54✔
578

579
            value = 0;
54✔
580
            p = cursor + 1;
54✔
581
            while (p < limit && *p >= '0' && *p <= '9') {
108!
582
                value = value * 10 + (*p - '0');
54✔
583
                p += 1;
54✔
584
            }
585
            if (value <= 0) {
54!
586
                value = 1;
587
            }
588
            repeat = value;
54✔
589
            cursor = p;
54✔
590
            continue;
54✔
591
        }
592

593
        if (ch == '#') {
72✔
594
            int value;
54✔
595
            unsigned char *p;
54✔
596

597
            value = 0;
54✔
598
            p = cursor + 1;
54✔
599
            while (p < limit && *p >= '0' && *p <= '9') {
108!
600
                value = value * 10 + (*p - '0');
54✔
601
                p += 1;
54✔
602
            }
603
            if (p < limit && *p == ';') {
54!
604
                fallback = 1;
605
                status = (-1);
606
                break;
607
            }
608
            color_index = value;
54✔
609
            if (color_index < 0) {
54!
610
                color_index = 0;
611
            }
612
            if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
54!
613
                color_index = SIXEL_PALETTE_MAX_DECODER - 1;
614
            }
615
            cursor = p;
54✔
616
            continue;
54✔
617
        }
1!
618

619
        if (ch == '$') {
18!
620
            cursor += 1;
×
621
            pos_x = 0;
×
622
            continue;
×
623
        }
624

625
        if (ch == '-') {
18!
626
            if (cursor >= stop) {
×
627
                break;
628
            }
629
            cursor += 1;
×
630
            pos_x = 0;
×
631
            pos_y += 6;
×
632
            chunk_cursor = sixel_local_buffer_reserve_row(&local_buffer,
×
633
                                                          pos_y);
634
            if (chunk_cursor == NULL) {
×
635
                fallback = 1;
636
                status = (-1);
637
                break;
638
            }
639
            continue;
×
640
        }
641

642
        if (ch < '?' || ch > '~') {
18!
643
            cursor += 1;
×
644
            continue;
×
645
        }
646

647
        bits = ch - '?';
54✔
648
        for (i = 0; i < 6; ++i) {
342✔
649
            if ((bits & (1 << i)) != 0) {
294!
650
                if (pos_x + repeat > width ||
294!
651
                        row_offset + pos_y + i >= height) {
288!
652
                    fallback = 1;
653
                    status = (-1);
654
                    break;
655
                }
656
                chunk_cursor = sixel_local_buffer_reserve_row(
288✔
657
                    &local_buffer, pos_y + i);
658
                if (chunk_cursor == NULL) {
288!
659
                    fallback = 1;
660
                    status = (-1);
661
                    break;
662
                }
663

664
                row_base = (pos_y + i - chunk_cursor->start_row) * width +
288✔
665
                    pos_x;
666
                relative = (pos_y + i) * width + pos_x;
288✔
667

668
                if (pixel_size == 1 && repeat > 3) {
288!
669
                    memset(chunk_cursor->data + (size_t)row_base,
144✔
670
                           color_index,
671
                           repeat);
672
                } else {
673
                    for (r = 0; r < repeat; ++r) {
1,008✔
674
                        sixel_decoder_parallel_store_pixel(
864✔
675
                            chunk_cursor->data +
864✔
676
                            (size_t)(row_base + r) * (size_t)pixel_size,
864✔
677
                            depth,
678
                            color_index,
679
                            context->palette);
680
                    }
681
                }
682
                written += repeat;
288✔
683
                if (min_relative < 0 || relative < min_relative) {
288!
684
                    min_relative = relative;
685
                }
686
                if (max_relative < relative + repeat - 1) {
288✔
687
                    max_relative = relative + repeat - 1;
688
                }
689
            }
690
        }
691

692
        if (fallback) {
54✔
693
            break;
694
        }
695

696
        cursor += 1;
48✔
697
        pos_x += repeat;
48✔
698
        repeat = 1;
48✔
699

700
    }
701

702
    copy_span = max_relative + 1;
24✔
703
    if (copy_span < 0) {
24!
704
        copy_span = 0;
705
    }
706

707
    if (logger != NULL) {
24!
708
        sixel_logger_logf(logger,
×
709
                          "decode",
710
                          "decoder",
711
                          fallback ? "abort" : "finish",
712
                          context->index,
713
                          context->index,
714
                          0,
715
                          0,
716
                          context->start_offset,
717
                          context->end_offset,
718
                          "worker %d decode wrote=%d status=%d",
719
                          context->index,
720
                          written,
721
                          status);
722
    }
723

724
    if (status != 0) {
24✔
725
        sixel_mutex_lock(&chain->mutex);
6✔
726
        chain->abort_requested = 1;
6✔
727
        sixel_cond_broadcast(&chain->cond);
6✔
728
        sixel_mutex_unlock(&chain->mutex);
6✔
729
        sixel_local_buffer_dispose(&local_buffer);
6✔
730
        context->result = status;
6✔
731
        return status;
6✔
732
    }
733

734
    context->local_buffer = local_buffer.head != NULL ?
36!
735
        local_buffer.head->data : NULL;
18!
736
    context->local_capacity = capacity;
18✔
737
    context->local_written = copy_span;
18✔
738

739
    sixel_mutex_lock(&chain->mutex);
18✔
740
    while (!chain->copy_ready[context->index] && !chain->abort_requested) {
19!
741
        sixel_cond_wait(&chain->cond, &chain->mutex);
1✔
742
    }
743
    if (chain->abort_requested) {
18!
744
        sixel_mutex_unlock(&chain->mutex);
18✔
745
        sixel_local_buffer_dispose(&local_buffer);
18✔
746
        context->local_buffer = NULL;
18✔
747
        context->local_capacity = 0;
18✔
748
        context->local_written = 0;
18✔
749
        status = (-1);
18✔
750
        context->result = status;
18✔
751
        return status;
18✔
752
    }
753
    chain_offset = chain->copy_offsets[context->index];
×
754
    copy_offset = chain_offset;
×
755
    next_offset = copy_offset + copy_span;
×
756
    context->local_written = copy_span;
×
757
    if (context->index + 1 < chain->thread_count) {
×
758
        chain->copy_offsets[context->index + 1] = next_offset;
×
759
        chain->copy_ready[context->index + 1] = 1;
×
760
    }
761
    sixel_cond_broadcast(&chain->cond);
×
762
    sixel_mutex_unlock(&chain->mutex);
×
763

764
    if (copy_offset < 0 || chain->global_buffer == NULL) {
×
765
        sixel_mutex_lock(&chain->mutex);
×
766
        chain->abort_requested = 1;
×
767
        sixel_cond_broadcast(&chain->cond);
×
768
        sixel_mutex_unlock(&chain->mutex);
×
769
        sixel_local_buffer_dispose(&local_buffer);
×
770
        context->local_buffer = NULL;
×
771
        context->local_capacity = 0;
×
772
        context->local_written = 0;
×
773
        status = (-1);
×
774
        context->result = status;
×
775
        return status;
×
776
    }
777

778
    if (logger != NULL) {
×
779
        /* Chain memcpy execution so the timeline shows the serialized copy */
780
        sixel_logger_logf(logger,
×
781
                          "copy",
782
                          "decoder",
783
                          "start",
784
                          context->index,
785
                          context->index,
786
                          0,
787
                          0,
788
                          copy_offset,
789
                          next_offset,
790
                          "worker %d memcpy count=%d",
791
                          context->index,
792
                          copy_span);
793
    }
794

795
    if (max_relative >= 0) {
×
796
        chunk_cursor = local_buffer.head;
×
797
        while (chunk_cursor != NULL) {
×
798
            int rows;
×
799
            size_t chunk_bytes;
×
800
            size_t chunk_offset;
×
801

802
            rows = chunk_cursor->rows;
×
803
            if (chunk_cursor->start_row + rows >
×
804
                    (max_relative / width) + 1) {
×
805
                rows = (max_relative / width) + 1 -
×
806
                    chunk_cursor->start_row;
807
            }
808
            if (rows < 0) {
×
809
                rows = 0;
810
            }
811
            if (rows > 0) {
×
812
                chunk_bytes = (size_t)(rows * width) *
×
813
                    (size_t)chain->pixel_size;
×
814
                chunk_offset = (size_t)((row_offset +
×
815
                    chunk_cursor->start_row) * width) *
×
816
                    (size_t)chain->pixel_size;
817
                memcpy(chain->global_buffer + chunk_offset,
×
818
                       chunk_cursor->data,
×
819
                       chunk_bytes);
820
            }
821
            chunk_cursor = chunk_cursor->next;
×
822
        }
823
    }
824

825
    if (logger != NULL) {
×
826
        sixel_logger_logf(logger,
×
827
                          "copy",
828
                          "decoder",
829
                          "finish",
830
                          context->index,
831
                          context->index,
832
                          0,
833
                          0,
834
                          copy_offset,
835
                          next_offset,
836
                          "worker %d memcpy done", 
837
                          context->index);
838
    }
839

840
    sixel_local_buffer_dispose(&local_buffer);
×
841
    context->local_buffer = NULL;
×
842
    context->local_capacity = 0;
×
843
    context->local_written = 0;
×
844

845
    context->result = status;
×
846
    return status;
×
847
}
848
#endif
849

850
static int
851
sixel_decoder_threads_token_is_auto(char const *text)
204✔
852
{
853
    if (text == NULL) {
204!
854
        return 0;
855
    }
856
    if ((text[0] == 'a' || text[0] == 'A') &&
204!
857
            (text[1] == 'u' || text[1] == 'U') &&
×
858
            (text[2] == 't' || text[2] == 'T') &&
×
859
            (text[3] == 'o' || text[3] == 'O') &&
×
860
            text[4] == '\0') {
×
861
        return 1;
×
862
    }
863
    return 0;
864
}
865

866
static int
867
sixel_decoder_threads_normalize(int requested)
204✔
868
{
869
    int normalized;
204✔
870

871
#if SIXEL_ENABLE_THREADS
872
    int hw_threads;
204✔
873

874
    if (requested <= 0) {
204!
875
        hw_threads = sixel_get_hw_threads();
×
876
        if (hw_threads < 1) {
×
877
            hw_threads = 1;
878
        }
879
        normalized = hw_threads;
880
    } else {
881
        normalized = requested;
882
    }
883
#else
884
    (void)requested;
885
    normalized = 1;
886
#endif
887
    if (normalized < 1) {
1!
888
        normalized = 1;
889
    }
890
    return normalized;
×
891
}
892

893
static int
894
sixel_decoder_threads_parse_value(char const *text, int *value)
204✔
895
{
896
    long parsed;
204✔
897
    char *endptr;
204✔
898
    int normalized;
204✔
899

900
    if (text == NULL || value == NULL) {
204!
901
        return 0;
902
    }
903
    if (sixel_decoder_threads_token_is_auto(text)) {
204!
904
        normalized = sixel_decoder_threads_normalize(0);
×
905
        *value = normalized;
×
906
        return 1;
×
907
    }
908
    errno = 0;
204✔
909
    parsed = strtol(text, &endptr, 10);
204✔
910
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
204!
911
        return 0;
912
    }
913
    if (parsed < 1) {
201!
914
        normalized = sixel_decoder_threads_normalize(1);
915
    } else if (parsed > INT_MAX) {
201!
916
        normalized = sixel_decoder_threads_normalize(INT_MAX);
917
    } else {
918
        normalized = sixel_decoder_threads_normalize((int)parsed);
201✔
919
    }
920
    *value = normalized;
201✔
921
    return 1;
201✔
922
}
923

924
static void
925
sixel_decoder_threads_load_env(void)
213✔
926
{
927
    char const *text;
213✔
928
    int parsed;
213✔
929

930
    if (g_decoder_threads.env_checked) {
213✔
931
        return;
12✔
932
    }
933
    g_decoder_threads.env_checked = 1;
210✔
934
    g_decoder_threads.env_valid = 0;
210✔
935
    text = sixel_compat_getenv("SIXEL_THREADS");
210✔
936
    if (text == NULL || text[0] == '\0') {
210!
937
        return;
938
    }
939
    if (sixel_decoder_threads_parse_value(text, &parsed)) {
201!
940
        g_decoder_threads.env_threads = parsed;
201✔
941
        g_decoder_threads.env_valid = 1;
201✔
942
    }
943
}
1!
944

945
SIXELSTATUS
946
sixel_decoder_parallel_override_threads(char const *text)
3✔
947
{
948
    SIXELSTATUS status;
3✔
949
    int parsed;
3✔
950

951
    status = SIXEL_BAD_ARGUMENT;
3✔
952
    if (text == NULL || text[0] == '\0') {
3!
953
        sixel_helper_set_additional_message(
×
954
            "decoder: missing thread count after -=/--threads.");
955
        goto end;
×
956
    }
957
    if (!sixel_decoder_threads_parse_value(text, &parsed)) {
3!
958
        sixel_helper_set_additional_message(
3✔
959
            "decoder: threads must be a positive integer or 'auto'.");
960
        goto end;
3✔
961
    }
962
    g_decoder_threads.override_active = 1;
×
963
    g_decoder_threads.override_threads = parsed;
×
964
    status = SIXEL_OK;
×
965
end:
3✔
966
    return status;
3✔
967
}
968

969
int
970
sixel_decoder_parallel_resolve_threads(void)
213✔
971
{
972
    int threads;
213✔
973

974
    threads = 1;
213✔
975
    sixel_decoder_threads_load_env();
213✔
976
    if (g_decoder_threads.override_active) {
213!
977
        threads = sixel_decoder_threads_normalize(
×
978
            g_decoder_threads.override_threads);
979
    } else if (g_decoder_threads.env_valid) {
213✔
980
        threads = sixel_decoder_threads_normalize(
204!
981
            g_decoder_threads.env_threads);
982
    }
983
    if (threads < 1) {
×
984
        threads = 1;
985
    }
986
    return threads;
213✔
987
}
988

989
SIXELSTATUS
990
sixel_decoder_parallel_request_start(int direct_mode,
213✔
991
                                     unsigned char *input,
992
                                     int length,
993
                                     unsigned char *anchor,
994
                                     image_buffer_t *image,
995
                                     int const *palette,
996
                                     sixel_logger_t *logger)
997
{
998
#if SIXEL_ENABLE_THREADS
999
    SIXELSTATUS status;
213✔
1000
    int threads;
213✔
1001
    int payload_start;
213✔
1002
    int payload_len;
213✔
1003
    sixel_thread_t *workers;
213✔
1004
    sixel_decoder_worker_context_t *contexts;
213✔
1005
    sixel_decoder_worker_chain_t chain;
213✔
1006
    int *copy_offsets;
213✔
1007
    int *copy_ready;
213✔
1008
    int global_capacity;
213✔
1009
    int *spans;
213✔
1010
    int i;
213✔
1011
    int offset;
213✔
1012
    int created;
213✔
1013
    int parallel_failed;
213✔
1014
    int runtime_error;
213✔
1015
    int sync_ready;
213✔
1016
    int pixel_size;
213✔
1017
    int palette_limit;
213✔
1018

1019
    status = SIXEL_RUNTIME_ERROR;
213✔
1020
    workers = NULL;
213✔
1021
    contexts = NULL;
213✔
1022
    copy_offsets = NULL;
213✔
1023
    copy_ready = NULL;
213✔
1024
    spans = NULL;
213✔
1025
    threads = 0;
213✔
1026
    payload_start = 0;
213✔
1027
    payload_len = 0;
213✔
1028
    global_capacity = 0;
213✔
1029
    offset = 0;
213✔
1030
    created = 0;
213✔
1031
    parallel_failed = 0;
213✔
1032
    runtime_error = 0;
213✔
1033
    sync_ready = 0;
213✔
1034
    pixel_size = 4;
213✔
1035
    palette_limit = SIXEL_PALETTE_MAX_DECODER;
213✔
1036
    memset(&chain, 0, sizeof(chain));
213!
1037

1038
    if (input == NULL || anchor == NULL || length <= 0 || image == NULL) {
213!
1039
        runtime_error = 1;
×
1040
        goto cleanup;
×
1041
    }
1042

1043
    pixel_size = sixel_decoder_parallel_pixel_size(image->depth);
213✔
1044

1045
    payload_start = (int)(anchor - input);
213✔
1046
    if (payload_start < 0 || payload_start >= length) {
213!
1047
        runtime_error = 1;
×
1048
        goto cleanup;
×
1049
    }
1050

1051
    payload_len = length - payload_start;
213✔
1052
    if (payload_len <= 0) {
213!
1053
        runtime_error = 1;
1054
        goto cleanup;
1055
    }
1056

1057
    palette_limit = image->ncolors;
213✔
1058
    if (palette_limit <= 0 || palette_limit > SIXEL_PALETTE_MAX_DECODER) {
213!
1059
        palette_limit = SIXEL_PALETTE_MAX_DECODER;
×
1060
    }
1061

1062
    threads = sixel_decoder_parallel_resolve_threads();
213✔
1063
    if (threads < 1) {
213!
1064
        threads = 1;
1065
    }
1066
    if (threads > payload_len) {
213!
1067
        threads = payload_len;
1068
    }
1069
    if (threads < 2) {
213✔
1070
        status = SIXEL_FALSE;
207✔
1071
        goto cleanup;
207✔
1072
    }
1073

1074
    workers = (sixel_thread_t *)calloc((size_t)threads,
6✔
1075
                                       sizeof(sixel_thread_t));
1076
    contexts = (sixel_decoder_worker_context_t *)calloc(
6✔
1077
        (size_t)threads, sizeof(sixel_decoder_worker_context_t));
1078
    spans = (int *)calloc((size_t)threads, sizeof(int));
6✔
1079
    copy_offsets = (int *)calloc((size_t)threads, sizeof(int));
6✔
1080
    copy_ready = (int *)calloc((size_t)threads, sizeof(int));
6✔
1081
    if (workers == NULL || contexts == NULL || spans == NULL ||
6!
1082
            copy_offsets == NULL || copy_ready == NULL) {
6!
1083
        runtime_error = 1;
×
1084
        goto cleanup;
×
1085
    }
1086

1087
    global_capacity = image->width * image->height;
6✔
1088
    if (global_capacity <= 0) {
6!
1089
        runtime_error = 1;
×
1090
        goto cleanup;
×
1091
    }
1092
    chain.global_buffer = (unsigned char *)image->pixels.p;
6✔
1093
    chain.pixel_size = pixel_size;
6✔
1094
    chain.copy_offsets = copy_offsets;
6✔
1095
    chain.copy_ready = copy_ready;
6✔
1096
    chain.thread_count = threads;
6✔
1097
    chain.global_capacity = global_capacity;
6✔
1098
    sixel_mutex_init(&chain.mutex);
6✔
1099
    sixel_cond_init(&chain.cond);
6✔
1100
    chain.copy_ready[0] = 1;
6✔
1101
    chain.copy_offsets[0] = 0;
6✔
1102
    sync_ready = 1;
6✔
1103

1104
    sixel_decoder_parallel_fill_spans(payload_len, threads, spans);
6✔
1105

1106
    if (logger != NULL) {
6!
1107
        /*
1108
         * Record when the controller hands control to worker threads so the
1109
         * timeline can show the gap between parser startup and worker
1110
         * creation.
1111
         */
1112
        sixel_logger_logf(logger,
×
1113
                          "decoder",
1114
                          "controller",
1115
                          "launch",
1116
                          0,
1117
                          0,
1118
                          0,
1119
                          length,
1120
                          payload_start,
1121
                          length,
1122
                          "spawn %d workers payload=%d",
1123
                          threads,
1124
                          payload_len);
1125
    }
1126

1127
    offset = payload_start;
1128
    created = 0;
1129
    for (i = 0; i < threads; ++i) {
30✔
1130
        contexts[i].chain = &chain;
24✔
1131
        contexts[i].input = input;
24✔
1132
        contexts[i].anchor = anchor;
24✔
1133
        contexts[i].length = length;
24✔
1134
        contexts[i].payload_len = payload_len;
24✔
1135
        contexts[i].start_offset = offset;
24✔
1136
        offset += spans[i];
24✔
1137
        if (offset > length) {
24!
1138
            offset = length;
1139
        }
1140
        if (i == threads - 1) {
24✔
1141
            contexts[i].end_offset = length - 1;
6✔
1142
        } else {
1143
            contexts[i].end_offset = offset - 1;
18✔
1144
            if (contexts[i].end_offset < contexts[i].start_offset) {
18!
1145
                contexts[i].end_offset = contexts[i].start_offset;
×
1146
            }
1147
        }
1148
        contexts[i].index = i;
24✔
1149
        contexts[i].direct_mode = direct_mode;
24✔
1150
        contexts[i].palette = palette;
24✔
1151
        contexts[i].palette_limit = palette_limit;
24✔
1152
        contexts[i].width = image->width;
24✔
1153
        contexts[i].height = image->height;
24✔
1154
        contexts[i].pixel_size = pixel_size;
24✔
1155
        contexts[i].depth = image->depth;
24✔
1156
        contexts[i].logger = logger;
24✔
1157
        contexts[i].result = (-1);
24✔
1158
        status = sixel_thread_create(&workers[i],
24✔
1159
                                     sixel_decoder_parallel_worker,
1160
                                     &contexts[i]);
1161
        if (SIXEL_FAILED(status)) {
24!
1162
            runtime_error = 1;
1163
            break;
1164
        }
1165
        created += 1;
24✔
1166
    }
1167

1168
    for (i = 0; i < created; ++i) {
30✔
1169
        sixel_thread_join(&workers[i]);
24✔
1170
        if (contexts[i].result != 0) {
24!
1171
            parallel_failed = 1;
24✔
1172
        }
1173
    }
1174

1175
    if (chain.abort_requested) {
6!
1176
        parallel_failed = 1;
6✔
1177
    }
1178

1179
    if (runtime_error || created < threads) {
6!
1180
        status = SIXEL_RUNTIME_ERROR;
1181
    } else if (parallel_failed) {
6!
1182
        status = SIXEL_FALSE;
1183
    } else {
1184
        status = SIXEL_OK;
1185
    }
1186

1187
cleanup:
1188
    if (sync_ready) {
207✔
1189
        sixel_mutex_destroy(&chain.mutex);
6✔
1190
        sixel_cond_destroy(&chain.cond);
6✔
1191
    }
1192

1193
    free(workers);
213✔
1194
    free(contexts);
213✔
1195
    free(spans);
213✔
1196
    free(copy_offsets);
213✔
1197
    free(copy_ready);
213✔
1198

1199
    return status;
213✔
1200
#else
1201
    (void)direct_mode;
1202
    (void)input;
1203
    (void)length;
1204
    (void)anchor;
1205
    (void)image;
1206
    (void)palette;
1207
    (void)logger;
1208

1209
    return SIXEL_RUNTIME_ERROR;
1210
#endif
1211
}
1212

1213
/* emacs Local Variables:      */
1214
/* emacs mode: c               */
1215
/* emacs tab-width: 4          */
1216
/* emacs indent-tabs-mode: nil */
1217
/* emacs c-basic-offset: 4     */
1218
/* emacs End:                  */
1219
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1220
/* EOF */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc