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);
+	}
+}