246 lines
9.8 KiB
C++
246 lines
9.8 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2020 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/gpu/xenos.h"
|
|
#include "xenia/base/memory.h"
|
|
|
|
namespace xe {
|
|
namespace gpu {
|
|
namespace xenos {
|
|
|
|
// Based on X360GammaToLinear and X360LinearToGamma from the Source Engine, with
|
|
// additional logic from Direct3D 9 code in game executable disassembly, located
|
|
// via the floating-point constants involved.
|
|
// https://github.com/ValveSoftware/source-sdk-2013/blob/master/mp/src/mathlib/color_conversion.cpp#L329
|
|
// These are provided here in part as a reference for shader translators.
|
|
|
|
float PWLGammaToLinear(float gamma) {
|
|
// Not found in game executables, so just using the logic similar to that in
|
|
// the Source Engine.
|
|
gamma = xe::saturate(gamma);
|
|
float scale, offset;
|
|
// While the compiled code for linear to gamma conversion uses `vcmpgtfp
|
|
// constant, value` comparison (constant > value, or value < constant), it's
|
|
// preferable to use `value >= constant` condition for the higher pieces, as
|
|
// it will never pass for NaN, and in case of NaN, the 0...64/255 case will be
|
|
// selected regardless of whether it's saturated before or after the
|
|
// comparisons (always pre-saturating here, but shader translators may choose
|
|
// to saturate later for convenience), as saturation will flush NaN to 0.
|
|
if (gamma >= 96.0f / 255.0f) {
|
|
if (gamma >= 192.0f / 255.0f) {
|
|
scale = 8.0f / 1024.0f;
|
|
offset = -1024.0f;
|
|
} else {
|
|
scale = 4.0f / 1024.0f;
|
|
offset = -256.0f;
|
|
}
|
|
} else {
|
|
if (gamma >= 64.0f / 255.0f) {
|
|
scale = 2.0f / 1024.0f;
|
|
offset = -64.0f;
|
|
} else {
|
|
scale = 1.0f / 1024.0f;
|
|
offset = 0.0f;
|
|
// No `floor` term in this case in the Source Engine, but for the largest
|
|
// value, 1.0, `floor(255.0f * (1.0f / 1024.0f))` is 0 anyway.
|
|
}
|
|
}
|
|
// Though in the Source Engine, the 1/1024 multiplication is done for the
|
|
// truncated part specifically, pre-baking it into the scale is lossless -
|
|
// both 1024 and `scale` are powers of 2.
|
|
float linear = gamma * ((255.0f * 1024.0f) * scale) + offset;
|
|
// For consistency with linear to gamma, and because it's more logical here
|
|
// (0 rather than 1 at -epsilon), using `trunc` instead of `floor`.
|
|
linear += std::trunc(linear * scale);
|
|
linear *= 1.0f / 1023.0f;
|
|
// Clamping is not necessary (1 * (255 * 8) - 1024 + 7 is exactly 1023).
|
|
return linear;
|
|
}
|
|
|
|
float LinearToPWLGamma(float linear) {
|
|
linear = xe::saturate(linear);
|
|
float scale, offset;
|
|
// While the compiled code uses `vcmpgtfp constant, value` comparison
|
|
// (constant > value, or value < constant), it's preferable to use `value >=
|
|
// constant` condition for the higher pieces, as it will never pass for NaN,
|
|
// and in case of NaN, the 0...64/1023 case will be selected regardless of
|
|
// whether it's saturated before or after the comparisons (always
|
|
// pre-saturating here, but shader translators may choose to saturate later
|
|
// for convenience), as saturation will flush NaN to 0.
|
|
if (linear >= 128.0f / 1023.0f) {
|
|
if (linear >= 512.0f / 1023.0f) {
|
|
scale = 1023.0f / 8.0f;
|
|
offset = 128.0f / 255.0f;
|
|
} else {
|
|
scale = 1023.0f / 4.0f;
|
|
offset = 64.0f / 255.0f;
|
|
}
|
|
} else {
|
|
if (linear >= 64.0f / 1023.0f) {
|
|
scale = 1023.0f / 2.0f;
|
|
offset = 32.0f / 255.0f;
|
|
} else {
|
|
scale = 1023.0f;
|
|
offset = 0.0f;
|
|
}
|
|
}
|
|
// The truncation isn't in X360LinearToGamma in the Source Engine, but is
|
|
// there in Direct3D 9 disassembly (the `vrfiz` instructions).
|
|
// It also prevents conversion of 1.0 to 1.0034313725490196078431372549016
|
|
// that's handled via clamping in the Source Engine.
|
|
// 127.875 (1023 / 8) is truncated to 127, which, after scaling, becomes
|
|
// 127 / 255, and when 128 / 255 is added, the result is 1.
|
|
return std::trunc(linear * scale) * (1.0f / 255.0f) + offset;
|
|
}
|
|
|
|
// https://github.com/Microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexConvert.cpp
|
|
|
|
float Float7e3To32(uint32_t f10) {
|
|
f10 &= 0x3FF;
|
|
if (!f10) {
|
|
return 0.0f;
|
|
}
|
|
uint32_t mantissa = f10 & 0x7F;
|
|
uint32_t exponent = f10 >> 7;
|
|
if (!exponent) {
|
|
// Normalize the value in the resulting float.
|
|
// do { Exponent--; Mantissa <<= 1; } while ((Mantissa & 0x80) == 0)
|
|
uint32_t mantissa_lzcnt = xe::lzcnt(mantissa) - (32 - 8);
|
|
exponent = uint32_t(1 - int32_t(mantissa_lzcnt));
|
|
mantissa = (mantissa << mantissa_lzcnt) & 0x7F;
|
|
}
|
|
return xe::memory::Reinterpret<float>(
|
|
uint32_t(((exponent + 124) << 23) | (mantissa << 3)));
|
|
}
|
|
|
|
// Based on CFloat24 from d3dref9.dll and the 6e4 code from:
|
|
// https://github.com/Microsoft/DirectXTex/blob/master/DirectXTex/DirectXTexConvert.cpp
|
|
// 6e4 has a different exponent bias allowing [0,512) values, 20e4 allows [0,2).
|
|
XE_NOALIAS
|
|
uint32_t Float32To20e4(float f32, bool round_to_nearest_even) noexcept {
|
|
if (!(f32 > 0.0f)) {
|
|
// Positive only, and not -0 or NaN.
|
|
return 0;
|
|
}
|
|
auto f32u32 = xe::memory::Reinterpret<uint32_t>(f32);
|
|
if (f32u32 >= 0x3FFFFFF8) {
|
|
// Saturate.
|
|
return 0xFFFFFF;
|
|
}
|
|
if (f32u32 < 0x38800000) {
|
|
// The number is too small to be represented as a normalized 20e4.
|
|
// Convert it to a denormalized value.
|
|
uint32_t shift = std::min(uint32_t(113 - (f32u32 >> 23)), uint32_t(24));
|
|
f32u32 = (0x800000 | (f32u32 & 0x7FFFFF)) >> shift;
|
|
} else {
|
|
// Rebias the exponent to represent the value as a normalized 20e4.
|
|
f32u32 += 0xC8000000u;
|
|
}
|
|
if (round_to_nearest_even) {
|
|
f32u32 += 3 + ((f32u32 >> 3) & 1);
|
|
}
|
|
return (f32u32 >> 3) & 0xFFFFFF;
|
|
}
|
|
XE_NOALIAS
|
|
float Float20e4To32(uint32_t f24) noexcept {
|
|
f24 &= 0xFFFFFF;
|
|
if (!f24) {
|
|
return 0.0f;
|
|
}
|
|
uint32_t mantissa = f24 & 0xFFFFF;
|
|
uint32_t exponent = f24 >> 20;
|
|
if (!exponent) {
|
|
// Normalize the value in the resulting float.
|
|
// do { Exponent--; Mantissa <<= 1; } while ((Mantissa & 0x100000) == 0)
|
|
uint32_t mantissa_lzcnt = xe::lzcnt(mantissa) - (32 - 21);
|
|
exponent = uint32_t(1 - int32_t(mantissa_lzcnt));
|
|
mantissa = (mantissa << mantissa_lzcnt) & 0xFFFFF;
|
|
}
|
|
return xe::memory::Reinterpret<float>(
|
|
uint32_t(((exponent + 112) << 23) | (mantissa << 3)));
|
|
}
|
|
|
|
const char* GetColorRenderTargetFormatName(ColorRenderTargetFormat format) {
|
|
switch (format) {
|
|
case ColorRenderTargetFormat::k_8_8_8_8:
|
|
return "k_8_8_8_8";
|
|
case ColorRenderTargetFormat::k_8_8_8_8_GAMMA:
|
|
return "k_8_8_8_8_GAMMA";
|
|
case ColorRenderTargetFormat::k_2_10_10_10:
|
|
return "k_2_10_10_10";
|
|
case ColorRenderTargetFormat::k_2_10_10_10_FLOAT:
|
|
return "k_2_10_10_10_FLOAT";
|
|
case ColorRenderTargetFormat::k_16_16:
|
|
return "k_16_16";
|
|
case ColorRenderTargetFormat::k_16_16_16_16:
|
|
return "k_16_16_16_16";
|
|
case ColorRenderTargetFormat::k_16_16_FLOAT:
|
|
return "k_16_16_FLOAT";
|
|
case ColorRenderTargetFormat::k_16_16_16_16_FLOAT:
|
|
return "k_16_16_16_16_FLOAT";
|
|
case ColorRenderTargetFormat::k_2_10_10_10_AS_10_10_10_10:
|
|
return "k_2_10_10_10_AS_10_10_10_10";
|
|
case ColorRenderTargetFormat::k_2_10_10_10_FLOAT_AS_16_16_16_16:
|
|
return "k_2_10_10_10_FLOAT_AS_16_16_16_16";
|
|
case ColorRenderTargetFormat::k_32_FLOAT:
|
|
return "k_32_FLOAT";
|
|
case ColorRenderTargetFormat::k_32_32_FLOAT:
|
|
return "k_32_32_FLOAT";
|
|
default:
|
|
return "kUnknown";
|
|
}
|
|
}
|
|
|
|
const char* GetDepthRenderTargetFormatName(DepthRenderTargetFormat format) {
|
|
switch (format) {
|
|
case DepthRenderTargetFormat::kD24S8:
|
|
return "kD24S8";
|
|
case DepthRenderTargetFormat::kD24FS8:
|
|
return "kD24FS8";
|
|
default:
|
|
return "kUnknown";
|
|
}
|
|
}
|
|
static const char* const g_endian_names[] = {"none", "8 in 16", "8 in 32",
|
|
"16 in 32"};
|
|
|
|
const char* GetEndianEnglishDescription(xenos::Endian endian) {
|
|
return g_endian_names[static_cast<uint32_t>(endian)];
|
|
}
|
|
static const char* const g_primtype_human_names[] = {"none",
|
|
"point list",
|
|
"line list",
|
|
"line strip",
|
|
"triangle list",
|
|
"triangle fan",
|
|
"triangle strip",
|
|
"triangle with flags",
|
|
"rectangle list",
|
|
"unused1",
|
|
"unused2",
|
|
"unused3",
|
|
"line loop",
|
|
"quad list",
|
|
"quad strip",
|
|
"polygon",
|
|
"2D copy rect list v0",
|
|
"2D copy rect list v1",
|
|
"2D copy rect list v2",
|
|
"2D copy rect list v3",
|
|
"2D fillrect list",
|
|
"2D line strip",
|
|
"2D triangle strip"};
|
|
|
|
const char* GetPrimitiveTypeEnglishDescription(xenos::PrimitiveType prim_type) {
|
|
return g_primtype_human_names[static_cast<uint32_t>(prim_type)];
|
|
}
|
|
} // namespace xenos
|
|
} // namespace gpu
|
|
} // namespace xe
|