mirror of
https://github.com/AssetRipper/AssetRipper.TextureDecoder.git
synced 2026-11-12 18:07:28 -10:00
142 lines
4.5 KiB
C#
142 lines
4.5 KiB
C#
using System.Runtime.CompilerServices;
|
|
|
|
namespace IntegerConversion.ConstantEstimation;
|
|
|
|
internal class Program
|
|
{
|
|
static void Main(string[] args)
|
|
{
|
|
int originalBitCount = int.Parse(args[0]);
|
|
int newBitCount = int.Parse(args[1]);
|
|
bool perfectMatch = EstimateConstants(originalBitCount, newBitCount, out uint a, out long b, out int c);
|
|
Console.WriteLine("Done!");
|
|
Console.WriteLine($"Result: ({a} * x + {b}) >> {c}");
|
|
Console.WriteLine($"Perfect match: {perfectMatch}");
|
|
}
|
|
|
|
private static bool EstimateConstants(int originalBitCount, int newBitCount, out uint a, out long b, out int c)
|
|
{
|
|
a = default;
|
|
b = default;
|
|
c = default;
|
|
double bestError = double.MaxValue;//Sum of squared errors
|
|
uint originalMaxValue = GetIntegerMaxValue(originalBitCount);
|
|
uint newMaxValue = GetIntegerMaxValue(newBitCount);
|
|
int bitDiff = newBitCount - originalBitCount;
|
|
|
|
//c: Max(0, -bitDiff) to 32 - newBitCount
|
|
//If bitDiff is negative, it means we are going from large numbers to small numbers.
|
|
//In other words, we will always have to shift right by at least enough to overcome the bit surplus.
|
|
int minc = int.Max(0, -bitDiff);
|
|
// We are limited to a 32-bit result, so we cannot shift left so much that we exceed 32 bits.
|
|
int maxc = 32 - newBitCount;
|
|
|
|
for (int ic = minc; ic <= maxc; ic++)
|
|
{
|
|
Console.WriteLine(ic);
|
|
|
|
//a: 2^c * newMaxValue / originalMaxValue rounded to nearest integer
|
|
double ia_exact = (double)Pow2(ic) * newMaxValue / originalMaxValue;
|
|
|
|
ReadOnlySpan<uint> ia_values = ia_exact < 1 ? [(uint)double.Ceiling(ia_exact)] : [(uint)double.Floor(ia_exact), (uint)double.Ceiling(ia_exact)];
|
|
|
|
foreach (uint ia in ia_values)
|
|
{
|
|
//b: 0 to 2^c - 1
|
|
//Any bigger and it will cause 0 not to correspond with 0.
|
|
uint minb;
|
|
uint maxb;
|
|
{
|
|
//We can restrict b further by ensuring that originalMaxValue maps to newMaxValue.
|
|
long minb_one = (long)(newMaxValue << ic) - (ia * originalMaxValue);
|
|
long maxb_one = minb_one + Pow2(ic) - 1;
|
|
|
|
minb = (uint)long.Max(0, minb_one);
|
|
maxb = (uint)long.Min(Pow2(ic) - 1, long.Max(0, maxb_one));
|
|
minb = uint.Min(minb, maxb);
|
|
}
|
|
|
|
//Errors for b are nearly parabolic, so we try to find the minimum by ternary search.
|
|
//This is not guaranteed to find the absolute minimum since there's noise in the error, but it should get close enough.
|
|
uint current_b_low = minb;
|
|
uint current_b_high = maxb;
|
|
while (current_b_high - current_b_low > 3)
|
|
{
|
|
uint range = current_b_high - current_b_low;
|
|
uint b1 = current_b_low + range / 3;
|
|
uint b2 = current_b_high - range / 3;
|
|
double error1 = CalculateMeanSquaredError(ia, b1, ic, originalMaxValue, newMaxValue, out _);
|
|
double error2 = CalculateMeanSquaredError(ia, b2, ic, originalMaxValue, newMaxValue, out _);
|
|
if (error1 < error2)
|
|
{
|
|
current_b_high = b2;
|
|
}
|
|
else
|
|
{
|
|
current_b_low = b1;
|
|
}
|
|
}
|
|
|
|
for (uint ib = current_b_low; ib <= current_b_high; ib++)
|
|
{
|
|
double error = CalculateMeanSquaredError(ia, ib, ic, originalMaxValue, newMaxValue, out bool anyIncorrect);
|
|
if (error < bestError)
|
|
{
|
|
bestError = error;
|
|
a = ia;
|
|
b = ib;
|
|
c = ic;
|
|
Console.WriteLine($"New best: a={a}, b={b}, c={c}, MSE={bestError}");
|
|
}
|
|
if (!anyIncorrect)
|
|
{
|
|
//Perfect match
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static double CalculateMeanSquaredError(uint a, uint b, int c, uint originalMaxValue, uint newMaxValue, out bool anyIncorrect)
|
|
{
|
|
double meanSquaredError = 0;
|
|
anyIncorrect = false;
|
|
for (uint x = 0; x <= originalMaxValue; x++)
|
|
{
|
|
uint y = ConvertEstimate(x, a, b, c);
|
|
double yExact = (double)x * newMaxValue / originalMaxValue;
|
|
|
|
double diff = yExact - y;
|
|
meanSquaredError = meanSquaredError * x / (x + 1) + (diff * diff) / (x + 1);
|
|
|
|
uint yExactInt = (uint)double.Round(yExact, MidpointRounding.AwayFromZero);
|
|
anyIncorrect |= (y != yExactInt);
|
|
}
|
|
return meanSquaredError;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private static uint ConvertEstimate(uint original, uint a, uint b, int c)
|
|
{
|
|
unchecked
|
|
{
|
|
return ((original * a) + b) >> c;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private static uint GetIntegerMaxValue(int bitCount)
|
|
{
|
|
return Pow2(bitCount) - 1;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private static uint Pow2(int exponent)
|
|
{
|
|
return (1u << exponent);
|
|
}
|
|
}
|