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

saitoha / libsixel / 20535614041

27 Dec 2025 06:39AM UTC coverage: 51.369% (-0.2%) from 51.522%
20535614041

push

github

saitoha
build: propagate amalgamation flags to objc builds

14509 of 44957 branches covered (32.27%)

21016 of 40912 relevant lines covered (51.37%)

3921796.99 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
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
7
 * this software and associated documentation files (the "Software"), to deal in
8
 * the Software without restriction, including without limitation the rights to
9
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
 * the Software, and to permit persons to whom the Software is furnished to do so,
11
 * subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 */
23

24
#if defined(HAVE_CONFIG_H)
25
#include "config.h"
26
#endif
27

28
#include <stdlib.h>
29
#include <string.h>
30
#if HAVE_ERRNO_H
31
# include <errno.h>
32
#endif  /* HAVE_ERRNO_H */
33
#if HAVE_LIMITS_H
34
# include <limits.h>
35
#endif /* HAVE_LIMITS_H */
36

37
#include <sixel.h>
38

39
#include "decoder-parallel.h"
40
#if SIXEL_ENABLE_THREADS
41
# include "logger.h"
42
# include "threading.h"
43
#endif
44
#include "compat_stub.h"
45

46
/*
47
 * The previous prescan-based parallel decoder has been removed.
48
 * The current implementation keeps the public entry points so that
49
 * callers can fall back to the single threaded decoder while we
50
 * prepare the new chunked worker pipeline.
51
 */
52

53
typedef struct sixel_decoder_thread_config {
54
    int env_checked;
55
    int env_valid;
56
    int env_threads;
57
    int override_active;
58
    int override_threads;
59
} sixel_decoder_thread_config_t;
60

61
#if SIXEL_ENABLE_THREADS
62
typedef struct sixel_decoder_worker_context {
63
    struct sixel_decoder_worker_chain *chain;
64
    unsigned char *input;
65
    unsigned char *anchor;
66
    int length;
67
    int payload_len;
68
    int start_offset;
69
    int end_offset;
70
    int index;
71
    int direct_mode;
72
    int const *palette;
73
    int palette_limit;
74
    int width;
75
    int height;
76
    int pixel_size;
77
    int depth;
78
    sixel_logger_t *logger;
79
    unsigned char *local_buffer;
80
    int local_capacity;
81
    int local_written;
82
    int result;
83
} sixel_decoder_worker_context_t;
84

85
typedef struct sixel_decoder_local_chunk {
86
    unsigned char *data;
87
    int start_row;
88
    int rows;
89
    struct sixel_decoder_local_chunk *next;
90
} sixel_decoder_local_chunk_t;
91

92
typedef struct sixel_local_buffer {
93
    sixel_decoder_local_chunk_t *head;
94
    sixel_decoder_local_chunk_t *tail;
95
    sixel_decoder_local_chunk_t *cursor;
96
    int width;
97
    int pixel_size;
98
} sixel_local_buffer_t;
99

100
typedef struct sixel_decoder_worker_chain {
101
    sixel_mutex_t mutex;
102
    sixel_cond_t cond;
103
    int *copy_offsets;
104
    int *copy_ready;
105
    int thread_count;
106
    unsigned char *global_buffer;
107
    int global_capacity;
108
    int pixel_size;
109
    int abort_requested;
110
} sixel_decoder_worker_chain_t;
111
#endif
112

113
static sixel_decoder_thread_config_t g_decoder_threads = {
114
    0,
115
    0,
116
    1,
117
    0,
118
    1
119
};
120

121
#if SIXEL_ENABLE_THREADS
122
static void
123
sixel_decoder_parallel_fill_spans(int payload_len,
124
                                  int threads,
125
                                  int *spans);
126

127
static int
128
sixel_decoder_parallel_skew_percent(void)
6✔
129
{
130
    char const *text;
6✔
131
    char *endptr;
6✔
132
    long value;
6✔
133

134
    /*
135
     * SIXEL_PARALLEL_SKEW lets operators bias span lengths by +/-20% so the
136
     * trailing workers take slightly more work.  The default keeps spans
137
     * balanced.
138
     */
139
    text = sixel_compat_getenv("SIXEL_PARALLEL_SKEW");
6✔
140
    if (text == NULL || text[0] == '\0') {
6!
141
        return 0;
142
    }
143

144
    errno = 0;
×
145
    value = strtol(text, &endptr, 10);
×
146
    if (errno != 0 || endptr == text || *endptr != '\0') {
×
147
        return 0;
148
    }
149

150
    if (value < -20) {
×
151
        value = -20;
152
    } else if (value > 20) {
×
153
        value = 20;
154
    }
155

156
    return (int)value;
×
157
}
158

159
static void
160
sixel_decoder_parallel_fill_spans(int payload_len,
6✔
161
                                  int threads,
162
                                  int *spans)
163
{
164
    int base_share;
6✔
165
    int skew_percent;
6✔
166
    double skew;
6✔
167
    int i;
6✔
168
    double center;
6✔
169
    int total;
6✔
170
    int remainder;
6✔
171

172
    base_share = payload_len / threads;
6✔
173
    skew_percent = sixel_decoder_parallel_skew_percent();
6✔
174
    skew = ((double)base_share * (double)skew_percent) / 100.0;
6✔
175
    total = 0;
6✔
176
    for (i = 0; i < threads; ++i) {
30✔
177
        center = (double)i - (double)(threads - 1) / 2.0;
24✔
178
        spans[i] = base_share + (int)(skew * center);
24✔
179
        if (spans[i] < 1) {
24!
180
            spans[i] = 1;
×
181
        }
182
        total += spans[i];
24✔
183
    }
184

185
    remainder = payload_len - total;
6✔
186
    while (remainder > 0) {
6!
187
        for (i = threads - 1; i >= 0 && remainder > 0; --i) {
×
188
            spans[i] += 1;
×
189
            --remainder;
×
190
        }
191
    }
192
    while (remainder < 0) {
6!
193
        for (i = threads - 1; i >= 0 && remainder < 0; --i) {
×
194
            if (spans[i] > 1) {
×
195
                spans[i] -= 1;
×
196
                ++remainder;
×
197
            }
198
        }
199
        if (remainder < 0) {
×
200
            break;
201
        }
202
    }
203
}
6✔
204

205
static int
206
sixel_decoder_parallel_pixel_size(int depth)
213✔
207
{
208
    int pixel_size;
213✔
209

210
    pixel_size = 4;
213✔
211
    if (depth == 1) {
213✔
212
        pixel_size = 1;
213
    } else if (depth == 2) {
12!
214
        pixel_size = 2;
×
215
    }
216

217
    return pixel_size;
213✔
218
}
219

220
static void
221
sixel_decoder_parallel_store_pixel(unsigned char *dst,
864✔
222
                                   int depth,
223
                                   int color_index,
224
                                   int const *palette)
225
{
226
    int color;
864✔
227

228
    if (depth == 1) {
864!
229
        dst[0] = (unsigned char)color_index;
×
230
        return;
×
231
    }
232

233
    if (depth == 2) {
864!
234
        unsigned short packed;
×
235

236
        packed = (unsigned short)color_index;
×
237
        memcpy(dst, &packed, sizeof(packed));
×
238
        return;
×
239
    }
240

241
    color = 0;
864✔
242
    if (palette != NULL &&
864!
243
            color_index >= 0 &&
864!
244
            color_index < SIXEL_PALETTE_MAX_DECODER) {
245
        color = palette[color_index];
864✔
246
    }
247
    dst[0] = (unsigned char)((color >> 16) & 0xff);
864✔
248
    dst[1] = (unsigned char)((color >> 8) & 0xff);
864✔
249
    dst[2] = (unsigned char)(color & 0xff);
864✔
250
    dst[3] = 255u;
864✔
251
}
1!
252

253
static void
254
sixel_local_buffer_init(sixel_local_buffer_t *buffer,
24✔
255
                        int width,
256
                        int pixel_size)
257
{
258
    buffer->head = NULL;
24✔
259
    buffer->tail = NULL;
24✔
260
    buffer->cursor = NULL;
24✔
261
    buffer->width = width;
24✔
262
    buffer->pixel_size = pixel_size;
24✔
263
}
264

265
static void
266
sixel_local_buffer_dispose(sixel_local_buffer_t *buffer)
24✔
267
{
268
    sixel_decoder_local_chunk_t *cursor;
24✔
269

270
    cursor = buffer->head;
24✔
271
    while (cursor != NULL) {
48✔
272
        sixel_decoder_local_chunk_t *tmp;
24✔
273

274
        free(cursor->data);
24✔
275
        tmp = cursor->next;
24✔
276
        free(cursor);
24✔
277
        cursor = tmp;
24✔
278
    }
279

280
    buffer->head = NULL;
24✔
281
    buffer->tail = NULL;
24✔
282
    buffer->cursor = NULL;
24✔
283
}
24✔
284

285
static sixel_decoder_local_chunk_t *
286
sixel_local_buffer_append(sixel_local_buffer_t *buffer,
24✔
287
                          int rows,
288
                          int start_row)
289
{
290
    sixel_decoder_local_chunk_t *chunk;
24✔
291
    size_t bytes;
24✔
292

293
    if (rows % 6 != 0) {
24!
294
        rows += 6 - (rows % 6);
×
295
    }
296

297
    chunk = (sixel_decoder_local_chunk_t *)calloc(
24✔
298
        1, sizeof(sixel_decoder_local_chunk_t));
299
    if (chunk == NULL) {
24!
300
        return NULL;
301
    }
302

303
    bytes = (size_t)(rows * buffer->width) * (size_t)buffer->pixel_size;
24✔
304
    chunk->data = (unsigned char *)calloc(bytes, 1);
24✔
305
    if (chunk->data == NULL) {
24!
306
        free(chunk);
×
307
        return NULL;
×
308
    }
309

310
    chunk->start_row = start_row;
24✔
311
    chunk->rows = rows;
24✔
312
    chunk->next = NULL;
24✔
313

314
    if (buffer->head == NULL) {
24!
315
        buffer->head = chunk;
24✔
316
    } else {
317
        buffer->tail->next = chunk;
×
318
    }
319
    buffer->tail = chunk;
24✔
320
    buffer->cursor = chunk;
24✔
321

322
    return chunk;
24✔
323
}
324

325
static sixel_decoder_local_chunk_t *
326
sixel_local_buffer_reserve_row(sixel_local_buffer_t *buffer,
288✔
327
                               int row)
328
{
329
    sixel_decoder_local_chunk_t *cursor;
288✔
330

331
    cursor = buffer->cursor;
288✔
332
    while (cursor != NULL && row >= cursor->start_row + cursor->rows) {
288!
333
        cursor = cursor->next;
×
334
    }
335
    if (cursor != NULL && row >= cursor->start_row) {
288!
336
        buffer->cursor = cursor;
288✔
337
        return cursor;
288✔
338
    }
339

340
    cursor = buffer->tail;
×
341
    if (cursor == NULL) {
×
342
        return NULL;
343
    }
344

345
    while (row >= cursor->start_row + cursor->rows) {
×
346
        sixel_decoder_local_chunk_t *next;
×
347
        int start_row;
×
348

349
        start_row = cursor->start_row + cursor->rows;
×
350
        next = sixel_local_buffer_append(buffer, cursor->rows, start_row);
×
351
        if (next == NULL) {
×
352
            return NULL;
353
        }
354
        cursor = next;
355
    }
×
356

357
    buffer->cursor = cursor;
×
358
    return cursor;
×
359
}
360

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

413
    context = (sixel_decoder_worker_context_t *)arg;
24✔
414
    if (context == NULL) {
24!
415
        return (-1);
416
    }
417

418
    context->result = status;
24✔
419

420
    chain = context->chain;
24✔
421
    if (chain == NULL) {
24!
422
        context->result = status;
423
        return status;
424
    }
425

426
    logger = context->logger;
24✔
427
    anchor = context->anchor;
24✔
428
    if (context->input == NULL || context->length <= 0) {
24!
429
        context->result = status;
430
        return status;
431
    }
432

433
    width = context->width;
24✔
434
    if (width <= 0) {
24!
435
        context->result = status;
436
        return status;
437
    }
438

439
    pixel_size = context->pixel_size;
24✔
440
    depth = context->depth;
24✔
441
    height = context->height;
24✔
442
    if (height <= 0) {
24!
443
        context->result = status;
444
        return status;
445
    }
446
    sixel_local_buffer_init(&local_buffer, width, pixel_size);
24✔
447
    if (context->payload_len <= 0) {
24!
448
        context->result = status;
449
        return status;
450
    }
451

452
    start = context->input + context->start_offset;
24✔
453
    if (start < context->input ||
24!
454
            start >= context->input + context->length) {
24!
455
        context->result = status;
456
        return status;
457
    }
458

459
    assigned = context->end_offset - context->start_offset + 1;
24✔
460
    if (assigned <= 0) {
24!
461
        context->result = status;
462
        return status;
463
    }
464

465
    status = 0;
24✔
466

467
    if (logger != NULL) {
24!
468
        /*
469
         * Decode window for this worker. The key keeps decode spans grouped
470
         * per-thread in the timeline output.
471
         */
472
        sixel_logger_logf(logger,
×
473
                          "decode",
474
                          "decoder",
475
                          "start",
476
                          context->index,
477
                          context->index,
478
                          0,
479
                          0,
480
                          context->start_offset,
481
                          context->end_offset,
482
                          "worker %d decode span [%d,%d]",
483
                          context->index,
484
                          context->start_offset,
485
                          context->end_offset);
486
    }
487

488
    cursor = start;
24✔
489
    if (context->index > 0) {
24✔
490
        cursor = (unsigned char *)memchr(start,
18✔
491
                                         '-',
492
                                         (size_t)(context->length -
18✔
493
                                         context->start_offset));
18✔
494
        if (cursor != NULL &&
18!
495
                cursor + 1 < context->input + context->length) {
18!
496
            cursor += 1;
24✔
497
        } else {
498
            cursor = start;
499
        }
500
    }
501
    if (context->index > 0 && cursor == start) {
24!
502
        status = (-1);
×
503
        context->result = status;
×
504
        return status;
×
505
    }
506

507
    if (anchor != NULL && anchor < cursor) {
24!
508
        scan = anchor;
509
        while (scan < cursor) {
5,814✔
510
            if (*scan == '-') {
5,796✔
511
                line_count += 1;
18✔
512
                scan += 1;
18✔
513
                continue;
18✔
514
            }
515

516
            if (*scan == '#') {
5,778✔
517
                int value;
1,152✔
518
                unsigned char *p;
1,152✔
519

520
                value = 0;
1,152✔
521
                p = scan + 1;
1,152✔
522
                while (p < cursor && *p >= '0' && *p <= '9') {
3,276!
523
                    value = value * 10 + (*p - '0');
2,124✔
524
                    p += 1;
2,124✔
525
                }
526
                color_index = value;
1,152✔
527
                if (color_index < 0) {
1,152!
528
                    color_index = 0;
529
                }
530
                if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
1,152!
531
                    color_index = SIXEL_PALETTE_MAX_DECODER - 1;
532
                }
533
                if (p < cursor && *p == ';') {
1,152!
534
                    scan = p;
535
                    continue;
536
                }
537
                scan = p;
1,152✔
538
                continue;
1,152✔
539
            }
540

541
            scan += 1;
4,626✔
542
        }
543
    }
544
    row_offset = line_count * 6;
24✔
545

546
    capacity = (int)((double)chain->global_capacity *
24✔
547
        ((double)assigned / (double)context->payload_len));
24✔
548
    capacity = (int)((double)capacity * 1.10);
24✔
549
    if (capacity < 1) {
24!
550
        capacity = 1;
551
    }
552
    if (capacity < width * 6) {
24!
553
        capacity = width * 6;
554
    }
555
    if (capacity % (width * 6) != 0) {
24!
556
        capacity += (width * 6) - (capacity % (width * 6));
×
557
    }
558

559
    chunk_cursor = sixel_local_buffer_append(&local_buffer,
24✔
560
                                             capacity / width,
561
                                             0);
562
    if (chunk_cursor == NULL) {
24!
563
        status = (-1);
×
564
        context->result = status;
×
565
        return status;
×
566
    }
567
    capacity = chunk_cursor->rows * width;
24✔
568

569
    stop = context->input + context->end_offset;
24✔
570
    limit = context->input + context->length;
24✔
571
    while (cursor < limit) {
252!
572
        ch = *cursor;
252✔
573

574
        if (ch < 0x20) {
252!
575
            if (ch == 0x18 || ch == 0x1a) {
90!
576
                fallback = 1;
577
                status = (-1);
578
                break;
579
            }
580
            cursor += 1;
90✔
581
            if (ch == 0x1b && cursor < limit && *cursor == '\\') {
90!
582
                status = (0);
583
                break;
584
            }
585
            continue;
72✔
586
        }
587

588
        if (ch == '"') {
54!
589
            fallback = 1;
590
            status = (-1);
591
            break;
592
        }
593

594
        if (ch == '!') {
90✔
595
            int value;
54✔
596
            unsigned char *p;
54✔
597

598
            value = 0;
54✔
599
            p = cursor + 1;
54✔
600
            while (p < limit && *p >= '0' && *p <= '9') {
108!
601
                value = value * 10 + (*p - '0');
54✔
602
                p += 1;
54✔
603
            }
604
            if (value <= 0) {
54!
605
                value = 1;
606
            }
607
            repeat = value;
54✔
608
            cursor = p;
54✔
609
            continue;
54✔
610
        }
611

612
        if (ch == '#') {
72✔
613
            int value;
54✔
614
            unsigned char *p;
54✔
615

616
            value = 0;
54✔
617
            p = cursor + 1;
54✔
618
            while (p < limit && *p >= '0' && *p <= '9') {
108!
619
                value = value * 10 + (*p - '0');
54✔
620
                p += 1;
54✔
621
            }
622
            if (p < limit && *p == ';') {
54!
623
                fallback = 1;
624
                status = (-1);
625
                break;
626
            }
627
            color_index = value;
54✔
628
            if (color_index < 0) {
54!
629
                color_index = 0;
630
            }
631
            if (color_index >= SIXEL_PALETTE_MAX_DECODER) {
54!
632
                color_index = SIXEL_PALETTE_MAX_DECODER - 1;
633
            }
634
            cursor = p;
54✔
635
            continue;
54✔
636
        }
1!
637

638
        if (ch == '$') {
18!
639
            cursor += 1;
×
640
            pos_x = 0;
×
641
            continue;
×
642
        }
643

644
        if (ch == '-') {
18!
645
            if (cursor >= stop) {
×
646
                break;
647
            }
648
            cursor += 1;
×
649
            pos_x = 0;
×
650
            pos_y += 6;
×
651
            chunk_cursor = sixel_local_buffer_reserve_row(&local_buffer,
×
652
                                                          pos_y);
653
            if (chunk_cursor == NULL) {
×
654
                fallback = 1;
655
                status = (-1);
656
                break;
657
            }
658
            continue;
×
659
        }
660

661
        if (ch < '?' || ch > '~') {
18!
662
            cursor += 1;
×
663
            continue;
×
664
        }
665

666
        bits = ch - '?';
54✔
667
        for (i = 0; i < 6; ++i) {
342✔
668
            if ((bits & (1 << i)) != 0) {
294!
669
                if (pos_x + repeat > width ||
294!
670
                        row_offset + pos_y + i >= height) {
288!
671
                    fallback = 1;
672
                    status = (-1);
673
                    break;
674
                }
675
                chunk_cursor = sixel_local_buffer_reserve_row(
288✔
676
                    &local_buffer, pos_y + i);
677
                if (chunk_cursor == NULL) {
288!
678
                    fallback = 1;
679
                    status = (-1);
680
                    break;
681
                }
682

683
                row_base = (pos_y + i - chunk_cursor->start_row) * width +
288✔
684
                    pos_x;
685
                relative = (pos_y + i) * width + pos_x;
288✔
686

687
                if (pixel_size == 1 && repeat > 3) {
288!
688
                    memset(chunk_cursor->data + (size_t)row_base,
144✔
689
                           color_index,
690
                           repeat);
691
                } else {
692
                    for (r = 0; r < repeat; ++r) {
1,008✔
693
                        sixel_decoder_parallel_store_pixel(
864✔
694
                            chunk_cursor->data +
864✔
695
                            (size_t)(row_base + r) * (size_t)pixel_size,
864✔
696
                            depth,
697
                            color_index,
698
                            context->palette);
699
                    }
700
                }
701
                written += repeat;
288✔
702
                if (min_relative < 0 || relative < min_relative) {
288!
703
                    min_relative = relative;
704
                }
705
                if (max_relative < relative + repeat - 1) {
288✔
706
                    max_relative = relative + repeat - 1;
707
                }
708
            }
709
        }
710

711
        if (fallback) {
54✔
712
            break;
713
        }
714

715
        cursor += 1;
48✔
716
        pos_x += repeat;
48✔
717
        repeat = 1;
48✔
718

719
    }
720

721
    copy_span = max_relative + 1;
24✔
722
    if (copy_span < 0) {
24!
723
        copy_span = 0;
724
    }
725

726
    if (logger != NULL) {
24!
727
        sixel_logger_logf(logger,
×
728
                          "decode",
729
                          "decoder",
730
                          fallback ? "abort" : "finish",
731
                          context->index,
732
                          context->index,
733
                          0,
734
                          0,
735
                          context->start_offset,
736
                          context->end_offset,
737
                          "worker %d decode wrote=%d status=%d",
738
                          context->index,
739
                          written,
740
                          status);
741
    }
742

743
    if (status != 0) {
24✔
744
        sixel_mutex_lock(&chain->mutex);
6✔
745
        chain->abort_requested = 1;
6✔
746
        sixel_cond_broadcast(&chain->cond);
6✔
747
        sixel_mutex_unlock(&chain->mutex);
6✔
748
        sixel_local_buffer_dispose(&local_buffer);
6✔
749
        context->result = status;
6✔
750
        return status;
6✔
751
    }
752

753
    context->local_buffer = local_buffer.head != NULL ?
36!
754
        local_buffer.head->data : NULL;
18!
755
    context->local_capacity = capacity;
18✔
756
    context->local_written = copy_span;
18✔
757

758
    sixel_mutex_lock(&chain->mutex);
18✔
759
    while (!chain->copy_ready[context->index] && !chain->abort_requested) {
20!
760
        sixel_cond_wait(&chain->cond, &chain->mutex);
2✔
761
    }
762
    if (chain->abort_requested) {
18!
763
        sixel_mutex_unlock(&chain->mutex);
18✔
764
        sixel_local_buffer_dispose(&local_buffer);
18✔
765
        context->local_buffer = NULL;
18✔
766
        context->local_capacity = 0;
18✔
767
        context->local_written = 0;
18✔
768
        status = (-1);
18✔
769
        context->result = status;
18✔
770
        return status;
18✔
771
    }
772
    chain_offset = chain->copy_offsets[context->index];
×
773
    copy_offset = chain_offset;
×
774
    next_offset = copy_offset + copy_span;
×
775
    context->local_written = copy_span;
×
776
    if (context->index + 1 < chain->thread_count) {
×
777
        chain->copy_offsets[context->index + 1] = next_offset;
×
778
        chain->copy_ready[context->index + 1] = 1;
×
779
    }
780
    sixel_cond_broadcast(&chain->cond);
×
781
    sixel_mutex_unlock(&chain->mutex);
×
782

783
    if (copy_offset < 0 || chain->global_buffer == NULL) {
×
784
        sixel_mutex_lock(&chain->mutex);
×
785
        chain->abort_requested = 1;
×
786
        sixel_cond_broadcast(&chain->cond);
×
787
        sixel_mutex_unlock(&chain->mutex);
×
788
        sixel_local_buffer_dispose(&local_buffer);
×
789
        context->local_buffer = NULL;
×
790
        context->local_capacity = 0;
×
791
        context->local_written = 0;
×
792
        status = (-1);
×
793
        context->result = status;
×
794
        return status;
×
795
    }
796

797
    if (logger != NULL) {
×
798
        /* Chain memcpy execution so the timeline shows the serialized copy */
799
        sixel_logger_logf(logger,
×
800
                          "copy",
801
                          "decoder",
802
                          "start",
803
                          context->index,
804
                          context->index,
805
                          0,
806
                          0,
807
                          copy_offset,
808
                          next_offset,
809
                          "worker %d memcpy count=%d",
810
                          context->index,
811
                          copy_span);
812
    }
813

814
    if (max_relative >= 0) {
×
815
        chunk_cursor = local_buffer.head;
×
816
        while (chunk_cursor != NULL) {
×
817
            int rows;
×
818
            size_t chunk_bytes;
×
819
            size_t chunk_offset;
×
820

821
            rows = chunk_cursor->rows;
×
822
            if (chunk_cursor->start_row + rows >
×
823
                    (max_relative / width) + 1) {
×
824
                rows = (max_relative / width) + 1 -
×
825
                    chunk_cursor->start_row;
826
            }
827
            if (rows < 0) {
×
828
                rows = 0;
829
            }
830
            if (rows > 0) {
×
831
                chunk_bytes = (size_t)(rows * width) *
×
832
                    (size_t)chain->pixel_size;
×
833
                chunk_offset = (size_t)((row_offset +
×
834
                    chunk_cursor->start_row) * width) *
×
835
                    (size_t)chain->pixel_size;
836
                memcpy(chain->global_buffer + chunk_offset,
×
837
                       chunk_cursor->data,
×
838
                       chunk_bytes);
839
            }
840
            chunk_cursor = chunk_cursor->next;
×
841
        }
842
    }
843

844
    if (logger != NULL) {
×
845
        sixel_logger_logf(logger,
×
846
                          "copy",
847
                          "decoder",
848
                          "finish",
849
                          context->index,
850
                          context->index,
851
                          0,
852
                          0,
853
                          copy_offset,
854
                          next_offset,
855
                          "worker %d memcpy done", 
856
                          context->index);
857
    }
858

859
    sixel_local_buffer_dispose(&local_buffer);
×
860
    context->local_buffer = NULL;
×
861
    context->local_capacity = 0;
×
862
    context->local_written = 0;
×
863

864
    context->result = status;
×
865
    return status;
×
866
}
867
#endif
868

869
static int
870
sixel_decoder_threads_token_is_auto(char const *text)
204✔
871
{
872
    if (text == NULL) {
204!
873
        return 0;
874
    }
875
    if ((text[0] == 'a' || text[0] == 'A') &&
204!
876
            (text[1] == 'u' || text[1] == 'U') &&
×
877
            (text[2] == 't' || text[2] == 'T') &&
×
878
            (text[3] == 'o' || text[3] == 'O') &&
×
879
            text[4] == '\0') {
×
880
        return 1;
×
881
    }
882
    return 0;
883
}
884

885
static int
886
sixel_decoder_threads_normalize(int requested)
204✔
887
{
888
    int normalized;
204✔
889

890
#if SIXEL_ENABLE_THREADS
891
    int hw_threads;
204✔
892

893
    if (requested <= 0) {
204!
894
        hw_threads = sixel_get_hw_threads();
×
895
        if (hw_threads < 1) {
×
896
            hw_threads = 1;
897
        }
898
        normalized = hw_threads;
899
    } else {
900
        normalized = requested;
901
    }
902
#else
903
    (void)requested;
904
    normalized = 1;
905
#endif
906
    if (normalized < 1) {
1!
907
        normalized = 1;
908
    }
909
    return normalized;
×
910
}
911

912
static int
913
sixel_decoder_threads_parse_value(char const *text, int *value)
204✔
914
{
915
    long parsed;
204✔
916
    char *endptr;
204✔
917
    int normalized;
204✔
918

919
    if (text == NULL || value == NULL) {
204!
920
        return 0;
921
    }
922
    if (sixel_decoder_threads_token_is_auto(text)) {
204!
923
        normalized = sixel_decoder_threads_normalize(0);
×
924
        *value = normalized;
×
925
        return 1;
×
926
    }
927
    errno = 0;
204✔
928
    parsed = strtol(text, &endptr, 10);
204✔
929
    if (endptr == text || *endptr != '\0' || errno == ERANGE) {
204!
930
        return 0;
931
    }
932
    if (parsed < 1) {
201!
933
        normalized = sixel_decoder_threads_normalize(1);
934
    } else if (parsed > INT_MAX) {
201!
935
        normalized = sixel_decoder_threads_normalize(INT_MAX);
936
    } else {
937
        normalized = sixel_decoder_threads_normalize((int)parsed);
201✔
938
    }
939
    *value = normalized;
201✔
940
    return 1;
201✔
941
}
942

943
static void
944
sixel_decoder_threads_load_env(void)
213✔
945
{
946
    char const *text;
213✔
947
    int parsed;
213✔
948

949
    if (g_decoder_threads.env_checked) {
213✔
950
        return;
12✔
951
    }
952
    g_decoder_threads.env_checked = 1;
210✔
953
    g_decoder_threads.env_valid = 0;
210✔
954
    text = sixel_compat_getenv("SIXEL_THREADS");
210✔
955
    if (text == NULL || text[0] == '\0') {
210!
956
        return;
957
    }
958
    if (sixel_decoder_threads_parse_value(text, &parsed)) {
201!
959
        g_decoder_threads.env_threads = parsed;
201✔
960
        g_decoder_threads.env_valid = 1;
201✔
961
    }
962
}
1!
963

964
SIXELSTATUS
965
sixel_decoder_parallel_override_threads(char const *text)
3✔
966
{
967
    SIXELSTATUS status;
3✔
968
    int parsed;
3✔
969

970
    status = SIXEL_BAD_ARGUMENT;
3✔
971
    if (text == NULL || text[0] == '\0') {
3!
972
        sixel_helper_set_additional_message(
×
973
            "decoder: missing thread count after -=/--threads.");
974
        goto end;
×
975
    }
976
    if (!sixel_decoder_threads_parse_value(text, &parsed)) {
3!
977
        sixel_helper_set_additional_message(
3✔
978
            "decoder: threads must be a positive integer or 'auto'.");
979
        goto end;
3✔
980
    }
981
    g_decoder_threads.override_active = 1;
×
982
    g_decoder_threads.override_threads = parsed;
×
983
    status = SIXEL_OK;
×
984
end:
3✔
985
    return status;
3✔
986
}
987

988
int
989
sixel_decoder_parallel_resolve_threads(void)
213✔
990
{
991
    int threads;
213✔
992

993
    threads = 1;
213✔
994
    sixel_decoder_threads_load_env();
213✔
995
    if (g_decoder_threads.override_active) {
213!
996
        threads = sixel_decoder_threads_normalize(
×
997
            g_decoder_threads.override_threads);
998
    } else if (g_decoder_threads.env_valid) {
213✔
999
        threads = sixel_decoder_threads_normalize(
204!
1000
            g_decoder_threads.env_threads);
1001
    }
1002
    if (threads < 1) {
×
1003
        threads = 1;
1004
    }
1005
    return threads;
213✔
1006
}
1007

1008
SIXELSTATUS
1009
sixel_decoder_parallel_request_start(int direct_mode,
213✔
1010
                                     unsigned char *input,
1011
                                     int length,
1012
                                     unsigned char *anchor,
1013
                                     image_buffer_t *image,
1014
                                     int const *palette,
1015
                                     sixel_logger_t *logger)
1016
{
1017
#if SIXEL_ENABLE_THREADS
1018
    SIXELSTATUS status;
213✔
1019
    int threads;
213✔
1020
    int payload_start;
213✔
1021
    int payload_len;
213✔
1022
    sixel_thread_t *workers;
213✔
1023
    sixel_decoder_worker_context_t *contexts;
213✔
1024
    sixel_decoder_worker_chain_t chain;
213✔
1025
    int *copy_offsets;
213✔
1026
    int *copy_ready;
213✔
1027
    int global_capacity;
213✔
1028
    int *spans;
213✔
1029
    int i;
213✔
1030
    int offset;
213✔
1031
    int created;
213✔
1032
    int parallel_failed;
213✔
1033
    int runtime_error;
213✔
1034
    int sync_ready;
213✔
1035
    int pixel_size;
213✔
1036
    int palette_limit;
213✔
1037

1038
    status = SIXEL_RUNTIME_ERROR;
213✔
1039
    workers = NULL;
213✔
1040
    contexts = NULL;
213✔
1041
    copy_offsets = NULL;
213✔
1042
    copy_ready = NULL;
213✔
1043
    spans = NULL;
213✔
1044
    threads = 0;
213✔
1045
    payload_start = 0;
213✔
1046
    payload_len = 0;
213✔
1047
    global_capacity = 0;
213✔
1048
    offset = 0;
213✔
1049
    created = 0;
213✔
1050
    parallel_failed = 0;
213✔
1051
    runtime_error = 0;
213✔
1052
    sync_ready = 0;
213✔
1053
    pixel_size = 4;
213✔
1054
    palette_limit = SIXEL_PALETTE_MAX_DECODER;
213✔
1055
    memset(&chain, 0, sizeof(chain));
213!
1056

1057
    if (input == NULL || anchor == NULL || length <= 0 || image == NULL) {
213!
1058
        runtime_error = 1;
×
1059
        goto cleanup;
×
1060
    }
1061

1062
    pixel_size = sixel_decoder_parallel_pixel_size(image->depth);
213✔
1063

1064
    payload_start = (int)(anchor - input);
213✔
1065
    if (payload_start < 0 || payload_start >= length) {
213!
1066
        runtime_error = 1;
×
1067
        goto cleanup;
×
1068
    }
1069

1070
    payload_len = length - payload_start;
213✔
1071
    if (payload_len <= 0) {
213!
1072
        runtime_error = 1;
1073
        goto cleanup;
1074
    }
1075

1076
    palette_limit = image->ncolors;
213✔
1077
    if (palette_limit <= 0 || palette_limit > SIXEL_PALETTE_MAX_DECODER) {
213!
1078
        palette_limit = SIXEL_PALETTE_MAX_DECODER;
×
1079
    }
1080

1081
    threads = sixel_decoder_parallel_resolve_threads();
213✔
1082
    if (threads < 1) {
213!
1083
        threads = 1;
1084
    }
1085
    if (threads > payload_len) {
213!
1086
        threads = payload_len;
1087
    }
1088
    if (threads < 2) {
213✔
1089
        status = SIXEL_FALSE;
207✔
1090
        goto cleanup;
207✔
1091
    }
1092

1093
    workers = (sixel_thread_t *)calloc((size_t)threads,
6✔
1094
                                       sizeof(sixel_thread_t));
1095
    contexts = (sixel_decoder_worker_context_t *)calloc(
6✔
1096
        (size_t)threads, sizeof(sixel_decoder_worker_context_t));
1097
    spans = (int *)calloc((size_t)threads, sizeof(int));
6✔
1098
    copy_offsets = (int *)calloc((size_t)threads, sizeof(int));
6✔
1099
    copy_ready = (int *)calloc((size_t)threads, sizeof(int));
6✔
1100
    if (workers == NULL || contexts == NULL || spans == NULL ||
6!
1101
            copy_offsets == NULL || copy_ready == NULL) {
6!
1102
        runtime_error = 1;
×
1103
        goto cleanup;
×
1104
    }
1105

1106
    global_capacity = image->width * image->height;
6✔
1107
    if (global_capacity <= 0) {
6!
1108
        runtime_error = 1;
×
1109
        goto cleanup;
×
1110
    }
1111
    chain.global_buffer = (unsigned char *)image->pixels.p;
6✔
1112
    chain.pixel_size = pixel_size;
6✔
1113
    chain.copy_offsets = copy_offsets;
6✔
1114
    chain.copy_ready = copy_ready;
6✔
1115
    chain.thread_count = threads;
6✔
1116
    chain.global_capacity = global_capacity;
6✔
1117
    sixel_mutex_init(&chain.mutex);
6✔
1118
    sixel_cond_init(&chain.cond);
6✔
1119
    chain.copy_ready[0] = 1;
6✔
1120
    chain.copy_offsets[0] = 0;
6✔
1121
    sync_ready = 1;
6✔
1122

1123
    sixel_decoder_parallel_fill_spans(payload_len, threads, spans);
6✔
1124

1125
    if (logger != NULL) {
6!
1126
        /*
1127
         * Record when the controller hands control to worker threads so the
1128
         * timeline can show the gap between parser startup and worker
1129
         * creation.
1130
         */
1131
        sixel_logger_logf(logger,
×
1132
                          "decoder",
1133
                          "controller",
1134
                          "launch",
1135
                          0,
1136
                          0,
1137
                          0,
1138
                          length,
1139
                          payload_start,
1140
                          length,
1141
                          "spawn %d workers payload=%d",
1142
                          threads,
1143
                          payload_len);
1144
    }
1145

1146
    offset = payload_start;
1147
    created = 0;
1148
    for (i = 0; i < threads; ++i) {
30✔
1149
        contexts[i].chain = &chain;
24✔
1150
        contexts[i].input = input;
24✔
1151
        contexts[i].anchor = anchor;
24✔
1152
        contexts[i].length = length;
24✔
1153
        contexts[i].payload_len = payload_len;
24✔
1154
        contexts[i].start_offset = offset;
24✔
1155
        offset += spans[i];
24✔
1156
        if (offset > length) {
24!
1157
            offset = length;
1158
        }
1159
        if (i == threads - 1) {
24✔
1160
            contexts[i].end_offset = length - 1;
6✔
1161
        } else {
1162
            contexts[i].end_offset = offset - 1;
18✔
1163
            if (contexts[i].end_offset < contexts[i].start_offset) {
18!
1164
                contexts[i].end_offset = contexts[i].start_offset;
×
1165
            }
1166
        }
1167
        contexts[i].index = i;
24✔
1168
        contexts[i].direct_mode = direct_mode;
24✔
1169
        contexts[i].palette = palette;
24✔
1170
        contexts[i].palette_limit = palette_limit;
24✔
1171
        contexts[i].width = image->width;
24✔
1172
        contexts[i].height = image->height;
24✔
1173
        contexts[i].pixel_size = pixel_size;
24✔
1174
        contexts[i].depth = image->depth;
24✔
1175
        contexts[i].logger = logger;
24✔
1176
        contexts[i].result = (-1);
24✔
1177
        status = sixel_thread_create(&workers[i],
24✔
1178
                                     sixel_decoder_parallel_worker,
1179
                                     &contexts[i]);
1180
        if (SIXEL_FAILED(status)) {
24!
1181
            runtime_error = 1;
1182
            break;
1183
        }
1184
        created += 1;
24✔
1185
    }
1186

1187
    for (i = 0; i < created; ++i) {
30✔
1188
        sixel_thread_join(&workers[i]);
24✔
1189
        if (contexts[i].result != 0) {
24!
1190
            parallel_failed = 1;
24✔
1191
        }
1192
    }
1193

1194
    if (chain.abort_requested) {
6!
1195
        parallel_failed = 1;
6✔
1196
    }
1197

1198
    if (runtime_error || created < threads) {
6!
1199
        status = SIXEL_RUNTIME_ERROR;
1200
    } else if (parallel_failed) {
6!
1201
        status = SIXEL_FALSE;
1202
    } else {
1203
        status = SIXEL_OK;
1204
    }
1205

1206
cleanup:
1207
    if (sync_ready) {
207✔
1208
        sixel_mutex_destroy(&chain.mutex);
6✔
1209
        sixel_cond_destroy(&chain.cond);
6✔
1210
    }
1211

1212
    free(workers);
213✔
1213
    free(contexts);
213✔
1214
    free(spans);
213✔
1215
    free(copy_offsets);
213✔
1216
    free(copy_ready);
213✔
1217

1218
    return status;
213✔
1219
#else
1220
    (void)direct_mode;
1221
    (void)input;
1222
    (void)length;
1223
    (void)anchor;
1224
    (void)image;
1225
    (void)palette;
1226
    (void)logger;
1227

1228
    return SIXEL_RUNTIME_ERROR;
1229
#endif
1230
}
1231

1232
/* emacs Local Variables:      */
1233
/* emacs mode: c               */
1234
/* emacs tab-width: 4          */
1235
/* emacs indent-tabs-mode: nil */
1236
/* emacs c-basic-offset: 4     */
1237
/* emacs End:                  */
1238
/* vim: set expandtab ts=4 sts=4 sw=4 : */
1239
/* 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