Skip to content

Commit 5f07655

Browse files
committed
Low dynamic range ASTC decoding
1 parent 4fffeda commit 5f07655

115 files changed

Lines changed: 7153 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
*.dds filter=lfs diff=lfs merge=lfs -text
127127
*.ktx filter=lfs diff=lfs merge=lfs -text
128128
*.ktx2 filter=lfs diff=lfs merge=lfs -text
129+
*.astc filter=lfs diff=lfs merge=lfs -text
129130
*.pam filter=lfs diff=lfs merge=lfs -text
130131
*.pbm filter=lfs diff=lfs merge=lfs -text
131132
*.pgm filter=lfs diff=lfs merge=lfs -text

src/ImageSharp.Textures/Compression/Astc/AstcDecoder.cs

Lines changed: 399 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
namespace SixLabors.ImageSharp.Textures.Compression.Astc.BiseEncoding;
5+
6+
/// <summary>
7+
/// The encoding modes supported by BISE.
8+
/// </summary>
9+
/// <remarks>
10+
/// Note that the values correspond to the number of symbols in each alphabet.
11+
/// </remarks>
12+
internal enum BiseEncodingMode
13+
{
14+
Unknown = 0,
15+
BitEncoding = 1,
16+
TritEncoding = 3,
17+
QuintEncoding = 5,
18+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using SixLabors.ImageSharp.Textures.Compression.Astc.Core;
5+
6+
namespace SixLabors.ImageSharp.Textures.Compression.Astc.BiseEncoding;
7+
8+
/// <summary>
9+
/// A simple bit stream used for reading/writing arbitrary-sized chunks.
10+
/// </summary>
11+
internal struct BitStream
12+
{
13+
private ulong low;
14+
private ulong high;
15+
private uint dataSize; // number of valid bits in the 128-bit buffer
16+
17+
public BitStream(ulong data = 0, uint dataSize = 0)
18+
{
19+
this.low = data;
20+
this.high = 0;
21+
this.dataSize = dataSize;
22+
}
23+
24+
public BitStream(UInt128 data, uint dataSize)
25+
{
26+
this.low = data.Low();
27+
this.high = data.High();
28+
this.dataSize = dataSize;
29+
}
30+
31+
public readonly uint Bits => this.dataSize;
32+
33+
public void PutBits(ulong value, int size)
34+
{
35+
if (this.dataSize + (uint)size > 128)
36+
{
37+
throw new InvalidOperationException("Not enough space in BitStream");
38+
}
39+
40+
if (this.dataSize < 64)
41+
{
42+
int lowFree = (int)(64 - this.dataSize);
43+
if (size <= lowFree)
44+
{
45+
this.low |= (value & MaskFor(size)) << (int)this.dataSize;
46+
}
47+
else
48+
{
49+
this.low |= (value & MaskFor(lowFree)) << (int)this.dataSize;
50+
this.high |= (value >> lowFree) & MaskFor(size - lowFree);
51+
}
52+
}
53+
else
54+
{
55+
int shift = (int)(this.dataSize - 64);
56+
this.high |= (value & MaskFor(size)) << shift;
57+
}
58+
59+
this.dataSize += (uint)size;
60+
}
61+
62+
/// <summary>
63+
/// Attempt to retrieve the specified number of bits from the buffer as a <see cref="UInt128"/>.
64+
/// The buffer is shifted accordingly if successful.
65+
/// </summary>
66+
public bool TryGetBits(int count, out UInt128 bits)
67+
{
68+
UInt128? result = this.GetBitsUInt128(count);
69+
bits = result ?? default;
70+
return result is not null;
71+
}
72+
73+
public bool TryGetBits(int count, out ulong bits)
74+
{
75+
if (count > this.dataSize)
76+
{
77+
bits = 0;
78+
return false;
79+
}
80+
81+
bits = count switch
82+
{
83+
0 => 0,
84+
<= 64 => this.low & MaskFor(count),
85+
_ => this.low
86+
};
87+
this.ShiftBuffer(count);
88+
return true;
89+
}
90+
91+
public bool TryGetBits(int count, out uint bits)
92+
{
93+
if (count > this.dataSize)
94+
{
95+
bits = 0;
96+
return false;
97+
}
98+
99+
bits = (uint)(count switch
100+
{
101+
0 => 0UL,
102+
<= 64 => this.low & MaskFor(count),
103+
_ => this.low
104+
});
105+
this.ShiftBuffer(count);
106+
return true;
107+
}
108+
109+
private static ulong MaskFor(int bits)
110+
=> bits == 64
111+
? ~0UL
112+
: ((1UL << bits) - 1UL);
113+
114+
private UInt128? GetBitsUInt128(int count)
115+
{
116+
if (count > this.dataSize)
117+
{
118+
return null;
119+
}
120+
121+
UInt128 result = count switch
122+
{
123+
0 => UInt128.Zero,
124+
<= 64 => (UInt128)(this.low & MaskFor(count)),
125+
128 => new UInt128(this.high, this.low),
126+
_ => new UInt128(
127+
(count - 64 == 64) ? this.high : (this.high & MaskFor(count - 64)),
128+
this.low)
129+
};
130+
131+
this.ShiftBuffer(count);
132+
133+
return result;
134+
}
135+
136+
private void ShiftBuffer(int count)
137+
{
138+
// C# masks shift amounts to the width of the operand, so `ulong << 64` and `ulong >> 64`
139+
// are identity, not zero. Special-case count == 0 and count >= 128 to avoid polluting
140+
// the low/high halves on boundary shifts.
141+
if (count == 0)
142+
{
143+
// Reading zero bits is a no-op.
144+
}
145+
else if (count < 64)
146+
{
147+
this.low = (this.low >> count) | (this.high << (64 - count));
148+
this.high >>= count;
149+
}
150+
else if (count == 64)
151+
{
152+
this.low = this.high;
153+
this.high = 0;
154+
}
155+
else if (count < 128)
156+
{
157+
this.low = this.high >> (count - 64);
158+
this.high = 0;
159+
}
160+
else
161+
{
162+
this.low = 0;
163+
this.high = 0;
164+
}
165+
166+
this.dataSize -= (uint)count;
167+
}
168+
}

0 commit comments

Comments
 (0)