From daf09510c20435825bbb09afb323856b5570d53f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Sat, 3 Oct 2026 18:37:23 -0700 Subject: [PATCH] Added Superluminal. (#442) --- include/bx/inline/superluminal.inl | 55 +++++++++++ include/bx/superluminal.h | 106 ++++++++++++++++++++ scripts/bx.lua | 1 + src/amalgamated.cpp | 1 + src/superluminal.cpp | 151 +++++++++++++++++++++++++++++ tests/superluminal_test.cpp | 47 +++++++++ 6 files changed, 361 insertions(+) create mode 100644 include/bx/inline/superluminal.inl create mode 100644 include/bx/superluminal.h create mode 100644 src/superluminal.cpp create mode 100644 tests/superluminal_test.cpp diff --git a/include/bx/inline/superluminal.inl b/include/bx/inline/superluminal.inl new file mode 100644 index 0000000..dfd1a8a --- /dev/null +++ b/include/bx/inline/superluminal.inl @@ -0,0 +1,55 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx/blob/master/LICENSE + */ + +#ifndef BX_SUPERLUMINAL_H_HEADER_GUARD +# error "Must be included from bx/superluminal.h!" +#endif // BX_SUPERLUMINAL_H_HEADER_GUARD + +#include + +namespace bx +{ + inline bool Superluminal::isLoaded() const + { + return NULL != m_dll; + } + + inline void Superluminal::beginEvent(const char* _id, const char* _data, uint32_t _abgr) + { + // Superluminal's color is RGBA, all bits set is default color. + const uint32_t rgba = 0 == _abgr + ? UINT32_MAX + : (endianSwap(_abgr) & 0xffffff00) | 0xff + ; + + m_beginEvent(_id, _data, rgba); + } + + inline void Superluminal::endEvent() + { + m_endEvent(); + } + + inline void Superluminal::registerFiber(uint64_t _fiber) + { + m_registerFiber(_fiber); + } + + inline void Superluminal::unregisterFiber(uint64_t _fiber) + { + m_unregisterFiber(_fiber); + } + + inline void Superluminal::beginFiberSwitch(uint64_t _current, uint64_t _new) + { + m_beginFiberSwitch(_current, _new); + } + + inline void Superluminal::endFiberSwitch(uint64_t _fiber) + { + m_endFiberSwitch(_fiber); + } + +} // namespace bx diff --git a/include/bx/superluminal.h b/include/bx/superluminal.h new file mode 100644 index 0000000..13e2334 --- /dev/null +++ b/include/bx/superluminal.h @@ -0,0 +1,106 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx/blob/master/LICENSE + */ + +#ifndef BX_SUPERLUMINAL_H_HEADER_GUARD +#define BX_SUPERLUMINAL_H_HEADER_GUARD + +#include "bx.h" + +namespace bx +{ + /// Superluminal profiler's Performance API (https://superluminal.eu), loaded at + /// runtime from `PerformanceAPI.dll`, without Superluminal headers, or import + /// library. DLL is looked up next to executable, and then in Superluminal's + /// install directory. Only Windows is supported. When API is not loaded, all + /// functions do nothing. + class Superluminal + { + BX_CLASS(Superluminal + , NO_COPY + ); + + public: + /// + Superluminal(); + + /// Unloads API if it's loaded. + ~Superluminal(); + + /// Load API. + /// + /// @returns True if API is loaded. + /// + bool load(); + + /// Unload API. + void unload(); + + /// Returns true if API is loaded. + bool isLoaded() const; + + /// Begin instrumentation event. + /// + /// @param[in] _id Event id. It must be string literal, Superluminal keeps + /// pointer to it. + /// @param[in] _data Data that's shown with event, it can be dynamic string, or + /// NULL. + /// @param[in] _abgr Color in ABGR format, 0 is default color. + /// + void beginEvent(const char* _id, const char* _data = NULL, uint32_t _abgr = 0); + + /// End instrumentation event that was most recently begun on calling thread. + void endEvent(); + + /// Register fiber. Fiber must be registered before it's switched to. Thread + /// that switches to fibers is fiber too, it must be registered as well. + /// + /// @param[in] _fiber Unique id of fiber. + /// + void registerFiber(uint64_t _fiber); + + /// Unregister fiber. + void unregisterFiber(uint64_t _fiber); + + /// Called right before switching from fiber `_current` to fiber `_new`. + /// Instrumentation events that are open on fiber stay open while it's + /// switched out. + void beginFiberSwitch(uint64_t _current, uint64_t _new); + + /// Called right after switch, by fiber that called `beginFiberSwitch`, once + /// it's switched back in. `_fiber` is that fiber's id. + void endFiberSwitch(uint64_t _fiber); + + private: + struct SuppressTailCallOptimization + { + int64_t suppressTailCall[3]; + }; + + typedef void (*BeginEventFn)(const char* _id, const char* _data, uint32_t _rgba); + typedef SuppressTailCallOptimization (*EndEventFn)(); + typedef void (*FiberFn)(uint64_t _fiber); + typedef void (*BeginFiberSwitchFn)(uint64_t _current, uint64_t _new); + + static void stubBeginEvent(const char* _id, const char* _data, uint32_t _rgba); + static SuppressTailCallOptimization stubEndEvent(); + static void stubFiber(uint64_t _fiber); + static void stubBeginFiberSwitch(uint64_t _current, uint64_t _new); + + void stubAll(); + + void* m_dll; + BeginEventFn m_beginEvent; + EndEventFn m_endEvent; + FiberFn m_registerFiber; + FiberFn m_unregisterFiber; + BeginFiberSwitchFn m_beginFiberSwitch; + FiberFn m_endFiberSwitch; + }; + +} // namespace bx + +#include "inline/superluminal.inl" + +#endif // BX_SUPERLUMINAL_H_HEADER_GUARD diff --git a/scripts/bx.lua b/scripts/bx.lua index f84d202..c07897a 100644 --- a/scripts/bx.lua +++ b/scripts/bx.lua @@ -88,6 +88,7 @@ project "bx" path.join(BX_DIR, "src/settings.cpp"), path.join(BX_DIR, "src/sort.cpp"), path.join(BX_DIR, "src/string.cpp"), + path.join(BX_DIR, "src/superluminal.cpp"), path.join(BX_DIR, "src/thread.cpp"), path.join(BX_DIR, "src/timer.cpp"), path.join(BX_DIR, "src/url.cpp"), diff --git a/src/amalgamated.cpp b/src/amalgamated.cpp index 1210681..4532b54 100644 --- a/src/amalgamated.cpp +++ b/src/amalgamated.cpp @@ -24,6 +24,7 @@ #include "settings.cpp" #include "sort.cpp" #include "string.cpp" +#include "superluminal.cpp" #include "thread.cpp" #include "timer.cpp" #include "url.cpp" diff --git a/src/superluminal.cpp b/src/superluminal.cpp new file mode 100644 index 0000000..7d5c4c0 --- /dev/null +++ b/src/superluminal.cpp @@ -0,0 +1,151 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx/blob/master/LICENSE + */ + +#include +#include +#include + +namespace bx +{ + namespace + { + /// Finds `PerformanceAPI.dll` next to executable first, then where Superluminal is + /// installed. Only file that exists is opened, failed `dlopen` warns. + bool findDll(FilePath& _outPath) + { + _outPath.set(FilePath(Dir::Executable).getPath() ); + _outPath.join("PerformanceAPI.dll"); + + FileInfo fileInfo; + + if (stat(fileInfo, _outPath) ) + { + return true; + } + + char programFiles[kMaxFilePath]; + uint32_t size = sizeof(programFiles); + + if (!getEnv(programFiles, &size, "ProgramFiles") ) + { + return false; + } + + _outPath.set(programFiles); + _outPath.join(BX_ENABLED(BX_CPU_ARM) + ? "Superluminal/Performance/API/dll/arm64/PerformanceAPI.dll" + : "Superluminal/Performance/API/dll/x64/PerformanceAPI.dll" + ); + + return stat(fileInfo, _outPath); + } + + } // namespace + + void Superluminal::stubBeginEvent(const char* _id, const char* _data, uint32_t _rgba) + { + BX_UNUSED(_id, _data, _rgba); + } + + Superluminal::SuppressTailCallOptimization Superluminal::stubEndEvent() + { + return {}; + } + + void Superluminal::stubFiber(uint64_t _fiber) + { + BX_UNUSED(_fiber); + } + + void Superluminal::stubBeginFiberSwitch(uint64_t _current, uint64_t _new) + { + BX_UNUSED(_current, _new); + } + + Superluminal::Superluminal() + : m_dll(NULL) + { + stubAll(); + } + + Superluminal::~Superluminal() + { + unload(); + } + + void Superluminal::stubAll() + { + m_beginEvent = stubBeginEvent; + m_endEvent = stubEndEvent; + m_registerFiber = stubFiber; + m_unregisterFiber = stubFiber; + m_beginFiberSwitch = stubBeginFiberSwitch; + m_endFiberSwitch = stubFiber; + } + + bool Superluminal::load() + { + if (!BX_ENABLED(BX_PLATFORM_WINDOWS) ) + { + return false; + } + + if (NULL != m_dll) + { + return true; + } + + FilePath path; + + if (!findDll(path) ) + { + return false; + } + + void* dll = dlopen(path); + + if (NULL == dll) + { + return false; + } + + typedef int (*GetApiFn)(int32_t _version, void** _outFuncs); + GetApiFn getApi = dlsym(dll, "PerformanceAPI_GetAPI"); + + // Table layout of API version 3.0. + void* funcs[11]; + + if (NULL == getApi + || 0 == getApi(0x30000, funcs) ) + { + BX_TRACE("Failed to obtain Superluminal's API from %s!", path.getCPtr() ); + dlclose(dll); + return false; + } + + m_dll = dll; + m_beginEvent = (BeginEventFn )funcs[2]; + m_endEvent = (EndEventFn )funcs[6]; + m_registerFiber = (FiberFn )funcs[7]; + m_unregisterFiber = (FiberFn )funcs[8]; + m_beginFiberSwitch = (BeginFiberSwitchFn)funcs[9]; + m_endFiberSwitch = (FiberFn )funcs[10]; + + BX_TRACE("Superluminal's API is loaded from %s.", path.getCPtr() ); + + return true; + } + + void Superluminal::unload() + { + if (NULL != m_dll) + { + stubAll(); + dlclose(m_dll); + m_dll = NULL; + } + } + +} // namespace bx diff --git a/tests/superluminal_test.cpp b/tests/superluminal_test.cpp new file mode 100644 index 0000000..ec05e20 --- /dev/null +++ b/tests/superluminal_test.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2011-2026 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx/blob/master/LICENSE + */ + +#include "test.h" +#include + +TEST_CASE("Superluminal", "[superluminal]") +{ + bx::Superluminal superluminal; + REQUIRE(!superluminal.isLoaded() ); + + // Calls without API loaded do nothing. + superluminal.beginEvent("test", "data", 0xff0000ff); + superluminal.endEvent(); + superluminal.registerFiber(1); + superluminal.unregisterFiber(1); + + if (!superluminal.load() ) + { + REQUIRE(!superluminal.isLoaded() ); + SKIP("Superluminal is not installed."); + } + + REQUIRE(superluminal.isLoaded() ); + REQUIRE(superluminal.load() ); + + superluminal.beginEvent("test"); + superluminal.beginEvent("test", "data", 0xff00ff00); + superluminal.endEvent(); + superluminal.endEvent(); + + // Thread is fiber too, before it switches to other fibers. + const uint64_t thread = 1; + const uint64_t fiber = 2; + superluminal.registerFiber(thread); + superluminal.registerFiber(fiber); + superluminal.unregisterFiber(fiber); + superluminal.unregisterFiber(thread); + + superluminal.unload(); + REQUIRE(!superluminal.isLoaded() ); + + superluminal.beginEvent("test"); + superluminal.endEvent(); +}