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

PredatorCZ / PreCore / 575

18 Nov 2025 09:45PM UTC coverage: 51.773% (+0.006%) from 51.767%
575

push

github

PredatorCZ
move from logic errors

0 of 5 new or added lines in 3 files covered. (0.0%)

1 existing line in 1 file now uncovered.

4131 of 7979 relevant lines covered (51.77%)

11430.45 hits per line

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

42.08
/src/app/texel.cpp
1
#define QOI_IMPLEMENTATION
2
#define QOI_NO_STDIO
3

4
#include "spike/app/texel.hpp"
5
#include "bc7decomp.h"
6
#include "pvr_decompress.hpp"
7
#include "qoi.h"
8
#include "spike/app/context.hpp"
9
#include "spike/crypto/crc32.hpp"
10
#include "spike/except.hpp"
11
#include "spike/format/DDS.hpp"
12
#include "spike/gpu/BlockDecoder.inl"
13
#include "spike/gpu/addr_ps3.hpp"
14
#include "spike/io/binwritter_stream.hpp"
15
#include "spike/reflect/reflector.hpp"
16
#include "spike/uni/format.hpp"
17
#include "spike/util/endian.hpp"
18
#include <sstream>
19
#include <variant>
20

21
bool BlockCompression(TexelInputFormatType fmt) {
×
22
  using F = TexelInputFormatType;
23
  switch (fmt) {
×
24
  case F::BC1:
25
  case F::BC2:
26
  case F::BC3:
27
  case F::BC4:
28
  case F::BC5:
29
  case F::BC7:
30
    return true;
31
  default:
×
32
    return false;
×
33
  }
34
};
35

36
TexelContextFormat OutputFormat() {
×
37
  return mainSettings.texelSettings.outputFormat;
56✔
38
}
39

40
bool IsFormatSupported(TexelContextFormat ofmt, TexelInputFormatType fmt) {
128✔
41
  using F = TexelInputFormatType;
42

43
  switch (fmt) {
128✔
44
    // Not supported
45
  case F::PVRTC2:
46
  case F::PVRTC4:
47
  case F::ETC1:
48
  case F::ETC1A4:
49
  case F::R4:
50
  case F::RG4:
51
    return true;
52

53
    // DDS, DDS_Legacy only
54
  case F::BC1:
17✔
55
  case F::BC2:
56
  case F::BC3:
57
    return ofmt == TexelContextFormat::UPNG ||
58
           ofmt == TexelContextFormat::QOI ||
17✔
59
           ofmt == TexelContextFormat::QOI_BMP;
17✔
60

61
    // DDS only
62
  case F::BC4:
8✔
63
  case F::BC5:
64
  case F::BC7:
65
  case F::RG8:
66
  case F::RGBA16:
67
  case F::BC6:
68
  case F::RGB9E5:
69
    return ofmt != TexelContextFormat::DDS;
8✔
70

71
    // BMP, DDS only
72
  case F::RGB10A2:
4✔
73
  case F::RGB5A1:
74
    return ofmt == TexelContextFormat::UPNG ||
4✔
75
           ofmt == TexelContextFormat::QOI ||
76
           ofmt == TexelContextFormat::DDS_Legacy;
4✔
77

78
    // BMP only
79
  case F::P8:
×
80
  case F::P4:
81
    return ofmt != TexelContextFormat::QOI_BMP;
×
82

83
  // DDS, DDS_Legacy, BMP only
84
  case F::RGBA4:
6✔
85
  case F::R5G6B5:
86
    return ofmt == TexelContextFormat::UPNG || ofmt == TexelContextFormat::QOI;
6✔
87

88
  // DDS, DDS_Legacy, BMP, UPNG only
89
  case F::R8:
4✔
90
    return ofmt == TexelContextFormat::QOI;
4✔
91

92
    // Supported for all
93
  case F::RGBA8:
94
  case F::INVALID:
95
    return false;
96

97
  // QOI, DDS_Legacy, UPNG only
98
  case F::RGB8:
2✔
99
    return ofmt == TexelContextFormat::DDS;
2✔
100
  }
101

102
  return false;
103
}
104

105
bool MustSwap(TexelInputFormatType fmt, bool shouldSwap) {
×
106
  using F = TexelInputFormatType;
107

108
  switch (fmt) {
×
109
  case F::R5G6B5:
110
  case F::RGBA4:
111
  case F::RGB10A2:
112
  case F::RGB5A1:
113
    return shouldSwap;
114
  default:
×
115
    return false;
×
116
    break;
117
  }
118
}
119

120
bool MustSwizzle(TexelSwizzle swizzle, uint32 numChannels) {
×
121
  if (swizzle.a != TexelSwizzleType::Alpha ||
101✔
122
      swizzle.r != TexelSwizzleType::Red ||
101✔
123
      swizzle.g != TexelSwizzleType::Green) {
×
124
    return true;
125
  }
126

127
  if (numChannels > 2) {
101✔
128
    return swizzle.b != TexelSwizzleType::Blue &&
98✔
129
           swizzle.b != TexelSwizzleType::DeriveZOrBlue;
98✔
130
  }
131

132
  return swizzle.b != TexelSwizzleType::Blue;
3✔
133
};
134

135
uint32 GetBPT(TexelInputFormatType fmt) {
25✔
136
  using F = TexelInputFormatType;
137

138
  switch (fmt) {
25✔
139
  case F::PVRTC2:
140
  case F::PVRTC4:
141
  case F::BC2:
142
  case F::BC3:
143
  case F::BC5:
144
  case F::BC7:
145
  case F::BC6:
146
  case F::ETC1A4:
147
    return 16;
148

149
  case F::ETC1:
3✔
150
  case F::BC1:
151
  case F::BC4:
152
  case F::RGBA16:
153
    return 8;
3✔
154

155
  case F::RG8:
5✔
156
  case F::RGB5A1:
157
  case F::RGBA4:
158
  case F::R5G6B5:
159
  case F::R4:
160
    return 2;
5✔
161

162
  case F::RGB10A2:
8✔
163
  case F::RGBA8:
164
  case F::RGB9E5:
165
    return 4;
8✔
166

167
  case F::P8:
6✔
168
  case F::P4:
169
  case F::R8:
170
  case F::RG4:
171
    return 1;
6✔
172
  case F::RGB8:
×
173
    return 3;
×
174
  case F::INVALID:
175
    return 0;
176
  }
177

178
  return 0;
179
}
180

181
TexelDataLayout NewTexelContextImpl::ComputeTraditionalDataLayout(
×
182
    TexelInputFormatType *typeOverrides) {
183
  uint32 mipCount = std::max(ctx.numMipmaps, uint8(1));
×
184
  uint32 width = ctx.width;
×
185
  uint32 height = ctx.height;
×
186
  uint32 depth = std::max(ctx.depth, uint16(1));
×
187
  uint32 numFaces = std::max(ctx.numFaces, int8(1));
×
188

189
  TexelDataLayout retVal{};
×
190

191
  for (uint32 m = 0; m < mipCount; m++) {
×
192
    uint32 _width = width;
193
    uint32 _height = height;
194
    TexelInputFormatType type =
195
        typeOverrides ? typeOverrides[m] : ctx.baseFormat.type;
×
196
    uint32 bpt = GetBPT(type);
×
197

198
    if (BlockCompression(type)) {
199
      _width = (_width + 3) / 4;
×
200
      _height = (_height + 3) / 4;
×
201
    }
202

203
    retVal.mipSizes[m] = depth * _width * _height * bpt;
×
204
    retVal.mipOffsets[m] = retVal.mipGroupSize;
×
205
    retVal.mipGroupSize += retVal.mipSizes[m];
×
206
    width = std::max(1U, width / 2);
×
207
    height = std::max(1U, height / 2);
×
208
    depth = std::max(1U, depth / 2);
×
209
  }
210

211
  retVal.groupSize = retVal.mipGroupSize * numFaces;
×
212
  return retVal;
×
213
}
214

215
bool BMPFallback(TexelInputFormatType fmt) {
×
216
  using F = TexelInputFormatType;
217

218
  switch (fmt) {
×
219
  case F::RGB10A2:
220
  case F::RGB5A1:
221
  case F::P8:
222
  case F::P4:
223
  case F::RGBA4:
224
  case F::R5G6B5:
225
  case F::R8:
226
    return OutputFormat() == TexelContextFormat::QOI_BMP;
×
227

228
  default:
229
    return false;
230
  }
231

232
  return false;
233
}
234

235
#pragma pack(2)
236
struct BMPHeader {
×
237
  uint16 bfType = 0x4D42; // BM
238
  uint32 bfSize;
239
  uint16 bfReserved1 = 0;
240
  uint16 bfReserved2 = 0;
241
  uint32 bfOffBits = 54;
242
};
243
#pragma pack()
244

245
struct BMPInfoHeader {
×
246
  uint32 biSize = 40;
247
  uint32 biWidth;
248
  uint32 biHeight;
249
  uint16 biPlanes = 1;
250
  uint16 biBitCount = 32;
251
  uint32 biCompression = 0;
252
  uint32 biSizeImage;
253
  uint32 biXPelsPerMeter = 0;
254
  uint32 biYPelsPerMeter = 0;
255
  uint32 biClrUsed = 0;
256
  uint32 biClrImportant = 0;
257
};
258

259
union BMPMask {
260
  struct {
261
    uint32 red;
262
    uint32 green;
263
    uint32 blue;
264
    uint32 alpha;
265
  };
266
  uint32 data[4];
267
};
268

269
BMPMask SwizzleMask(BMPMask bits, TexelSwizzle swizzle) {
×
270
  uint32 currentOffset = 0;
271
  BMPMask retVal{};
×
272

273
  for (uint32 c = 0; c < 4; c++) {
×
274
    uint32 swizzleIndex = swizzle.types[c] > TexelSwizzleType::Alpha
×
275
                              ? c
×
276
                              : uint32(swizzle.types[c]);
277

278
    uint32 numBits = bits.data[swizzleIndex];
×
279
    uint32 mask = (1 << numBits) - 1;
×
280

281
    retVal.data[c] = mask << currentOffset;
×
282
    currentOffset += numBits;
×
283
  }
284

285
  return retVal;
×
286
}
287

288
BMPMask FillBmpFormat(NewTexelContextCreate ctx, TexelInputFormat fmt,
×
289
                      BMPInfoHeader &info) {
290
  using F = TexelInputFormatType;
291
  info.biWidth = ctx.width;
×
292
  info.biHeight = ctx.height;
×
293

294
  switch (fmt.type) {
×
295
  case F::RGB10A2:
×
296
    info.biBitCount = 32;
×
297
    info.biCompression = 6;
×
298
    return SwizzleMask({{.red = 10, .green = 10, .blue = 10, .alpha = 2}},
×
299
                       fmt.swizzle);
×
300
  case F::RGB5A1:
×
301
    info.biBitCount = 16;
×
302
    info.biCompression = 6;
×
303
    return SwizzleMask({{.red = 4, .green = 4, .blue = 4, .alpha = 1}},
×
304
                       fmt.swizzle);
×
305
  case F::RGBA4:
×
306
    info.biBitCount = 16;
×
307
    info.biCompression = 6;
×
308
    return SwizzleMask({{.red = 4, .green = 4, .blue = 4, .alpha = 4}},
×
309
                       fmt.swizzle);
×
310
  case F::R5G6B5:
×
311
    info.biBitCount = 16;
×
312
    info.biCompression = 3;
×
313
    return SwizzleMask({{.red = 5, .green = 6, .blue = 5, .alpha = 0}},
×
314
                       fmt.swizzle);
×
315
  default:
×
316
    return {};
×
317
  }
318

319
  return {};
320
}
321

322
DDS MakeDDS(NewTexelContextCreate ctx) {
28✔
323
  DDS dds;
324
  dds.width = ctx.width;
28✔
325
  dds.height = ctx.height;
28✔
326
  if (ctx.baseFormat.premultAlpha) {
28✔
327
    dds.alphaMode = dds.AlphaMode_Premultiplied;
×
328
  }
329

330
  dds.arraySize = ctx.arraySize;
28✔
331

332
  if (ctx.depth > 1) {
28✔
333
    dds.caps01 += dds.Caps01Flags_Volume;
334
    dds.flags += dds.Flags_Depth;
335
    dds.dimension = dds.Dimension_3D;
1✔
336
    dds.depth = ctx.depth;
1✔
337
  } else if (ctx.numFaces > 0) {
27✔
338
    dds.caps01 += dds.Caps01Flags_CubeMap;
339
    dds.miscFlag += dds.MiscFlag_CubeTexture;
340

341
    for (int32 i = 0; i < ctx.numFaces; i++) {
14✔
342
      dds.caps01 += static_cast<DDS_HeaderEnd::Caps01Flags>(i + 10);
12✔
343
    }
344
  }
345

346
  if (mainSettings.texelSettings.processMipMaps) {
28✔
347
    dds.NumMipmaps(ctx.numMipmaps);
5✔
348
  }
349

350
  return dds;
28✔
351
}
352

353
void SetDDSFormat(DDS &dds, TexelInputFormat fmt) {
×
354
  dds = DDSFormat_DX10;
355
  switch (fmt.type) {
×
356
    using F = TexelInputFormatType;
357
  case F::BC1:
×
358
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC1_UNORM + fmt.srgb);
×
359
    break;
×
360

361
  case F::BC2:
×
362
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC2_UNORM + fmt.srgb);
×
363
    break;
×
364

365
  case F::BC3:
×
366
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC3_UNORM + fmt.srgb);
×
367
    break;
×
368

369
  case F::BC4:
×
370
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC4_UNORM + fmt.snorm);
×
371
    break;
×
372

373
  case F::BC5:
×
374
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC5_UNORM + fmt.snorm);
×
375
    break;
×
376

377
  case F::BC7:
×
378
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC7_UNORM + fmt.srgb);
×
379
    break;
×
380

381
  case F::RGBA4:
×
382
    dds.dxgiFormat = DXGI_FORMAT_B4G4R4A4_UNORM;
×
383
    break;
×
384

385
  case F::R5G6B5:
×
386
    dds.dxgiFormat = DXGI_FORMAT_B5G6R5_UNORM;
×
387
    break;
×
388

389
  case F::RGB5A1:
×
390
    dds.dxgiFormat = DXGI_FORMAT_B5G5R5A1_UNORM;
×
391
    break;
×
392

393
  case F::RGB10A2:
×
394
    dds.dxgiFormat = DXGI_FORMAT_R10G10B10A2_UNORM;
×
395
    break;
×
396

397
  case F::RG8:
×
398
  case F::RG4:
399
    if (fmt.snorm) {
×
400
      dds.dxgiFormat = DXGI_FORMAT_R8G8_SNORM;
×
401
    } else if (fmt.swizzle.a == TexelSwizzleType::Green &&
×
402
               fmt.swizzle.r == TexelSwizzleType::Red) {
×
403
      dds.dxgiFormat = DXGI_FORMAT_A8P8;
×
404
    } else {
405
      dds.dxgiFormat = DXGI_FORMAT_R8G8_UNORM;
×
406
    }
407
    break;
408

409
  case F::R8:
×
410
  case F::R4:
411
    dds.dxgiFormat = fmt.snorm ? DXGI_FORMAT_R8_SNORM : DXGI_FORMAT_R8_UNORM;
×
412
    break;
×
413

414
  case F::RGBA8:
×
415
  case F::P8:
416
  case F::P4:
417
  case F::PVRTC2:
418
  case F::PVRTC4:
419
  case F::ETC1:
420
  case F::RGB8:
421
  case F::ETC1A4:
422
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_R8G8B8A8_UNORM + fmt.srgb);
×
423
    break;
×
424

425
  case F::RGBA16:
×
426
    dds.dxgiFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
×
427
    break;
×
428

429
  case F::BC6:
×
430
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC6H_UF16 + fmt.snorm);
×
431
    break;
×
432

433
  case F::RGB9E5:
×
434
    dds.dxgiFormat = DXGI_FORMAT_R9G9B9E5_SHAREDEXP;
×
435
    break;
×
436

437
  case F::INVALID:
×
438
    dds.dxgiFormat = DXGI_FORMAT_UNKNOWN;
×
439
    break;
×
440
  }
441
}
442

443
void SetDDSLegacyFormat(DDS &dds, TexelInputFormat fmt) {
28✔
444
  switch (fmt.type) {
28✔
445
    using F = TexelInputFormatType;
446
  case F::BC1:
447
    dds = DDSFormat_DXT1;
448
    break;
449

450
  case F::BC2:
451
    dds = DDSFormat_DXT3;
452
    break;
453

454
  case F::BC3:
455
    dds = DDSFormat_DXT5;
456
    break;
457

458
  case F::BC4:
459
  case F::R8:
460
  case F::R4:
461
    dds = DDSFormat_L8;
462
    break;
463

464
  case F::RGBA4:
465
    dds = DDSFormat_A4R4G4B4;
466
    break;
467

468
  case F::R5G6B5:
469
    dds = DDSFormat_R5G6B5;
470
    break;
471

472
  case F::RGB8:
473
    dds = DDSFormat_R8G8B8;
474
    break;
475

476
  case F::RGBA8:
477
  case F::BC7:
478
  case F::RGB5A1:
479
  case F::RGB10A2:
480
  case F::P8:
481
  case F::P4:
482
  case F::PVRTC2:
483
  case F::PVRTC4:
484
  case F::ETC1:
485
  case F::RGBA16:
486
  case F::BC6:
487
  case F::RGB9E5:
488
  case F::ETC1A4:
489
    dds = DDSFormat_A8R8G8B8;
490
    break;
491

492
  case F::BC5:
×
493
  case F::RG8:
494
  case F::RG4:
495
    if (fmt.swizzle.a == TexelSwizzleType::Green &&
×
496
        fmt.swizzle.r == TexelSwizzleType::Red) {
×
497
      dds = DDSFormat_A8L8;
498
    } else {
499
      dds = DDSFormat_R8G8B8;
500
    }
501
    break;
502

503
  case F::INVALID:
504
    break;
505
  }
506
}
28✔
507

508
uint8 DesiredQOIChannels(TexelInputFormatType fmt) {
28✔
509
  switch (fmt) {
28✔
510
    using F = TexelInputFormatType;
511
  case F::BC4:
512
  case F::R8:
513
  case F::R5G6B5:
514
  case F::BC5:
515
  case F::RG8:
516
  case F::RGB8:
517
  case F::R4:
518
  case F::RG4:
519
    return 3;
520

521
  case F::BC1:
522
  case F::BC2:
523
  case F::BC3:
524
  case F::RGBA4:
525
  case F::RGBA8:
526
  case F::BC7:
527
  case F::RGB5A1:
528
  case F::RGB10A2:
529
  case F::P8:
530
  case F::P4:
531
  case F::PVRTC2:
532
  case F::PVRTC4:
533
  case F::ETC1:
534
  case F::ETC1A4:
535
  case F::RGBA16:
536
  case F::BC6:
537
  case F::RGB9E5:
538
    return 4;
539

540
  case F::INVALID:
×
541
    return 0;
×
542
  }
543

544
  return 4;
545
}
546

547
enum class PngColorType : uint8 {
548
  Gray = 0,
549
  RGB = 2,
550
  Palette = 3,
551
  GrayAlpha = 4,
552
  RGBA = 6,
553
};
554

555
uint32 GetPngChannels(PngColorType fmt) {
×
556
  switch (fmt) {
557
  case PngColorType::Gray:
558
    return 1;
559
  case PngColorType::GrayAlpha:
560
    return 2;
561
  case PngColorType::RGB:
562
    return 3;
563
  case PngColorType::RGBA:
564
    return 4;
565
  }
566

567
  return 0;
568
}
569

570
PngColorType DesiredPngColorType(TexelInputFormatType fmt) {
×
571
  switch (fmt) {
×
572
    using F = TexelInputFormatType;
573
  case F::R8:
574
  case F::BC4:
575
  case F::R4:
576
    return PngColorType::Gray;
577

578
  case F::BC5:
×
579
  case F::RG8:
580
  case F::RG4:
581
    // return PngColorType::GrayAlpha;
582

583
  case F::R5G6B5:
584
  case F::RGB8:
585
    return PngColorType::RGB;
×
586

587
  case F::BC1:
588
  case F::BC2:
589
  case F::BC3:
590
  case F::RGBA4:
591
  case F::RGBA8:
592
  case F::BC7:
593
  case F::RGB5A1:
594
  case F::RGB10A2:
595
  case F::P8:
596
  case F::P4:
597
  case F::PVRTC2:
598
  case F::PVRTC4:
599
  case F::ETC1:
600
  case F::ETC1A4:
601
  case F::RGBA16:
602
  case F::BC6:
603
  case F::RGB9E5:
604
    return PngColorType::RGBA;
605

606
  case F::INVALID:
607
    return PngColorType::RGBA;
608
  }
609

610
  return PngColorType::RGBA;
611
}
612

613
PngColorType DesiredPngColorType(PngColorType type, TexelSwizzle &swizzle) {
×
614
  switch (type) {
×
615
  case PngColorType::RGBA: {
×
616
    if (swizzle.r == swizzle.g && swizzle.g == swizzle.b) {
×
617
      if (swizzle.a == TexelSwizzleType::White) {
×
618
        return PngColorType::Gray;
619
      }
620

621
      swizzle.g = swizzle.a;
×
622
      return PngColorType::GrayAlpha;
×
623
    }
624
    if (swizzle.a == TexelSwizzleType::White) {
×
625
      return PngColorType::RGB;
×
626
    }
627

628
    return type;
629
  }
630

631
  case PngColorType::RGB: {
×
632
    if (swizzle.r == swizzle.g && swizzle.g == swizzle.b) {
×
633
      if (swizzle.a == TexelSwizzleType::White) {
×
634
        return PngColorType::Gray;
635
      }
636

637
      swizzle.g = swizzle.a;
×
638
      return PngColorType::GrayAlpha;
×
639
    }
640

641
    if (swizzle.a != TexelSwizzleType::White) {
×
642
      return PngColorType::RGBA;
×
643
    }
644

645
    return type;
646
  }
647
  }
648

649
  return type;
650
}
651

652
/*
653
PngColorType DesiredPngColorType(PngColorType type, TexelSwizzle swizzle) {
654
  switch (type) {
655
  case PngColorType::Gray: {
656
    int8 factors[4]{0, 0, 0, 0};
657

658
    for (uint32 f = 0; f < 4; f++) {
659
      switch (swizzle.types[f]) {
660
      case TexelSwizzleType::Red:
661
        factors[f] = 1;
662
        break;
663
      case TexelSwizzleType::RedInverted:
664
        factors[f] = -1;
665
        break;
666

667
      default:
668
        break;
669
      }
670
    }
671

672
    if ((factors[0] == factors[1] &&
673
         factors[1] == factors[2]) ||           // rgb == red || redinverted
674
        (factors[0] == 0 && factors[1] == 0) || // r == red || redinverted
675
        (factors[0] == 0 && factors[2] == 0) || // g == red || redinverted
676
        (factors[1] == 0 && factors[2] == 0)) { // // b == red || redinverted
677
      if (factors[3]) {
678
        return PngColorType::GrayAlpha;
679
      }
680
      return type;
681
    } else {
682
      if (factors[3]) {
683
        return PngColorType::RGBA;
684
      }
685
      return PngColorType::RGB;
686
    }
687
  }
688

689
  case PngColorType::GrayAlpha: {
690
    int8 factors[4]{0, 0, 0, 0};
691

692
    for (uint32 f = 0; f < 4; f++) {
693
      switch (swizzle.types[f]) {
694
      case TexelSwizzleType::Red:
695
      case TexelSwizzleType::Alpha:
696
        factors[f] = 1;
697
        break;
698
      case TexelSwizzleType::RedInverted:
699
      case TexelSwizzleType::AlphaInverted:
700
        factors[f] = -1;
701
        break;
702

703
      default:
704
        break;
705
      }
706
    }
707

708
    if ((factors[0] == factors[1] &&
709
         factors[1] == factors[2]) ||           // rgb == red || redinverted
710
        (factors[0] == 0 && factors[1] == 0) || // r == red || redinverted
711
        (factors[0] == 0 && factors[2] == 0) || // g == red || redinverted
712
        (factors[1] == 0 && factors[2] == 0)) { // // b == red || redinverted
713
      if (factors[3]) {
714
        return PngColorType::GrayAlpha;
715
      }
716
      return type;
717
    } else {
718
      if (factors[3]) {
719
        return PngColorType::RGBA;
720
      }
721
      return PngColorType::RGB;
722
    }
723
  }
724
  }
725
}*/
726

727
uint8 DesiredDDSChannels(TexelInputFormatType fmt) {
28✔
728
  switch (fmt) {
28✔
729
    using F = TexelInputFormatType;
730
  case F::BC4:
731
  case F::R8:
732
  case F::R4:
733
    return 1;
734

735
  case F::R5G6B5:
2✔
736
    return 3;
2✔
737

738
  case F::BC5:
2✔
739
  case F::RG8:
740
  case F::RG4:
741
    return 2;
2✔
742

743
  case F::BC1:
744
  case F::BC2:
745
  case F::BC3:
746
  case F::RGBA4:
747
  case F::RGBA8:
748
  case F::BC7:
749
  case F::RGB5A1:
750
  case F::RGB10A2:
751
  case F::P8:
752
  case F::P4:
753
  case F::PVRTC2:
754
  case F::PVRTC4:
755
  case F::ETC1:
756
  case F::ETC1A4:
757
  case F::RGB8:
758
  case F::RGBA16:
759
  case F::BC6:
760
  case F::RGB9E5:
761
    return 4;
762

763
  case F::INVALID:
×
764
    return 0;
×
765
  }
766

767
  return 4;
768
}
769

770
uint8 DesiredDDSLegacyChannels(TexelInputFormat fmt) {
28✔
771
  switch (fmt.type) {
28✔
772
    using F = TexelInputFormatType;
773
  case F::BC4:
774
  case F::R8:
775
  case F::R4:
776
    return 1;
777

778
  case F::R5G6B5:
779
  case F::BC5:
780
  case F::RGB8:
781
    return 3;
782

783
  case F::BC1:
784
  case F::BC2:
785
  case F::BC3:
786
  case F::RGBA4:
787
  case F::RGBA8:
788
  case F::BC7:
789
  case F::RGB5A1:
790
  case F::RGB10A2:
791
  case F::P8:
792
  case F::P4:
793
  case F::PVRTC2:
794
  case F::PVRTC4:
795
  case F::ETC1:
796
  case F::ETC1A4:
797
  case F::RGBA16:
798
  case F::BC6:
799
  case F::RGB9E5:
800
    return 4;
801

802
  case F::RG8:
1✔
803
  case F::RG4:
804
    if (fmt.swizzle.a == TexelSwizzleType::Green &&
1✔
805
        fmt.swizzle.r == TexelSwizzleType::Red) {
×
806
      return 2;
807
    } else {
808
      return 3;
809
    }
810
    break;
811
  case F::INVALID:
×
812
    return 0;
×
813
  }
814

815
  return 4;
816
}
817

818
uint8 FormatChannels(TexelInputFormatType fmt) {
38✔
819
  switch (fmt) {
38✔
820
    using F = TexelInputFormatType;
821
  case F::BC4:
822
  case F::R8:
823
  case F::R4:
824
    return 1;
825

826
  case F::BC5:
4✔
827
  case F::RG8:
828
  case F::RG4:
829
    return 2;
4✔
830

831
  case F::R5G6B5:
2✔
832
  case F::RGB8:
833
    return 3;
2✔
834

835
  case F::BC1:
26✔
836
  case F::BC2:
837
  case F::BC3:
838
  case F::RGBA4:
839
  case F::RGBA8:
840
  case F::BC7:
841
  case F::RGB5A1:
842
  case F::RGB10A2:
843
  case F::P8:
844
  case F::P4:
845
  case F::PVRTC2:
846
  case F::PVRTC4:
847
  case F::ETC1:
848
  case F::ETC1A4:
849
  case F::RGBA16:
850
  case F::BC6:
851
  case F::RGB9E5:
852
    return 4;
26✔
853

854
  case F::INVALID:
855
    return 0;
856
  }
857

858
  return 0;
859
}
860

861
struct LinearTile : TileBase {
×
862
  void reset(uint32, uint32, uint32) override {}
×
863
  uint32 get(uint32 inTexel) const override { return inTexel; }
693,760✔
864
};
865

866
struct MortonTile : TileBase {
×
867
  MortonSettings settings;
868

869
  MortonTile(uint32 width, uint32 height) : settings(width, height) {}
×
870

871
  void reset(uint32, uint32, uint32) override {}
×
872

873
  uint32 get(uint32 inTexel) const override {
×
874
    return MortonAddr(inTexel % settings.width, inTexel / settings.height,
×
875
                      settings);
×
876
  }
877
};
878

879
uint32 RoundToPow2(uint32 number) {
×
880
  number--;
×
881
  number |= number >> 1;
×
882
  number |= number >> 2;
×
883
  number |= number >> 4;
×
884
  number |= number >> 8;
×
885
  number |= number >> 16;
×
886
  number++;
×
887
  return number;
×
888
}
889

890
struct PS4Tile : TileBase {
×
891
  size_t width;
892
  size_t height;
893
  size_t widthp2;
894

895
  PS4Tile(size_t width_, size_t height_)
896
      : width(width_), height(height_),
897
        widthp2(RoundToPow2(std::max(width_, size_t(8)))) {}
×
898

899
  void reset(uint32, uint32, uint32) override {}
×
900

901
  static size_t MortonAddr(size_t x, size_t y, size_t width) {
902
    const size_t x0 = x & 1;
×
903
    const size_t x1 = (x & 2) << 1;
×
904
    const size_t x2 = (x & 4) << 2;
×
905

906
    const size_t y0 = (y & 1) << 1;
×
907
    const size_t y1 = (y & 2) << 2;
×
908
    const size_t y2 = (y & 4) << 3;
×
909

910
    size_t retval = x0 | x1 | x2 | y0 | y1 | y2;
×
911

912
    const size_t macroX = x / 8;
×
913
    const size_t macroY = y / 8;
×
914
    const size_t macroWidth = width / 8;
×
915

916
    const size_t macroAddr = (macroWidth * macroY) + macroX;
×
917

918
    return retval | (macroAddr << 6);
×
919
  }
920

921
  uint32 get(uint32 inTexel) const override {
×
922
    return MortonAddr(inTexel % width, inTexel / height, widthp2);
×
923
  }
924
};
925

926
struct N3DSTile : TileBase {
×
927
  uint32 width;
928
  uint32 height;
929
  uint32 (*getter)(uint32, const N3DSTile &);
930

931
  N3DSTile(size_t width_, size_t height_, TexelInputFormatType fmt)
932
      : width(width_), height(height_) {
933
    if (fmt == TexelInputFormatType::ETC1 ||
×
934
        fmt == TexelInputFormatType::ETC1A4) {
935
      width = (width + 3) / 4;
×
936
      height = (height + 3) / 4;
×
937
      getter = GetCompressed;
938
    } else {
939
      getter = GetRaw;
940
    }
941
  }
942

943
  void reset(uint32, uint32, uint32) override {}
×
944

945
  static uint32 GetRaw(uint32 inTexel, const N3DSTile &self) {
946
    uint32 x = inTexel % self.width;
×
947
    uint32 y = inTexel / self.width;
×
948
    // y = self.height - y - 1;
949

950
    const size_t x0 = x & 1;
951
    const size_t x1 = (x & 2) << 1;
×
952
    const size_t x2 = (x & 4) << 2;
×
953

954
    const size_t y0 = (y & 1) << 1;
×
955
    const size_t y1 = (y & 2) << 2;
×
956
    const size_t y2 = (y & 4) << 3;
×
957

958
    size_t retval = x0 | x1 | x2 | y0 | y1 | y2;
959

960
    const size_t macroX = x / 8;
×
961
    const size_t macroY = y / 8;
×
962
    const size_t macroWidth = self.width / 8;
×
963

964
    const size_t macroAddr = (macroWidth * macroY) + macroX;
×
965

966
    return retval | (macroAddr << 6);
×
967
  }
968

969
  static uint32 GetCompressed(uint32 inTexel, const N3DSTile &self) {
970
    uint32 x = inTexel % self.width;
×
971
    uint32 y = inTexel / self.width;
×
972
    // y = self.height - y - 1;
973

974
    const size_t x0 = x & 1;
975

976
    const size_t y0 = (y & 1) << 1;
×
977

978
    size_t retval = x0 | y0;
979

980
    const size_t macroX = x / 2;
×
981
    const size_t macroY = y / 2;
×
982
    const size_t macroWidth = self.width / 2;
×
983

984
    const size_t macroAddr = (macroWidth * macroY) + macroX;
×
985

986
    return retval | (macroAddr << 2);
×
987
  }
988

989
  uint32 get(uint32 inTexel) const override { return getter(inTexel, *this); }
×
990
};
991

992
struct NXTile : TileBase {
12✔
993
  uint32 width;
994
  uint32 height;
995
  uint32 numUsedYBlockBits;
996
  uint32 yBlockMask;
997
  uint32 yTailMask;
998
  uint32 macroTileWidth;
999
  uint32 BPT;
1000
  uint32 yTailOffset;
1001
  uint32 upperBound;
1002
  uint32 numUsedMicroYBits;
1003
  uint32 midYShift;
1004

1005
  NXTile(size_t width_, size_t height_, TexelInputFormatType fmt)
12✔
1006
      : width(width_), height(height_), BPT(GetBPT(fmt)),
12✔
1007
        upperBound(width * height) {
12✔
1008
    numUsedMicroYBits = std::min(1 << uint32(std::round(log2(height))), 3);
12✔
1009
    midYShift = numUsedMicroYBits + 3;
12✔
1010

1011
    const uint32 macroTileHeight =
1012
        std::min(uint32(std::round(log2((height + 7) / 8))), 4U);
12✔
1013

1014
    // addr bits 9 - 13
1015
    // y bits 3 - 7
1016
    yBlockMask = ((1 << macroTileHeight) - 1) << numUsedMicroYBits;
12✔
1017
    numUsedYBlockBits = numUsedMicroYBits + macroTileHeight;
12✔
1018

1019
    // y bits 8 - end
1020
    yTailMask = ~((1 << numUsedYBlockBits) - 1);
12✔
1021

1022
    macroTileWidth = ((width * BPT) + 63) / 64;
12✔
1023

1024
    yTailOffset = macroTileHeight + 2;
12✔
1025
  }
12✔
1026

1027
  void reset(uint32, uint32, uint32) override {}
×
1028

1029
  uint32 get(uint32 inTexel) const override {
260,672✔
1030
    // 12 11 10 9  8  7  6  5  4  3  2  1  0
1031
    // y  y  y  y  x  y  y  x  y  x  x  x  x
1032
    // y*x*y{0,4}xyyxyx[x|0]{4}
1033

1034
    uint32 x = (inTexel % width) * BPT;
260,672✔
1035
    uint32 y = inTexel / width;
260,672✔
1036

1037
    // Volumetrics not implemented
1038
    // Some small rasters use alignment
1039

1040
    // x bits 0 - 5
1041
    // y bits 0 - 2
1042
    uint32 microTile = (x & 0xf) | ((y & 1) << 4) | ((x & 0x10) << 1) |
260,672✔
1043
                       ((y & 0x6) << 5) | ((x & 0x20) << numUsedMicroYBits);
260,672✔
1044

1045
    // addr tail after yBlockMask bits
1046
    // x bits 6 - end
1047
    constexpr uint32 xTailMask = ~0x3f;
1048

1049
    uint32 macroTile = (((y & yBlockMask) << midYShift) | //
260,672✔
1050
                        ((x & xTailMask) << numUsedYBlockBits)) +
260,672✔
1051
                       (((y & yTailMask) << yTailOffset) * macroTileWidth);
260,672✔
1052

1053
    uint32 wholeTile = (microTile | macroTile) / BPT;
260,672✔
1054

1055
    /*if (wholeTile >= upperBound) [[unlikely]] {
1056
      throw es::RuntimeError("NX tile error, accessing block out of range");
1057
    }*/
1058

1059
    return wholeTile;
260,672✔
1060
  }
1061
};
1062

1063
using TileVariant =
1064
    std::variant<LinearTile, MortonTile, PS4Tile, NXTile, N3DSTile>;
1065

1066
TileVariant TileVariantFromCtx(NewTexelContextCreate ctx) {
43✔
1067
  uint32 width = ctx.width;
43✔
1068
  uint32 height = ctx.height;
43✔
1069

1070
  switch (ctx.baseFormat.tile) {
43✔
1071
  case TexelTile::Linear:
31✔
1072
    return LinearTile{};
1073

1074
  case TexelTile::Morton:
1075
    return MortonTile(width, height);
×
1076

1077
  case TexelTile::PS4:
×
1078
    return PS4Tile(width, height);
×
1079

1080
  case TexelTile::NX:
12✔
1081
    return NXTile(width, height, ctx.baseFormat.type);
12✔
1082

1083
  case TexelTile::N3DS:
×
1084
    return N3DSTile(width, height, ctx.baseFormat.type);
×
1085

1086
  case TexelTile::Custom:
1087
    break;
1088
  }
1089

1090
  return LinearTile{};
1091
}
1092

1093
void DecodeToRGB(const char *data, NewTexelContextCreate ctx,
2✔
1094
                 std::span<UCVector> outData) {
1095
  if (BlockCompression(ctx.baseFormat.type)) {
2✔
1096
    ctx.width = (ctx.width + 3) / 4;
×
1097
    ctx.height = (ctx.height + 3) / 4;
×
1098
  }
1099
  TileVariant tvar(TileVariantFromCtx(ctx));
2✔
1100
  const TileBase *tiler =
1101
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
1102
          ? [&] {
2✔
1103
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
1104
              return ctx.customTile;
×
1105
            }()
1106
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
4✔
1107

1108
  const size_t numBlocks = ctx.width * ctx.height;
2✔
1109

1110
  switch (ctx.baseFormat.type) {
2✔
1111
    using F = TexelInputFormatType;
1112
  case F::R5G6B5: {
2✔
1113
    auto &codec = uni::FormatCodec::Get({
2✔
1114
        .outType = uni::FormatType::UNORM,
1115
        .compType = uni::DataType::R5G6B5,
1116
    });
1117

1118
    size_t curTexel = 0;
1119
    if (ctx.baseFormat.swapPacked) {
2✔
1120
      const uint16 *iData = reinterpret_cast<const uint16 *>(data);
1121
      for (auto &t : outData) {
×
1122
        Vector4A16 value;
1123
        uint16 col = *(iData + tiler->get(curTexel++));
×
1124
        FByteswapper(col);
1125

1126
        codec.GetValue(value, reinterpret_cast<const char *>(&col));
×
1127
        t = Vector(Vector4A16(value * 0xff)).Convert<uint8>();
×
1128
      }
1129
    } else {
1130
      for (auto &t : outData) {
86,274✔
1131
        Vector4A16 value;
1132
        codec.GetValue(value, data + tiler->get(curTexel++) * 2);
86,272✔
1133
        t = Vector(Vector4A16(value * 0xff)).Convert<uint8>();
86,272✔
1134
      }
1135
    }
1136

1137
    break;
1138
  }
1139

1140
  case F::RG8: {
×
1141
    size_t curTexel = 0;
1142

1143
    if (ctx.baseFormat.snorm) {
×
1144
      const CVector2 *iData = reinterpret_cast<const CVector2 *>(data);
1145
      for (auto &t : outData) {
×
1146
        auto fData = (iData + tiler->get(curTexel++))->Convert<int>() + 0x80;
×
1147
        t = UCVector(0, fData.y, fData.x);
×
1148
      }
1149
    } else {
1150
      const UCVector2 *iData = reinterpret_cast<const UCVector2 *>(data);
1151
      for (auto &t : outData) {
×
1152
        auto tData = iData + tiler->get(curTexel++);
×
1153
        t = UCVector(0, tData->y, tData->x);
×
1154
      }
1155
    }
1156
    break;
1157
  }
1158

1159
  case F::BC5:
1160
    for (size_t p = 0; p < numBlocks; p++) {
×
1161
      DecodeBC5Block(data + tiler->get(p) * 16,
×
1162
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
×
1163
                     p / ctx.width, ctx.width, 3);
×
1164
    }
1165
    break;
1166

1167
  case F::BC4:
1168
    for (size_t p = 0; p < numBlocks; p++) {
×
1169
      _DecodeBC4Block(data + tiler->get(p) * 8,
×
1170
                      reinterpret_cast<char *>(outData.data()), p % ctx.width,
×
1171
                      p / ctx.width, ctx.width, 3);
×
1172
    }
1173

1174
    for (auto &p : outData) {
×
1175
      p.y = p.z = p.x;
×
1176
    }
1177

1178
    break;
1179

1180
  case F::RG4: {
×
1181
    const uint8 *iData = reinterpret_cast<const uint8 *>(data);
1182
    size_t curTexel = 0;
1183

1184
    for (auto &t : outData) {
×
1185
      uint8 col = *(iData + tiler->get(curTexel++));
×
1186
      t = UCVector(0, col << 4, col & 0xf0);
×
1187
    }
1188

1189
    break;
1190
  }
1191

1192
  case F::R8: {
×
1193
    size_t curTexel = 0;
1194
    for (auto &t : outData) {
×
1195
      t = UCVector(*(data + tiler->get(curTexel++)));
×
1196
    }
1197

1198
    break;
1199
  }
1200

1201
  default:
×
NEW
1202
    throw es::ImplementationError("Implement rgb decode");
×
1203
  }
1204
}
2✔
1205

1206
void DecodeToRGBA(const char *data, NewTexelContextCreate ctx,
24✔
1207
                  std::span<UCVector4> outData) {
1208
  if (BlockCompression(ctx.baseFormat.type)) {
24✔
1209
    ctx.width = (ctx.width + 3) / 4;
13✔
1210
    ctx.height = (ctx.height + 3) / 4;
13✔
1211
  }
1212
  TileVariant tvar(TileVariantFromCtx(ctx));
24✔
1213
  const TileBase *tiler =
1214
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
1215
          ? [&] {
24✔
1216
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
1217
              return ctx.customTile;
×
1218
            }()
1219
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
48✔
1220

1221
  const size_t numBlocks = ctx.width * ctx.height;
24✔
1222

1223
  switch (ctx.baseFormat.type) {
24✔
1224
    using F = TexelInputFormatType;
1225
  case F::RGB10A2: {
2✔
1226
    auto &codec = uni::FormatCodec::Get({
2✔
1227
        .outType = uni::FormatType::UNORM,
1228
        .compType = uni::DataType::R10G10B10A2,
1229
    });
1230

1231
    size_t curTexel = 0;
1232
    if (ctx.baseFormat.swapPacked) {
2✔
1233
      const uint32 *iData = reinterpret_cast<const uint32 *>(data);
1234
      for (auto &t : outData) {
×
1235
        Vector4A16 value;
1236
        uint32 col = *(iData + tiler->get(curTexel++));
×
1237
        FByteswapper(col);
×
1238

1239
        codec.GetValue(value, reinterpret_cast<const char *>(&col));
×
1240
        t = (value * 0xff).Convert<uint8>();
1241
      }
1242
    } else {
1243
      for (auto &t : outData) {
131,074✔
1244
        Vector4A16 value;
1245
        codec.GetValue(value, data + tiler->get(curTexel++) * 4);
131,072✔
1246
        t = (value * 0xff).Convert<uint8>();
1247
      }
1248
    }
1249
    break;
1250
  }
1251

1252
  case F::RGBA4: {
1✔
1253
    const uint16 *iData = reinterpret_cast<const uint16 *>(data);
1254
    size_t curTexel = 0;
1255
    if (ctx.baseFormat.swapPacked) {
1✔
1256
      for (auto &t : outData) {
×
1257
        uint16 col = *(iData + tiler->get(curTexel++));
×
1258
        FByteswapper(col);
1259
        t = UCVector4(col << 4, col & 0xf0, (col >> 4) & 0xf0,
×
1260
                      (col >> 8) & 0xf0);
×
1261
      }
1262
    } else {
1263
      for (auto &t : outData) {
65,537✔
1264
        uint16 col = *(iData + tiler->get(curTexel++));
65,536✔
1265
        t = UCVector4(col << 4, col & 0xf0, (col >> 4) & 0xf0,
65,536✔
1266
                      (col >> 8) & 0xf0);
65,536✔
1267
      }
1268
    }
1269

1270
    break;
1271
  }
1272

1273
  case F::RGB5A1: {
2✔
1274
    const uint16 *iData = reinterpret_cast<const uint16 *>(data);
1275
    size_t curTexel = 0;
1276

1277
    if (ctx.baseFormat.swapPacked) {
2✔
1278
      for (auto &t : outData) {
×
1279
        uint16 col = *(iData + tiler->get(curTexel++));
×
1280
        FByteswapper(col);
1281
        t = UCVector4(col << 3, (col >> 2) & 0xf8, (col >> 7) & 0xf8,
×
1282
                      int16(col) >> 15);
×
1283
      }
1284
    } else {
1285
      for (auto &t : outData) {
131,074✔
1286
        uint16 col = *(iData + tiler->get(curTexel++));
131,072✔
1287
        t = UCVector4(col << 3, (col >> 2) & 0xf8, (col >> 7) & 0xf8,
131,072✔
1288
                      int16(col) >> 15);
131,072✔
1289
      }
1290
    }
1291

1292
    break;
1293
  }
1294

1295
  case F::PVRTC2:
1296
    pvr::PVRTDecompressPVRTC(data, 1, ctx.width, ctx.height,
2✔
1297
                             reinterpret_cast<uint8_t *>(outData.data()));
1298
    break;
1299

1300
  case F::PVRTC4:
1301
    pvr::PVRTDecompressPVRTC(data, 0, ctx.width, ctx.height,
2✔
1302
                             reinterpret_cast<uint8_t *>(outData.data()));
1303
    break;
1304

1305
  case F::ETC1:
2✔
1306
    if (ctx.baseFormat.tile != TexelTile::Linear) {
2✔
1307
      uint32 bwidth = (ctx.width + 3) / 4;
×
1308
      uint32 bheight = (ctx.height + 3) / 4;
×
1309
      uint32 bnumBlocks = bwidth * bheight;
×
1310
      std::vector<char> tmpBuffer(bwidth * bheight * 8);
×
1311
      for (size_t p = 0; p < bnumBlocks; p++) {
×
1312
        memcpy(tmpBuffer.data() + p * 8, data + tiler->get(p) * 8, 8);
×
1313
      }
1314

1315
      if (ctx.baseFormat.tile == TexelTile::N3DS) {
×
1316
        for (uint32 b = 0; b < bnumBlocks; b++) {
×
1317
          FByteswapper(reinterpret_cast<uint64 *>(tmpBuffer.data())[b]);
×
1318
        }
1319
      }
1320

1321
      pvr::PVRTDecompressETC(tmpBuffer.data(), ctx.width, ctx.height,
×
1322
                             reinterpret_cast<uint8_t *>(outData.data()),
1323
                             0x100);
1324
    } else {
1325
      pvr::PVRTDecompressETC(data, ctx.width, ctx.height,
2✔
1326
                             reinterpret_cast<uint8_t *>(outData.data()),
1327
                             0x100);
1328
    }
1329
    break;
1330

1331
  case F::ETC1A4:
×
1332
    if (ctx.baseFormat.tile == TexelTile::N3DS) {
×
1333
      uint32 bwidth = (ctx.width + 3) / 4;
×
1334
      uint32 bheight = (ctx.height + 3) / 4;
×
1335
      uint32 bnumBlocks = bwidth * bheight;
×
1336
      std::vector<char> tmpBuffer(bwidth * bheight * 16);
×
1337
      for (size_t p = 0; p < bnumBlocks; p++) {
×
1338
        memcpy(tmpBuffer.data() + p * 16, data + tiler->get(p) * 16, 16);
×
1339
      }
1340

1341
      for (uint32 b = 0; b < bnumBlocks * 2; b++) {
×
1342
        b++;
×
1343
        FByteswapper(reinterpret_cast<uint64 *>(tmpBuffer.data())[b]);
×
1344
      }
1345

1346
      pvr::PVRTDecompressETC(tmpBuffer.data(), ctx.width, ctx.height,
×
1347
                             reinterpret_cast<uint8_t *>(outData.data()),
1348
                             0x102);
1349

1350
      for (uint32 p = 0; p < bnumBlocks; p++) {
×
1351
        uint8 *iData = reinterpret_cast<uint8 *>(tmpBuffer.data() + p * 16);
×
1352

1353
        uint32 x = p % bwidth;
×
1354
        uint32 y = p / bwidth;
×
1355
        uint32 blockOffset = bwidth * y * 16 + x * 4;
×
1356

1357
        for (size_t h = 0; h < 4; h++) {
×
1358
          UCVector4 *addr = outData.data() + blockOffset + h * ctx.width;
×
1359
          for (size_t w = 0; w < 4; w++) {
×
1360
            uint32 tile = w * 4 + h;
×
1361
            uint8 col = *(iData + (tile >> 1));
×
1362
            addr[w].w = col << (4 * !(tile & 1)) & 0xf0;
×
1363
          }
1364
        }
1365
      }
1366
    } else {
1367
      throw es::RuntimeError("ETC1A4 is only supported for TexelTile::N3DS");
×
1368
    }
1369
    break;
×
1370

1371
  case F::BC7: {
1372
    uint32 localBlock[16];
1373
    for (size_t p = 0; p < numBlocks; p++) {
8,194✔
1374
      if (!detexDecompressBlockBPTC(
8,192✔
1375
              reinterpret_cast<const uint8_t *>(data) + tiler->get(p) * 16, -1,
8,192✔
1376
              0, reinterpret_cast<uint8_t *>(localBlock))) [[unlikely]] {
×
1377
        throw es::RuntimeError("Failed to decompress BC7 block");
×
1378
      }
1379
      uint32 x = p % ctx.width;
8,192✔
1380
      uint32 y = p / ctx.width;
8,192✔
1381
      uint32 blockOffset = ctx.width * y * 16 + x * 4;
8,192✔
1382

1383
      for (size_t r = 0; r < 4; r++) {
40,960✔
1384
        UCVector4 *addr = outData.data() + blockOffset + r * ctx.width * 4;
32,768✔
1385
        memcpy(static_cast<void *>(addr), localBlock + r * 4, 16);
32,768✔
1386
      }
1387
    }
1388
    break;
1389
  }
1390

1391
  case F::BC1:
1392
    for (size_t p = 0; p < numBlocks; p++) {
5,394✔
1393
      DecodeBC1BlockA(data + tiler->get(p) * 8,
5,392✔
1394
                      reinterpret_cast<char *>(outData.data()), p % ctx.width,
5,392✔
1395
                      p / ctx.width, ctx.width);
5,392✔
1396
    }
1397

1398
    break;
1399

1400
  case F::BC2:
1401
    for (size_t p = 0; p < numBlocks; p++) {
4,097✔
1402
      DecodeBC2Block(data + tiler->get(p) * 16,
4,096✔
1403
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
4,096✔
1404
                     p / ctx.width, ctx.width);
4,096✔
1405
    }
1406

1407
    break;
1408

1409
  case F::BC3:
1410
    for (size_t p = 0; p < numBlocks; p++) {
6,936✔
1411
      DecodeBC3Block(data + tiler->get(p) * 16,
6,928✔
1412
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
6,928✔
1413
                     p / ctx.width, ctx.width);
6,928✔
1414
    }
1415

1416
    break;
1417

1418
  default:
×
NEW
1419
    throw es::ImplementationError("Implement rgba decode");
×
1420
  }
1421
}
24✔
1422

1423
void DecodeToRG(const char *data, NewTexelContextCreate ctx,
2✔
1424
                std::span<UCVector2> outData) {
1425
  if (BlockCompression(ctx.baseFormat.type)) {
2✔
1426
    ctx.width = (ctx.width + 3) / 4;
2✔
1427
    ctx.height = (ctx.height + 3) / 4;
2✔
1428
  }
1429
  TileVariant tvar(TileVariantFromCtx(ctx));
2✔
1430
  const TileBase *tiler =
1431
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
1432
          ? [&] {
2✔
1433
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
1434
              return ctx.customTile;
×
1435
            }()
1436
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
4✔
1437
  const size_t numBlocks = ctx.width * ctx.height;
2✔
1438

1439
  switch (ctx.baseFormat.type) {
2✔
1440
    using F = TexelInputFormatType;
1441
  case F::BC5:
1442
    for (size_t p = 0; p < numBlocks; p++) {
8,194✔
1443
      DecodeBC5Block(data + tiler->get(p) * 16,
8,192✔
1444
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
8,192✔
1445
                     p / ctx.width, ctx.width, 2);
8,192✔
1446
    }
1447
    break;
1448

1449
  case F::RG4: {
×
1450
    const uint8 *iData = reinterpret_cast<const uint8 *>(data);
1451
    size_t curTexel = 0;
1452

1453
    for (auto &t : outData) {
×
1454
      uint8 col = *(iData + tiler->get(curTexel++));
×
1455
      t = UCVector2(col << 4, col & 0xf0);
×
1456
    }
1457

1458
    break;
1459
  }
1460

1461
  default:
1462
    break;
1463
  }
1464
}
2✔
1465

1466
void DecodeToGray(const char *data, NewTexelContextCreate ctx,
2✔
1467
                  std::span<char> outData) {
1468
  if (BlockCompression(ctx.baseFormat.type)) {
2✔
1469
    ctx.width = (ctx.width + 3) / 4;
2✔
1470
    ctx.height = (ctx.height + 3) / 4;
2✔
1471
  }
1472
  TileVariant tvar(TileVariantFromCtx(ctx));
2✔
1473
  const TileBase *tiler =
1474
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
1475
          ? [&] {
2✔
1476
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
1477
              return ctx.customTile;
×
1478
            }()
1479
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
4✔
1480

1481
  switch (ctx.baseFormat.type) {
2✔
1482
    using F = TexelInputFormatType;
1483
  case F::BC4: {
2✔
1484
    const size_t numBlocks = ctx.width * ctx.height;
2✔
1485
    for (size_t p = 0; p < numBlocks; p++) {
8,194✔
1486
      DecodeBC4Block(data + tiler->get(p) * 8,
8,192✔
1487
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
8,192✔
1488
                     p / ctx.width, ctx.width);
8,192✔
1489
    }
1490
    break;
1491
  }
1492

1493
  case F::R4: {
×
1494
    const uint8 *iData = reinterpret_cast<const uint8 *>(data);
1495
    size_t curTexel = 0;
1496

1497
    for (auto &t : outData) {
×
1498
      uint32 tile = tiler->get(curTexel++);
×
1499
      uint8 col = *(iData + (tile >> 1));
×
1500
      t = col << (4 * !(tile & 1)) & 0xf0;
×
1501
    }
1502

1503
    break;
1504
  }
1505

1506
  default:
1507
    break;
1508
  }
1509
}
2✔
1510

1511
void RetileData(const char *data, NewTexelContextCreate ctx, char *outData) {
13✔
1512
  const size_t BPT = GetBPT(ctx.baseFormat.type);
13✔
1513

1514
  if (BlockCompression(ctx.baseFormat.type)) {
1515
    ctx.width = (ctx.width + 3) / 4;
2✔
1516
    ctx.height = (ctx.height + 3) / 4;
2✔
1517
  }
1518
  TileVariant tvar(TileVariantFromCtx(ctx));
13✔
1519
  const TileBase *tiler =
1520
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
1521
          ? [&] {
13✔
1522
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
1523
              return ctx.customTile;
×
1524
            }()
1525
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
26✔
1526

1527
  const size_t numBlocks = ctx.width * ctx.height;
13✔
1528

1529
  if (ctx.baseFormat.swapPacked) {
13✔
1530
    if (ctx.baseFormat.type == TexelInputFormatType::R5G6B5 ||
×
1531
        ctx.baseFormat.type == TexelInputFormatType::RGBA4) {
1532
      uint16 *oData = reinterpret_cast<uint16 *>(outData);
1533
      for (size_t p = 0; p < numBlocks; p++, oData++) {
×
1534
        memcpy(oData, data + tiler->get(p) * BPT, BPT);
×
1535
        FByteswapper(*oData);
1536
      }
1537

1538
      return;
×
1539
    } else if (ctx.baseFormat.type == TexelInputFormatType::RGB10A2 ||
×
1540
               ctx.baseFormat.type == TexelInputFormatType::RGBA8) {
1541
      uint32 *oData = reinterpret_cast<uint32 *>(outData);
1542
      for (size_t p = 0; p < numBlocks; p++, oData++) {
×
1543
        memcpy(oData, data + tiler->get(p) * BPT, BPT);
×
1544
        FByteswapper(*oData);
×
1545
      }
1546

1547
      return;
1548
    }
1549
  }
1550

1551
  for (size_t p = 0; p < numBlocks; p++) {
499,501✔
1552
    memcpy(outData + p * BPT, data + tiler->get(p) * BPT, BPT);
499,488✔
1553
  }
1554
}
1555

1556
void Reencode(NewTexelContextCreate ctx, uint32 numDesiredChannels,
38✔
1557
              const char *data, std::span<char> outData_) {
1558
  const uint32 numInputChannels = FormatChannels(ctx.baseFormat.type);
38✔
1559
  const uint32 numTexels = ctx.width * ctx.height;
38✔
1560
  std::string tempBuffer;
1561
  std::span<char> outData = outData_;
38✔
1562
  uint32 outDataOffset = numTexels * (numDesiredChannels - numInputChannels);
38✔
1563

1564
  if (numDesiredChannels < numInputChannels) {
38✔
1565
    tempBuffer.resize(numTexels * numInputChannels);
×
1566
    outData = tempBuffer;
1567
    outDataOffset = 0;
1568
  }
1569

1570
  char *outDataBegin = outData.data() + outDataOffset;
38✔
1571

1572
  char white = 0xff;
38✔
1573
  char black = 0;
38✔
1574

1575
  const char *swizzleData[4];
1576
  uint8 factors[4]{1, 1, 1, 1};
38✔
1577
  uint8 invert[4]{};
38✔
1578

1579
  for (uint8 index = 0; TexelSwizzleType t : ctx.baseFormat.swizzle.types) {
190✔
1580
    switch (t) {
152✔
1581
    case TexelSwizzleType::RedInverted:
×
1582
      invert[index] = 0xff;
×
1583
      [[fallthrough]];
1584
    case TexelSwizzleType::Red:
38✔
1585
      swizzleData[index] = outDataBegin + 2;
38✔
1586
      break;
38✔
1587
    case TexelSwizzleType::GreenInverted:
×
1588
      invert[index] = 0xff;
×
1589
      [[fallthrough]];
1590
    case TexelSwizzleType::Green:
38✔
1591
      swizzleData[index] = outDataBegin + 1;
38✔
1592
      break;
38✔
1593
    case TexelSwizzleType::BlueInverted:
×
1594
    case TexelSwizzleType::DeriveZOrBlueInverted:
1595
      invert[index] = 0xff;
×
1596
      [[fallthrough]];
1597
    case TexelSwizzleType::Blue:
38✔
1598
    case TexelSwizzleType::DeriveZOrBlue:
1599
      swizzleData[index] = outDataBegin;
38✔
1600
      break;
38✔
1601
    case TexelSwizzleType::AlphaInverted:
×
1602
      invert[index] = 0xff;
×
1603
      [[fallthrough]];
1604
    case TexelSwizzleType::Alpha:
38✔
1605
      swizzleData[index] = outDataBegin + 3;
38✔
1606
      break;
38✔
1607
    case TexelSwizzleType::Black:
×
1608
    case TexelSwizzleType::DeriveZ:
1609
      swizzleData[index] = &black;
×
1610
      factors[index] = 0;
×
1611
      break;
×
1612
    case TexelSwizzleType::White:
×
1613
      swizzleData[index] = &white;
×
1614
      factors[index] = 0;
×
1615
      break;
×
1616

1617
    default:
1618
      break;
1619
    }
1620

1621
    index++;
152✔
1622
  }
1623

1624
  if (numInputChannels == 1) {
38✔
1625
    for (uint32 i = 1; i < numDesiredChannels; i++) {
12✔
1626
      if (swizzleData[i] != &white && swizzleData[i] != &black) {
6✔
1627
        invert[i] = invert[0];
6✔
1628
        swizzleData[i] = swizzleData[0];
6✔
1629
        factors[i] = factors[0];
6✔
1630
      }
1631
    }
1632

1633
    if (ctx.baseFormat.type == TexelInputFormatType::R8) {
6✔
1634
      RetileData(data, ctx, outDataBegin);
8✔
1635
    } else {
1636
      DecodeToGray(
4✔
1637
          data, ctx,
1638
          outData.subspan(numTexels * (numDesiredChannels - 1), numTexels));
2✔
1639
    }
1640
  } else if (numInputChannels == 2) {
32✔
1641
    if (ctx.baseFormat.type == TexelInputFormatType::RG8) {
4✔
1642
      RetileData(data, ctx, outDataBegin);
4✔
1643
    } else {
1644
      DecodeToRG(data, ctx,
4✔
1645
                 {reinterpret_cast<UCVector2 *>(outDataBegin), numTexels});
1646
    }
1647
  } else if (numInputChannels == 3) {
28✔
1648
    if (ctx.baseFormat.type == TexelInputFormatType::RGB8) {
2✔
1649
      RetileData(data, ctx, outDataBegin);
×
1650
    } else {
1651
      DecodeToRGB(data, ctx,
4✔
1652
                  {reinterpret_cast<UCVector *>(outDataBegin), numTexels});
1653
    }
1654
  } else if (numInputChannels == 4) {
26✔
1655
    if (ctx.baseFormat.type == TexelInputFormatType::RGBA8) {
26✔
1656
      RetileData(data, ctx, outDataBegin);
4✔
1657
    } else {
1658
      DecodeToRGBA(data, ctx,
48✔
1659
                   {reinterpret_cast<UCVector4 *>(outDataBegin), numTexels});
1660
    }
1661
  }
1662

1663
  for (uint32 t = 0; t < numTexels; t++) {
1,852,966✔
1664
    char texel[4];
1665

1666
    for (uint32 s = 0; s < numDesiredChannels; s++) {
8,308,992✔
1667
      texel[s] = (invert[s] - uint8(*swizzleData[s])) * int8(~invert[s] | 1);
6,456,064✔
1668

1669
      swizzleData[s] += factors[s] * numInputChannels;
6,456,064✔
1670
    }
1671

1672
    memcpy(outData_.data() + t * numDesiredChannels, texel, numDesiredChannels);
1,852,928✔
1673
  }
1674

1675
  [&] {
114✔
1676
    if (numDesiredChannels < 3) {
38✔
1677
      return;
1678
    }
1679

1680
    if (ctx.baseFormat.swizzle.b == TexelSwizzleType::DeriveZ) {
35✔
1681
      ComputeBC5Blue(outData_.data(), numTexels * numDesiredChannels,
×
1682
                     numDesiredChannels);
1683
      return;
×
1684
    }
1685

1686
    if (numInputChannels == 2) {
35✔
1687
      if (ctx.baseFormat.swizzle.b != TexelSwizzleType::DeriveZOrBlue &&
4✔
1688
          ctx.baseFormat.swizzle.b != TexelSwizzleType::DeriveZOrBlueInverted) {
1689
        return;
1690
      }
1691

1692
      ComputeBC5Blue(outData_.data(), numTexels * numDesiredChannels,
4✔
1693
                     numDesiredChannels);
1694
    }
1695
  }();
38✔
1696

1697
  if ((numDesiredChannels > 2 &&
38✔
1698
       ctx.baseFormat.swizzle.b == TexelSwizzleType::DeriveZ) ||
38✔
1699
      (numDesiredChannels == 2 &&
×
1700
       (ctx.baseFormat.swizzle.b == TexelSwizzleType::DeriveZOrBlue ||
×
1701
        ctx.baseFormat.swizzle.b == TexelSwizzleType::DeriveZOrBlueInverted))) {
1702
    ComputeBC5Blue(outData_.data(), numTexels * numDesiredChannels,
×
1703
                   numDesiredChannels);
1704
  }
1705

1706
  if (ctx.postProcess) {
38✔
1707
    ctx.postProcess(outData_.data(), numDesiredChannels, numTexels);
×
1708
  }
1709
}
38✔
1710

1711
struct NewTexelContextQOI : NewTexelContextImpl {
×
1712
  qoi_desc qoiDesc{};
1713
  std::string yasBuffer;
1714

1715
  NewTexelContextQOI(NewTexelContextCreate ctx_) : NewTexelContextImpl(ctx_) {
56✔
1716
    qoiDesc.width = ctx.width;
28✔
1717
    qoiDesc.height = ctx.height * std::max(uint16(1), ctx.depth);
28✔
1718
    qoiDesc.colorspace = !ctx.baseFormat.srgb;
28✔
1719
    qoiDesc.channels = DesiredQOIChannels(ctx.baseFormat.type);
28✔
1720
  }
28✔
1721

1722
  void InitBuffer() {
27✔
1723
    if (BlockCompression(ctx.baseFormat.type)) {
27✔
1724
      uint32 widthPadding = qoiDesc.width % 4;
14✔
1725
      widthPadding = widthPadding ? 4 - widthPadding : 0;
14✔
1726
      uint32 heightPadding = qoiDesc.height % 4;
14✔
1727
      heightPadding = heightPadding ? 4 - heightPadding : 0;
14✔
1728

1729
      const uint32 rasterDataSize = (qoiDesc.width + widthPadding) *
14✔
1730
                                    (qoiDesc.height + heightPadding) *
14✔
1731
                                    qoiDesc.channels;
14✔
1732
      yasBuffer.resize(rasterDataSize);
14✔
1733
      return;
14✔
1734
    }
1735

1736
    const uint32 rasterDataSize =
13✔
1737
        qoiDesc.width * qoiDesc.height * qoiDesc.channels;
13✔
1738
    yasBuffer.resize(rasterDataSize);
13✔
1739
  }
1740

1741
  void SendRasterData(const void *data, TexelInputLayout layout,
100✔
1742
                      TexelInputFormat *) override {
1743
    if (layout.mipMap > 0) {
100✔
1744
      return;
×
1745
    }
1746

1747
    auto mctx = ctx;
100✔
1748
    mctx.height *= std::max(mctx.depth, uint16(1));
100✔
1749

1750
    auto Write = [&](const void *buffer) {
100✔
1751
      int encodedSize = 0;
100✔
1752
      void *buffa = qoi_encode(buffer, &qoiDesc, &encodedSize);
100✔
1753
      std::string suffix;
1754

1755
      if (ctx.arraySize > 1) {
100✔
1756
        suffix.push_back('_');
64✔
1757
        suffix.append(std::to_string(layout.layer));
128✔
1758
      }
1759

1760
      if (layout.face != CubemapFace::NONE) {
100✔
1761
        suffix.push_back('_');
12✔
1762
        static const ReflectedEnum *refl = GetReflectedEnum<CubemapFace>();
12✔
1763
        suffix.append(refl->names[uint32(layout.face)]);
12✔
1764
      }
1765

1766
      suffix.append(".qoi");
100✔
1767

1768
      outCtx->NewFile(std::string(pathOverride.ChangeExtension(suffix)));
100✔
1769
      outCtx->SendData({static_cast<char *>(buffa), size_t(encodedSize)});
100✔
1770

1771
      free(buffa);
100✔
1772
    };
100✔
1773

1774
    bool mustDecode =
1775
        IsFormatSupported(ctx.formatOverride, ctx.baseFormat.type) ||
×
1776
        MustSwap(ctx.baseFormat.type, ctx.baseFormat.swapPacked) ||
75✔
1777
        ctx.baseFormat.tile != TexelTile::Linear ||
248✔
1778
        MustSwizzle(ctx.baseFormat.swizzle, qoiDesc.channels);
73✔
1779

1780
    if (mustDecode) {
1781
      InitBuffer();
27✔
1782
      Reencode(mctx, qoiDesc.channels, static_cast<const char *>(data),
54✔
1783
               yasBuffer);
1784
      Write(yasBuffer.data());
27✔
1785
    } else {
1786
      Write(data);
73✔
1787
    }
1788
  }
1789

1790
  bool ShouldDoMipmaps() override { return false; }
×
1791

1792
  void Finish() override {}
28✔
1793
};
1794

1795
struct NewTexelContextQOIBMP : NewTexelContextQOI {
1796
  BMPHeader bmpHdr;
1797
  BMPInfoHeader bmpInfo;
1798

1799
  NewTexelContextQOIBMP(NewTexelContextCreate ctx_) : NewTexelContextQOI(ctx_) {
×
1800
    if (BMPFallback(ctx.baseFormat.type)) {
×
1801
      FillBmpFormat(ctx, ctx.baseFormat, bmpInfo);
×
1802
    }
1803
  }
1804

1805
  bool ShouldDoMipmaps() override { return false; }
×
1806

1807
  void Finish() override {}
×
1808
};
1809

1810
struct NewTexelContextDDS : NewTexelContextImpl {
1811
  DDS dds;
1812
  DDS::Mips ddsMips{};
1813
  std::vector<std::vector<bool>> mipmaps;
1814
  std::string yasBuffer;
1815
  bool mustDecode;
1816
  uint8 numChannels;
1817

1818
  NewTexelContextDDS(NewTexelContextCreate ctx_, bool isBase = false)
28✔
1819
      : NewTexelContextImpl(ctx_), dds(MakeDDS(ctx)),
28✔
1820
        mustDecode(IsFormatSupported(ctx.formatOverride, ctx.baseFormat.type)),
28✔
1821
        numChannels(DesiredDDSChannels(ctx.baseFormat.type)) {
84✔
1822
    mipmaps.resize(std::max(1U, dds.mipMapCount));
31✔
1823
    const int8 numFaces = std::max(ctx.numFaces, int8(1));
28✔
1824
    const uint32 arraySize = dds.arraySize * numFaces;
28✔
1825

1826
    if (arraySize > 1) {
28✔
1827
      for (auto &a : mipmaps) {
19✔
1828
        a.resize(arraySize);
15✔
1829
      }
1830
    } else {
1831
      size_t curSlice = 0;
1832
      for (auto &a : mipmaps) {
53✔
1833
        a.resize(std::max(uint16(1), ddsMips.numSlices[curSlice++]));
29✔
1834
      }
1835
    }
1836

1837
    if (!isBase) {
28✔
1838
      TexelInputFormat baseFmt = ctx.baseFormat;
×
1839
      mustDecode |= MustSwizzle(baseFmt.swizzle, numChannels);
×
1840

1841
      if (mustDecode) {
×
1842
        switch (numChannels) {
×
1843
        case 1:
×
1844
          baseFmt.type = TexelInputFormatType::R8;
×
1845
          break;
×
1846
        case 2:
×
1847
          baseFmt.type = TexelInputFormatType::RG8;
×
1848
          break;
×
1849
        case 3:
×
1850
          baseFmt.type = TexelInputFormatType::RGB8;
×
1851
          break;
×
1852
        case 4:
×
1853
          baseFmt.type = TexelInputFormatType::RGBA8;
×
1854
          break;
×
1855
        default:
1856
          break;
1857
        }
1858
      }
1859

1860
      SetDDSFormat(dds, baseFmt);
×
1861
      dds.ComputeBPP();
×
1862
      yasBuffer.resize(dds.ComputeBufferSize(ddsMips));
×
1863
      for (int32 i = 0; i < ctx.numFaces; i++) {
×
1864
        dds.caps01 -= static_cast<DDS_HeaderEnd::Caps01Flags>(i + 10);
×
1865
      }
1866
    }
1867
  }
28✔
1868

1869
  bool ShouldWrite() const {
×
1870
    for (auto &l : mipmaps) {
×
1871
      for (bool m : l) {
×
1872
        if (!m) {
×
1873
          return false;
1874
        }
1875
      }
1876
    }
1877

1878
    return true;
1879
  }
1880

1881
  void SendRasterData(const void *data, TexelInputLayout layout,
×
1882
                      TexelInputFormat *) override {
1883
    if (!ShouldDoMipmaps() && layout.mipMap > 0) {
×
1884
      return;
×
1885
    }
1886

1887
    auto &mipLayers = mipmaps.at(layout.mipMap);
×
1888

1889
    const uint32 layer =
1890
        layout.layer * std::max(int8(1), ctx.numFaces) + uint8(layout.face);
×
1891

1892
    if (mipLayers.empty() || mipLayers.at(layer)) {
×
1893
      // mipmap already filled
1894
      return;
×
1895
    }
1896

1897
    mipLayers.at(layer).flip();
×
1898

1899
    auto rData = yasBuffer.data() + ddsMips.offsets[layout.mipMap];
×
1900

1901
    const size_t rDataSize =
1902
        ddsMips.sizes[layout.mipMap] *
×
1903
        std::max(ddsMips.numSlices[layout.mipMap], uint16(1));
×
1904

1905
    if (layout.face != CubemapFace::NONE) {
×
1906
      dds.caps01 +=
1907
          static_cast<DDS_HeaderEnd::Caps01Flags>(int(layout.face) + 9);
1908
      rData += ddsMips.frameStride * (int(layout.face) - 1);
×
1909
    }
1910

1911
    ptrdiff_t boundDiff = (&yasBuffer.back() + 1) - (rData + rDataSize);
×
1912
    if (boundDiff < 0) {
×
1913
      throw es::RuntimeError("Writing image data beyond buffer's bounds");
×
1914
    }
1915

1916
    auto mctx = ctx;
×
1917

1918
    for (uint32 m = 0; m < layout.mipMap; m++) {
×
1919
      mctx.width = std::max(1, mctx.width / 2);
×
1920
      mctx.depth = std::max(1, mctx.depth / 2);
×
1921
      mctx.height = std::max(1, mctx.height / 2);
×
1922
    }
1923

1924
    mctx.height *= mctx.depth;
×
1925

1926
    if (mustDecode) {
×
1927
      Reencode(mctx, numChannels, static_cast<const char *>(data),
×
1928
               {rData, rDataSize});
1929
    } else if (ctx.baseFormat.tile == TexelTile::Linear &&
×
1930
               !ctx.baseFormat.swapPacked) {
×
1931
      memcpy(rData, data, rDataSize);
1932
    } else {
1933
      RetileData(static_cast<const char *>(data), mctx, rData);
×
1934
    }
1935

1936
    if (ShouldWrite()) {
×
1937
      outCtx->NewFile(std::string(pathOverride.ChangeExtension2("dds")));
×
1938
      outCtx->SendData(
×
1939
          {reinterpret_cast<const char *>(&dds), size_t(dds.DDS_SIZE)});
×
1940
      outCtx->SendData(yasBuffer);
×
1941

1942
      es::Dispose(yasBuffer);
×
1943
    }
1944
  }
1945

1946
  bool ShouldDoMipmaps() override {
358✔
1947
    return mainSettings.texelSettings.processMipMaps;
358✔
1948
  }
1949

1950
  void Finish() override {
×
1951
    if (!ShouldWrite()) {
×
1952
      throw es::RuntimeError("Incomplete dds file");
×
1953
    }
1954
  }
1955
};
1956

1957
struct NewTexelContextDDSLegacy : NewTexelContextDDS {
1958
  std::vector<std::string> arrayMipmapBuffers;
1959
  uint8 numChannels;
1960

1961
  NewTexelContextDDSLegacy(NewTexelContextCreate ctx_)
28✔
1962
      : NewTexelContextDDS(ctx_, true),
28✔
1963
        numChannels(DesiredDDSLegacyChannels(ctx_.baseFormat)) {
56✔
1964
    arrayMipmapBuffers.resize(dds.arraySize);
28✔
1965
    dds.arraySize = 1;
28✔
1966
    TexelInputFormat baseFmt = ctx.baseFormat;
28✔
1967
    mustDecode |= MustSwizzle(ctx.baseFormat.swizzle, numChannels);
28✔
1968

1969
    if (mustDecode) {
28✔
1970
      switch (numChannels) {
11✔
1971
      case 1:
3✔
1972
        baseFmt.type = TexelInputFormatType::R8;
3✔
1973
        break;
3✔
1974
      case 2:
×
1975
        baseFmt.type = TexelInputFormatType::RG8;
×
1976
        break;
×
1977
      case 3:
2✔
1978
        baseFmt.type = TexelInputFormatType::RGB8;
2✔
1979
        break;
2✔
1980
      case 4:
6✔
1981
        baseFmt.type = TexelInputFormatType::RGBA8;
6✔
1982
        break;
6✔
1983
      default:
1984
        break;
1985
      }
1986
    }
1987

1988
    SetDDSLegacyFormat(dds, baseFmt);
28✔
1989
    dds.ComputeBPP();
28✔
1990
    dds.ComputeBufferSize(ddsMips);
28✔
1991

1992
    for (int32 i = 0; i < ctx.numFaces; i++) {
40✔
1993
      dds.caps01 -= static_cast<DDS_HeaderEnd::Caps01Flags>(i + 10);
12✔
1994
    }
1995
  }
28✔
1996

1997
  bool ShouldWrite(int32 layer) const {
329✔
1998
    if (ctx.arraySize > 1 && layer > -1) {
329✔
1999
      const int8 numFaces = std::max(int8(1), ctx.numFaces);
224✔
2000
      for (auto &m : mipmaps) {
928✔
2001
        for (int8 f = 0; f < numFaces; f++) {
1,568✔
2002
          if (!m.at(layer * numFaces + f)) {
864✔
2003
            return false;
2004
          }
2005
        }
2006
      }
2007
    } else {
2008
      for (auto &l : mipmaps) {
222✔
2009
        for (bool m : l) {
168✔
2010
          if (!m) {
720✔
2011
            return false;
2012
          }
2013
        }
2014
      }
2015
    }
2016

2017
    return true;
2018
  }
2019

2020
  void SendRasterData(const void *data, TexelInputLayout layout,
358✔
2021
                      TexelInputFormat *) override {
2022
    if (!ShouldDoMipmaps() && layout.mipMap > 0) {
358✔
2023
      return;
57✔
2024
    }
2025

2026
    auto &buffar = arrayMipmapBuffers.at(layout.layer);
358✔
2027
    auto &mipLayers = mipmaps.at(layout.mipMap);
358✔
2028

2029
    const uint32 layer = layout.layer * std::max(int8(1), ctx.numFaces) +
358✔
2030
                         std::max(int(layout.face) - 1, 0);
668✔
2031

2032
    if (mipLayers.empty() || mipLayers.at(layer)) {
358✔
2033
      // mipmap already filled
2034
      return;
57✔
2035
    }
2036

2037
    mipLayers.at(layer).flip();
301✔
2038

2039
    if (buffar.empty()) {
301✔
2040
      buffar.resize(ddsMips.frameStride * std::max(int8(1), ctx.numFaces));
92✔
2041
    }
2042

2043
    const size_t rDataSize =
2044
        ddsMips.sizes[layout.mipMap] *
602✔
2045
        std::max(ddsMips.numSlices[layout.mipMap], uint16(1));
301✔
2046

2047
    auto rData = buffar.data() + ddsMips.offsets[layout.mipMap];
301✔
2048

2049
    if (layout.face != CubemapFace::NONE) {
301✔
2050
      dds.caps01 +=
2051
          static_cast<DDS_HeaderEnd::Caps01Flags>(int(layout.face) + 9);
48✔
2052
      rData += ddsMips.frameStride * (int(layout.face) - 1);
48✔
2053
    }
2054

2055
    ptrdiff_t boundDiff = (&buffar.back() + 1) - (rData + rDataSize);
301✔
2056
    if (boundDiff < 0) {
301✔
2057
      throw es::RuntimeError("Writing image data beyond buffer's bounds");
×
2058
    }
2059

2060
    auto mctx = ctx;
301✔
2061

2062
    for (uint32 m = 0; m < layout.mipMap; m++) {
922✔
2063
      mctx.width = std::max(1, mctx.width / 2);
621✔
2064
      mctx.depth = std::max(1, mctx.depth / 2);
621✔
2065
      mctx.height = std::max(1, mctx.height / 2);
1,203✔
2066
    }
2067

2068
    mctx.height *= mctx.depth;
301✔
2069

2070
    if (mustDecode) {
301✔
2071
      Reencode(mctx, numChannels, static_cast<const char *>(data),
22✔
2072
               {rData, rDataSize});
2073
    } else if (ctx.baseFormat.tile == TexelTile::Linear &&
290✔
2074
               !ctx.baseFormat.swapPacked) {
285✔
2075
      memcpy(rData, data, rDataSize);
2076
    } else {
2077
      RetileData(static_cast<const char *>(data), mctx, rData);
10✔
2078
    }
2079

2080
    if (ShouldWrite(layout.layer)) {
301✔
2081
      std::string suffix;
2082

2083
      if (ctx.arraySize > 1) {
90✔
2084
        suffix.push_back('_');
64✔
2085
        suffix.append(std::to_string(layout.layer));
128✔
2086
      }
2087
      suffix.append(".dds");
90✔
2088

2089
      outCtx->NewFile(std::string(pathOverride.ChangeExtension(suffix)));
90✔
2090
      outCtx->SendData(
×
2091
          {reinterpret_cast<const char *>(&dds), size_t(dds.LEGACY_SIZE)});
90✔
2092
      outCtx->SendData(buffar);
90✔
2093

2094
      es::Dispose(buffar);
2095
    }
2096
  }
2097

2098
  void Finish() override {
28✔
2099
    if (!ShouldWrite(-1)) {
28✔
2100
      throw es::RuntimeError("Incomplete dds file");
×
2101
    }
2102
  }
28✔
2103
};
2104

2105
struct PngIHDR {
×
2106
  uint32 id = CompileFourCC("IHDR");
2107
  uint32 width;
2108
  uint32 height;
2109
  uint8 bitDepth = 8;
2110
  PngColorType colorType = PngColorType::RGBA;
2111
  uint8 compressionMethod = 0;
2112
  uint8 filterMethod = 0;
2113
  uint8 interlaceMethod = 0;
2114
};
2115

2116
void FByteswapper(PngIHDR &item) {
×
2117
  FByteswapper(item.width);
×
2118
  FByteswapper(item.height);
×
2119
}
2120

2121
struct DeflateBlock {
2122
  int16 blockSize;
2123
  int16 blockSizeComplement;
2124

2125
  void BlockSize(int16 size) {
2126
    blockSize = size;
×
2127
    blockSizeComplement = ~size;
×
2128
  }
2129

2130
  void SwapEndian() {}
2131
};
2132

2133
struct Png {
×
2134
  uint64 id = 0x0A1A0A0D474E5089;
2135
  uint32 ihdrSize = 13;
2136
  PngIHDR ihdr;
2137
};
2138

2139
struct PngData {
2140
  uint32 ihdrCRC;
2141
  uint32 idatSize;
2142
  uint32 idat = CompileFourCC("IDAT");
2143
};
2144

2145
void FByteswapper(Png &item) {
×
2146
  FByteswapper(item.ihdrSize);
×
2147
  FByteswapper(item.ihdr);
2148
}
2149

2150
void FByteswapper(PngData &item) {
×
2151
  FByteswapper(item.ihdrCRC);
×
2152
  FByteswapper(item.idatSize);
×
2153
}
2154

2155
struct PngEnd {
2156
  uint32 idatCrc;
2157
  uint32 iendSize = 0;
2158
  uint32 iend = CompileFourCC("IEND");
2159
  uint32 iendCrc = crc32b(0, "IEND", 4);
2160
};
2161

2162
void FByteswapper(PngEnd &item) {
×
2163
  FByteswapper(item.idatCrc);
×
2164
  FByteswapper(item.iendSize);
×
2165
  FByteswapper(item.iendCrc);
×
2166
}
2167

2168
std::string MakeZlibStream(const char *buffer_, PngIHDR &hdr) {
×
2169
  std::stringstream str;
×
2170
  BinWritterRef_e wr(str);
2171
  wr.Write(uint16(0x178));
×
2172
  wr.SwapEndian(true);
2173
  const uint32 numChannels = GetPngChannels(hdr.colorType);
×
2174
  const uint32 pitch = hdr.width * numChannels;
×
2175
  const uint32 scanLine = pitch + 1;
×
2176
  uint32 adlerA = 1;
×
2177
  uint32 adlerB = 0;
×
2178

2179
  auto Adler = [&](uint8 c) {
2180
    adlerA = (c + adlerA) % 65521;
×
2181
    adlerB = (adlerA + adlerB) % 65521;
×
2182
  };
2183

2184
  auto AdlerL = [&](const char *data, size_t size) {
2185
    for (size_t i = 0; i < size; i++) {
×
2186
      Adler(data[i]);
×
2187
    }
2188
  };
2189

2190
  DeflateBlock block;
2191
  block.BlockSize(scanLine);
×
2192

2193
  if (numChannels < 3) {
×
2194
    for (uint32 h = 0; h < hdr.height; h++) {
×
2195
      str.put(h == hdr.height - 1);
×
2196
      wr.Write(block);
×
2197
      str.put(0); // filter type
×
2198
      Adler(0);
2199
      AdlerL(buffer_ + pitch * h, pitch);
×
2200
      str.write(buffer_ + pitch * h, pitch);
×
2201
    }
2202
  } else if (numChannels == 3) {
×
2203
    for (uint32 h = 0; h < hdr.height; h++) {
×
2204
      str.put(h == hdr.height - 1);
×
2205
      wr.Write(block);
×
2206
      str.put(0); // filter type
×
2207
      Adler(0);
2208
      const char *startLine = buffer_ + pitch * h;
×
2209

2210
      for (uint32 w = 0; w < hdr.width; w++) {
×
2211
        char rgb[3];
2212
        memcpy(rgb, startLine + w * 3, 3);
×
2213
        std::swap(rgb[0], rgb[2]);
2214
        AdlerL(rgb, 3);
2215
        str.write(rgb, 3);
×
2216
      }
2217
    }
2218
  } else {
2219
    for (uint32 h = 0; h < hdr.height; h++) {
×
2220
      str.put(h == hdr.height - 1);
×
2221
      wr.Write(block);
×
2222
      str.put(0); // filter type
×
2223
      Adler(0);
2224
      const char *startLine = buffer_ + pitch * h;
×
2225

2226
      for (uint32 w = 0; w < hdr.width; w++) {
×
2227
        char rgb[4];
2228
        memcpy(rgb, startLine + w * 4, 4);
×
2229
        std::swap(rgb[0], rgb[2]);
2230
        AdlerL(rgb, 4);
2231
        str.write(rgb, 4);
×
2232
      }
2233
    }
2234
  }
2235

2236
  adlerA |= adlerB << 16;
×
2237
  wr.Write(adlerA);
×
2238
  return std::move(str).str();
×
2239
}
2240

2241
struct NewTexelContextPNG : NewTexelContextImpl {
2242
  std::string yasBuffer;
2243
  Png hdr;
2244
  uint32 numChannels;
2245

2246
  NewTexelContextPNG(NewTexelContextCreate ctx_) : NewTexelContextImpl(ctx_) {
×
2247
    hdr.ihdr.width = ctx.width;
×
2248
    hdr.ihdr.height = ctx.height * std::max(uint16(1), ctx.depth);
×
2249
    hdr.ihdr.colorType = DesiredPngColorType(
×
2250
        DesiredPngColorType(ctx.baseFormat.type), ctx.baseFormat.swizzle);
×
2251
    numChannels = GetPngChannels(hdr.ihdr.colorType);
×
2252
  }
2253

2254
  void InitBuffer() {
×
2255
    if (BlockCompression(ctx.baseFormat.type)) {
×
2256
      uint32 widthPadding = hdr.ihdr.width % 4;
×
2257
      widthPadding = widthPadding ? 4 - widthPadding : 0;
×
2258
      uint32 heightPadding = hdr.ihdr.height % 4;
×
2259
      heightPadding = heightPadding ? 4 - heightPadding : 0;
×
2260

2261
      const uint32 rasterDataSize = (hdr.ihdr.width + widthPadding) *
×
2262
                                    (hdr.ihdr.height + heightPadding) *
×
2263
                                    numChannels;
×
2264
      yasBuffer.resize(rasterDataSize);
×
2265
      return;
×
2266
    }
2267

2268
    const uint32 rasterDataSize =
×
2269
        hdr.ihdr.width * hdr.ihdr.height * numChannels;
×
2270
    yasBuffer.resize(rasterDataSize);
×
2271
  }
2272

2273
  void SendRasterData(const void *data, TexelInputLayout layout,
×
2274
                      TexelInputFormat *) override {
2275
    if (layout.mipMap > 0) {
×
2276
      return;
×
2277
    }
2278

2279
    auto mctx = ctx;
×
2280
    mctx.height *= std::max(mctx.depth, uint16(1));
×
2281

2282
    auto Write = [&](const void *buffer) {
×
2283
      std::string suffix;
2284

2285
      if (ctx.arraySize > 1) {
×
2286
        suffix.push_back('_');
×
2287
        suffix.append(std::to_string(layout.layer));
×
2288
      }
2289

2290
      if (layout.face != CubemapFace::NONE) {
×
2291
        suffix.push_back('_');
×
2292
        static const ReflectedEnum *refl = GetReflectedEnum<CubemapFace>();
2293
        suffix.append(refl->names[uint32(layout.face)]);
×
2294
      }
2295

2296
      suffix.append(".png");
×
2297

2298
      std::string encodedData =
2299
          MakeZlibStream(static_cast<const char *>(buffer), hdr.ihdr);
×
2300

2301
      Png hdrCopy = hdr;
×
2302
      FByteswapper(hdrCopy);
×
2303
      PngData hdrData{
×
2304
          .ihdrCRC = crc32b(0, reinterpret_cast<const char *>(&hdrCopy.ihdr),
×
2305
                            sizeof(hdrCopy.ihdr) - 3),
2306
          .idatSize = uint32(encodedData.size()),
2307

2308
      };
2309
      FByteswapper(hdrData);
2310

2311
      PngEnd tail{
×
2312
          .idatCrc = crc32b(crc32b(0, "IDAT", 4), encodedData.data(),
×
2313
                            encodedData.size()),
2314
      };
2315

2316
      FByteswapper(tail);
×
2317

2318
      outCtx->NewFile(std::string(pathOverride.ChangeExtension(suffix)));
×
2319
      outCtx->SendData(
×
2320
          {reinterpret_cast<const char *>(&hdrCopy), sizeof(hdrCopy) - 3});
2321
      outCtx->SendData(
×
2322
          {reinterpret_cast<const char *>(&hdrData), sizeof(hdrData)});
2323
      outCtx->SendData(encodedData);
×
2324
      outCtx->SendData({reinterpret_cast<const char *>(&tail), sizeof(tail)});
×
2325
    };
2326

2327
    bool mustDecode =
2328
        IsFormatSupported(ctx.formatOverride, ctx.baseFormat.type) ||
×
2329
        MustSwap(ctx.baseFormat.type, ctx.baseFormat.swapPacked) ||
×
2330
        ctx.baseFormat.tile != TexelTile::Linear ||
×
2331
        MustSwizzle(ctx.baseFormat.swizzle, numChannels);
×
2332

2333
    if (mustDecode) {
2334
      InitBuffer();
×
2335
      Reencode(mctx, numChannels, static_cast<const char *>(data), yasBuffer);
×
2336
      Write(yasBuffer.data());
×
2337
    } else {
2338
      Write(data);
×
2339
    }
2340
  }
2341

2342
  bool ShouldDoMipmaps() override { return false; }
×
2343

2344
  void Finish() override {}
×
2345
};
2346

2347
std::unique_ptr<NewTexelContextImpl>
2348
CreateTexelContext(NewTexelContextCreate ctx) {
56✔
2349
  if (ctx.formatOverride == TexelContextFormat::Config) {
56✔
2350
    ctx.formatOverride = OutputFormat();
56✔
2351
  }
2352

2353
  switch (ctx.formatOverride) {
56✔
2354
  case TexelContextFormat::DDS:
×
2355
    return std::make_unique<NewTexelContextDDS>(ctx);
×
2356
  case TexelContextFormat::DDS_Legacy:
28✔
2357
    return std::make_unique<NewTexelContextDDSLegacy>(ctx);
28✔
2358
  case TexelContextFormat::QOI_BMP:
×
2359
    return std::make_unique<NewTexelContextQOIBMP>(ctx);
×
2360
  case TexelContextFormat::QOI:
28✔
2361
    return std::make_unique<NewTexelContextQOI>(ctx);
28✔
2362
  case TexelContextFormat::UPNG:
×
2363
    return std::make_unique<NewTexelContextPNG>(ctx);
×
2364
  default:
×
NEW
2365
    throw es::ImplementationError("Image format not supported");
×
2366
  }
2367
}
2368

2369
void NewTexelContextImpl::ProcessContextData() {
×
2370
  auto layout = ComputeTraditionalDataLayout();
×
2371

2372
  for (uint32 a = 0; a < ctx.arraySize; a++) {
×
2373
    const char *entryBegin =
×
2374
        static_cast<const char *>(ctx.data) + a * layout.groupSize;
2375

2376
    if (ctx.numFaces > 0) {
×
2377
      for (int8 f = 0; f < ctx.numFaces; f++) {
×
2378
        const char *faceBegin = entryBegin + f * layout.mipGroupSize;
×
2379

2380
        for (uint32 m = 0; m < ctx.numMipmaps; m++) {
×
2381
          SendRasterData(faceBegin + layout.mipOffsets[m],
×
2382
                         {
2383
                             .mipMap = uint8(m),
2384
                             .face = static_cast<CubemapFace>(f + 1),
×
2385
                             .layer = uint16(a),
2386
                         });
2387
        }
2388
      }
2389
    } else {
2390
      for (uint32 m = 0; m < ctx.numMipmaps; m++) {
×
2391
        SendRasterData(entryBegin + layout.mipOffsets[m],
×
2392
                       {
2393
                           .mipMap = uint8(m),
2394
                           .layer = uint16(a),
2395
                       });
2396
      }
2397
    }
2398
  }
2399
}
2400

2401
std::unique_ptr<NewTexelContextImpl>
2402
CreateTexelContext(NewTexelContextCreate ctx, AppContext *actx) {
56✔
2403
  auto retVal = CreateTexelContext(ctx);
56✔
2404
  if (ctx.texelOutput) {
56✔
2405
    retVal->outCtx = ctx.texelOutput;
×
2406
  } else {
2407
    TexelOutputContext otx;
2408
    otx.ctx = actx;
2409
    retVal->outVariant = otx;
2410
    retVal->outCtx = &std::get<TexelOutputContext>(retVal->outVariant);
56✔
2411
  }
2412

2413
  retVal->pathOverride = actx->workingFile;
2414
  if (ctx.data) {
56✔
2415
    retVal->ProcessContextData();
×
2416
    return {};
2417
  }
2418

2419
  return retVal;
2420
}
2421

2422
std::unique_ptr<NewTexelContextImpl>
2423
CreateTexelContext(NewTexelContextCreate ctx, AppExtractContext *ectx,
×
2424
                   const std::string &path) {
2425
  auto retVal = CreateTexelContext(ctx);
×
2426
  if (ctx.texelOutput) {
×
2427
    retVal->outCtx = ctx.texelOutput;
×
2428
  } else {
2429
    TexelOutputExtractContext otx;
2430
    otx.ctx = ectx;
2431
    retVal->outVariant = otx;
2432
    retVal->outCtx = &std::get<TexelOutputExtractContext>(retVal->outVariant);
×
2433
  }
2434

2435
  retVal->pathOverride.Load(path);
×
2436

2437
  if (ctx.data) {
×
2438
    retVal->ProcessContextData();
×
2439
    return {};
2440
  }
2441
  return retVal;
2442
}
2443

2444
void TexelOutputContext::SendData(std::string_view data) {
280✔
2445
  str->write(data.data(), data.size());
280✔
2446
}
280✔
2447
void TexelOutputContext::NewFile(std::string filePath) {
190✔
2448
  str = &ctx->NewFile(filePath).str;
190✔
2449
}
190✔
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