Implement faster E5B9G9R9 conversion
We can avoid calling transcendental functions by manipulating the
IEEE-754 representation of the single-precision floating point values.
This implementation produces results identical to the reference code.
Bug: b/138944025
Change-Id: I95faf7e80d0604be115dd1b8dfec72f3b8ece841
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/34748
Presubmit-Ready: Nicolas Capens <nicolascapens@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4a86d09..d2efade 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2127,6 +2127,32 @@
endif()
endif()
+if(BUILD_TESTS)
+ set(MATH_UNITTESTS_LIST
+ ${CMAKE_CURRENT_SOURCE_DIR}/tests/MathUnitTests/main.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/tests/MathUnitTests/unittests.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest/googletest/src/gtest-all.cc
+ )
+
+ set(MATH_UNITTESTS_INCLUDE_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest/googletest/include/
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest/googlemock/include/
+ ${CMAKE_CURRENT_SOURCE_DIR}/third_party/googletest/googletest/
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/
+ )
+
+ add_executable(math-unittests ${MATH_UNITTESTS_LIST})
+ set_target_properties(math-unittests PROPERTIES
+ INCLUDE_DIRECTORIES "${MATH_UNITTESTS_INCLUDE_DIR}"
+ FOLDER "Tests"
+ COMPILE_OPTIONS "${SWIFTSHADER_COMPILE_OPTIONS}"
+ )
+
+ if(NOT WIN32)
+ target_link_libraries(math-unittests pthread)
+ endif()
+endif()
+
if(BUILD_TESTS AND BUILD_VULKAN)
set(VK_UNITTESTS_LIST
${CMAKE_CURRENT_SOURCE_DIR}/tests/VulkanUnitTests/Device.cpp
diff --git a/SwiftShader.sln b/SwiftShader.sln
index 95e3001..0ad4eb0 100644
--- a/SwiftShader.sln
+++ b/SwiftShader.sln
@@ -120,6 +120,8 @@
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spv-tools-spv-amd-stm", "build\Visual Studio 15 2017 Win64\third_party\SPIRV-Tools\source\spv-tools-spv-amd-stm.vcxproj", "{9E8623BF-3469-3104-9674-DED25861C7CF}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MathUnitTests", "tests\MathUnitTests\MathUnitTests.vcxproj", "{B4FDF21A-D945-40B2-B459-2A8707879980}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -518,6 +520,18 @@
{9E8623BF-3469-3104-9674-DED25861C7CF}.Release|x64.ActiveCfg = Release|x64
{9E8623BF-3469-3104-9674-DED25861C7CF}.Release|x64.Build.0 = Release|x64
{9E8623BF-3469-3104-9674-DED25861C7CF}.Release|x86.ActiveCfg = Release|x64
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Debug|x64.ActiveCfg = Debug|x64
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Debug|x64.Build.0 = Debug|x64
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Debug|x86.ActiveCfg = Debug|Win32
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Debug|x86.Build.0 = Debug|Win32
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Profile|x64.ActiveCfg = Release|x64
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Profile|x64.Build.0 = Release|x64
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Profile|x86.ActiveCfg = Release|Win32
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Profile|x86.Build.0 = Release|Win32
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Release|x64.ActiveCfg = Release|x64
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Release|x64.Build.0 = Release|x64
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Release|x86.ActiveCfg = Release|Win32
+ {B4FDF21A-D945-40B2-B459-2A8707879980}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -552,6 +566,7 @@
{1262CB67-3BC7-35D5-9036-3D3B3A9DC3CE} = {ABF69E39-C15E-4DAC-A27E-3480DE2C0CF0}
{32C0DDEA-EC24-3465-B0F9-9DA187D45ED3} = {ABF69E39-C15E-4DAC-A27E-3480DE2C0CF0}
{9E8623BF-3469-3104-9674-DED25861C7CF} = {ABF69E39-C15E-4DAC-A27E-3480DE2C0CF0}
+ {B4FDF21A-D945-40B2-B459-2A8707879980} = {ED25C308-5BDB-43A7-BED6-C2C059FC2D7D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4DF423D2-8425-48A7-9CEC-835C4C3CA957}
diff --git a/src/System/Half.hpp b/src/System/Half.hpp
index ec5fba9..d908f35 100644
--- a/src/System/Half.hpp
+++ b/src/System/Half.hpp
@@ -15,6 +15,8 @@
#ifndef sw_Half_hpp
#define sw_Half_hpp
+#include "Math.hpp"
+
#include <algorithm>
#include <cmath>
@@ -56,8 +58,14 @@
unsigned int E : 5;
public:
- RGB9E5(float rgb[3])
+ RGB9E5(float rgb[3]) : RGB9E5(rgb[0], rgb[1], rgb[2])
{
+ }
+
+ RGB9E5(float r, float g, float b)
+ {
+ // Vulkan 1.1.117 section 15.2.1 RGB to Shared Exponent Conversion
+
// B is the exponent bias (15)
constexpr int g_sharedexp_bias = 15;
@@ -72,19 +80,33 @@
static_cast<float>(1 << g_sharedexp_mantissabits)) *
static_cast<float>(1 << (g_sharedexp_maxexponent - g_sharedexp_bias));
- const float red_c = std::max<float>(0, std::min(g_sharedexp_max, rgb[0]));
- const float green_c = std::max<float>(0, std::min(g_sharedexp_max, rgb[1]));
- const float blue_c = std::max<float>(0, std::min(g_sharedexp_max, rgb[2]));
+ // Clamp components to valid range.
+ const float red_c = std::max<float>(0, std::min(g_sharedexp_max, r));
+ const float green_c = std::max<float>(0, std::min(g_sharedexp_max, g));
+ const float blue_c = std::max<float>(0, std::min(g_sharedexp_max, b));
- const float max_c = std::max<float>(std::max<float>(red_c, green_c), blue_c);
- const float exp_p = std::max<float>(-g_sharedexp_bias - 1, floor(log2(max_c))) + 1 + g_sharedexp_bias;
- const int max_s = static_cast<int>(floor((max_c / exp2(exp_p - g_sharedexp_bias - g_sharedexp_mantissabits)) + 0.5f));
- const int exp_s = static_cast<int>((max_s < exp2(g_sharedexp_mantissabits)) ? exp_p : exp_p + 1);
+ // We're reducing the mantissa to 9 bits, so we must round up if the next
+ // bit is 1. In other words add 0.5 to the new mantissa's position and
+ // allow overflow into the exponent so we can scale correctly.
+ constexpr int half = 1 << (23 - g_sharedexp_mantissabits);
+ const float red_r = bit_cast<float>(bit_cast<int>(red_c) + half);
+ const float green_r = bit_cast<float>(bit_cast<int>(green_c) + half);
+ const float blue_r = bit_cast<float>(bit_cast<int>(blue_c) + half);
- R = static_cast<unsigned int>(floor((red_c / exp2(exp_s - g_sharedexp_bias - g_sharedexp_mantissabits)) + 0.5f));
- G = static_cast<unsigned int>(floor((green_c / exp2(exp_s - g_sharedexp_bias - g_sharedexp_mantissabits)) + 0.5f));
- B = static_cast<unsigned int>(floor((blue_c / exp2(exp_s - g_sharedexp_bias - g_sharedexp_mantissabits)) + 0.5f));
- E = exp_s;
+ // The largest component determines the shared exponent. It can't be lower
+ // than 0 (after bias subtraction) so also limit to the mimimum representable.
+ constexpr float min_s = 0.5f / (1 << g_sharedexp_bias);
+ float max_s = std::max(std::max(red_r, green_r), std::max(blue_r, min_s));
+
+ // Obtain the reciprocal of the shared exponent by inverting the bits,
+ // and scale by the new mantissa's size. Note that the IEEE-754 single-precision
+ // format has an implicit leading 1, but this shared component format does not.
+ float scale = bit_cast<float>((bit_cast<int>(max_s) & 0x7F800000) ^ 0x7F800000) * (1 << (g_sharedexp_mantissabits - 2));
+
+ R = static_cast<unsigned int>(round(red_c * scale));
+ G = static_cast<unsigned int>(round(green_c * scale));
+ B = static_cast<unsigned int>(round(blue_c * scale));
+ E = (bit_cast<unsigned int>(max_s) >> 23) - 127 + 15 + 1;
}
operator unsigned int() const
diff --git a/tests/MathUnitTests/MathUnitTests.vcxproj b/tests/MathUnitTests/MathUnitTests.vcxproj
new file mode 100644
index 0000000..0973658
--- /dev/null
+++ b/tests/MathUnitTests/MathUnitTests.vcxproj
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <VCProjectVersion>15.0</VCProjectVersion>
+ <ProjectGuid>{B4FDF21A-D945-40B2-B459-2A8707879980}</ProjectGuid>
+ <RootNamespace>MathUnitTests</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>$(SolutionDir)bin\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(SolutionDir)obj\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <OutDir>$(SolutionDir)bin\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(SolutionDir)obj\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <OutDir>$(SolutionDir)bin\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(SolutionDir)obj\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <OutDir>$(SolutionDir)bin\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(SolutionDir)obj\$(MSBuildProjectName)\$(Platform)\$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <SDLCheck>true</SDLCheck>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>$(SolutionDir)src;$(SolutionDir)third_party\googletest\googletest\include\;$(SolutionDir)third_party\googletest\googletest\;$(SolutionDir)third_party\googletest\googlemock\include\;SubmoduleCheck;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <SDLCheck>true</SDLCheck>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>$(SolutionDir)src;$(SolutionDir)third_party\googletest\googletest\include\;$(SolutionDir)third_party\googletest\googletest\;$(SolutionDir)third_party\googletest\googlemock\include\;SubmoduleCheck;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>$(SolutionDir)src;$(SolutionDir)third_party\googletest\googletest\include\;$(SolutionDir)third_party\googletest\googletest\;$(SolutionDir)third_party\googletest\googlemock\include\;SubmoduleCheck;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <SubSystem>Console</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <SDLCheck>true</SDLCheck>
+ <ConformanceMode>true</ConformanceMode>
+ <AdditionalIncludeDirectories>$(SolutionDir)src;$(SolutionDir)third_party\googletest\googletest\include\;$(SolutionDir)third_party\googletest\googletest\;$(SolutionDir)third_party\googletest\googlemock\include\;SubmoduleCheck;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <SubSystem>Console</SubSystem>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\third_party\googletest\googletest\src\gtest-all.cc" />
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="unittests.cpp" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/tests/MathUnitTests/MathUnitTests.vcxproj.filters b/tests/MathUnitTests/MathUnitTests.vcxproj.filters
new file mode 100644
index 0000000..5e36d8f
--- /dev/null
+++ b/tests/MathUnitTests/MathUnitTests.vcxproj.filters
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\third_party\googletest\googletest\src\gtest-all.cc">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="unittests.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/tests/MathUnitTests/main.cpp b/tests/MathUnitTests/main.cpp
new file mode 100644
index 0000000..8f7e13f
--- /dev/null
+++ b/tests/MathUnitTests/main.cpp
@@ -0,0 +1,21 @@
+// Copyright 2017 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gtest/gtest.h"
+
+int main(int argc, char **argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/tests/MathUnitTests/unittests.cpp b/tests/MathUnitTests/unittests.cpp
new file mode 100644
index 0000000..c56940b
--- /dev/null
+++ b/tests/MathUnitTests/unittests.cpp
@@ -0,0 +1,108 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "System/Half.hpp"
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+#include <cstdlib>
+
+using namespace sw;
+
+unsigned int RGB9E5_reference(float r, float g, float b)
+{
+ // Vulkan 1.1.117 section 15.2.1 RGB to Shared Exponent Conversion
+
+ // B is the exponent bias (15)
+ constexpr int g_sharedexp_bias = 15;
+
+ // N is the number of mantissa bits per component (9)
+ constexpr int g_sharedexp_mantissabits = 9;
+
+ // Emax is the maximum allowed biased exponent value (31)
+ constexpr int g_sharedexp_maxexponent = 31;
+
+ constexpr float g_sharedexp_max =
+ ((static_cast<float>(1 << g_sharedexp_mantissabits) - 1) /
+ static_cast<float>(1 << g_sharedexp_mantissabits)) *
+ static_cast<float>(1 << (g_sharedexp_maxexponent - g_sharedexp_bias));
+
+ const float red_c = std::max<float>(0, std::min(g_sharedexp_max, r));
+ const float green_c = std::max<float>(0, std::min(g_sharedexp_max, g));
+ const float blue_c = std::max<float>(0, std::min(g_sharedexp_max, b));
+
+ const float max_c = std::max<float>(std::max<float>(red_c, green_c), blue_c);
+ const float exp_p = std::max<float>(-g_sharedexp_bias - 1, floor(log2(max_c))) + 1 + g_sharedexp_bias;
+ const int max_s = static_cast<int>(floor((max_c / exp2(exp_p - g_sharedexp_bias - g_sharedexp_mantissabits)) + 0.5f));
+ const int exp_s = static_cast<int>((max_s < exp2(g_sharedexp_mantissabits)) ? exp_p : exp_p + 1);
+
+ unsigned int R = static_cast<unsigned int>(floor((red_c / exp2(exp_s - g_sharedexp_bias - g_sharedexp_mantissabits)) + 0.5f));
+ unsigned int G = static_cast<unsigned int>(floor((green_c / exp2(exp_s - g_sharedexp_bias - g_sharedexp_mantissabits)) + 0.5f));
+ unsigned int B = static_cast<unsigned int>(floor((blue_c / exp2(exp_s - g_sharedexp_bias - g_sharedexp_mantissabits)) + 0.5f));
+ unsigned int E = exp_s;
+
+ return (E << 27) | (B << 18) | (G << 9) | R;
+}
+
+TEST(MathTest, SharedExponentSparse)
+{
+ for(uint64_t i = 0; i < 0x0000000100000000; i += 0x400)
+ {
+ float f = bit_cast<float>(i);
+
+ unsigned int ref = RGB9E5_reference(f, 0.0f, 0.0f);
+ unsigned int val = RGB9E5(f, 0.0f, 0.0f);
+
+ EXPECT_EQ(ref, val);
+ }
+}
+
+TEST(MathTest, SharedExponentRandom)
+{
+ srand(0);
+
+ unsigned int x = 0;
+ unsigned int y = 0;
+ unsigned int z = 0;
+
+ for(int i = 0; i < 10000000; i++)
+ {
+ float r = bit_cast<float>(x);
+ float g = bit_cast<float>(y);
+ float b = bit_cast<float>(z);
+
+ unsigned int ref = RGB9E5_reference(r, g, b);
+ unsigned int val = RGB9E5(r, g, b);
+
+ EXPECT_EQ(ref, val);
+
+ x += rand();
+ y += rand();
+ z += rand();
+ }
+}
+
+TEST(MathTest, SharedExponentExhaustive)
+{
+ for(uint64_t i = 0; i < 0x0000000100000000; i += 1)
+ {
+ float f = bit_cast<float>(i);
+
+ unsigned int ref = RGB9E5_reference(f, 0.0f, 0.0f);
+ unsigned int val = RGB9E5(f, 0.0f, 0.0f);
+
+ EXPECT_EQ(ref, val);
+ }
+}