ETC2Decoder.cs
1 using Ryujinx.Common; 2 using Ryujinx.Common.Memory; 3 using System; 4 using System.Buffers.Binary; 5 using System.Runtime.InteropServices; 6 7 namespace Ryujinx.Graphics.Texture 8 { 9 public static class ETC2Decoder 10 { 11 private const uint AlphaMask = 0xff000000u; 12 13 private const int BlockWidth = 4; 14 private const int BlockHeight = 4; 15 16 private static readonly int[][] _etc1Lut = 17 { 18 new int[] { 2, 8, -2, -8 }, 19 new int[] { 5, 17, -5, -17 }, 20 new int[] { 9, 29, -9, -29 }, 21 new int[] { 13, 42, -13, -42 }, 22 new int[] { 18, 60, -18, -60 }, 23 new int[] { 24, 80, -24, -80 }, 24 new int[] { 33, 106, -33, -106 }, 25 new int[] { 47, 183, -47, -183 }, 26 }; 27 28 private static readonly int[] _etc2Lut = 29 { 30 3, 6, 11, 16, 23, 32, 41, 64, 31 }; 32 33 private static readonly int[][] _etc2AlphaLut = 34 { 35 new int[] { -3, -6, -9, -15, 2, 5, 8, 14 }, 36 new int[] { -3, -7, -10, -13, 2, 6, 9, 12 }, 37 new int[] { -2, -5, -8, -13, 1, 4, 7, 12 }, 38 new int[] { -2, -4, -6, -13, 1, 3, 5, 12 }, 39 new int[] { -3, -6, -8, -12, 2, 5, 7, 11 }, 40 new int[] { -3, -7, -9, -11, 2, 6, 8, 10 }, 41 new int[] { -4, -7, -8, -11, 3, 6, 7, 10 }, 42 new int[] { -3, -5, -8, -11, 2, 4, 7, 10 }, 43 new int[] { -2, -6, -8, -10, 1, 5, 7, 9 }, 44 new int[] { -2, -5, -8, -10, 1, 4, 7, 9 }, 45 new int[] { -2, -4, -8, -10, 1, 3, 7, 9 }, 46 new int[] { -2, -5, -7, -10, 1, 4, 6, 9 }, 47 new int[] { -3, -4, -7, -10, 2, 3, 6, 9 }, 48 new int[] { -1, -2, -3, -10, 0, 1, 2, 9 }, 49 new int[] { -4, -6, -8, -9, 3, 5, 7, 8 }, 50 new int[] { -3, -5, -7, -9, 2, 4, 6, 8 }, 51 }; 52 53 public static MemoryOwner<byte> DecodeRgb(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) 54 { 55 ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data); 56 57 int inputOffset = 0; 58 59 MemoryOwner<byte> output = MemoryOwner<byte>.Rent(CalculateOutputSize(width, height, depth, levels, layers)); 60 61 Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Span); 62 Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight]; 63 64 int imageBaseOOffs = 0; 65 66 for (int l = 0; l < levels; l++) 67 { 68 int wInBlocks = BitUtils.DivRoundUp(width, BlockWidth); 69 int hInBlocks = BitUtils.DivRoundUp(height, BlockHeight); 70 71 for (int l2 = 0; l2 < layers; l2++) 72 { 73 for (int z = 0; z < depth; z++) 74 { 75 for (int y = 0; y < hInBlocks; y++) 76 { 77 int ty = y * BlockHeight; 78 int bh = Math.Min(BlockHeight, height - ty); 79 80 for (int x = 0; x < wInBlocks; x++) 81 { 82 int tx = x * BlockWidth; 83 int bw = Math.Min(BlockWidth, width - tx); 84 85 ulong colorBlock = dataUlong[inputOffset++]; 86 87 DecodeBlock(tile, colorBlock); 88 89 for (int py = 0; py < bh; py++) 90 { 91 int oOffsBase = imageBaseOOffs + ((ty + py) * width) + tx; 92 93 for (int px = 0; px < bw; px++) 94 { 95 int oOffs = oOffsBase + px; 96 97 outputUint[oOffs] = tile[py * BlockWidth + px] | AlphaMask; 98 } 99 } 100 } 101 } 102 103 imageBaseOOffs += width * height; 104 } 105 } 106 107 width = Math.Max(1, width >> 1); 108 height = Math.Max(1, height >> 1); 109 depth = Math.Max(1, depth >> 1); 110 } 111 112 return output; 113 } 114 115 public static MemoryOwner<byte> DecodePta(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) 116 { 117 ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data); 118 119 int inputOffset = 0; 120 121 MemoryOwner<byte> output = MemoryOwner<byte>.Rent(CalculateOutputSize(width, height, depth, levels, layers)); 122 123 Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Span); 124 Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight]; 125 126 int imageBaseOOffs = 0; 127 128 for (int l = 0; l < levels; l++) 129 { 130 int wInBlocks = BitUtils.DivRoundUp(width, BlockWidth); 131 int hInBlocks = BitUtils.DivRoundUp(height, BlockHeight); 132 133 for (int l2 = 0; l2 < layers; l2++) 134 { 135 for (int z = 0; z < depth; z++) 136 { 137 for (int y = 0; y < hInBlocks; y++) 138 { 139 int ty = y * BlockHeight; 140 int bh = Math.Min(BlockHeight, height - ty); 141 142 for (int x = 0; x < wInBlocks; x++) 143 { 144 int tx = x * BlockWidth; 145 int bw = Math.Min(BlockWidth, width - tx); 146 147 ulong colorBlock = dataUlong[inputOffset++]; 148 149 DecodeBlockPta(tile, colorBlock); 150 151 for (int py = 0; py < bh; py++) 152 { 153 int oOffsBase = imageBaseOOffs + ((ty + py) * width) + tx; 154 155 tile.Slice(py * BlockWidth, bw).CopyTo(outputUint.Slice(oOffsBase, bw)); 156 } 157 } 158 } 159 160 imageBaseOOffs += width * height; 161 } 162 } 163 164 width = Math.Max(1, width >> 1); 165 height = Math.Max(1, height >> 1); 166 depth = Math.Max(1, depth >> 1); 167 } 168 169 return output; 170 } 171 172 public static MemoryOwner<byte> DecodeRgba(ReadOnlySpan<byte> data, int width, int height, int depth, int levels, int layers) 173 { 174 ReadOnlySpan<ulong> dataUlong = MemoryMarshal.Cast<byte, ulong>(data); 175 176 int inputOffset = 0; 177 178 MemoryOwner<byte> output = MemoryOwner<byte>.Rent(CalculateOutputSize(width, height, depth, levels, layers)); 179 180 Span<uint> outputUint = MemoryMarshal.Cast<byte, uint>(output.Span); 181 Span<uint> tile = stackalloc uint[BlockWidth * BlockHeight]; 182 183 int imageBaseOOffs = 0; 184 185 for (int l = 0; l < levels; l++) 186 { 187 int wInBlocks = BitUtils.DivRoundUp(width, BlockWidth); 188 int hInBlocks = BitUtils.DivRoundUp(height, BlockHeight); 189 190 for (int l2 = 0; l2 < layers; l2++) 191 { 192 for (int z = 0; z < depth; z++) 193 { 194 for (int y = 0; y < hInBlocks; y++) 195 { 196 int ty = y * BlockHeight; 197 int bh = Math.Min(BlockHeight, height - ty); 198 199 for (int x = 0; x < wInBlocks; x++) 200 { 201 int tx = x * BlockWidth; 202 int bw = Math.Min(BlockWidth, width - tx); 203 204 ulong alphaBlock = dataUlong[inputOffset]; 205 ulong colorBlock = dataUlong[inputOffset + 1]; 206 207 inputOffset += 2; 208 209 DecodeBlock(tile, colorBlock); 210 211 byte alphaBase = (byte)alphaBlock; 212 int[] alphaTable = _etc2AlphaLut[(alphaBlock >> 8) & 0xf]; 213 int alphaMultiplier = (int)(alphaBlock >> 12) & 0xf; 214 ulong alphaIndices = BinaryPrimitives.ReverseEndianness(alphaBlock); 215 216 if (alphaMultiplier != 0) 217 { 218 for (int py = 0; py < bh; py++) 219 { 220 int oOffsBase = imageBaseOOffs + ((ty + py) * width) + tx; 221 222 for (int px = 0; px < bw; px++) 223 { 224 int oOffs = oOffsBase + px; 225 int alphaIndex = (int)((alphaIndices >> (((px * BlockHeight + py) ^ 0xf) * 3)) & 7); 226 227 byte a = Saturate(alphaBase + alphaTable[alphaIndex] * alphaMultiplier); 228 229 outputUint[oOffs] = tile[py * BlockWidth + px] | ((uint)a << 24); 230 } 231 } 232 } 233 else 234 { 235 uint a = (uint)alphaBase << 24; 236 237 for (int py = 0; py < bh; py++) 238 { 239 int oOffsBase = imageBaseOOffs + ((ty + py) * width) + tx; 240 241 for (int px = 0; px < bw; px++) 242 { 243 int oOffs = oOffsBase + px; 244 245 outputUint[oOffs] = tile[py * BlockWidth + px] | a; 246 } 247 } 248 } 249 } 250 } 251 252 imageBaseOOffs += width * height; 253 } 254 } 255 256 width = Math.Max(1, width >> 1); 257 height = Math.Max(1, height >> 1); 258 depth = Math.Max(1, depth >> 1); 259 } 260 261 return output; 262 } 263 264 private static void DecodeBlock(Span<uint> tile, ulong block) 265 { 266 uint blockLow = (uint)(block >> 0); 267 uint blockHigh = (uint)(block >> 32); 268 269 uint r1, g1, b1; 270 uint r2, g2, b2; 271 272 bool differentialMode = (blockLow & 0x2000000) != 0; 273 274 if (differentialMode) 275 { 276 (r1, g1, b1, r2, g2, b2) = UnpackRgb555DiffEndPoints(blockLow); 277 278 if (r2 > 31) 279 { 280 DecodeBlock59T(tile, blockLow, blockHigh); 281 } 282 else if (g2 > 31) 283 { 284 DecodeBlock58H(tile, blockLow, blockHigh); 285 } 286 else if (b2 > 31) 287 { 288 DecodeBlock57P(tile, block); 289 } 290 else 291 { 292 r1 |= r1 >> 5; 293 g1 |= g1 >> 5; 294 b1 |= b1 >> 5; 295 296 r2 = (r2 << 3) | (r2 >> 2); 297 g2 = (g2 << 3) | (g2 >> 2); 298 b2 = (b2 << 3) | (b2 >> 2); 299 300 DecodeBlockETC1(tile, blockLow, blockHigh, r1, g1, b1, r2, g2, b2); 301 } 302 } 303 else 304 { 305 r1 = (blockLow & 0x0000f0) >> 0; 306 g1 = (blockLow & 0x00f000) >> 8; 307 b1 = (blockLow & 0xf00000) >> 16; 308 309 r2 = (blockLow & 0x00000f) << 4; 310 g2 = (blockLow & 0x000f00) >> 4; 311 b2 = (blockLow & 0x0f0000) >> 12; 312 313 r1 |= r1 >> 4; 314 g1 |= g1 >> 4; 315 b1 |= b1 >> 4; 316 317 r2 |= r2 >> 4; 318 g2 |= g2 >> 4; 319 b2 |= b2 >> 4; 320 321 DecodeBlockETC1(tile, blockLow, blockHigh, r1, g1, b1, r2, g2, b2); 322 } 323 } 324 325 private static void DecodeBlockPta(Span<uint> tile, ulong block) 326 { 327 uint blockLow = (uint)(block >> 0); 328 uint blockHigh = (uint)(block >> 32); 329 330 (uint r1, uint g1, uint b1, uint r2, uint g2, uint b2) = UnpackRgb555DiffEndPoints(blockLow); 331 332 bool fullyOpaque = (blockLow & 0x2000000) != 0; 333 334 if (fullyOpaque) 335 { 336 if (r2 > 31) 337 { 338 DecodeBlock59T(tile, blockLow, blockHigh); 339 } 340 else if (g2 > 31) 341 { 342 DecodeBlock58H(tile, blockLow, blockHigh); 343 } 344 else if (b2 > 31) 345 { 346 DecodeBlock57P(tile, block); 347 } 348 else 349 { 350 r1 |= r1 >> 5; 351 g1 |= g1 >> 5; 352 b1 |= b1 >> 5; 353 354 r2 = (r2 << 3) | (r2 >> 2); 355 g2 = (g2 << 3) | (g2 >> 2); 356 b2 = (b2 << 3) | (b2 >> 2); 357 358 DecodeBlockETC1(tile, blockLow, blockHigh, r1, g1, b1, r2, g2, b2); 359 } 360 361 for (int i = 0; i < tile.Length; i++) 362 { 363 tile[i] |= AlphaMask; 364 } 365 } 366 else 367 { 368 if (r2 > 31) 369 { 370 DecodeBlock59T(tile, blockLow, blockHigh, AlphaMask); 371 } 372 else if (g2 > 31) 373 { 374 DecodeBlock58H(tile, blockLow, blockHigh, AlphaMask); 375 } 376 else if (b2 > 31) 377 { 378 DecodeBlock57P(tile, block); 379 380 for (int i = 0; i < tile.Length; i++) 381 { 382 tile[i] |= AlphaMask; 383 } 384 } 385 else 386 { 387 r1 |= r1 >> 5; 388 g1 |= g1 >> 5; 389 b1 |= b1 >> 5; 390 391 r2 = (r2 << 3) | (r2 >> 2); 392 g2 = (g2 << 3) | (g2 >> 2); 393 b2 = (b2 << 3) | (b2 >> 2); 394 395 DecodeBlockETC1(tile, blockLow, blockHigh, r1, g1, b1, r2, g2, b2, AlphaMask); 396 } 397 } 398 } 399 400 private static (uint, uint, uint, uint, uint, uint) UnpackRgb555DiffEndPoints(uint blockLow) 401 { 402 uint r1 = (blockLow & 0x0000f8) >> 0; 403 uint g1 = (blockLow & 0x00f800) >> 8; 404 uint b1 = (blockLow & 0xf80000) >> 16; 405 406 uint r2 = (uint)((sbyte)(r1 >> 3) + ((sbyte)((blockLow & 0x000007) << 5) >> 5)); 407 uint g2 = (uint)((sbyte)(g1 >> 3) + ((sbyte)((blockLow & 0x000700) >> 3) >> 5)); 408 uint b2 = (uint)((sbyte)(b1 >> 3) + ((sbyte)((blockLow & 0x070000) >> 11) >> 5)); 409 410 return (r1, g1, b1, r2, g2, b2); 411 } 412 413 private static void DecodeBlock59T(Span<uint> tile, uint blockLow, uint blockHigh, uint alphaMask = 0) 414 { 415 uint r1 = (blockLow & 3) | ((blockLow >> 1) & 0xc); 416 uint g1 = (blockLow >> 12) & 0xf; 417 uint b1 = (blockLow >> 8) & 0xf; 418 419 uint r2 = (blockLow >> 20) & 0xf; 420 uint g2 = (blockLow >> 16) & 0xf; 421 uint b2 = (blockLow >> 28) & 0xf; 422 423 r1 |= r1 << 4; 424 g1 |= g1 << 4; 425 b1 |= b1 << 4; 426 427 r2 |= r2 << 4; 428 g2 |= g2 << 4; 429 b2 |= b2 << 4; 430 431 int dist = _etc2Lut[((blockLow >> 24) & 1) | ((blockLow >> 25) & 6)]; 432 433 Span<uint> palette = stackalloc uint[4]; 434 435 palette[0] = Pack(r1, g1, b1); 436 palette[1] = Pack(r2, g2, b2, dist); 437 palette[2] = Pack(r2, g2, b2); 438 palette[3] = Pack(r2, g2, b2, -dist); 439 440 blockHigh = BinaryPrimitives.ReverseEndianness(blockHigh); 441 442 for (int y = 0; y < BlockHeight; y++) 443 { 444 for (int x = 0; x < BlockWidth; x++) 445 { 446 int offset = (y * 4) + x; 447 int index = (x * 4) + y; 448 449 int paletteIndex = (int)((blockHigh >> index) & 1) | (int)((blockHigh >> (index + 15)) & 2); 450 451 tile[offset] = palette[paletteIndex]; 452 453 if (alphaMask != 0) 454 { 455 if (paletteIndex == 2) 456 { 457 tile[offset] = 0; 458 } 459 else 460 { 461 tile[offset] |= alphaMask; 462 } 463 } 464 } 465 } 466 } 467 468 private static void DecodeBlock58H(Span<uint> tile, uint blockLow, uint blockHigh, uint alphaMask = 0) 469 { 470 uint r1 = (blockLow >> 3) & 0xf; 471 uint g1 = ((blockLow << 1) & 0xe) | ((blockLow >> 12) & 1); 472 uint b1 = ((blockLow >> 23) & 1) | ((blockLow >> 7) & 6) | ((blockLow >> 8) & 8); 473 474 uint r2 = (blockLow >> 19) & 0xf; 475 uint g2 = ((blockLow >> 31) & 1) | ((blockLow >> 15) & 0xe); 476 uint b2 = (blockLow >> 27) & 0xf; 477 478 uint rgb1 = Pack4Be(r1, g1, b1); 479 uint rgb2 = Pack4Be(r2, g2, b2); 480 481 r1 |= r1 << 4; 482 g1 |= g1 << 4; 483 b1 |= b1 << 4; 484 485 r2 |= r2 << 4; 486 g2 |= g2 << 4; 487 b2 |= b2 << 4; 488 489 int dist = _etc2Lut[(rgb1 >= rgb2 ? 1u : 0u) | ((blockLow >> 23) & 2) | ((blockLow >> 24) & 4)]; 490 491 Span<uint> palette = stackalloc uint[4]; 492 493 palette[0] = Pack(r1, g1, b1, dist); 494 palette[1] = Pack(r1, g1, b1, -dist); 495 palette[2] = Pack(r2, g2, b2, dist); 496 palette[3] = Pack(r2, g2, b2, -dist); 497 498 blockHigh = BinaryPrimitives.ReverseEndianness(blockHigh); 499 500 for (int y = 0; y < BlockHeight; y++) 501 { 502 for (int x = 0; x < BlockWidth; x++) 503 { 504 int offset = (y * 4) + x; 505 int index = (x * 4) + y; 506 507 int paletteIndex = (int)((blockHigh >> index) & 1) | (int)((blockHigh >> (index + 15)) & 2); 508 509 tile[offset] = palette[paletteIndex]; 510 511 if (alphaMask != 0) 512 { 513 if (paletteIndex == 2) 514 { 515 tile[offset] = 0; 516 } 517 else 518 { 519 tile[offset] |= alphaMask; 520 } 521 } 522 } 523 } 524 } 525 526 private static void DecodeBlock57P(Span<uint> tile, ulong block) 527 { 528 int r0 = (int)((block >> 1) & 0x3f); 529 int g0 = (int)(((block >> 9) & 0x3f) | ((block & 1) << 6)); 530 int b0 = (int)(((block >> 31) & 1) | ((block >> 15) & 6) | ((block >> 16) & 0x18) | ((block >> 3) & 0x20)); 531 532 int rh = (int)(((block >> 24) & 1) | ((block >> 25) & 0x3e)); 533 int gh = (int)((block >> 33) & 0x7f); 534 int bh = (int)(((block >> 43) & 0x1f) | ((block >> 27) & 0x20)); 535 536 int rv = (int)(((block >> 53) & 7) | ((block >> 37) & 0x38)); 537 int gv = (int)(((block >> 62) & 3) | ((block >> 46) & 0x7c)); 538 int bv = (int)((block >> 56) & 0x3f); 539 540 r0 = (r0 << 2) | (r0 >> 4); 541 g0 = (g0 << 1) | (g0 >> 6); 542 b0 = (b0 << 2) | (b0 >> 4); 543 544 rh = (rh << 2) | (rh >> 4); 545 gh = (gh << 1) | (gh >> 6); 546 bh = (bh << 2) | (bh >> 4); 547 548 rv = (rv << 2) | (rv >> 4); 549 gv = (gv << 1) | (gv >> 6); 550 bv = (bv << 2) | (bv >> 4); 551 552 for (int y = 0; y < BlockHeight; y++) 553 { 554 for (int x = 0; x < BlockWidth; x++) 555 { 556 int offset = y * BlockWidth + x; 557 558 byte r = Saturate(((x * (rh - r0)) + (y * (rv - r0)) + (r0 * 4) + 2) >> 2); 559 byte g = Saturate(((x * (gh - g0)) + (y * (gv - g0)) + (g0 * 4) + 2) >> 2); 560 byte b = Saturate(((x * (bh - b0)) + (y * (bv - b0)) + (b0 * 4) + 2) >> 2); 561 562 tile[offset] = Pack(r, g, b); 563 } 564 } 565 } 566 567 private static void DecodeBlockETC1( 568 Span<uint> tile, 569 uint blockLow, 570 uint blockHigh, 571 uint r1, 572 uint g1, 573 uint b1, 574 uint r2, 575 uint g2, 576 uint b2, 577 uint alphaMask = 0) 578 { 579 int[] table1 = _etc1Lut[(blockLow >> 29) & 7]; 580 int[] table2 = _etc1Lut[(blockLow >> 26) & 7]; 581 582 bool flip = (blockLow & 0x1000000) != 0; 583 584 if (!flip) 585 { 586 for (int y = 0; y < BlockHeight; y++) 587 { 588 for (int x = 0; x < BlockWidth / 2; x++) 589 { 590 uint color1 = CalculatePixel(r1, g1, b1, x + 0, y, blockHigh, table1, alphaMask); 591 uint color2 = CalculatePixel(r2, g2, b2, x + 2, y, blockHigh, table2, alphaMask); 592 593 int offset1 = y * BlockWidth + x; 594 int offset2 = y * BlockWidth + x + 2; 595 596 tile[offset1] = color1; 597 tile[offset2] = color2; 598 } 599 } 600 } 601 else 602 { 603 for (int y = 0; y < BlockHeight / 2; y++) 604 { 605 for (int x = 0; x < BlockWidth; x++) 606 { 607 uint color1 = CalculatePixel(r1, g1, b1, x, y + 0, blockHigh, table1, alphaMask); 608 uint color2 = CalculatePixel(r2, g2, b2, x, y + 2, blockHigh, table2, alphaMask); 609 610 int offset1 = (y * BlockWidth) + x; 611 int offset2 = ((y + 2) * BlockWidth) + x; 612 613 tile[offset1] = color1; 614 tile[offset2] = color2; 615 } 616 } 617 } 618 } 619 620 private static uint CalculatePixel(uint r, uint g, uint b, int x, int y, uint block, int[] table, uint alphaMask) 621 { 622 int index = x * BlockHeight + y; 623 uint msb = block << 1; 624 uint tableIndex = index < 8 625 ? ((block >> (index + 24)) & 1) + ((msb >> (index + 8)) & 2) 626 : ((block >> (index + 8)) & 1) + ((msb >> (index - 8)) & 2); 627 628 if (alphaMask != 0) 629 { 630 if (tableIndex == 0) 631 { 632 return Pack(r, g, b) | alphaMask; 633 } 634 else if (tableIndex == 2) 635 { 636 return 0; 637 } 638 else 639 { 640 return Pack(r, g, b, table[tableIndex]) | alphaMask; 641 } 642 } 643 644 return Pack(r, g, b, table[tableIndex]); 645 } 646 647 private static uint Pack(uint r, uint g, uint b, int offset) 648 { 649 r = Saturate((int)(r + offset)); 650 g = Saturate((int)(g + offset)); 651 b = Saturate((int)(b + offset)); 652 653 return Pack(r, g, b); 654 } 655 656 private static uint Pack(uint r, uint g, uint b) 657 { 658 return r | (g << 8) | (b << 16); 659 } 660 661 private static uint Pack4Be(uint r, uint g, uint b) 662 { 663 return (r << 8) | (g << 4) | b; 664 } 665 666 private static byte Saturate(int value) 667 { 668 return value > byte.MaxValue ? byte.MaxValue : value < byte.MinValue ? byte.MinValue : (byte)value; 669 } 670 671 private static int CalculateOutputSize(int width, int height, int depth, int levels, int layers) 672 { 673 int size = 0; 674 675 for (int l = 0; l < levels; l++) 676 { 677 size += Math.Max(1, width >> l) * Math.Max(1, height >> l) * Math.Max(1, depth >> l) * layers * 4; 678 } 679 680 return size; 681 } 682 } 683 }