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

PredatorCZ / PreCore / 567

15 Nov 2025 10:06AM UTC coverage: 52.235% (-0.07%) from 52.3%
567

push

github

PredatorCZ
full reencode for dds types

6 of 13 new or added lines in 1 file covered. (46.15%)

16 existing lines in 1 file now uncovered.

4113 of 7874 relevant lines covered (52.24%)

11664.26 hits per line

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

44.72
/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/format/DDS.hpp"
11
#include "spike/gpu/BlockDecoder.inl"
12
#include "spike/gpu/addr_ps3.hpp"
13
#include "spike/io/binwritter_stream.hpp"
14
#include "spike/reflect/reflector.hpp"
15
#include "spike/uni/format.hpp"
16
#include "spike/util/endian.hpp"
17
#include <sstream>
18
#include <variant>
19

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

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

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

42
  switch (fmt) {
128✔
43
    // Not supported
44
  case F::PVRTC2:
45
  case F::PVRTC4:
46
  case F::ETC1:
47
    return true;
48

49
    // DDS, DDS_Legacy only
50
  case F::BC1:
17✔
51
  case F::BC2:
52
  case F::BC3:
53
    return ofmt == TexelContextFormat::UPNG ||
54
           ofmt == TexelContextFormat::QOI ||
17✔
55
           ofmt == TexelContextFormat::QOI_BMP;
17✔
56

57
    // DDS only
58
  case F::BC4:
8✔
59
  case F::BC5:
60
  case F::BC7:
61
  case F::RG8:
62
  case F::RGBA16:
63
  case F::BC6:
64
  case F::RGB9E5:
65
    return ofmt != TexelContextFormat::DDS;
8✔
66

67
    // BMP, DDS only
68
  case F::RGB10A2:
4✔
69
  case F::RGB5A1:
70
    return ofmt == TexelContextFormat::UPNG ||
4✔
71
           ofmt == TexelContextFormat::QOI ||
72
           ofmt == TexelContextFormat::DDS_Legacy;
4✔
73

74
    // BMP only
75
  case F::P8:
×
76
  case F::P4:
77
    return ofmt != TexelContextFormat::QOI_BMP;
×
78

79
  // DDS, DDS_Legacy, BMP only
80
  case F::RGBA4:
6✔
81
  case F::R5G6B5:
82
    return ofmt == TexelContextFormat::UPNG || ofmt == TexelContextFormat::QOI;
6✔
83

84
  // DDS, DDS_Legacy, BMP, UPNG only
85
  case F::R8:
4✔
86
    return ofmt == TexelContextFormat::QOI;
4✔
87

88
    // Supported for all
89
  case F::RGBA8:
90
  case F::INVALID:
91
    return false;
92

93
  // QOI, DDS_Legacy, UPNG only
94
  case F::RGB8:
2✔
95
    return ofmt == TexelContextFormat::DDS;
2✔
96
  }
97

98
  return false;
99
}
100

101
bool MustSwap(TexelInputFormatType fmt, bool shouldSwap) {
×
102
  using F = TexelInputFormatType;
103

104
  switch (fmt) {
×
105
  case F::R5G6B5:
106
  case F::RGBA4:
107
  case F::RGB10A2:
108
  case F::RGB5A1:
109
    return shouldSwap;
110
  default:
×
111
    return false;
×
112
    break;
113
  }
114
}
115

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

123
  if (numChannels > 2) {
101✔
124
    return swizzle.b != TexelSwizzleType::Blue &&
98✔
125
           swizzle.b != TexelSwizzleType::DeriveZOrBlue;
98✔
126
  }
127

128
  return swizzle.b != TexelSwizzleType::Blue;
3✔
129
};
130

131
uint32 GetBPT(TexelInputFormatType fmt) {
25✔
132
  using F = TexelInputFormatType;
133

134
  switch (fmt) {
25✔
135
  case F::PVRTC2:
136
  case F::PVRTC4:
137
  case F::BC2:
138
  case F::BC3:
139
  case F::BC5:
140
  case F::BC7:
141
  case F::BC6:
142
    return 16;
143

144
  case F::ETC1:
3✔
145
  case F::BC1:
146
  case F::BC4:
147
  case F::RGBA16:
148
    return 8;
3✔
149

150
  case F::RG8:
5✔
151
  case F::RGB5A1:
152
  case F::RGBA4:
153
  case F::R5G6B5:
154
    return 2;
5✔
155

156
  case F::RGB10A2:
8✔
157
  case F::RGBA8:
158
  case F::RGB9E5:
159
    return 4;
8✔
160

161
  case F::P8:
6✔
162
  case F::P4:
163
  case F::R8:
164
    return 1;
6✔
165
  case F::RGB8:
×
166
    return 3;
×
167
  case F::INVALID:
168
    return 0;
169
  }
170

171
  return 0;
172
}
173

174
TexelDataLayout NewTexelContextImpl::ComputeTraditionalDataLayout(
×
175
    TexelInputFormatType *typeOverrides) {
176
  uint32 mipCount = std::max(ctx.numMipmaps, uint8(1));
×
177
  uint32 width = ctx.width;
×
178
  uint32 height = ctx.height;
×
179
  uint32 depth = std::max(ctx.depth, uint16(1));
×
180
  uint32 numFaces = std::max(ctx.numFaces, int8(1));
×
181

182
  TexelDataLayout retVal{};
×
183

184
  for (uint32 m = 0; m < mipCount; m++) {
×
185
    uint32 _width = width;
186
    uint32 _height = height;
187
    TexelInputFormatType type =
188
        typeOverrides ? typeOverrides[m] : ctx.baseFormat.type;
×
189
    uint32 bpt = GetBPT(type);
×
190

191
    if (BlockCompression(type)) {
192
      _width = (_width + 3) / 4;
×
193
      _height = (_height + 3) / 4;
×
194
    }
195

196
    retVal.mipSizes[m] = depth * _width * _height * bpt;
×
197
    retVal.mipOffsets[m] = retVal.mipGroupSize;
×
198
    retVal.mipGroupSize += retVal.mipSizes[m];
×
199
    width = std::max(1U, width / 2);
×
200
    height = std::max(1U, height / 2);
×
201
    depth = std::max(1U, depth / 2);
×
202
  }
203

204
  retVal.groupSize = retVal.mipGroupSize * numFaces;
×
205
  return retVal;
×
206
}
207

208
bool BMPFallback(TexelInputFormatType fmt) {
×
209
  using F = TexelInputFormatType;
210

211
  switch (fmt) {
×
212
  case F::RGB10A2:
213
  case F::RGB5A1:
214
  case F::P8:
215
  case F::P4:
216
  case F::RGBA4:
217
  case F::R5G6B5:
218
  case F::R8:
219
    return OutputFormat() == TexelContextFormat::QOI_BMP;
×
220

221
  default:
222
    return false;
223
  }
224

225
  return false;
226
}
227

228
#pragma pack(2)
229
struct BMPHeader {
×
230
  uint16 bfType = 0x4D42; // BM
231
  uint32 bfSize;
232
  uint16 bfReserved1 = 0;
233
  uint16 bfReserved2 = 0;
234
  uint32 bfOffBits = 54;
235
};
236
#pragma pack()
237

238
struct BMPInfoHeader {
×
239
  uint32 biSize = 40;
240
  uint32 biWidth;
241
  uint32 biHeight;
242
  uint16 biPlanes = 1;
243
  uint16 biBitCount = 32;
244
  uint32 biCompression = 0;
245
  uint32 biSizeImage;
246
  uint32 biXPelsPerMeter = 0;
247
  uint32 biYPelsPerMeter = 0;
248
  uint32 biClrUsed = 0;
249
  uint32 biClrImportant = 0;
250
};
251

252
union BMPMask {
253
  struct {
254
    uint32 red;
255
    uint32 green;
256
    uint32 blue;
257
    uint32 alpha;
258
  };
259
  uint32 data[4];
260
};
261

262
BMPMask SwizzleMask(BMPMask bits, TexelSwizzle swizzle) {
×
263
  uint32 currentOffset = 0;
264
  BMPMask retVal{};
×
265

266
  for (uint32 c = 0; c < 4; c++) {
×
267
    uint32 swizzleIndex = swizzle.types[c] > TexelSwizzleType::Alpha
×
268
                              ? c
×
269
                              : uint32(swizzle.types[c]);
270

271
    uint32 numBits = bits.data[swizzleIndex];
×
272
    uint32 mask = (1 << numBits) - 1;
×
273

274
    retVal.data[c] = mask << currentOffset;
×
275
    currentOffset += numBits;
×
276
  }
277

278
  return retVal;
×
279
}
280

281
BMPMask FillBmpFormat(NewTexelContextCreate ctx, TexelInputFormat fmt,
×
282
                      BMPInfoHeader &info) {
283
  using F = TexelInputFormatType;
284
  info.biWidth = ctx.width;
×
285
  info.biHeight = ctx.height;
×
286

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

312
  return {};
313
}
314

315
DDS MakeDDS(NewTexelContextCreate ctx) {
28✔
316
  DDS dds;
317
  dds.width = ctx.width;
28✔
318
  dds.height = ctx.height;
28✔
319
  if (ctx.baseFormat.premultAlpha) {
28✔
320
    dds.alphaMode = dds.AlphaMode_Premultiplied;
×
321
  }
322

323
  dds.arraySize = ctx.arraySize;
28✔
324

325
  if (ctx.depth > 1) {
28✔
326
    dds.caps01 += dds.Caps01Flags_Volume;
327
    dds.flags += dds.Flags_Depth;
328
    dds.dimension = dds.Dimension_3D;
1✔
329
    dds.depth = ctx.depth;
1✔
330
  } else if (ctx.numFaces > 0) {
27✔
331
    dds.caps01 += dds.Caps01Flags_CubeMap;
332
    dds.miscFlag += dds.MiscFlag_CubeTexture;
333

334
    for (int32 i = 0; i < ctx.numFaces; i++) {
14✔
335
      dds.caps01 += static_cast<DDS_HeaderEnd::Caps01Flags>(i + 10);
12✔
336
    }
337
  }
338

339
  if (mainSettings.texelSettings.processMipMaps) {
28✔
340
    dds.NumMipmaps(ctx.numMipmaps);
5✔
341
  }
342

343
  return dds;
28✔
344
}
345

346
void SetDDSFormat(DDS &dds, TexelInputFormat fmt) {
×
347
  dds = DDSFormat_DX10;
348
  switch (fmt.type) {
×
349
    using F = TexelInputFormatType;
350
  case F::BC1:
×
351
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC1_UNORM + fmt.srgb);
×
352
    break;
×
353

354
  case F::BC2:
×
355
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC2_UNORM + fmt.srgb);
×
356
    break;
×
357

358
  case F::BC3:
×
359
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC3_UNORM + fmt.srgb);
×
360
    break;
×
361

362
  case F::BC4:
×
363
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC4_UNORM + fmt.snorm);
×
364
    break;
×
365

366
  case F::BC5:
×
367
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC5_UNORM + fmt.snorm);
×
368
    break;
×
369

370
  case F::BC7:
×
371
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC7_UNORM + fmt.srgb);
×
372
    break;
×
373

374
  case F::RGBA4:
×
375
    dds.dxgiFormat = DXGI_FORMAT_B4G4R4A4_UNORM;
×
376
    break;
×
377

378
  case F::R5G6B5:
×
379
    dds.dxgiFormat = DXGI_FORMAT_B5G6R5_UNORM;
×
380
    break;
×
381

382
  case F::RGB5A1:
×
383
    dds.dxgiFormat = DXGI_FORMAT_B5G5R5A1_UNORM;
×
384
    break;
×
385

386
  case F::RGB10A2:
×
387
    dds.dxgiFormat = DXGI_FORMAT_R10G10B10A2_UNORM;
×
388
    break;
×
389

390
  case F::RG8:
×
391
    dds.dxgiFormat =
×
392
        fmt.snorm ? DXGI_FORMAT_R8G8_SNORM : DXGI_FORMAT_R8G8_UNORM;
×
393
    break;
×
394

395
  case F::R8:
×
396
    dds.dxgiFormat = fmt.snorm ? DXGI_FORMAT_R8_SNORM : DXGI_FORMAT_R8_UNORM;
×
397
    break;
×
398

399
  case F::RGBA8:
×
400
  case F::P8:
401
  case F::P4:
402
  case F::PVRTC2:
403
  case F::PVRTC4:
404
  case F::ETC1:
405
  case F::RGB8:
406
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_R8G8B8A8_UNORM + fmt.srgb);
×
407
    break;
×
408

409
  case F::RGBA16:
×
410
    dds.dxgiFormat = DXGI_FORMAT_R16G16B16A16_FLOAT;
×
411
    break;
×
412

413
  case F::BC6:
×
414
    dds.dxgiFormat = DXGI_FORMAT(DXGI_FORMAT_BC6H_UF16 + fmt.snorm);
×
415
    break;
×
416

417
  case F::RGB9E5:
×
418
    dds.dxgiFormat = DXGI_FORMAT_R9G9B9E5_SHAREDEXP;
×
419
    break;
×
420

421
  case F::INVALID:
×
422
    dds.dxgiFormat = DXGI_FORMAT_UNKNOWN;
×
423
    break;
×
424
  }
425
}
426

427
void SetDDSLegacyFormat(DDS &dds, TexelInputFormatType fmt) {
28✔
428
  switch (fmt) {
28✔
429
    using F = TexelInputFormatType;
430
  case F::BC1:
431
    dds = DDSFormat_DXT1;
432
    break;
433

434
  case F::BC2:
435
    dds = DDSFormat_DXT3;
436
    break;
437

438
  case F::BC3:
439
    dds = DDSFormat_DXT5;
440
    break;
441

442
  case F::BC4:
443
  case F::R8:
444
    dds = DDSFormat_L8;
445
    break;
446

447
  case F::RGBA4:
448
    dds = DDSFormat_A4R4G4B4;
449
    break;
450

451
  case F::R5G6B5:
452
    dds = DDSFormat_R5G6B5;
453
    break;
454

455
  case F::RGB8:
456
    dds = DDSFormat_R8G8B8;
457
    break;
458

459
  case F::RGBA8:
460
  case F::BC7:
461
  case F::RGB5A1:
462
  case F::RGB10A2:
463
  case F::P8:
464
  case F::P4:
465
  case F::PVRTC2:
466
  case F::PVRTC4:
467
  case F::ETC1:
468
  case F::RGBA16:
469
  case F::BC6:
470
  case F::RGB9E5:
471
    dds = DDSFormat_A8R8G8B8;
472
    break;
473

474
  case F::BC5:
475
  case F::RG8:
476
    dds = DDSFormat_R8G8B8;
477
    break;
478

479
  case F::INVALID:
480
    break;
481
  }
482
}
28✔
483

484
uint8 DesiredQOIChannels(TexelInputFormatType fmt) {
×
485
  switch (fmt) {
×
486
    using F = TexelInputFormatType;
487
  case F::BC4:
488
  case F::R8:
489
  case F::R5G6B5:
490
  case F::BC5:
491
  case F::RG8:
492
  case F::RGB8:
493
    return 3;
494

495
  case F::BC1:
496
  case F::BC2:
497
  case F::BC3:
498
  case F::RGBA4:
499
  case F::RGBA8:
500
  case F::BC7:
501
  case F::RGB5A1:
502
  case F::RGB10A2:
503
  case F::P8:
504
  case F::P4:
505
  case F::PVRTC2:
506
  case F::PVRTC4:
507
  case F::ETC1:
508
  case F::RGBA16:
509
  case F::BC6:
510
  case F::RGB9E5:
511
    return 4;
512

513
  case F::INVALID:
×
514
    return 0;
×
515
  }
516

517
  return 4;
518
}
519

520
enum class PngColorType : uint8 {
521
  Gray = 0,
522
  RGB = 2,
523
  Palette = 3,
524
  GrayAlpha = 4,
525
  RGBA = 6,
526
};
527

528
uint32 GetPngChannels(PngColorType fmt) {
×
529
  switch (fmt) {
530
  case PngColorType::Gray:
531
    return 1;
532
  case PngColorType::GrayAlpha:
533
    return 2;
534
  case PngColorType::RGB:
535
    return 3;
536
  case PngColorType::RGBA:
537
    return 4;
538
  }
539

540
  return 0;
541
}
542

543
PngColorType DesiredPngColorType(TexelInputFormatType fmt) {
×
544
  switch (fmt) {
×
545
    using F = TexelInputFormatType;
546
  case F::R8:
547
  case F::BC4:
548
    return PngColorType::Gray;
549

550
  case F::BC5:
×
551
  case F::RG8:
552
    // return PngColorType::GrayAlpha;
553

554
  case F::R5G6B5:
555
  case F::RGB8:
556
    return PngColorType::RGB;
×
557

558
  case F::BC1:
559
  case F::BC2:
560
  case F::BC3:
561
  case F::RGBA4:
562
  case F::RGBA8:
563
  case F::BC7:
564
  case F::RGB5A1:
565
  case F::RGB10A2:
566
  case F::P8:
567
  case F::P4:
568
  case F::PVRTC2:
569
  case F::PVRTC4:
570
  case F::ETC1:
571
  case F::RGBA16:
572
  case F::BC6:
573
  case F::RGB9E5:
574
    return PngColorType::RGBA;
575

576
  case F::INVALID:
577
    return PngColorType::RGBA;
578
  }
579

580
  return PngColorType::RGBA;
581
}
582

583
PngColorType DesiredPngColorType(PngColorType type, TexelSwizzle &swizzle) {
×
584
  switch (type) {
×
585
  case PngColorType::RGBA: {
×
586
    if (swizzle.r == swizzle.g && swizzle.g == swizzle.b) {
×
587
      if (swizzle.a == TexelSwizzleType::White) {
×
588
        return PngColorType::Gray;
589
      }
590

591
      swizzle.g = swizzle.a;
×
592
      return PngColorType::GrayAlpha;
×
593
    }
594
    if (swizzle.a == TexelSwizzleType::White) {
×
595
      return PngColorType::RGB;
×
596
    }
597

598
    return type;
599
  }
600

601
  case PngColorType::RGB: {
×
602
    if (swizzle.r == swizzle.g && swizzle.g == swizzle.b) {
×
603
      if (swizzle.a == TexelSwizzleType::White) {
×
604
        return PngColorType::Gray;
605
      }
606

607
      swizzle.g = swizzle.a;
×
608
      return PngColorType::GrayAlpha;
×
609
    }
610

611
    if (swizzle.a != TexelSwizzleType::White) {
×
612
      return PngColorType::RGBA;
×
613
    }
614

615
    return type;
616
  }
617
  }
618

619
  return type;
620
}
621

622
/*
623
PngColorType DesiredPngColorType(PngColorType type, TexelSwizzle swizzle) {
624
  switch (type) {
625
  case PngColorType::Gray: {
626
    int8 factors[4]{0, 0, 0, 0};
627

628
    for (uint32 f = 0; f < 4; f++) {
629
      switch (swizzle.types[f]) {
630
      case TexelSwizzleType::Red:
631
        factors[f] = 1;
632
        break;
633
      case TexelSwizzleType::RedInverted:
634
        factors[f] = -1;
635
        break;
636

637
      default:
638
        break;
639
      }
640
    }
641

642
    if ((factors[0] == factors[1] &&
643
         factors[1] == factors[2]) ||           // rgb == red || redinverted
644
        (factors[0] == 0 && factors[1] == 0) || // r == red || redinverted
645
        (factors[0] == 0 && factors[2] == 0) || // g == red || redinverted
646
        (factors[1] == 0 && factors[2] == 0)) { // // b == red || redinverted
647
      if (factors[3]) {
648
        return PngColorType::GrayAlpha;
649
      }
650
      return type;
651
    } else {
652
      if (factors[3]) {
653
        return PngColorType::RGBA;
654
      }
655
      return PngColorType::RGB;
656
    }
657
  }
658

659
  case PngColorType::GrayAlpha: {
660
    int8 factors[4]{0, 0, 0, 0};
661

662
    for (uint32 f = 0; f < 4; f++) {
663
      switch (swizzle.types[f]) {
664
      case TexelSwizzleType::Red:
665
      case TexelSwizzleType::Alpha:
666
        factors[f] = 1;
667
        break;
668
      case TexelSwizzleType::RedInverted:
669
      case TexelSwizzleType::AlphaInverted:
670
        factors[f] = -1;
671
        break;
672

673
      default:
674
        break;
675
      }
676
    }
677

678
    if ((factors[0] == factors[1] &&
679
         factors[1] == factors[2]) ||           // rgb == red || redinverted
680
        (factors[0] == 0 && factors[1] == 0) || // r == red || redinverted
681
        (factors[0] == 0 && factors[2] == 0) || // g == red || redinverted
682
        (factors[1] == 0 && factors[2] == 0)) { // // b == red || redinverted
683
      if (factors[3]) {
684
        return PngColorType::GrayAlpha;
685
      }
686
      return type;
687
    } else {
688
      if (factors[3]) {
689
        return PngColorType::RGBA;
690
      }
691
      return PngColorType::RGB;
692
    }
693
  }
694
  }
695
}*/
696

697
uint8 DesiredDDSChannels(TexelInputFormatType fmt) {
×
698
  switch (fmt) {
×
699
    using F = TexelInputFormatType;
700
  case F::BC4:
701
  case F::R8:
702
    return 1;
703

704
  case F::R5G6B5:
2✔
705
    return 3;
2✔
706

707
  case F::BC5:
2✔
708
  case F::RG8:
709
    return 2;
2✔
710

711
  case F::BC1:
712
  case F::BC2:
713
  case F::BC3:
714
  case F::RGBA4:
715
  case F::RGBA8:
716
  case F::BC7:
717
  case F::RGB5A1:
718
  case F::RGB10A2:
719
  case F::P8:
720
  case F::P4:
721
  case F::PVRTC2:
722
  case F::PVRTC4:
723
  case F::ETC1:
724
  case F::RGB8:
725
  case F::RGBA16:
726
  case F::BC6:
727
  case F::RGB9E5:
728
    return 4;
729

730
  case F::INVALID:
×
731
    return 0;
×
732
  }
733

734
  return 4;
735
}
736

737
uint8 DesiredDDSLegacyChannels(TexelInputFormatType fmt) {
×
738
  switch (fmt) {
28✔
739
    using F = TexelInputFormatType;
740
  case F::BC4:
741
  case F::R8:
742
    return 1;
743

744
  case F::R5G6B5:
5✔
745
  case F::BC5:
746
  case F::RG8:
747
  case F::RGB8:
748
    return 3;
5✔
749

750
  case F::BC1:
751
  case F::BC2:
752
  case F::BC3:
753
  case F::RGBA4:
754
  case F::RGBA8:
755
  case F::BC7:
756
  case F::RGB5A1:
757
  case F::RGB10A2:
758
  case F::P8:
759
  case F::P4:
760
  case F::PVRTC2:
761
  case F::PVRTC4:
762
  case F::ETC1:
763
  case F::RGBA16:
764
  case F::BC6:
765
  case F::RGB9E5:
766
    return 4;
767

768
  case F::INVALID:
×
769
    return 0;
×
770
  }
771

772
  return 4;
773
}
774

775
uint8 FormatChannels(TexelInputFormatType fmt) {
38✔
776
  switch (fmt) {
38✔
777
    using F = TexelInputFormatType;
778
  case F::BC4:
779
  case F::R8:
780
    return 1;
781

782
  case F::BC5:
4✔
783
  case F::RG8:
784
    return 2;
4✔
785

786
  case F::R5G6B5:
2✔
787
  case F::RGB8:
788
    return 3;
2✔
789

790
  case F::BC1:
26✔
791
  case F::BC2:
792
  case F::BC3:
793
  case F::RGBA4:
794
  case F::RGBA8:
795
  case F::BC7:
796
  case F::RGB5A1:
797
  case F::RGB10A2:
798
  case F::P8:
799
  case F::P4:
800
  case F::PVRTC2:
801
  case F::PVRTC4:
802
  case F::ETC1:
803
  case F::RGBA16:
804
  case F::BC6:
805
  case F::RGB9E5:
806
    return 4;
26✔
807

808
  case F::INVALID:
809
    return 0;
810
  }
811

812
  return 0;
813
}
814

815
struct LinearTile : TileBase {
×
NEW
816
  void reset(uint32, uint32, uint32) override {}
×
817
  uint32 get(uint32 inTexel) const override { return inTexel; }
693,760✔
818
};
819

820
struct MortonTile : TileBase {
×
821
  MortonSettings settings;
822

823
  MortonTile(uint32 width, uint32 height) : settings(width, height) {}
×
824

NEW
825
  void reset(uint32, uint32, uint32) override {}
×
826

827
  uint32 get(uint32 inTexel) const override {
×
828
    return MortonAddr(inTexel % settings.width, inTexel / settings.height,
×
829
                      settings);
×
830
  }
831
};
832

833
uint32 RoundToPow2(uint32 number) {
×
834
  number--;
×
835
  number |= number >> 1;
×
836
  number |= number >> 2;
×
837
  number |= number >> 4;
×
838
  number |= number >> 8;
×
839
  number |= number >> 16;
×
840
  number++;
×
841
  return number;
×
842
}
843

844
struct MortonPow2Tile : TileBase {
×
845
  size_t width;
846
  size_t height;
847
  size_t widthp2;
848

849
  MortonPow2Tile(size_t width_, size_t height_)
850
      : width(width_), height(height_),
851
        widthp2(RoundToPow2(std::max(width_, size_t(8)))) {}
×
852

NEW
853
  void reset(uint32, uint32, uint32) override {}
×
854

855
  static size_t MortonAddr(size_t x, size_t y, size_t width) {
856
    const size_t x0 = x & 1;
×
857
    const size_t x1 = (x & 2) << 1;
×
858
    const size_t x2 = (x & 4) << 2;
×
859

860
    const size_t y0 = (y & 1) << 1;
×
861
    const size_t y1 = (y & 2) << 2;
×
862
    const size_t y2 = (y & 4) << 3;
×
863

864
    size_t retval = x0 | x1 | x2 | y0 | y1 | y2;
×
865

866
    const size_t macroX = x / 8;
×
867
    const size_t macroY = y / 8;
×
868
    const size_t macroWidth = width / 8;
×
869

870
    const size_t macroAddr = (macroWidth * macroY) + macroX;
×
871

872
    return retval | (macroAddr << 6);
×
873
  }
874

875
  uint32 get(uint32 inTexel) const override {
×
876
    return MortonAddr(inTexel % width, inTexel / height, widthp2);
×
877
  }
878
};
879

880
struct NXTile : TileBase {
12✔
881
  uint32 width;
882
  uint32 height;
883
  uint32 numUsedYBlockBits;
884
  uint32 yBlockMask;
885
  uint32 yTailMask;
886
  uint32 macroTileWidth;
887
  uint32 BPT;
888
  uint32 yTailOffset;
889
  uint32 upperBound;
890
  uint32 numUsedMicroYBits;
891
  uint32 midYShift;
892

893
  NXTile(size_t width_, size_t height_, TexelInputFormatType fmt)
12✔
894
      : width(width_), height(height_), BPT(GetBPT(fmt)),
12✔
895
        upperBound(width * height) {
12✔
896
    numUsedMicroYBits = std::min(1 << uint32(std::round(log2(height))), 3);
12✔
897
    midYShift = numUsedMicroYBits + 3;
12✔
898

899
    const uint32 macroTileHeight =
900
        std::min(uint32(std::round(log2((height + 7) / 8))), 4U);
12✔
901

902
    // addr bits 9 - 13
903
    // y bits 3 - 7
904
    yBlockMask = ((1 << macroTileHeight) - 1) << numUsedMicroYBits;
12✔
905
    numUsedYBlockBits = numUsedMicroYBits + macroTileHeight;
12✔
906

907
    // y bits 8 - end
908
    yTailMask = ~((1 << numUsedYBlockBits) - 1);
12✔
909

910
    macroTileWidth = ((width * BPT) + 63) / 64;
12✔
911

912
    yTailOffset = macroTileHeight + 2;
12✔
913
  }
12✔
914

NEW
915
  void reset(uint32, uint32, uint32) override {}
×
916

917
  uint32 get(uint32 inTexel) const override {
260,672✔
918
    // 12 11 10 9  8  7  6  5  4  3  2  1  0
919
    // y  y  y  y  x  y  y  x  y  x  x  x  x
920
    // y*x*y{0,4}xyyxyx[x|0]{4}
921

922
    uint32 x = (inTexel % width) * BPT;
260,672✔
923
    uint32 y = inTexel / width;
260,672✔
924

925
    // Volumetrics not implemented
926
    // Some small rasters use alignment
927

928
    // x bits 0 - 5
929
    // y bits 0 - 2
930
    uint32 microTile = (x & 0xf) | ((y & 1) << 4) | ((x & 0x10) << 1) |
260,672✔
931
                       ((y & 0x6) << 5) | ((x & 0x20) << numUsedMicroYBits);
260,672✔
932

933
    // addr tail after yBlockMask bits
934
    // x bits 6 - end
935
    constexpr uint32 xTailMask = ~0x3f;
936

937
    uint32 macroTile = (((y & yBlockMask) << midYShift) | //
260,672✔
938
                        ((x & xTailMask) << numUsedYBlockBits)) +
260,672✔
939
                       (((y & yTailMask) << yTailOffset) * macroTileWidth);
260,672✔
940

941
    uint32 wholeTile = (microTile | macroTile) / BPT;
260,672✔
942

943
    /*if (wholeTile >= upperBound) [[unlikely]] {
944
      throw std::runtime_error("NX tile error, accessing block out of range");
945
    }*/
946

947
    return wholeTile;
260,672✔
948
  }
949
};
950

951
using TileVariant =
952
    std::variant<LinearTile, MortonTile, MortonPow2Tile, NXTile>;
953

954
TileVariant TileVariantFromCtx(NewTexelContextCreate ctx) {
43✔
955
  uint32 width = ctx.width;
43✔
956
  uint32 height = ctx.height;
43✔
957

958
  switch (ctx.baseFormat.tile) {
43✔
959
  case TexelTile::Linear:
31✔
960
    return LinearTile{};
961

962
  case TexelTile::Morton:
963
    return MortonTile(width, height);
×
964

965
  case TexelTile::MortonForcePow2:
×
966
    return MortonPow2Tile(width, height);
×
967

968
  case TexelTile::NX:
12✔
969
    return NXTile(width, height, ctx.baseFormat.type);
12✔
970

971
  case TexelTile::Custom:
972
    break;
973
  }
974

975
  return LinearTile{};
976
}
977

978
void DecodeToRGB(const char *data, NewTexelContextCreate ctx,
2✔
979
                 std::span<UCVector> outData) {
980
  if (BlockCompression(ctx.baseFormat.type)) {
2✔
UNCOV
981
    ctx.width = (ctx.width + 3) / 4;
×
UNCOV
982
    ctx.height = (ctx.height + 3) / 4;
×
983
  }
984
  TileVariant tvar(TileVariantFromCtx(ctx));
2✔
985
  const TileBase *tiler =
986
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
987
          ? [&] {
2✔
988
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
989
              return ctx.customTile;
×
990
            }()
991
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
4✔
992

993
  const size_t numBlocks = ctx.width * ctx.height;
2✔
994

995
  switch (ctx.baseFormat.type) {
2✔
996
    using F = TexelInputFormatType;
997
  case F::R5G6B5: {
2✔
998
    auto &codec = uni::FormatCodec::Get({
2✔
999
        .outType = uni::FormatType::UNORM,
1000
        .compType = uni::DataType::R5G6B5,
1001
    });
1002

1003
    size_t curTexel = 0;
1004
    if (ctx.baseFormat.swapPacked) {
2✔
1005
      const uint16 *iData = reinterpret_cast<const uint16 *>(data);
1006
      for (auto &t : outData) {
×
1007
        Vector4A16 value;
1008
        uint16 col = *(iData + tiler->get(curTexel++));
×
1009
        FByteswapper(col);
1010

1011
        codec.GetValue(value, reinterpret_cast<const char *>(&col));
×
1012
        t = Vector(Vector4A16(value * 0xff)).Convert<uint8>();
×
1013
      }
1014
    } else {
1015
      for (auto &t : outData) {
86,274✔
1016
        Vector4A16 value;
1017
        codec.GetValue(value, data + tiler->get(curTexel++) * 2);
86,272✔
1018
        t = Vector(Vector4A16(value * 0xff)).Convert<uint8>();
86,272✔
1019
      }
1020
    }
1021

1022
    break;
1023
  }
1024

UNCOV
1025
  case F::RG8: {
×
1026
    size_t curTexel = 0;
1027

UNCOV
1028
    if (ctx.baseFormat.snorm) {
×
1029
      const CVector2 *iData = reinterpret_cast<const CVector2 *>(data);
1030
      for (auto &t : outData) {
×
1031
        auto fData = (iData + tiler->get(curTexel++))->Convert<int>() + 0x80;
×
1032
        t = UCVector(0, fData.y, fData.x);
×
1033
      }
1034
    } else {
1035
      const UCVector2 *iData = reinterpret_cast<const UCVector2 *>(data);
UNCOV
1036
      for (auto &t : outData) {
×
UNCOV
1037
        auto tData = iData + tiler->get(curTexel++);
×
UNCOV
1038
        t = UCVector(0, tData->y, tData->x);
×
1039
      }
1040
    }
1041
    break;
1042
  }
1043

1044
  case F::BC5:
UNCOV
1045
    for (size_t p = 0; p < numBlocks; p++) {
×
UNCOV
1046
      DecodeBC5Block(data + tiler->get(p) * 16,
×
UNCOV
1047
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
×
UNCOV
1048
                     p / ctx.width, ctx.width, 3);
×
1049
    }
1050
    break;
1051

1052
  case F::BC4:
1053
    for (size_t p = 0; p < numBlocks; p++) {
×
1054
      _DecodeBC4Block(data + tiler->get(p) * 8,
×
1055
                      reinterpret_cast<char *>(outData.data()), p % ctx.width,
×
1056
                      p / ctx.width, ctx.width, 3);
×
1057
    }
1058

1059
    for (auto &p : outData) {
×
1060
      p.y = p.z = p.x;
×
1061
    }
1062

1063
    break;
1064

1065
  case F::R8: {
×
1066
    size_t curTexel = 0;
1067
    for (auto &t : outData) {
×
1068
      t = UCVector(*(data + tiler->get(curTexel++)));
×
1069
    }
1070

1071
    break;
1072
  }
1073

1074
  default:
×
1075
    throw std::logic_error("Implement rgb decode");
×
1076
  }
1077
}
2✔
1078

1079
void DecodeToRGBA(const char *data, NewTexelContextCreate ctx,
24✔
1080
                  std::span<UCVector4> outData) {
1081
  if (BlockCompression(ctx.baseFormat.type)) {
24✔
1082
    ctx.width = (ctx.width + 3) / 4;
13✔
1083
    ctx.height = (ctx.height + 3) / 4;
13✔
1084
  }
1085
  TileVariant tvar(TileVariantFromCtx(ctx));
24✔
1086
  const TileBase *tiler =
1087
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
1088
          ? [&] {
24✔
1089
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
1090
              return ctx.customTile;
×
1091
            }()
1092
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
48✔
1093

1094
  const size_t numBlocks = ctx.width * ctx.height;
24✔
1095

1096
  switch (ctx.baseFormat.type) {
24✔
1097
    using F = TexelInputFormatType;
1098
  case F::RGB10A2: {
2✔
1099
    auto &codec = uni::FormatCodec::Get({
2✔
1100
        .outType = uni::FormatType::UNORM,
1101
        .compType = uni::DataType::R10G10B10A2,
1102
    });
1103

1104
    size_t curTexel = 0;
1105
    if (ctx.baseFormat.swapPacked) {
2✔
1106
      const uint32 *iData = reinterpret_cast<const uint32 *>(data);
1107
      for (auto &t : outData) {
×
1108
        Vector4A16 value;
1109
        uint32 col = *(iData + tiler->get(curTexel++));
×
1110
        FByteswapper(col);
×
1111

1112
        codec.GetValue(value, reinterpret_cast<const char *>(&col));
×
1113
        t = (value * 0xff).Convert<uint8>();
1114
      }
1115
    } else {
1116
      for (auto &t : outData) {
131,074✔
1117
        Vector4A16 value;
1118
        codec.GetValue(value, data + tiler->get(curTexel++) * 4);
131,072✔
1119
        t = (value * 0xff).Convert<uint8>();
1120
      }
1121
    }
1122
    break;
1123
  }
1124

1125
  case F::RGBA4: {
1✔
1126
    const uint16 *iData = reinterpret_cast<const uint16 *>(data);
1127
    size_t curTexel = 0;
1128
    if (ctx.baseFormat.swapPacked) {
1✔
1129
      for (auto &t : outData) {
×
1130
        uint16 col = *(iData + tiler->get(curTexel++));
×
1131
        FByteswapper(col);
1132
        t = UCVector4(col << 4, col & 0xf0, (col >> 4) & 0xf0,
×
1133
                      (col >> 8) & 0xf0);
×
1134
      }
1135
    } else {
1136
      for (auto &t : outData) {
65,537✔
1137
        uint16 col = *(iData + tiler->get(curTexel++));
65,536✔
1138
        t = UCVector4(col << 4, col & 0xf0, (col >> 4) & 0xf0,
65,536✔
1139
                      (col >> 8) & 0xf0);
65,536✔
1140
      }
1141
    }
1142

1143
    break;
1144
  }
1145

1146
  case F::RGB5A1: {
2✔
1147
    const uint16 *iData = reinterpret_cast<const uint16 *>(data);
1148
    size_t curTexel = 0;
1149

1150
    if (ctx.baseFormat.swapPacked) {
2✔
1151
      for (auto &t : outData) {
×
1152
        uint16 col = *(iData + tiler->get(curTexel++));
×
1153
        FByteswapper(col);
1154
        t = UCVector4(col << 3, (col >> 2) & 0xf8, (col >> 7) & 0xf8,
×
1155
                      int16(col) >> 15);
×
1156
      }
1157
    } else {
1158
      for (auto &t : outData) {
131,074✔
1159
        uint16 col = *(iData + tiler->get(curTexel++));
131,072✔
1160
        t = UCVector4(col << 3, (col >> 2) & 0xf8, (col >> 7) & 0xf8,
131,072✔
1161
                      int16(col) >> 15);
131,072✔
1162
      }
1163
    }
1164

1165
    break;
1166
  }
1167

1168
  case F::PVRTC2:
1169
    pvr::PVRTDecompressPVRTC(data, 1, ctx.width, ctx.height,
2✔
1170
                             reinterpret_cast<uint8_t *>(outData.data()));
1171
    break;
1172

1173
  case F::PVRTC4:
1174
    pvr::PVRTDecompressPVRTC(data, 0, ctx.width, ctx.height,
2✔
1175
                             reinterpret_cast<uint8_t *>(outData.data()));
1176
    break;
1177

1178
  case F::ETC1:
1179
    pvr::PVRTDecompressETC(data, ctx.width, ctx.height,
2✔
1180
                           reinterpret_cast<uint8_t *>(outData.data()), 0);
1181
    break;
1182

1183
  case F::BC7: {
1184
    uint32 localBlock[16];
1185
    for (size_t p = 0; p < numBlocks; p++) {
8,194✔
1186
      if (!detexDecompressBlockBPTC(
8,192✔
1187
              reinterpret_cast<const uint8_t *>(data) + tiler->get(p) * 16, -1,
8,192✔
1188
              0, reinterpret_cast<uint8_t *>(localBlock))) [[unlikely]] {
×
1189
        throw std::runtime_error("Failed to decompress BC7 block");
×
1190
      }
1191
      uint32 x = p % ctx.width;
8,192✔
1192
      uint32 y = p / ctx.width;
8,192✔
1193
      uint32 blockOffset = ctx.width * y * 16 + x * 4;
8,192✔
1194

1195
      for (size_t r = 0; r < 4; r++) {
40,960✔
1196
        UCVector4 *addr = outData.data() + blockOffset + r * ctx.width * 4;
32,768✔
1197
        memcpy(static_cast<void *>(addr), localBlock + r * 4, 16);
32,768✔
1198
      }
1199
    }
1200
    break;
1201
  }
1202

1203
  case F::BC1:
1204
    for (size_t p = 0; p < numBlocks; p++) {
5,394✔
1205
      DecodeBC1BlockA(data + tiler->get(p) * 8,
5,392✔
1206
                      reinterpret_cast<char *>(outData.data()), p % ctx.width,
5,392✔
1207
                      p / ctx.width, ctx.width);
5,392✔
1208
    }
1209

1210
    break;
1211

1212
  case F::BC2:
1213
    for (size_t p = 0; p < numBlocks; p++) {
4,097✔
1214
      DecodeBC2Block(data + tiler->get(p) * 16,
4,096✔
1215
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
4,096✔
1216
                     p / ctx.width, ctx.width);
4,096✔
1217
    }
1218

1219
    break;
1220

1221
  case F::BC3:
1222
    for (size_t p = 0; p < numBlocks; p++) {
6,936✔
1223
      DecodeBC3Block(data + tiler->get(p) * 16,
6,928✔
1224
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
6,928✔
1225
                     p / ctx.width, ctx.width);
6,928✔
1226
    }
1227

1228
    break;
1229

1230
  default:
×
1231
    throw std::logic_error("Implement rgba decode");
×
1232
  }
1233
}
24✔
1234

1235
void DecodeToRG(const char *data, NewTexelContextCreate ctx,
2✔
1236
                std::span<UCVector2> outData) {
1237
  if (BlockCompression(ctx.baseFormat.type)) {
2✔
1238
    ctx.width = (ctx.width + 3) / 4;
2✔
1239
    ctx.height = (ctx.height + 3) / 4;
2✔
1240
  }
1241
  TileVariant tvar(TileVariantFromCtx(ctx));
2✔
1242
  const TileBase *tiler =
1243
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
1244
          ? [&] {
2✔
1245
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
1246
              return ctx.customTile;
×
1247
            }()
1248
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
4✔
1249
  const size_t numBlocks = ctx.width * ctx.height;
2✔
1250

1251
  switch (ctx.baseFormat.type) {
2✔
1252
    using F = TexelInputFormatType;
1253
  case F::BC5:
1254
    for (size_t p = 0; p < numBlocks; p++) {
8,194✔
1255
      DecodeBC5Block(data + tiler->get(p) * 16,
8,192✔
1256
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
8,192✔
1257
                     p / ctx.width, ctx.width, 2);
8,192✔
1258
    }
1259
    break;
1260

1261
  default:
1262
    break;
1263
  }
1264
}
2✔
1265

1266
void DecodeToGray(const char *data, NewTexelContextCreate ctx,
2✔
1267
                  std::span<char> outData) {
1268
  if (BlockCompression(ctx.baseFormat.type)) {
2✔
1269
    ctx.width = (ctx.width + 3) / 4;
2✔
1270
    ctx.height = (ctx.height + 3) / 4;
2✔
1271
  }
1272
  TileVariant tvar(TileVariantFromCtx(ctx));
2✔
1273
  const TileBase *tiler =
1274
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
1275
          ? [&] {
2✔
1276
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
1277
              return ctx.customTile;
×
1278
            }()
1279
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
4✔
1280

1281
  switch (ctx.baseFormat.type) {
2✔
1282
    using F = TexelInputFormatType;
1283
  case F::BC4: {
2✔
1284
    const size_t numBlocks = ctx.width * ctx.height;
2✔
1285
    for (size_t p = 0; p < numBlocks; p++) {
8,194✔
1286
      DecodeBC4Block(data + tiler->get(p) * 8,
8,192✔
1287
                     reinterpret_cast<char *>(outData.data()), p % ctx.width,
8,192✔
1288
                     p / ctx.width, ctx.width);
8,192✔
1289
    }
1290
    break;
1291
  }
1292

1293
  default:
1294
    break;
1295
  }
1296
}
2✔
1297

1298
void RetileData(const char *data, NewTexelContextCreate ctx, char *outData) {
13✔
1299
  const size_t BPT = GetBPT(ctx.baseFormat.type);
13✔
1300

1301
  if (BlockCompression(ctx.baseFormat.type)) {
1302
    ctx.width = (ctx.width + 3) / 4;
2✔
1303
    ctx.height = (ctx.height + 3) / 4;
2✔
1304
  }
1305
  TileVariant tvar(TileVariantFromCtx(ctx));
13✔
1306
  const TileBase *tiler =
1307
      ctx.customTile && ctx.baseFormat.tile == TexelTile::Custom
×
1308
          ? [&] {
13✔
1309
              ctx.customTile->reset(ctx.width, ctx.height, ctx.depth);
×
1310
              return ctx.customTile;
×
1311
            }()
1312
          : std::visit([](auto &item) -> const TileBase * { return &item; }, tvar);
26✔
1313

1314
  const size_t numBlocks = ctx.width * ctx.height;
13✔
1315

1316
  if (ctx.baseFormat.swapPacked) {
13✔
1317
    if (ctx.baseFormat.type == TexelInputFormatType::R5G6B5 ||
×
1318
        ctx.baseFormat.type == TexelInputFormatType::RGBA4) {
1319
      uint16 *oData = reinterpret_cast<uint16 *>(outData);
1320
      for (size_t p = 0; p < numBlocks; p++, oData++) {
×
1321
        memcpy(oData, data + tiler->get(p) * BPT, BPT);
×
1322
        FByteswapper(*oData);
1323
      }
1324

1325
      return;
×
1326
    } else if (ctx.baseFormat.type == TexelInputFormatType::RGB10A2) {
×
1327
      uint32 *oData = reinterpret_cast<uint32 *>(outData);
1328
      for (size_t p = 0; p < numBlocks; p++, oData++) {
×
1329
        memcpy(oData, data + tiler->get(p) * BPT, BPT);
×
1330
        FByteswapper(*oData);
×
1331
      }
1332

1333
      return;
1334
    }
1335
  }
1336

1337
  for (size_t p = 0; p < numBlocks; p++) {
499,501✔
1338
    memcpy(outData + p * BPT, data + tiler->get(p) * BPT, BPT);
499,488✔
1339
  }
1340
}
1341

1342
void Reencode(NewTexelContextCreate ctx, uint32 numDesiredChannels,
38✔
1343
              const char *data, std::span<char> outData_) {
1344
  const uint32 numInputChannels = FormatChannels(ctx.baseFormat.type);
38✔
1345
  const uint32 numTexels = ctx.width * ctx.height;
38✔
1346
  std::string tempBuffer;
1347
  std::span<char> outData = outData_;
38✔
1348
  uint32 outDataOffset = numTexels * (numDesiredChannels - numInputChannels);
38✔
1349

1350
  if (numDesiredChannels < numInputChannels) {
38✔
1351
    tempBuffer.resize(numTexels * numInputChannels);
×
1352
    outData = tempBuffer;
1353
    outDataOffset = 0;
1354
  }
1355

1356
  char *outDataBegin = outData.data() + outDataOffset;
38✔
1357

1358
  char white = 0xff;
38✔
1359
  char black = 0;
38✔
1360

1361
  const char *swizzleData[4];
1362
  uint8 factors[4]{1, 1, 1, 1};
38✔
1363
  uint8 invert[4]{};
38✔
1364

1365
  for (uint8 index = 0; TexelSwizzleType t : ctx.baseFormat.swizzle.types) {
190✔
1366
    switch (t) {
152✔
1367
    case TexelSwizzleType::RedInverted:
×
1368
      invert[index] = 0xff;
×
1369
      [[fallthrough]];
1370
    case TexelSwizzleType::Red:
38✔
1371
      swizzleData[index] = outDataBegin + 2;
38✔
1372
      break;
38✔
1373
    case TexelSwizzleType::GreenInverted:
×
1374
      invert[index] = 0xff;
×
1375
      [[fallthrough]];
1376
    case TexelSwizzleType::Green:
38✔
1377
      swizzleData[index] = outDataBegin + 1;
38✔
1378
      break;
38✔
1379
    case TexelSwizzleType::BlueInverted:
×
1380
    case TexelSwizzleType::DeriveZOrBlueInverted:
1381
      invert[index] = 0xff;
×
1382
      [[fallthrough]];
1383
    case TexelSwizzleType::Blue:
38✔
1384
    case TexelSwizzleType::DeriveZOrBlue:
1385
      swizzleData[index] = outDataBegin;
38✔
1386
      break;
38✔
1387
    case TexelSwizzleType::AlphaInverted:
×
1388
      invert[index] = 0xff;
×
1389
      [[fallthrough]];
1390
    case TexelSwizzleType::Alpha:
38✔
1391
      swizzleData[index] = outDataBegin + 3;
38✔
1392
      break;
38✔
1393
    case TexelSwizzleType::Black:
×
1394
    case TexelSwizzleType::DeriveZ:
1395
      swizzleData[index] = &black;
×
1396
      factors[index] = 0;
×
1397
      break;
×
1398
    case TexelSwizzleType::White:
×
1399
      swizzleData[index] = &white;
×
1400
      factors[index] = 0;
×
1401
      break;
×
1402

1403
    default:
1404
      break;
1405
    }
1406

1407
    index++;
152✔
1408
  }
1409

1410
  if (numInputChannels == 1) {
38✔
1411
    for (uint32 i = 1; i < numDesiredChannels; i++) {
12✔
1412
      if (swizzleData[i] != &white && swizzleData[i] != &black) {
6✔
1413
        invert[i] = invert[0];
6✔
1414
        swizzleData[i] = swizzleData[0];
6✔
1415
        factors[i] = factors[0];
6✔
1416
      }
1417
    }
1418

1419
    if (ctx.baseFormat.type == TexelInputFormatType::R8) {
6✔
1420
      RetileData(data, ctx, outDataBegin);
8✔
1421
    } else {
1422
      DecodeToGray(
4✔
1423
          data, ctx,
1424
          outData.subspan(numTexels * (numDesiredChannels - 1), numTexels));
2✔
1425
    }
1426
  } else if (numInputChannels == 2) {
32✔
1427
    if (ctx.baseFormat.type == TexelInputFormatType::RG8) {
4✔
1428
      RetileData(data, ctx, outDataBegin);
4✔
1429
    } else {
1430
      DecodeToRG(data, ctx,
4✔
1431
                 {reinterpret_cast<UCVector2 *>(outDataBegin), numTexels});
1432
    }
1433
  } else if (numInputChannels == 3) {
28✔
1434
    if (ctx.baseFormat.type == TexelInputFormatType::RGB8) {
2✔
1435
      RetileData(data, ctx, outDataBegin);
×
1436
    } else {
1437
      DecodeToRGB(data, ctx,
4✔
1438
                  {reinterpret_cast<UCVector *>(outDataBegin), numTexels});
1439
    }
1440
  } else if (numInputChannels == 4) {
26✔
1441
    if (ctx.baseFormat.type == TexelInputFormatType::RGBA8) {
26✔
1442
      RetileData(data, ctx, outDataBegin);
4✔
1443
    } else {
1444
      DecodeToRGBA(data, ctx,
48✔
1445
                   {reinterpret_cast<UCVector4 *>(outDataBegin), numTexels});
1446
    }
1447
  }
1448

1449
  for (uint32 t = 0; t < numTexels; t++) {
1,852,966✔
1450
    char texel[4];
1451

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

1455
      swizzleData[s] += factors[s] * numInputChannels;
6,456,064✔
1456
    }
1457

1458
    memcpy(outData_.data() + t * numDesiredChannels, texel, numDesiredChannels);
1,852,928✔
1459
  }
1460

1461
  [&] {
114✔
1462
    if (numDesiredChannels < 3) {
38✔
1463
      return;
1464
    }
1465

1466
    if (ctx.baseFormat.swizzle.b == TexelSwizzleType::DeriveZ) {
35✔
1467
      ComputeBC5Blue(outData_.data(), numTexels * numDesiredChannels,
×
1468
                     numDesiredChannels);
1469
      return;
×
1470
    }
1471

1472
    if (numInputChannels == 2) {
35✔
1473
      if (ctx.baseFormat.swizzle.b != TexelSwizzleType::DeriveZOrBlue &&
4✔
1474
          ctx.baseFormat.swizzle.b != TexelSwizzleType::DeriveZOrBlueInverted) {
1475
        return;
1476
      }
1477

1478
      ComputeBC5Blue(outData_.data(), numTexels * numDesiredChannels,
4✔
1479
                     numDesiredChannels);
1480
    }
1481
  }();
38✔
1482

1483
  if ((numDesiredChannels > 2 &&
38✔
1484
       ctx.baseFormat.swizzle.b == TexelSwizzleType::DeriveZ) ||
38✔
1485
      (numDesiredChannels == 2 &&
×
NEW
1486
       (ctx.baseFormat.swizzle.b == TexelSwizzleType::DeriveZOrBlue ||
×
1487
        ctx.baseFormat.swizzle.b == TexelSwizzleType::DeriveZOrBlueInverted))) {
UNCOV
1488
    ComputeBC5Blue(outData_.data(), numTexels * numDesiredChannels,
×
1489
                   numDesiredChannels);
1490
  }
1491

1492
  if (ctx.postProcess) {
38✔
1493
    ctx.postProcess(outData_.data(), numDesiredChannels, numTexels);
×
1494
  }
1495
}
38✔
1496

1497
struct NewTexelContextQOI : NewTexelContextImpl {
×
1498
  qoi_desc qoiDesc{};
1499
  std::string yasBuffer;
1500

1501
  NewTexelContextQOI(NewTexelContextCreate ctx_) : NewTexelContextImpl(ctx_) {
56✔
1502
    qoiDesc.width = ctx.width;
28✔
1503
    qoiDesc.height = ctx.height * std::max(uint16(1), ctx.depth);
28✔
1504
    qoiDesc.colorspace = !ctx.baseFormat.srgb;
28✔
1505
    qoiDesc.channels = DesiredQOIChannels(ctx.baseFormat.type);
28✔
1506
  }
28✔
1507

1508
  void InitBuffer() {
27✔
1509
    if (BlockCompression(ctx.baseFormat.type)) {
27✔
1510
      uint32 widthPadding = qoiDesc.width % 4;
14✔
1511
      widthPadding = widthPadding ? 4 - widthPadding : 0;
14✔
1512
      uint32 heightPadding = qoiDesc.height % 4;
14✔
1513
      heightPadding = heightPadding ? 4 - heightPadding : 0;
14✔
1514

1515
      const uint32 rasterDataSize = (qoiDesc.width + widthPadding) *
14✔
1516
                                    (qoiDesc.height + heightPadding) *
14✔
1517
                                    qoiDesc.channels;
14✔
1518
      yasBuffer.resize(rasterDataSize);
14✔
1519
      return;
14✔
1520
    }
1521

1522
    const uint32 rasterDataSize =
13✔
1523
        qoiDesc.width * qoiDesc.height * qoiDesc.channels;
13✔
1524
    yasBuffer.resize(rasterDataSize);
13✔
1525
  }
1526

1527
  void SendRasterData(const void *data, TexelInputLayout layout,
100✔
1528
                      TexelInputFormat *) override {
1529
    if (layout.mipMap > 0) {
100✔
1530
      return;
×
1531
    }
1532

1533
    auto mctx = ctx;
100✔
1534
    mctx.height *= std::max(mctx.depth, uint16(1));
100✔
1535

1536
    auto Write = [&](const void *buffer) {
100✔
1537
      int encodedSize = 0;
100✔
1538
      void *buffa = qoi_encode(buffer, &qoiDesc, &encodedSize);
100✔
1539
      std::string suffix;
1540

1541
      if (ctx.arraySize > 1) {
100✔
1542
        suffix.push_back('_');
64✔
1543
        suffix.append(std::to_string(layout.layer));
128✔
1544
      }
1545

1546
      if (layout.face != CubemapFace::NONE) {
100✔
1547
        suffix.push_back('_');
12✔
1548
        static const ReflectedEnum *refl = GetReflectedEnum<CubemapFace>();
12✔
1549
        suffix.append(refl->names[uint32(layout.face)]);
12✔
1550
      }
1551

1552
      suffix.append(".qoi");
100✔
1553

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

1557
      free(buffa);
100✔
1558
    };
100✔
1559

1560
    bool mustDecode =
1561
        IsFormatSupported(ctx.formatOverride, ctx.baseFormat.type) ||
×
1562
        MustSwap(ctx.baseFormat.type, ctx.baseFormat.swapPacked) ||
75✔
1563
        ctx.baseFormat.tile != TexelTile::Linear ||
248✔
1564
        MustSwizzle(ctx.baseFormat.swizzle, qoiDesc.channels);
73✔
1565

1566
    if (mustDecode) {
1567
      InitBuffer();
27✔
1568
      Reencode(mctx, qoiDesc.channels, static_cast<const char *>(data),
54✔
1569
               yasBuffer);
1570
      Write(yasBuffer.data());
27✔
1571
    } else {
1572
      Write(data);
73✔
1573
    }
1574
  }
1575

1576
  bool ShouldDoMipmaps() override { return false; }
×
1577

1578
  void Finish() override {}
28✔
1579
};
1580

1581
struct NewTexelContextQOIBMP : NewTexelContextQOI {
1582
  BMPHeader bmpHdr;
1583
  BMPInfoHeader bmpInfo;
1584

1585
  NewTexelContextQOIBMP(NewTexelContextCreate ctx_) : NewTexelContextQOI(ctx_) {
×
1586
    if (BMPFallback(ctx.baseFormat.type)) {
×
1587
      FillBmpFormat(ctx, ctx.baseFormat, bmpInfo);
×
1588
    }
1589
  }
1590

1591
  bool ShouldDoMipmaps() override { return false; }
×
1592

1593
  void Finish() override {}
×
1594
};
1595

1596
struct NewTexelContextDDS : NewTexelContextImpl {
1597
  DDS dds;
1598
  DDS::Mips ddsMips{};
1599
  std::vector<std::vector<bool>> mipmaps;
1600
  std::string yasBuffer;
1601
  bool mustDecode;
1602
  uint8 numChannels;
1603

1604
  NewTexelContextDDS(NewTexelContextCreate ctx_, bool isBase = false)
28✔
1605
      : NewTexelContextImpl(ctx_), dds(MakeDDS(ctx)),
28✔
1606
        mustDecode(IsFormatSupported(ctx.formatOverride, ctx.baseFormat.type)),
28✔
1607
        numChannels(DesiredDDSChannels(ctx.baseFormat.type)) {
84✔
1608
    mipmaps.resize(std::max(1U, dds.mipMapCount));
31✔
1609
    const int8 numFaces = std::max(ctx.numFaces, int8(1));
28✔
1610
    const uint32 arraySize = dds.arraySize * numFaces;
28✔
1611

1612
    if (arraySize > 1) {
28✔
1613
      for (auto &a : mipmaps) {
19✔
1614
        a.resize(arraySize);
15✔
1615
      }
1616
    } else {
1617
      size_t curSlice = 0;
1618
      for (auto &a : mipmaps) {
53✔
1619
        a.resize(std::max(uint16(1), ddsMips.numSlices[curSlice++]));
29✔
1620
      }
1621
    }
1622

1623
    if (!isBase) {
28✔
1624
      TexelInputFormat baseFmt = ctx.baseFormat;
×
NEW
1625
      mustDecode |= MustSwizzle(baseFmt.swizzle, numChannels);
×
1626

1627
      if (mustDecode) {
×
1628
        switch (numChannels) {
×
UNCOV
1629
        case 1:
×
1630
          baseFmt.type = TexelInputFormatType::R8;
×
1631
          break;
×
UNCOV
1632
        case 2:
×
1633
          baseFmt.type = TexelInputFormatType::RG8;
×
1634
          break;
×
UNCOV
1635
        case 3:
×
1636
          baseFmt.type = TexelInputFormatType::RGB8;
×
1637
          break;
×
UNCOV
1638
        case 4:
×
1639
          baseFmt.type = TexelInputFormatType::RGBA8;
×
1640
          break;
×
1641
        default:
1642
          break;
1643
        }
1644
      }
1645

1646
      SetDDSFormat(dds, baseFmt);
×
1647
      dds.ComputeBPP();
×
1648
      yasBuffer.resize(dds.ComputeBufferSize(ddsMips));
×
1649
      for (int32 i = 0; i < ctx.numFaces; i++) {
×
1650
        dds.caps01 -= static_cast<DDS_HeaderEnd::Caps01Flags>(i + 10);
×
1651
      }
1652
    }
1653
  }
28✔
1654

1655
  bool ShouldWrite() const {
×
1656
    for (auto &l : mipmaps) {
×
1657
      for (bool m : l) {
×
1658
        if (!m) {
×
1659
          return false;
1660
        }
1661
      }
1662
    }
1663

1664
    return true;
1665
  }
1666

1667
  void SendRasterData(const void *data, TexelInputLayout layout,
×
1668
                      TexelInputFormat *) override {
1669
    if (!ShouldDoMipmaps() && layout.mipMap > 0) {
×
1670
      return;
×
1671
    }
1672

1673
    auto &mipLayers = mipmaps.at(layout.mipMap);
×
1674

1675
    const uint32 layer =
1676
        layout.layer * std::max(int8(1), ctx.numFaces) + uint8(layout.face);
×
1677

1678
    if (mipLayers.empty() || mipLayers.at(layer)) {
×
1679
      // mipmap already filled
1680
      return;
×
1681
    }
1682

1683
    mipLayers.at(layer).flip();
×
1684

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

1687
    const size_t rDataSize =
1688
        ddsMips.sizes[layout.mipMap] *
×
1689
        std::max(ddsMips.numSlices[layout.mipMap], uint16(1));
×
1690

1691
    if (layout.face != CubemapFace::NONE) {
×
1692
      dds.caps01 +=
1693
          static_cast<DDS_HeaderEnd::Caps01Flags>(int(layout.face) + 9);
1694
      rData += ddsMips.frameStride * (int(layout.face) - 1);
×
1695
    }
1696

1697
    ptrdiff_t boundDiff = (&yasBuffer.back() + 1) - (rData + rDataSize);
×
1698
    if (boundDiff < 0) {
×
1699
      throw std::runtime_error("Writing image data beyond buffer's bounds");
×
1700
    }
1701

1702
    auto mctx = ctx;
×
1703

1704
    for (uint32 m = 0; m < layout.mipMap; m++) {
×
1705
      mctx.width = std::max(1, mctx.width / 2);
×
1706
      mctx.depth = std::max(1, mctx.depth / 2);
×
1707
      mctx.height = std::max(1, mctx.height / 2);
×
1708
    }
1709

1710
    mctx.height *= mctx.depth;
×
1711

1712
    if (mustDecode) {
×
NEW
1713
      Reencode(mctx, numChannels, static_cast<const char *>(data),
×
1714
               {rData, rDataSize});
1715
    } else if (ctx.baseFormat.tile == TexelTile::Linear &&
×
1716
               !ctx.baseFormat.swapPacked) {
×
1717
      memcpy(rData, data, rDataSize);
1718
    } else {
1719
      RetileData(static_cast<const char *>(data), mctx, rData);
×
1720
    }
1721

1722
    if (ShouldWrite()) {
×
1723
      outCtx->NewFile(std::string(pathOverride.ChangeExtension2("dds")));
×
1724
      outCtx->SendData(
×
1725
          {reinterpret_cast<const char *>(&dds), size_t(dds.DDS_SIZE)});
×
1726
      outCtx->SendData(yasBuffer);
×
1727

1728
      es::Dispose(yasBuffer);
×
1729
    }
1730
  }
1731

1732
  bool ShouldDoMipmaps() override {
358✔
1733
    return mainSettings.texelSettings.processMipMaps;
358✔
1734
  }
1735

1736
  void Finish() override {
×
1737
    if (!ShouldWrite()) {
×
1738
      throw std::runtime_error("Incomplete dds file");
×
1739
    }
1740
  }
1741
};
1742

1743
struct NewTexelContextDDSLegacy : NewTexelContextDDS {
1744
  std::vector<std::string> arrayMipmapBuffers;
1745
  uint8 numChannels;
1746

1747
  NewTexelContextDDSLegacy(NewTexelContextCreate ctx_)
28✔
1748
      : NewTexelContextDDS(ctx_, true),
28✔
1749
        numChannels(DesiredDDSLegacyChannels(ctx_.baseFormat.type)) {
56✔
1750
    arrayMipmapBuffers.resize(dds.arraySize);
28✔
1751
    dds.arraySize = 1;
28✔
1752
    TexelInputFormat baseFmt = ctx.baseFormat;
28✔
1753
    mustDecode |= MustSwizzle(ctx.baseFormat.swizzle, numChannels);
28✔
1754

1755
    if (mustDecode) {
28✔
1756
      switch (numChannels) {
1757
      case 1:
1758
        baseFmt.type = TexelInputFormatType::R8;
1759
        break;
1760
      case 2:
1761
        baseFmt.type = TexelInputFormatType::RG8;
1762
        break;
1763
      case 3:
1764
        baseFmt.type = TexelInputFormatType::RGB8;
1765
        break;
1766
      case 4:
1767
        baseFmt.type = TexelInputFormatType::RGBA8;
1768
        break;
1769
      default:
1770
        break;
1771
      }
1772
    }
1773

1774
    SetDDSLegacyFormat(dds, baseFmt.type);
28✔
1775
    dds.ComputeBPP();
28✔
1776
    dds.ComputeBufferSize(ddsMips);
28✔
1777

1778
    for (int32 i = 0; i < ctx.numFaces; i++) {
40✔
1779
      dds.caps01 -= static_cast<DDS_HeaderEnd::Caps01Flags>(i + 10);
12✔
1780
    }
1781
  }
28✔
1782

1783
  bool ShouldWrite(int32 layer) const {
329✔
1784
    if (ctx.arraySize > 1 && layer > -1) {
329✔
1785
      const int8 numFaces = std::max(int8(1), ctx.numFaces);
224✔
1786
      for (auto &m : mipmaps) {
928✔
1787
        for (int8 f = 0; f < numFaces; f++) {
1,568✔
1788
          if (!m.at(layer * numFaces + f)) {
864✔
1789
            return false;
1790
          }
1791
        }
1792
      }
1793
    } else {
1794
      for (auto &l : mipmaps) {
222✔
1795
        for (bool m : l) {
168✔
1796
          if (!m) {
720✔
1797
            return false;
1798
          }
1799
        }
1800
      }
1801
    }
1802

1803
    return true;
1804
  }
1805

1806
  void SendRasterData(const void *data, TexelInputLayout layout,
358✔
1807
                      TexelInputFormat *) override {
1808
    if (!ShouldDoMipmaps() && layout.mipMap > 0) {
358✔
1809
      return;
57✔
1810
    }
1811

1812
    auto &buffar = arrayMipmapBuffers.at(layout.layer);
358✔
1813
    auto &mipLayers = mipmaps.at(layout.mipMap);
358✔
1814

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

1818
    if (mipLayers.empty() || mipLayers.at(layer)) {
358✔
1819
      // mipmap already filled
1820
      return;
57✔
1821
    }
1822

1823
    mipLayers.at(layer).flip();
301✔
1824

1825
    if (buffar.empty()) {
301✔
1826
      buffar.resize(ddsMips.frameStride * std::max(int8(1), ctx.numFaces));
92✔
1827
    }
1828

1829
    const size_t rDataSize =
1830
        ddsMips.sizes[layout.mipMap] *
602✔
1831
        std::max(ddsMips.numSlices[layout.mipMap], uint16(1));
301✔
1832

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

1835
    if (layout.face != CubemapFace::NONE) {
301✔
1836
      dds.caps01 +=
1837
          static_cast<DDS_HeaderEnd::Caps01Flags>(int(layout.face) + 9);
48✔
1838
      rData += ddsMips.frameStride * (int(layout.face) - 1);
48✔
1839
    }
1840

1841
    ptrdiff_t boundDiff = (&buffar.back() + 1) - (rData + rDataSize);
301✔
1842
    if (boundDiff < 0) {
301✔
1843
      throw std::runtime_error("Writing image data beyond buffer's bounds");
×
1844
    }
1845

1846
    auto mctx = ctx;
301✔
1847

1848
    for (uint32 m = 0; m < layout.mipMap; m++) {
922✔
1849
      mctx.width = std::max(1, mctx.width / 2);
621✔
1850
      mctx.depth = std::max(1, mctx.depth / 2);
621✔
1851
      mctx.height = std::max(1, mctx.height / 2);
1,203✔
1852
    }
1853

1854
    mctx.height *= mctx.depth;
301✔
1855

1856
    if (mustDecode) {
301✔
1857
      Reencode(mctx, numChannels, static_cast<const char *>(data),
22✔
1858
               {rData, rDataSize});
1859
    } else if (ctx.baseFormat.tile == TexelTile::Linear &&
290✔
1860
               !ctx.baseFormat.swapPacked) {
285✔
1861
      memcpy(rData, data, rDataSize);
1862
    } else {
1863
      RetileData(static_cast<const char *>(data), mctx, rData);
10✔
1864
    }
1865

1866
    if (ShouldWrite(layout.layer)) {
301✔
1867
      std::string suffix;
1868

1869
      if (ctx.arraySize > 1) {
90✔
1870
        suffix.push_back('_');
64✔
1871
        suffix.append(std::to_string(layout.layer));
128✔
1872
      }
1873
      suffix.append(".dds");
90✔
1874

1875
      outCtx->NewFile(std::string(pathOverride.ChangeExtension(suffix)));
90✔
1876
      outCtx->SendData(
×
1877
          {reinterpret_cast<const char *>(&dds), size_t(dds.LEGACY_SIZE)});
90✔
1878
      outCtx->SendData(buffar);
90✔
1879

1880
      es::Dispose(buffar);
1881
    }
1882
  }
1883

1884
  void Finish() override {
28✔
1885
    if (!ShouldWrite(-1)) {
28✔
1886
      throw std::runtime_error("Incomplete dds file");
×
1887
    }
1888
  }
28✔
1889
};
1890

1891
struct PngIHDR {
×
1892
  uint32 id = CompileFourCC("IHDR");
1893
  uint32 width;
1894
  uint32 height;
1895
  uint8 bitDepth = 8;
1896
  PngColorType colorType = PngColorType::RGBA;
1897
  uint8 compressionMethod = 0;
1898
  uint8 filterMethod = 0;
1899
  uint8 interlaceMethod = 0;
1900
};
1901

1902
void FByteswapper(PngIHDR &item) {
×
1903
  FByteswapper(item.width);
×
1904
  FByteswapper(item.height);
×
1905
}
1906

1907
struct DeflateBlock {
1908
  int16 blockSize;
1909
  int16 blockSizeComplement;
1910

1911
  void BlockSize(int16 size) {
1912
    blockSize = size;
×
1913
    blockSizeComplement = ~size;
×
1914
  }
1915

1916
  void SwapEndian() {}
1917
};
1918

1919
struct Png {
×
1920
  uint64 id = 0x0A1A0A0D474E5089;
1921
  uint32 ihdrSize = 13;
1922
  PngIHDR ihdr;
1923
};
1924

1925
struct PngData {
1926
  uint32 ihdrCRC;
1927
  uint32 idatSize;
1928
  uint32 idat = CompileFourCC("IDAT");
1929
};
1930

1931
void FByteswapper(Png &item) {
×
1932
  FByteswapper(item.ihdrSize);
×
1933
  FByteswapper(item.ihdr);
1934
}
1935

1936
void FByteswapper(PngData &item) {
×
1937
  FByteswapper(item.ihdrCRC);
×
1938
  FByteswapper(item.idatSize);
×
1939
}
1940

1941
struct PngEnd {
1942
  uint32 idatCrc;
1943
  uint32 iendSize = 0;
1944
  uint32 iend = CompileFourCC("IEND");
1945
  uint32 iendCrc = crc32b(0, "IEND", 4);
1946
};
1947

1948
void FByteswapper(PngEnd &item) {
×
1949
  FByteswapper(item.idatCrc);
×
1950
  FByteswapper(item.iendSize);
×
1951
  FByteswapper(item.iendCrc);
×
1952
}
1953

1954
std::string MakeZlibStream(const char *buffer_, PngIHDR &hdr) {
×
1955
  std::stringstream str;
×
1956
  BinWritterRef_e wr(str);
1957
  wr.Write(uint16(0x178));
×
1958
  wr.SwapEndian(true);
1959
  const uint32 numChannels = GetPngChannels(hdr.colorType);
×
1960
  const uint32 pitch = hdr.width * numChannels;
×
1961
  const uint32 scanLine = pitch + 1;
×
1962
  uint32 adlerA = 1;
×
1963
  uint32 adlerB = 0;
×
1964

1965
  auto Adler = [&](uint8 c) {
1966
    adlerA = (c + adlerA) % 65521;
×
1967
    adlerB = (adlerA + adlerB) % 65521;
×
1968
  };
1969

1970
  auto AdlerL = [&](const char *data, size_t size) {
1971
    for (size_t i = 0; i < size; i++) {
×
1972
      Adler(data[i]);
×
1973
    }
1974
  };
1975

1976
  DeflateBlock block;
1977
  block.BlockSize(scanLine);
×
1978

1979
  if (numChannels < 3) {
×
1980
    for (uint32 h = 0; h < hdr.height; h++) {
×
1981
      str.put(h == hdr.height - 1);
×
1982
      wr.Write(block);
×
1983
      str.put(0); // filter type
×
1984
      Adler(0);
1985
      AdlerL(buffer_ + pitch * h, pitch);
×
1986
      str.write(buffer_ + pitch * h, pitch);
×
1987
    }
1988
  } else if (numChannels == 3) {
×
1989
    for (uint32 h = 0; h < hdr.height; h++) {
×
1990
      str.put(h == hdr.height - 1);
×
1991
      wr.Write(block);
×
1992
      str.put(0); // filter type
×
1993
      Adler(0);
1994
      const char *startLine = buffer_ + pitch * h;
×
1995

1996
      for (uint32 w = 0; w < hdr.width; w++) {
×
1997
        char rgb[3];
1998
        memcpy(rgb, startLine + w * 3, 3);
×
1999
        std::swap(rgb[0], rgb[2]);
2000
        AdlerL(rgb, 3);
2001
        str.write(rgb, 3);
×
2002
      }
2003
    }
2004
  } else {
2005
    for (uint32 h = 0; h < hdr.height; h++) {
×
2006
      str.put(h == hdr.height - 1);
×
2007
      wr.Write(block);
×
2008
      str.put(0); // filter type
×
2009
      Adler(0);
2010
      const char *startLine = buffer_ + pitch * h;
×
2011

2012
      for (uint32 w = 0; w < hdr.width; w++) {
×
2013
        char rgb[4];
2014
        memcpy(rgb, startLine + w * 4, 4);
×
2015
        std::swap(rgb[0], rgb[2]);
2016
        AdlerL(rgb, 4);
2017
        str.write(rgb, 4);
×
2018
      }
2019
    }
2020
  }
2021

2022
  adlerA |= adlerB << 16;
×
2023
  wr.Write(adlerA);
×
2024
  return std::move(str).str();
×
2025
}
2026

2027
struct NewTexelContextPNG : NewTexelContextImpl {
2028
  std::string yasBuffer;
2029
  Png hdr;
2030
  uint32 numChannels;
2031

2032
  NewTexelContextPNG(NewTexelContextCreate ctx_) : NewTexelContextImpl(ctx_) {
×
2033
    hdr.ihdr.width = ctx.width;
×
2034
    hdr.ihdr.height = ctx.height * std::max(uint16(1), ctx.depth);
×
2035
    hdr.ihdr.colorType = DesiredPngColorType(
×
2036
        DesiredPngColorType(ctx.baseFormat.type), ctx.baseFormat.swizzle);
×
2037
    numChannels = GetPngChannels(hdr.ihdr.colorType);
×
2038
  }
2039

2040
  void InitBuffer() {
×
2041
    if (BlockCompression(ctx.baseFormat.type)) {
×
2042
      uint32 widthPadding = hdr.ihdr.width % 4;
×
2043
      widthPadding = widthPadding ? 4 - widthPadding : 0;
×
2044
      uint32 heightPadding = hdr.ihdr.height % 4;
×
2045
      heightPadding = heightPadding ? 4 - heightPadding : 0;
×
2046

2047
      const uint32 rasterDataSize = (hdr.ihdr.width + widthPadding) *
×
2048
                                    (hdr.ihdr.height + heightPadding) *
×
2049
                                    numChannels;
×
2050
      yasBuffer.resize(rasterDataSize);
×
2051
      return;
×
2052
    }
2053

2054
    const uint32 rasterDataSize =
×
2055
        hdr.ihdr.width * hdr.ihdr.height * numChannels;
×
2056
    yasBuffer.resize(rasterDataSize);
×
2057
  }
2058

2059
  void SendRasterData(const void *data, TexelInputLayout layout,
×
2060
                      TexelInputFormat *) override {
2061
    if (layout.mipMap > 0) {
×
2062
      return;
×
2063
    }
2064

2065
    auto mctx = ctx;
×
2066
    mctx.height *= std::max(mctx.depth, uint16(1));
×
2067

2068
    auto Write = [&](const void *buffer) {
×
2069
      std::string suffix;
2070

2071
      if (ctx.arraySize > 1) {
×
2072
        suffix.push_back('_');
×
2073
        suffix.append(std::to_string(layout.layer));
×
2074
      }
2075

2076
      if (layout.face != CubemapFace::NONE) {
×
2077
        suffix.push_back('_');
×
2078
        static const ReflectedEnum *refl = GetReflectedEnum<CubemapFace>();
2079
        suffix.append(refl->names[uint32(layout.face)]);
×
2080
      }
2081

2082
      suffix.append(".png");
×
2083

2084
      std::string encodedData =
2085
          MakeZlibStream(static_cast<const char *>(buffer), hdr.ihdr);
×
2086

2087
      Png hdrCopy = hdr;
×
2088
      FByteswapper(hdrCopy);
×
2089
      PngData hdrData{
×
2090
          .ihdrCRC = crc32b(0, reinterpret_cast<const char *>(&hdrCopy.ihdr),
×
2091
                            sizeof(hdrCopy.ihdr) - 3),
2092
          .idatSize = uint32(encodedData.size()),
2093

2094
      };
2095
      FByteswapper(hdrData);
2096

2097
      PngEnd tail{
×
2098
          .idatCrc = crc32b(crc32b(0, "IDAT", 4), encodedData.data(),
×
2099
                            encodedData.size()),
2100
      };
2101

2102
      FByteswapper(tail);
×
2103

2104
      outCtx->NewFile(std::string(pathOverride.ChangeExtension(suffix)));
×
2105
      outCtx->SendData(
×
2106
          {reinterpret_cast<const char *>(&hdrCopy), sizeof(hdrCopy) - 3});
2107
      outCtx->SendData(
×
2108
          {reinterpret_cast<const char *>(&hdrData), sizeof(hdrData)});
2109
      outCtx->SendData(encodedData);
×
2110
      outCtx->SendData({reinterpret_cast<const char *>(&tail), sizeof(tail)});
×
2111
    };
2112

2113
    bool mustDecode =
2114
        IsFormatSupported(ctx.formatOverride, ctx.baseFormat.type) ||
×
2115
        MustSwap(ctx.baseFormat.type, ctx.baseFormat.swapPacked) ||
×
2116
        ctx.baseFormat.tile != TexelTile::Linear ||
×
2117
        MustSwizzle(ctx.baseFormat.swizzle, numChannels);
×
2118

2119
    if (mustDecode) {
2120
      InitBuffer();
×
2121
      Reencode(mctx, numChannels, static_cast<const char *>(data), yasBuffer);
×
2122
      Write(yasBuffer.data());
×
2123
    } else {
2124
      Write(data);
×
2125
    }
2126
  }
2127

2128
  bool ShouldDoMipmaps() override { return false; }
×
2129

2130
  void Finish() override {}
×
2131
};
2132

2133
std::unique_ptr<NewTexelContextImpl>
2134
CreateTexelContext(NewTexelContextCreate ctx) {
56✔
2135
  if (ctx.formatOverride == TexelContextFormat::Config) {
56✔
2136
    ctx.formatOverride = OutputFormat();
56✔
2137
  }
2138

2139
  switch (ctx.formatOverride) {
56✔
2140
  case TexelContextFormat::DDS:
×
2141
    return std::make_unique<NewTexelContextDDS>(ctx);
×
2142
  case TexelContextFormat::DDS_Legacy:
28✔
2143
    return std::make_unique<NewTexelContextDDSLegacy>(ctx);
28✔
2144
  case TexelContextFormat::QOI_BMP:
×
2145
    return std::make_unique<NewTexelContextQOIBMP>(ctx);
×
2146
  case TexelContextFormat::QOI:
28✔
2147
    return std::make_unique<NewTexelContextQOI>(ctx);
28✔
2148
  case TexelContextFormat::UPNG:
×
2149
    return std::make_unique<NewTexelContextPNG>(ctx);
×
2150
  default:
×
2151
    throw std::logic_error("Image format not supported");
×
2152
  }
2153
}
2154

2155
void NewTexelContextImpl::ProcessContextData() {
×
2156
  auto layout = ComputeTraditionalDataLayout();
×
2157

2158
  for (uint32 a = 0; a < ctx.arraySize; a++) {
×
2159
    const char *entryBegin =
×
2160
        static_cast<const char *>(ctx.data) + a * layout.groupSize;
2161

2162
    if (ctx.numFaces > 0) {
×
2163
      for (int8 f = 0; f < ctx.numFaces; f++) {
×
2164
        const char *faceBegin = entryBegin + f * layout.mipGroupSize;
×
2165

2166
        for (uint32 m = 0; m < ctx.numMipmaps; m++) {
×
2167
          SendRasterData(faceBegin + layout.mipOffsets[m],
×
2168
                         {
2169
                             .mipMap = uint8(m),
2170
                             .face = static_cast<CubemapFace>(f + 1),
×
2171
                             .layer = uint16(a),
2172
                         });
2173
        }
2174
      }
2175
    } else {
2176
      for (uint32 m = 0; m < ctx.numMipmaps; m++) {
×
2177
        SendRasterData(entryBegin + layout.mipOffsets[m],
×
2178
                       {
2179
                           .mipMap = uint8(m),
2180
                           .layer = uint16(a),
2181
                       });
2182
      }
2183
    }
2184
  }
2185
}
2186

2187
std::unique_ptr<NewTexelContextImpl>
2188
CreateTexelContext(NewTexelContextCreate ctx, AppContext *actx) {
56✔
2189
  auto retVal = CreateTexelContext(ctx);
56✔
2190
  if (ctx.texelOutput) {
56✔
2191
    retVal->outCtx = ctx.texelOutput;
×
2192
  } else {
2193
    TexelOutputContext otx;
2194
    otx.ctx = actx;
2195
    retVal->outVariant = otx;
2196
    retVal->outCtx = &std::get<TexelOutputContext>(retVal->outVariant);
56✔
2197
  }
2198

2199
  retVal->pathOverride = actx->workingFile;
2200
  if (ctx.data) {
56✔
2201
    retVal->ProcessContextData();
×
2202
    return {};
2203
  }
2204

2205
  return retVal;
2206
}
2207

2208
std::unique_ptr<NewTexelContextImpl>
2209
CreateTexelContext(NewTexelContextCreate ctx, AppExtractContext *ectx,
×
2210
                   const std::string &path) {
2211
  auto retVal = CreateTexelContext(ctx);
×
2212
  if (ctx.texelOutput) {
×
2213
    retVal->outCtx = ctx.texelOutput;
×
2214
  } else {
2215
    TexelOutputExtractContext otx;
2216
    otx.ctx = ectx;
2217
    retVal->outVariant = otx;
2218
    retVal->outCtx = &std::get<TexelOutputExtractContext>(retVal->outVariant);
×
2219
  }
2220

2221
  retVal->pathOverride.Load(path);
×
2222

2223
  if (ctx.data) {
×
2224
    retVal->ProcessContextData();
×
2225
    return {};
2226
  }
2227
  return retVal;
2228
}
2229

2230
void TexelOutputContext::SendData(std::string_view data) {
280✔
2231
  str->write(data.data(), data.size());
280✔
2232
}
280✔
2233
void TexelOutputContext::NewFile(std::string filePath) {
190✔
2234
  str = &ctx->NewFile(filePath).str;
190✔
2235
}
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

© 2026 Coveralls, Inc