diff --git a/Frameworks/include/CppUtils.h b/Frameworks/include/CppUtils.h index 82c00d1700..8460ed0d0f 100644 --- a/Frameworks/include/CppUtils.h +++ b/Frameworks/include/CppUtils.h @@ -58,6 +58,10 @@ std::basic_ostream& operator<<(std::basic_ostream& os, const CGSize& size) return os; } +inline CGSize operator*(const CGSize& lhs, CGFloat multiplier) { + return{ lhs.width * multiplier, lhs.height * multiplier }; +} + #pragma endregion #pragma region CGRect diff --git a/build/Tests/Benchmark/Framework.Benchmark.vcxproj b/build/Tests/Benchmark/Framework.Benchmark.vcxproj new file mode 100644 index 0000000000..272c5d00ef --- /dev/null +++ b/build/Tests/Benchmark/Framework.Benchmark.vcxproj @@ -0,0 +1,248 @@ + + + + + Debug + ARM + + + Debug + Win32 + + + Release + ARM + + + Release + Win32 + + + + + {86127226-9A6E-439B-A070-420A572AF0C7} + + + {81F30AF6-EAC3-4DFA-929A-C25D69E8080B} + + + {862d36c2-cc83-4d04-b9b8-bef07f479905} + + + {0AC27ECF-E2AB-420B-9359-4843FFF4CBFA} + + + {8E79930B-7EF6-4A4E-B46C-EFC0A49C55D9} + + + {26da08da-d0b9-4579-b168-e7f0a5f20e57} + + + {36deec5d-f77b-4c94-a63c-86fb716833de} + + + {585b4870-0d6b-43a6-8e7e-ad08f7f507b6} + + + + + + + + WindowsLocalDebugger + {7A062AEC-5AED-4F83-8716-4C078F75177B} + Win32Proj + Framework.Benchmark + en-US + 14.0 + Windows Store + false + 10.0 + 10.0.14393.0 + 10.0.10586.0 + 10.0.14393.0 + 10.0.10586.0 + false + Universal Windows + ..\..\.. + true + false + $(SolutionDir)$(Platform)\$(Configuration)\$(RootNamespace)\ + + + + Application + true + v140 + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(Configuration)\$(ProjectName)\ + + + true + + + false + $(Configuration)\$(ProjectName)\ + + + false + + + + NotUsing + Level3 + Disabled + NO_STUBS;WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(StarboardBasePath)\tests\frameworks\include;$(StarboardBasePath)\tests\frameworks\gtest;$(StarboardBasePath)\tests\frameworks\gtest\include;$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories) + + + Console + true + libdispatch.lib;mincore.lib;%(AdditionalDependencies) + false + + + $(StarboardBasePath)\Frameworks\include;$(StarboardBasePath)\include\xplat;$(StarboardBasePath)\tests\frameworks\include;$(StarboardBasePath)\tests\frameworks\gtest;$(StarboardBasePath)\tests\frameworks\gtest\include;$(StarboardBasePath)\;%(AdditionalIncludeDirectories) + CompileAsObjCpp + -fmsvc-real-char -Wdeprecated-declarations + NO_STUBS;_CRT_SECURE_NO_WARNINGS;DEBUG=1;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + + + + + NotUsing + Level3 + Disabled + NO_STUBS;WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(StarboardBasePath)\tests\frameworks\include;$(StarboardBasePath)\tests\frameworks\gtest;$(StarboardBasePath)\tests\frameworks\gtest\include;$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories) + + + Console + true + libdispatch.lib;mincore.lib;%(AdditionalDependencies) + false + + + $(StarboardBasePath)\Frameworks\include;$(StarboardBasePath)\include\xplat;$(StarboardBasePath)\tests\frameworks\include;$(StarboardBasePath)\tests\frameworks\gtest;$(StarboardBasePath)\tests\frameworks\gtest\include;$(StarboardBasePath)\;%(AdditionalIncludeDirectories) + CompileAsObjCpp + -fmsvc-real-char -Wdeprecated-declarations + NO_STUBS;_CRT_SECURE_NO_WARNINGS;DEBUG=1;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + + + + + Level3 + NotUsing + MaxSpeed + true + true + NO_STUBS;WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(StarboardBasePath)\tests\frameworks\include;$(StarboardBasePath)\tests\frameworks\gtest;$(StarboardBasePath)\tests\frameworks\gtest\include;$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories) + + + Console + true + true + true + libdispatch.lib;mincore.lib;%(AdditionalDependencies) + false + + + $(StarboardBasePath)\Frameworks\include;$(StarboardBasePath)\include\xplat;$(StarboardBasePath)\tests\frameworks\include;$(StarboardBasePath)\tests\frameworks\gtest;$(StarboardBasePath)\tests\frameworks\gtest\include;$(StarboardBasePath)\;%(AdditionalIncludeDirectories) + CompileAsObjCpp + -fmsvc-real-char + NO_STUBS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + + + + + Level3 + NotUsing + MaxSpeed + true + true + NO_STUBS;WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + $(StarboardBasePath)\tests\frameworks\include;$(StarboardBasePath)\tests\frameworks\gtest;$(StarboardBasePath)\tests\frameworks\gtest\include;$(MSBuildThisFileDirectory);%(AdditionalIncludeDirectories) + + + Console + true + true + true + libdispatch.lib;mincore.lib;%(AdditionalDependencies) + false + + + $(StarboardBasePath)\Frameworks\include;$(StarboardBasePath)\include\xplat;$(StarboardBasePath)\tests\frameworks\include;$(StarboardBasePath)\tests\frameworks\gtest;$(StarboardBasePath)\tests\frameworks\gtest\include;$(StarboardBasePath)\;%(AdditionalIncludeDirectories) + CompileAsObjCpp + -fmsvc-real-char + NO_STUBS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + + + + + + + + + + + + + + + + + + + diff --git a/build/build.sln b/build/build.sln index ae06b766e2..107fff6459 100644 --- a/build/build.sln +++ b/build/build.sln @@ -781,6 +781,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AddressBook.UnitTests", "Te EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CoreGraphics.Drawing.UnitTests", "Tests\UnitTests\CoreGraphics.Drawing\CoreGraphics.Drawing.UnitTests.vcxproj", "{DE51CDE9-F326-49B6-8C5B-35D5B091878C}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmark", "Benchmark", "{99A75321-675A-4C92-9848-96D3C8460625}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Framework.Benchmark", "Tests\Benchmark\Framework.Benchmark.vcxproj", "{7A062AEC-5AED-4F83-8716-4C078F75177B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM = Debug|ARM @@ -1963,6 +1967,14 @@ Global {DE51CDE9-F326-49B6-8C5B-35D5B091878C}.Release|ARM.Build.0 = Release|ARM {DE51CDE9-F326-49B6-8C5B-35D5B091878C}.Release|x86.ActiveCfg = Release|Win32 {DE51CDE9-F326-49B6-8C5B-35D5B091878C}.Release|x86.Build.0 = Release|Win32 + {7A062AEC-5AED-4F83-8716-4C078F75177B}.Debug|ARM.ActiveCfg = Debug|ARM + {7A062AEC-5AED-4F83-8716-4C078F75177B}.Debug|ARM.Build.0 = Debug|ARM + {7A062AEC-5AED-4F83-8716-4C078F75177B}.Debug|x86.ActiveCfg = Debug|Win32 + {7A062AEC-5AED-4F83-8716-4C078F75177B}.Debug|x86.Build.0 = Debug|Win32 + {7A062AEC-5AED-4F83-8716-4C078F75177B}.Release|ARM.ActiveCfg = Release|ARM + {7A062AEC-5AED-4F83-8716-4C078F75177B}.Release|ARM.Build.0 = Release|ARM + {7A062AEC-5AED-4F83-8716-4C078F75177B}.Release|x86.ActiveCfg = Release|Win32 + {7A062AEC-5AED-4F83-8716-4C078F75177B}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2264,5 +2276,7 @@ Global {69D1F829-1843-4395-BCE9-69C4CEB59004} = {88413F6C-C27A-4B48-9AE5-D36161920F6D} {62E53898-65C2-4401-BF58-FBFB728E1B27} = {69D1F829-1843-4395-BCE9-69C4CEB59004} {DE51CDE9-F326-49B6-8C5B-35D5B091878C} = {0A79AFE5-2685-433C-BC78-A4AD09CD7BF8} + {99A75321-675A-4C92-9848-96D3C8460625} = {58128019-F022-44F1-9D59-811230051CD8} + {7A062AEC-5AED-4F83-8716-4C078F75177B} = {99A75321-675A-4C92-9848-96D3C8460625} EndGlobalSection EndGlobal diff --git a/include/CoreGraphics/CGContext.h b/include/CoreGraphics/CGContext.h index 4e75a20e31..b02ad36c65 100644 --- a/include/CoreGraphics/CGContext.h +++ b/include/CoreGraphics/CGContext.h @@ -234,7 +234,7 @@ COREGRAPHICS_EXPORT void CGContextShowGlyphs(CGContextRef c, const CGGlyph* g, s COREGRAPHICS_EXPORT void CGContextShowGlyphsAtPoint(CGContextRef c, CGFloat x, CGFloat y, const CGGlyph* glyphs, size_t count); COREGRAPHICS_EXPORT void CGContextShowGlyphsWithAdvances(CGContextRef c, const CGGlyph* glyphs, const CGSize* advances, size_t count); -COREGRAPHICS_EXPORT void CGContextShowGlyphsAtPositions(CGContextRef c, const CGGlyph* glyphs, const CGPoint* Lpositions, size_t count) +COREGRAPHICS_EXPORT void CGContextShowGlyphsAtPositions(CGContextRef c, const CGGlyph* glyphs, const CGPoint* positions, size_t count) STUB_METHOD; COREGRAPHICS_EXPORT CGAffineTransform CGContextGetTextMatrix(CGContextRef c); diff --git a/tests/Benchmark/Benchmark.h b/tests/Benchmark/Benchmark.h new file mode 100644 index 0000000000..c535d2fcc4 --- /dev/null +++ b/tests/Benchmark/Benchmark.h @@ -0,0 +1,142 @@ +//****************************************************************************** +// +// Copyright (c) Microsoft. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "BenchmarkPublisher.h" + +namespace benchmark { +class BenchmarkCaseBase { +public: + inline void Run(); + + // Number of times to run each test, default to 2 + size_t GetRunCount() const { + return 2; + } + + // Pre-run and Post-run equivalent to SetUp and TearDown per test case + // Setup and Tear down that is performed once per all tests should be done in the constructor/destructor + void PreRun() { + // Default do nothing + } + + void PostRun() { + // Default do nothing + } +}; + +namespace internal { + +// Helper method shared between BenchmarkCaseRunner classes +// This is called to actually run and benchmark the tests +// Can't be shared internally because parameterized tests need a different inheritance than non +template +static void __RunCase(T& test, const char* caseName) { + using Microseconds = double; + size_t runCount = test.GetRunCount(); + ASSERT_LT(1, runCount); + std::vector results(runCount); + @autoreleasepool { + for (size_t i = 0; i < runCount; ++i) { + test.PreRun(); + auto start = std::chrono::high_resolution_clock::now(); + test.Run(); + auto end = std::chrono::high_resolution_clock::now(); + test.PostRun(); + std::chrono::duration duration = end - start; + results[i] = duration.count(); + } + } + + std::shared_ptr<::benchmark::BenchmarkPublisher> publisher = ::benchmark::BenchmarkPublisherFactory::GetPublisher(); + auto testName = std::string(caseName); + publisher->RegisterCaseResults(testName, results); +} + +template +class BenchmarkCaseRunner : public ::testing::Test { + static_assert(std::is_base_of::value, "Benchmark Case should derive from ::benchmark::BenchmarkCaseBase"); + static_assert(std::is_default_constructible::value, "Benchmark Case Class MUST be default constructible"); + +public: + BenchmarkCaseRunner() : m_test() { + } + +protected: + CaseClass m_test; +}; + +template +class BenchmarkCaseRunnerP : public ::testing::TestWithParam { + static_assert(std::is_base_of::value, "Benchmark Case should derive from ::benchmark::BenchmarkCaseBase"); + static_assert(std::is_constructible::value, + "Benchmark Case Class MUST be constructible from template arguments, if any"); + +public: + BenchmarkCaseRunnerP() { + // Note: This will break when multiple args have the same type + m_test.reset(new CaseClass(::testing::WithParamInterface::GetParam())); + } + +protected: + std::unique_ptr m_test; +}; +} +} + +// clang-format off +#define _BENCHMARK_CLASSNAME(test_name, test_case_name) test_name##__##test_case_name +#define _STRINGIFY_INTERNAL(var) #var +#define _STRINGIFY(var) _STRINGIFY_INTERNAL(var) + +#define BENCHMARK(test_name, test_case_name, run_count) \ +class _BENCHMARK_CLASSNAME(test_name, test_case_name) : public ::benchmark::BenchmarkCaseBase { \ +public: \ + size_t GetRunCount() const { \ + return run_count; \ + } \ + inline void Run(); \ +}; \ +GTEST_TEST_(test_name, \ + test_case_name, \ + ::benchmark::internal::BenchmarkCaseRunner<_BENCHMARK_CLASSNAME(test_name, test_case_name)>,\ + ::testing::internal::GetTestTypeId()) { \ + ::benchmark::internal::__RunCase(m_test, _STRINGIFY(_BENCHMARK_CLASSNAME(test_name, test_case_name))); \ +} \ +inline void _BENCHMARK_CLASSNAME(test_name, test_case_name)::Run() + +#define BENCHMARK_F(test_name, test_class_name) \ +GTEST_TEST_(test_name, test_class_name, ::benchmark::internal::BenchmarkCaseRunner, ::testing::internal::GetTestTypeId()) { \ + ::benchmark::internal::__RunCase(m_test, _STRINGIFY(_BENCHMARK_CLASSNAME(test_name, test_class_name))); \ +} + +#define BENCHMARK_REGISTER_CASE_P(test_name, test_class, generator, ...) \ +class _Benchmark_##test_class : public ::benchmark::internal::BenchmarkCaseRunnerP {}; \ +TEST_P(_Benchmark_##test_class, test_name) { \ + const char* testName = _STRINGIFY(__##test_class); \ + auto fullName = std::string(::testing::UnitTest::GetInstance()->current_test_info()->name()) + testName; \ + ::benchmark::internal::__RunCase(*m_test, fullName.c_str()); \ +} \ +INSTANTIATE_TEST_CASE_P(test_name, _Benchmark_##test_class, generator); + +// clang-format on \ No newline at end of file diff --git a/tests/Benchmark/BenchmarkPublisher.h b/tests/Benchmark/BenchmarkPublisher.h new file mode 100644 index 0000000000..28819ee663 --- /dev/null +++ b/tests/Benchmark/BenchmarkPublisher.h @@ -0,0 +1,36 @@ +#//****************************************************************************** +// +// Copyright (c) Microsoft. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +#pragma once +#include +#include +#include + +namespace benchmark { +using Microseconds = double; + +class BenchmarkPublisher { +public: + virtual void RegisterCaseResults(const std::string& caseName, const std::vector& results) = 0; + virtual void PublishResults() = 0; +}; + +class BenchmarkPublisherFactory { +public: + static void CreatePublisher(int argc, char** argv); + static std::shared_ptr GetPublisher(); +}; +} \ No newline at end of file diff --git a/tests/Benchmark/BenchmarkSampleTests.mm b/tests/Benchmark/BenchmarkSampleTests.mm new file mode 100644 index 0000000000..3f0226f248 --- /dev/null +++ b/tests/Benchmark/BenchmarkSampleTests.mm @@ -0,0 +1,129 @@ +//****************************************************************************** +// +// Copyright (c) Microsoft. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +#import +#import "Benchmark.h" +#import + +BENCHMARK(BenchmarkSample, BasicAddition, 1024) { + size_t sum = 0; + for (size_t i = 0; i < 1000; ++i) { + sum += i; + } +} + +class BenchmarkTestFSample : public ::benchmark::BenchmarkCaseBase { + NSMutableArray* m_array; + +public: + BenchmarkTestFSample() : m_array([[NSMutableArray alloc] initWithCapacity:1000]) { + for (unsigned int i = 0; i < 1000; ++i) { + m_array[i] = [NSNumber numberWithUnsignedInt:i]; + } + } + + ~BenchmarkTestFSample() { + [m_array release]; + } + + inline void Run() { + unsigned int sum = 0; + for (NSNumber* num in m_array) { + sum += [num unsignedIntValue]; + } + } + + size_t GetRunCount() const { + return 1024; + } +}; + +BENCHMARK_F(BenchmarkSample, BenchmarkTestFSample) + +class BenchmarkTestPreRunSample : public ::benchmark::BenchmarkCaseBase { + NSMutableArray* m_array; + +public: + void PreRun() { + m_array = [[NSMutableArray alloc] initWithCapacity:1000]; + } + + inline void Run() { + [m_array release]; + } + + size_t GetRunCount() const { + return 2048; + } +}; + +BENCHMARK_F(BenchmarkSample, BenchmarkTestPreRunSample) + +class BenchmarkTestPostRunSample : public ::benchmark::BenchmarkCaseBase { + NSMutableArray* m_array; + +public: + inline void Run() { + m_array = [[NSMutableArray alloc] initWithCapacity:1000]; + } + + void PostRun() { + [m_array release]; + } + + size_t GetRunCount() const { + return 12345; + } +}; + +BENCHMARK_F(BenchmarkSample, BenchmarkTestPostRunSample) + +BENCHMARK(BenchmarkSample, BigTest, 2) { + NSMutableArray* arr = [[NSMutableArray alloc] initWithCapacity:100000]; + for (size_t i = 0; i < 100000; ++i) { + arr[i] = [NSMutableArray arrayWithCapacity:10000]; + } + [arr release]; +} + +class BenchmarkTestMultipleParameters : public ::benchmark::BenchmarkCaseBase { + CGSize m_size; + CGFloat m_mult; + +public: + BenchmarkTestMultipleParameters(const ::testing::tuple& params) + : m_size(::testing::get<0>(params)), m_mult(::testing::get<1>(params)) { + } + + inline void Run() { + CGSize other = m_size; + for (size_t i = 0; i < 1000; ++i) { + other = other * m_mult; + } + } + + size_t GetRunCount() const { + return 4545; + } +}; + +static constexpr CGSize c_sizes[] = { { 0, 0 }, { 0, 512 }, { 256, 0 }, { 256, 512 } }; +static constexpr CGFloat c_mults[] = { .1f, 3.25f, 99.99f, 1234.5f, .00001f }; +; +BENCHMARK_REGISTER_CASE_P(BenchmarkSample, + BenchmarkTestMultipleParameters, + ::testing::Combine(::testing::ValuesIn(c_sizes), ::testing::ValuesIn(c_mults)), + ::testing::tuple); \ No newline at end of file diff --git a/tests/Benchmark/EntryPoint.cpp b/tests/Benchmark/EntryPoint.cpp new file mode 100644 index 0000000000..7c295ee7ab --- /dev/null +++ b/tests/Benchmark/EntryPoint.cpp @@ -0,0 +1,155 @@ +//****************************************************************************** +// +// Copyright (c) Microsoft. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +#include +#include +#include "BenchmarkPublisher.h" +#include +#include + +#ifdef WIN32 +#include +using namespace Microsoft::WRL::Wrappers; +#endif + +using namespace benchmark; +struct __Result { + std::string testName; + Microseconds totalRuntime; + Microseconds meanRuntime; + Microseconds stdDeviation; + size_t runCount; +}; + +class PublisherBase : public BenchmarkPublisher { +public: + // Expect results.size() > 1 + virtual void RegisterCaseResults(const std::string& caseName, const std::vector& results) { + // Algorithm for calculating sample mean and variance with minimal rounding errors + Microseconds netRuntime = 0.0; + Microseconds mean = 0.0; + Microseconds q = 0.0; + size_t runCount = results.size(); + for (size_t i = 0; i < runCount; ++i) { + Microseconds previousMean = mean; + netRuntime += results[i]; + mean += (results[i] - mean) / (i + 1); + q += (results[i] - previousMean) * (results[i] - mean); + } + Microseconds sampleStandardDeviation = std::sqrt(q / (runCount - 1)); + m_results.emplace_back(__Result{ caseName, netRuntime, mean, sampleStandardDeviation, runCount }); + } + +protected: + std::vector<__Result> m_results; +}; + +class CSVBenchmarkPublisher : public PublisherBase { + std::string m_outPath; + +public: + CSVBenchmarkPublisher(std::string&& outPath) : m_outPath(outPath) { + } + + virtual void PublishResults() { + std::ofstream outFile(m_outPath); + outFile << "Case Name" + << "," + << "Total Runtime" + << "," + << "Mean Runtime" + << "," + << "Standard Deviation" + << "," + << "Run Count" + << "\n"; + for (auto res : m_results) { + outFile << res.testName << "," << res.totalRuntime << "," << res.meanRuntime << "," << res.stdDeviation << "," << res.runCount + << "\n"; + } + + outFile.flush(); + outFile.close(); + m_results.clear(); + } +}; + +class LogBenchmarkPublisher : public PublisherBase { + size_t m_maxNameWidth = 0; + +public: + virtual void RegisterCaseResults(const std::string& testName, const std::vector& results) { + m_maxNameWidth = max(m_maxNameWidth, testName.size()); + PublisherBase::RegisterCaseResults(testName, results); + } + + virtual void PublishResults() { + // Align output horizontally + std::string spaces(m_maxNameWidth - 5, ' '); + std::cout << "Case Name" << spaces << "|" + << "Total Runtime" + << "\t\t|" + << "Mean Runtime" + << "\t\t|" + << "Standard Deviation" + << "\t|" + << "Run Count" + << "\n"; + for (auto res : m_results) { + std::string spaces(m_maxNameWidth - res.testName.size() + 4, ' '); + std::cout << std::fixed << res.testName << spaces << "|" << res.totalRuntime << "\t\t|" << res.meanRuntime << "\t\t|" + << res.stdDeviation << "\t\t|" << res.runCount << "\n"; + } + + std::cout << std::endl; + m_results.clear(); + } +}; + +static std::shared_ptr s_publisher; +void BenchmarkPublisherFactory::CreatePublisher(int argc, char** argv) { + // Currently only support CSV format + for (int i = 1; i < argc && argv[i]; ++i) { + char* arg = argv[i]; + if (strncmp(arg, "--out=", 6) == 0) { + s_publisher.reset(new CSVBenchmarkPublisher(std::move(std::string(arg + 6)))); + } + } + + if (!s_publisher) { + LOG_INFO("No arguments given to benchmark. Defaulting to logging results"); + s_publisher.reset(new LogBenchmarkPublisher()); + } +} + +std::shared_ptr BenchmarkPublisherFactory::GetPublisher() { + return s_publisher; +} + +int main(int argc, char** argv) { +#ifdef WIN32 + // Initialize the windows runtime, with uninitialized upon destructor invocation. + RoInitializeWrapper initialize(RO_INIT_MULTITHREADED); + if (FAILED(initialize)) { + return -1; + } +#endif + BenchmarkPublisherFactory::CreatePublisher(argc, argv); + testing::InitGoogleTest(&argc, argv); + auto result = RUN_ALL_TESTS(); + BenchmarkPublisherFactory::GetPublisher()->PublishResults(); + return result; +} \ No newline at end of file diff --git a/tests/Benchmark/TextBenchmarkTests.mm b/tests/Benchmark/TextBenchmarkTests.mm new file mode 100644 index 0000000000..e807c6dcd9 --- /dev/null +++ b/tests/Benchmark/TextBenchmarkTests.mm @@ -0,0 +1,575 @@ +//****************************************************************************** +// +// Copyright (c) Microsoft. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +#import +#import +#import +#import +#import +#import + +#import "Benchmark.h" + +static constexpr CGSize sc_defaultSize{ 512.f, 256.f }; +static const NSString* sc_frameText = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut " + @"labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris " + @"nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " + @"velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " + @"proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; +static constexpr UniChar sc_chars[4] = { 'T', 'E', 'S', 'T' }; + +class CTFramesetterBase : public ::benchmark::BenchmarkCaseBase { +public: + CTFramesetterBase(CGSize size) { + CTParagraphStyleSetting setting; + CTTextAlignment alignment = kCTCenterTextAlignment; + setting.spec = kCTParagraphStyleSpecifierAlignment; + setting.valueSize = sizeof(CTTextAlignment); + setting.value = &alignment; + auto paragraphStyle = woc::MakeAutoCF(CTParagraphStyleCreate(&setting, std::extent::value)); + UIFont* font = [UIFont systemFontOfSize:24]; + + CFStringRef keys[2] = { kCTFontAttributeName, kCTParagraphStyleAttributeName }; + CFTypeRef values[2] = { font, paragraphStyle }; + auto dict = woc::MakeAutoCF(CFDictionaryCreate(nullptr, + (const void**)keys, + (const void**)values, + std::extent::value, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + auto attrString = woc::MakeAutoCF(CFAttributedStringCreate(nullptr, (CFStringRef)sc_frameText, dict)); + m_framesetter = woc::MakeAutoCF(CTFramesetterCreateWithAttributedString(attrString)); + m_size = size; + } + + size_t GetRunCount() const { + return 1000; + } + +protected: + woc::AutoCF m_framesetter; + CGSize m_size; +}; + +class CTFramesetterCreateFrameTest : public CTFramesetterBase { + woc::AutoCF m_path; + +public: + CTFramesetterCreateFrameTest(CGSize size) : CTFramesetterBase(size), m_path(CGPathCreateMutable()) { + CGPathAddRect(m_path, nullptr, CGRect{ CGPointZero, size }); + } + + inline void Run() { + auto frame = woc::MakeAutoCF(CTFramesetterCreateFrame(m_framesetter, CFRange{ 0, 0 }, m_path, nullptr)); + } +}; + +class CTFramesetterSuggestFrameSizeTest : public CTFramesetterBase { +public: + CTFramesetterSuggestFrameSizeTest(CGSize size) : CTFramesetterBase(size) { + } + + inline void Run() { + CTFramesetterSuggestFrameSizeWithConstraints(m_framesetter, CFRange{}, nullptr, m_size, nullptr); + } +}; + +static constexpr CGSize c_sizes[] = { { 0, 0 }, { 0, 512 }, { 256, 0 }, { 256, 512 } }; +BENCHMARK_REGISTER_CASE_P(CoreText, CTFramesetterCreateFrameTest, ::testing::ValuesIn(c_sizes), CGSize); +BENCHMARK_REGISTER_CASE_P(CoreText, CTFramesetterSuggestFrameSizeTest, ::testing::ValuesIn(c_sizes), CGSize); + +class CTLineCreateTest : public ::benchmark::BenchmarkCaseBase { + woc::AutoCF m_attrString; + +public: + CTLineCreateTest() { + UIFont* font = [UIFont systemFontOfSize:24]; + + CFStringRef keys[1] = { kCTFontAttributeName }; + CFTypeRef values[1] = { font }; + auto dict = woc::MakeAutoCF(CFDictionaryCreate(nullptr, + (const void**)keys, + (const void**)values, + std::extent::value, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + m_attrString = woc::MakeAutoCF(CFAttributedStringCreate(nullptr, (CFStringRef)sc_frameText, dict)); + } + + inline void Run() { + auto line = woc::MakeAutoCF(CTLineCreateWithAttributedString(m_attrString)); + } + + size_t GetRunCount() const { + return 1000; + } +}; + +BENCHMARK_F(CoreText, CTLineCreateTest); + +class TextBenchmarkBase : public ::benchmark::BenchmarkCaseBase { +public: + TextBenchmarkBase() { + auto colorspace = woc::MakeAutoCF(CGColorSpaceCreateDeviceRGB()); + m_context = woc::MakeAutoCF(CGBitmapContextCreate(nullptr, + sc_defaultSize.width, + sc_defaultSize.height, + 8, + sc_defaultSize.width * 4, + colorspace, + kCGImageAlphaPremultipliedFirst)); + } + +protected: + woc::AutoCF m_context; +}; + +class CTFrameDrawBase : public TextBenchmarkBase { +public: + CTFrameDrawBase() { + CTParagraphStyleSetting setting; + CTTextAlignment alignment = kCTCenterTextAlignment; + setting.spec = kCTParagraphStyleSpecifierAlignment; + setting.valueSize = sizeof(CTTextAlignment); + setting.value = &alignment; + auto paragraphStyle = woc::MakeAutoCF(CTParagraphStyleCreate(&setting, std::extent::value)); + UIFont* font = [UIFont systemFontOfSize:24]; + + CFStringRef keys[2] = { kCTFontAttributeName, kCTParagraphStyleAttributeName }; + CFTypeRef values[2] = { font, paragraphStyle }; + auto dict = woc::MakeAutoCF(CFDictionaryCreate(nullptr, + (const void**)keys, + (const void**)values, + std::extent::value, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + auto attrString = woc::MakeAutoCF(CFAttributedStringCreate(nullptr, (CFStringRef)sc_frameText, dict)); + auto framesetter = woc::MakeAutoCF(CTFramesetterCreateWithAttributedString(attrString)); + auto path = woc::MakeAutoCF(CGPathCreateMutable()); + CGPathAddRect(path, nullptr, CGRect{ CGPointZero, sc_defaultSize }); + m_frame = woc::MakeAutoCF(CTFramesetterCreateFrame(framesetter, CFRange{ 0, 0 }, path, nullptr)); + } + +protected: + woc::AutoCF m_frame; +}; + +class CTFrameDrawSingle : public CTFrameDrawBase { +public: + inline void Run() { + CTFrameDraw(m_frame, m_context); + } + + size_t GetRunCount() const { + return 1000; + } +}; + +class CTFrameDrawGroup : public CTFrameDrawBase { +public: + inline void Run() { + _CGContextPushBeginDraw(m_context); + for (size_t i = 0; i < 100; ++i) { + CTFrameDraw(m_frame, m_context); + } + _CGContextPopEndDraw(m_context); + } + + size_t GetRunCount() const { + return 50; + } +}; + +BENCHMARK_F(CoreText, CTFrameDrawSingle); +BENCHMARK_F(CoreText, CTFrameDrawGroup); + +class CTFrameDrawComplete : public TextBenchmarkBase { +public: + inline void Run() { + CTParagraphStyleSetting setting; + CTTextAlignment alignment = kCTCenterTextAlignment; + setting.spec = kCTParagraphStyleSpecifierAlignment; + setting.valueSize = sizeof(CTTextAlignment); + setting.value = &alignment; + auto paragraphStyle = woc::MakeAutoCF(CTParagraphStyleCreate(&setting, std::extent::value)); + UIFont* font = [UIFont systemFontOfSize:24]; + + CFStringRef keys[2] = { kCTFontAttributeName, kCTParagraphStyleAttributeName }; + CFTypeRef values[2] = { font, paragraphStyle }; + auto dict = woc::MakeAutoCF(CFDictionaryCreate(nullptr, + (const void**)keys, + (const void**)values, + std::extent::value, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + auto attrString = woc::MakeAutoCF(CFAttributedStringCreate(nullptr, (CFStringRef)sc_frameText, dict)); + auto framesetter = woc::MakeAutoCF(CTFramesetterCreateWithAttributedString(attrString)); + auto path = woc::MakeAutoCF(CGPathCreateMutable()); + CGPathAddRect(path, nullptr, CGRect{ CGPointZero, sc_defaultSize }); + auto frame = woc::MakeAutoCF(CTFramesetterCreateFrame(framesetter, CFRange{ 0, 0 }, path, nullptr)); + CTFrameDraw(frame, m_context); + } + + size_t GetRunCount() const { + return 1000; + } +}; + +BENCHMARK_F(CoreText, CTFrameDrawComplete); + +class CTFrameDrawYuge : public TextBenchmarkBase { +public: + CTFrameDrawYuge() { + CTParagraphStyleSetting setting; + CTTextAlignment alignment = kCTCenterTextAlignment; + setting.spec = kCTParagraphStyleSpecifierAlignment; + setting.valueSize = sizeof(CTTextAlignment); + setting.value = &alignment; + auto paragraphStyle = woc::MakeAutoCF(CTParagraphStyleCreate(&setting, std::extent::value)); + UIFont* font = [UIFont systemFontOfSize:24]; + + CFStringRef keys[2] = { kCTFontAttributeName, kCTParagraphStyleAttributeName }; + CFTypeRef values[2] = { font, paragraphStyle }; + auto dict = woc::MakeAutoCF(CFDictionaryCreate(nullptr, + (const void**)keys, + (const void**)values, + std::extent::value, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + + StrongId str = [NSMutableString stringWithCapacity:100 * sc_frameText.length]; + for (size_t i = 0; i < 100; ++i) { + [str appendString:(NSString*)sc_frameText]; + } + + auto attrString = woc::MakeAutoCF(CFAttributedStringCreate(nullptr, (CFStringRef)str.get(), dict)); + auto framesetter = woc::MakeAutoCF(CTFramesetterCreateWithAttributedString(attrString)); + auto path = woc::MakeAutoCF(CGPathCreateMutable()); + CGPathAddRect(path, nullptr, CGRect{ CGPointZero, sc_defaultSize * 10.0 }); + m_frame = woc::MakeAutoCF(CTFramesetterCreateFrame(framesetter, CFRange{ 0, 0 }, path, nullptr)); + } + + inline void Run() { + CTFrameDraw(m_frame, m_context); + } + + size_t GetRunCount() const { + return 100; + } + +private: + woc::AutoCF m_frame; +}; + +BENCHMARK_F(CoreText, CTFrameDrawYuge); + +class CTLineDrawBase : public TextBenchmarkBase { +public: + CTLineDrawBase() { + UIFont* font = [UIFont systemFontOfSize:24]; + + CFStringRef keys[1] = { kCTFontAttributeName }; + CFTypeRef values[1] = { font }; + auto dict = woc::MakeAutoCF(CFDictionaryCreate(nullptr, + (const void**)keys, + (const void**)values, + std::extent::value, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + auto attrString = woc::MakeAutoCF(CFAttributedStringCreate(nullptr, (CFStringRef)sc_frameText, dict)); + m_line = woc::MakeAutoCF(CTLineCreateWithAttributedString(attrString)); + } + +protected: + woc::AutoCF m_line; +}; + +class CTLineDrawSingle : public CTLineDrawBase { +public: + inline void Run() { + CTLineDraw(m_line, m_context); + } + + size_t GetRunCount() const { + return 10000; + } +}; + +class CTLineDrawGroup : public CTLineDrawBase { +public: + inline void Run() { + _CGContextPushBeginDraw(m_context); + for (size_t i = 0; i < 10000; ++i) { + CTLineDraw(m_line, m_context); + } + _CGContextPopEndDraw(m_context); + } + + size_t GetRunCount() const { + return 50; + } +}; + +BENCHMARK_F(CoreText, CTLineDrawSingle); +BENCHMARK_F(CoreText, CTLineDrawGroup); + +class CTLineDrawComplete : public TextBenchmarkBase { +public: + inline void Run() { + UIFont* font = [UIFont systemFontOfSize:24]; + + CFStringRef keys[1] = { kCTFontAttributeName }; + CFTypeRef values[1] = { font }; + auto dict = woc::MakeAutoCF(CFDictionaryCreate(nullptr, + (const void**)keys, + (const void**)values, + std::extent::value, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); + auto attrString = woc::MakeAutoCF(CFAttributedStringCreate(nullptr, (CFStringRef)sc_frameText, dict)); + auto line = woc::MakeAutoCF(CTLineCreateWithAttributedString(attrString)); + CTLineDraw(line, m_context); + } + + size_t GetRunCount() const { + return 1000; + } +}; + +BENCHMARK_F(CoreText, CTLineDrawComplete); + +class CTRunDrawBase : public CTLineDrawBase { +public: + CTRunDrawBase() : m_run((CTRunRef)CFArrayGetValueAtIndex(CTLineGetGlyphRuns(m_line), 0)) { + } + +protected: + // Weak reference to a run since it's owned by the CTLine in the base class + CTRunRef m_run; +}; + +class CTRunDrawSingle : public CTRunDrawBase { +public: + inline void Run() { + CTRunDraw(m_run, m_context, CFRange{}); + } + + size_t GetRunCount() const { + return 10000; + } +}; + +class CTRunDrawGroup : public CTRunDrawBase { +public: + inline void Run() { + _CGContextPushBeginDraw(m_context); + for (size_t i = 0; i < 10000; ++i) { + CTRunDraw(m_run, m_context, CFRange{}); + } + _CGContextPopEndDraw(m_context); + } + + size_t GetRunCount() const { + return 50; + } +}; + +class CTRunDrawRotated : public CTRunDrawSingle { +public: + CTRunDrawRotated() { + CGAffineTransform textMatrix = CGContextGetTextMatrix(m_context); + CGContextSetTextMatrix(m_context, CGAffineTransformRotate(textMatrix, 45.0 * M_PI / 180.0)); + } +}; + +BENCHMARK_F(CoreText, CTRunDrawSingle); +BENCHMARK_F(CoreText, CTRunDrawGroup); +BENCHMARK_F(CoreText, CTRunDrawRotated); + +class ShowGlyphsBase : public TextBenchmarkBase { +public: + ShowGlyphsBase() : m_glyphs(std::extent::value), m_font(std::move(CGFontCreateWithFontName(CFSTR("Arial")))) { + CGContextSetFont(m_context, m_font); + CGContextSetFontSize(m_context, 20); + + auto ctFont = woc::MakeAutoCF(CTFontCreateWithName(CFSTR("Arial"), 20, nullptr)); + EXPECT_TRUE(CTFontGetGlyphsForCharacters(ctFont, sc_chars, m_glyphs.data(), std::extent::value)); + } + + size_t GetRunCount() const { + return 100000; + } + +protected: + std::vector m_glyphs; + woc::AutoCF m_font; +}; + +class ShowGlyphsTest : public ShowGlyphsBase { +public: + inline void Run() { + CGContextShowGlyphs(m_context, m_glyphs.data(), m_glyphs.size()); + } +}; + +class ShowGlyphsWithAdvances : public ShowGlyphsBase { + std::vector m_advances; + +public: + ShowGlyphsWithAdvances() : m_advances(m_glyphs.size(), CGSize{ 5.0, 10.0 }) { + } + + inline void Run() { + CGContextShowGlyphsWithAdvances(m_context, m_glyphs.data(), m_advances.data(), m_glyphs.size()); + } +}; + +BENCHMARK_F(CGContext, ShowGlyphsTest); +BENCHMARK_F(CGContext, ShowGlyphsWithAdvances); + +class NSString_UIKitBase : public TextBenchmarkBase { +public: + NSString_UIKitBase() : m_font([UIFont systemFontOfSize:20]) { + UIGraphicsPushContext(m_context); + } + + ~NSString_UIKitBase() { + UIGraphicsPopContext(); + } + +protected: + StrongId m_font; +}; + +class DrawAtPoint : public NSString_UIKitBase { +public: + inline void Run() { + [sc_frameText drawAtPoint:{ 0, 0 } withFont:m_font]; + } + + size_t GetRunCount() const { + return 1000; + } +}; + +class DrawAtPointGroup : public NSString_UIKitBase { +public: + inline void Run() { + _CGContextPushBeginDraw(m_context); + for (size_t i = 0; i < 100; ++i) { + [sc_frameText drawAtPoint:{ 0, 0 } withFont:m_font]; + } + _CGContextPopEndDraw(m_context); + } + + size_t GetRunCount() const { + return 50; + } +}; + +BENCHMARK_F(NSString, DrawAtPoint); +BENCHMARK_F(NSString, DrawAtPointGroup); + +class DrawInRect : public NSString_UIKitBase { + CGSize m_rectSize; + +public: + DrawInRect(CGSize size) : m_rectSize(size) { + } + + inline void Run() { + [sc_frameText drawInRect:{ { 0, 0 }, m_rectSize } withFont:m_font]; + } + + size_t GetRunCount() const { + return 1000; + } +}; + +class DrawInRectGroup : public NSString_UIKitBase { + CGSize m_rectSize; + +public: + DrawInRectGroup(CGSize size) : m_rectSize(size) { + } + + inline void Run() { + _CGContextPushBeginDraw(m_context); + for (size_t i = 0; i < 100; ++i) { + [sc_frameText drawInRect:{ { 0, 0 }, m_rectSize } withFont:m_font]; + } + _CGContextPopEndDraw(m_context); + } + + size_t GetRunCount() const { + return 50; + } +}; + +BENCHMARK_REGISTER_CASE_P(NSString, DrawInRect, ::testing::ValuesIn(c_sizes), CGSize); +BENCHMARK_REGISTER_CASE_P(NSString, DrawInRectGroup, ::testing::ValuesIn(c_sizes), CGSize); + +class SizeWithFont : public NSString_UIKitBase { +public: + SizeWithFont(CGSize size) : m_rectSize(size) { + } + + inline void Run() { + [sc_frameText sizeWithFont:m_font constrainedToSize:m_rectSize]; + } + + size_t GetRunCount() const { + return 1000; + } + +protected: + CGSize m_rectSize; +}; + +BENCHMARK_REGISTER_CASE_P(NSString, SizeWithFont, ::testing::ValuesIn(c_sizes), CGSize); + +BENCHMARK(CoreText, CTFontCreateWithName, 10000) { + CTFontRef font = CTFontCreateWithName(CFSTR("Arial"), 24, nullptr); + CFRelease(font); +} + +BENCHMARK(CoreText, CTFontDescriptor, 10000) { + CTFontDescriptorRef descriptor = CTFontDescriptorCreateWithNameAndSize(CFSTR("Arial"), 88.5); + CTFontRef font = CTFontCreateWithFontDescriptor(descriptor, 25.25, nullptr); + CFRelease(descriptor); + CFRelease(font); +} + +class CTFontGetGlyphsTest : public ::benchmark::BenchmarkCaseBase { + woc::AutoCF m_font; + std::vector m_glyphs; + +public: + CTFontGetGlyphsTest() : m_font(CTFontCreateWithName(CFSTR("Arial"), 25, nullptr)), m_glyphs(std::extent::value) { + } + inline void Run() { + CTFontGetGlyphsForCharacters(m_font, sc_chars, m_glyphs.data(), m_glyphs.size()); + } + + size_t GetRunCount() const { + return 1000; + } +}; + +BENCHMARK_F(CoreText, CTFontGetGlyphsTest);