diff --git a/CMakeLists.txt b/CMakeLists.txt index b992d62..58ad026 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ if (MSVC) endif () list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/dep/superluminal/API") include(${PROJECT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake) @@ -20,6 +21,22 @@ set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ".") include(InstallRequiredSystemLibraries) add_subdirectory(dep/glm) +find_package(unofficial-angle CONFIG REQUIRED) +find_package(CURL CONFIG REQUIRED) +find_package(fmt CONFIG REQUIRED) +find_package(glfw3 CONFIG REQUIRED) +find_package(gli CONFIG REQUIRED) +find_package(LuaJIT REQUIRED) +find_package(Microsoft.GSL CONFIG REQUIRED) +find_package(PkgConfig REQUIRED) +find_package(re2 CONFIG REQUIRED) +find_package(sol2 CONFIG REQUIRED) +find_package(SuperluminalAPI) +find_package(Threads REQUIRED) +find_package(zstd REQUIRED) +find_package(ZLIB REQUIRED) +find_package(WebP) + set(SIMPLEGRAPHIC_SOURCES "config.h" "dep/stb/stb_image.h" @@ -67,8 +84,6 @@ set(SIMPLEGRAPHIC_SOURCES "ui_api.cpp" "ui_console.cpp" "ui_console.h" - "ui_debug.cpp" - "ui_debug.h" "ui_local.h" "ui_main.cpp" "ui_main.h" @@ -108,6 +123,7 @@ target_compile_definitions(SimpleGraphic "GLFW_INCLUDE_NONE" "GL_SILENCE_DEPRECATION" "SIMPLEGRAPHIC_EXPORTS" + "PERFORMANCEAPI_ENABLED=$" ) target_include_directories(SimpleGraphic @@ -116,21 +132,6 @@ target_include_directories(SimpleGraphic "${CMAKE_CURRENT_SOURCE_DIR}/engine" ) -find_package(unofficial-angle CONFIG REQUIRED) -find_package(CURL CONFIG REQUIRED) -find_package(fmt CONFIG REQUIRED) -find_package(glfw3 CONFIG REQUIRED) -find_package(gli CONFIG REQUIRED) -find_package(LuaJIT REQUIRED) -find_package(Microsoft.GSL CONFIG REQUIRED) -find_package(PkgConfig REQUIRED) -find_package(re2 CONFIG REQUIRED) -find_package(sol2 CONFIG REQUIRED) -find_package(Threads REQUIRED) -find_package(zstd REQUIRED) -find_package(ZLIB REQUIRED) -find_package(WebP) - add_library(cmp_core STATIC dep/compressonator/cmp_core/source/cmp_core.cpp dep/compressonator/cmp_core/source/cmp_core.h @@ -187,6 +188,7 @@ target_include_directories(SimpleGraphic PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/dep/glad/include ${CMAKE_CURRENT_SOURCE_DIR}/dep/stb + ${CMAKE_CURRENT_SOURCE_DIR}/dep/superluminal/API/include ) if (CMAKE_SYSTEM_NAME MATCHES "Linux") @@ -212,6 +214,13 @@ if (WIN32) PRIVATE "winmm.lib" ) + + if (SuperluminalAPI_FOUND) + target_link_libraries(SimpleGraphic + PRIVATE + SuperluminalAPI + ) + endif () endif () if (APPLE) diff --git a/dep/superluminal/API/FindSuperluminalAPI.cmake b/dep/superluminal/API/FindSuperluminalAPI.cmake new file mode 100644 index 0000000..8d52551 --- /dev/null +++ b/dep/superluminal/API/FindSuperluminalAPI.cmake @@ -0,0 +1,83 @@ +# This module can be used to find the Superluminal API libs & headers via find_package. +# +# For example: find_package(SuperluminalAPI REQUIRED) +# +# You can use it by adding the API directory of your Superluminal install to your CMAKE_PREFIX_PATH, or alternatively +# by copying the entire API directory to a place of your own choosing and adding that location to CMAKE_PREFIX_PATH. +# +# The following (optional) variables can be set prior to issueing the find_package command: +# - SuperluminalAPI_ROOT : The root directory where the libs & headers should be found. For example \API. +# If this is not set, the libs & headers are assumed to be next to the location of FindSuperluminalAPI.cmake +# - SuperluminalAPI_USE_STATIC_RUNTIME : If this is set, the libraries linked to the static C runtime (i.e. /MT and /MTd) will be returned +# If not set, the libraries linked to the dynamic C runtime (i.e. /MD and /MDd) will be returned +# +# On completion of find_package, the following variables will be set: +# +# SuperluminalAPI_FOUND : Whether the package was found +# SuperluminalAPI_LIBS_RELEASE : The Release libraries to link against +# SuperluminalAPI_LIBS_DEBUG : The Debug libraries to link against +# SuperluminalAPI_INCLUDE_DIRS : The include directories to use +# +# In addition, if find_package completed successfully, the target "SuperluminalAPI" will be defined. +# You should prefer to consume this target via target_link_libraries(YOUR_TARGET PRIVATE SuperluminalAPI), rather than by using the above variables directly +SET(SuperluminalAPI_SEARCH_PATHS + ${CMAKE_CURRENT_LIST_DIR} + ${SuperluminalAPI_ROOT} + ) + +find_path(SuperluminalAPI_INCLUDE_DIRS Superluminal/PerformanceAPI.h + PATHS ${SuperluminalAPI_SEARCH_PATHS} + PATH_SUFFIXES include + ) + +if (CMAKE_SIZEOF_VOID_P MATCHES 4) + set(SELECTED_ARCH "x86") +else(CMAKE_SIZEOF_VOID_P MATCHES 8) + set(SELECTED_ARCH "x64") +endif() + +if(WIN32) + if (NOT (${MSVC_VERSION} LESS 1900)) # Test for VS2015 and higher (older CMake versions don't have a >= operator) + if (${SuperluminalAPI_USE_STATIC_RUNTIME}) + find_library(SuperluminalAPI_LIBS_RELEASE + NAMES PerformanceAPI_MT + PATH_SUFFIXES lib/${SELECTED_ARCH} + PATHS ${SuperluminalAPI_SEARCH_PATHS} + ) + + find_library(SuperluminalAPI_LIBS_DEBUG + NAMES PerformanceAPI_MTd + PATH_SUFFIXES lib/${SELECTED_ARCH} + PATHS ${SuperluminalAPI_SEARCH_PATHS} + ) + else() + find_library(SuperluminalAPI_LIBS_RELEASE + NAMES PerformanceAPI_MD + PATH_SUFFIXES lib/${SELECTED_ARCH} + PATHS ${SuperluminalAPI_SEARCH_PATHS} + ) + + find_library(SuperluminalAPI_LIBS_DEBUG + NAMES PerformanceAPI_MDd + PATH_SUFFIXES lib/${SELECTED_ARCH} + PATHS ${SuperluminalAPI_SEARCH_PATHS} + ) + endif() + else() + message(SEND_ERROR "Your Visual Studio version is not currently supported. Please contact Superluminal support.") + endif() +endif() + +mark_as_advanced(SuperluminalAPI_FOUND) +mark_as_advanced(SuperluminalAPI_LIBS_RELEASE) +mark_as_advanced(SuperluminalAPI_LIBS_DEBUG) +mark_as_advanced(SuperluminalAPI_INCLUDE_DIRS) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SuperluminalAPI REQUIRED_VARS SuperluminalAPI_INCLUDE_DIRS SuperluminalAPI_LIBS_RELEASE SuperluminalAPI_LIBS_DEBUG) + +if(SuperluminalAPI_FOUND AND NOT TARGET SuperluminalAPI) + add_library(SuperluminalAPI INTERFACE IMPORTED) + target_include_directories(SuperluminalAPI INTERFACE "${SuperluminalAPI_INCLUDE_DIRS}") + target_link_libraries(SuperluminalAPI INTERFACE debug "${SuperluminalAPI_LIBS_DEBUG}" optimized "${SuperluminalAPI_LIBS_RELEASE}" ) +endif() \ No newline at end of file diff --git a/dep/superluminal/API/dll/x64/PerformanceAPI.dll b/dep/superluminal/API/dll/x64/PerformanceAPI.dll new file mode 100644 index 0000000..9321687 Binary files /dev/null and b/dep/superluminal/API/dll/x64/PerformanceAPI.dll differ diff --git a/dep/superluminal/API/include/Superluminal/PerformanceAPI.h b/dep/superluminal/API/include/Superluminal/PerformanceAPI.h new file mode 100644 index 0000000..62849aa --- /dev/null +++ b/dep/superluminal/API/include/Superluminal/PerformanceAPI.h @@ -0,0 +1,284 @@ +/* +BSD LICENSE + +Copyright (c) 2019-2020 Superluminal. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once + +#include "PerformanceAPI_capi.h" + +// When PERFORMANCEAPI_ENABLED is defined to 0, all calls to the PerformanceAPI (either through macro or direct function calls) will be compiled out. +#ifndef PERFORMANCEAPI_ENABLED + #ifdef _WIN32 + #define PERFORMANCEAPI_ENABLED 1 + #else + #define PERFORMANCEAPI_ENABLED 0 + #endif +#endif + +/* ------------------------------------------------------------------------ +* Documentation +* ------------------------------------------------------------------------ +* +* NOTE: The C++ free functions in this header are deprecated. They remain here only for backwards compatibility. The C functions, prefixed with PerformanceAPI_ and found in PerformanceAPI_capi.h, +* should be used instead. The C++ functions are no longer maintained and new features will be added to the C interface only. +* The InstrumentationScope helper is *not* deprecated. +* +* The Performance API can be used to augment the sampling data that is naturally collected by Superluminal Performance +* with instrumentation data. Instrumentation data is seamlessly blended in all views in the UI. +* +* To send instrumentation data to Superluminal, two mechanisms are provided: +* - The InstrumentationScope class. This class will signal the start of a scope in its constructor and signal the end of the scope in its destructor. +* InstrumentationScopes can be freely nested. +* +* - The PerformanceAPI_BeginScope/PerformanceAPI_EndScope free functions. These functions are designed for integration with existing profiling systems that, for example, +* already define their own scope-based profiling classes. +* +* Note: calls to the Begin/EndScope functions must be within the same function. For example, it is not allowed to call BeginScope in function Foo +* and EndScope in function Bar. +* +* When sending an instrumentation event through either of these mechanisms, two pieces of data can be provided: +* - The event ID [required] : This must be a static string (e.g. a regular C string literal). It is used to distinguish events +* in the UI and is displayed in all views (Instrumentation Chart, Timeline, CallGraph). +* It is important that the ID of a particular scope remains the same over the lifetime of the program: +* it's not allowed to use a string that changes for every invocation of the function/scope. +* Some examples of IDs: the name of a function ("Game::Update"), the operation being performed ("ReadFile"), etc +* +* - The event Data [optional] : This must be a string that is either dynamically allocated or a regular string literal. You are free to put +* whatever data you want in the string; there are no restrictions. The data is also free to change over the lifetime of the program. +* The intent of the data string is to include data in the event that can differ per instance. +* This data is displayed in the Instrumentation Chart and Timeline. +* Some examples of Data strings: the current frame number (for "Game::Update"), the path of the file being read (for "ReadFile"), etc +* This parameter is optional; use nullptr as argument if you don't have any contextual data. +* +* - The event Color [optional] : This is a color that will be used to display the event in the timeline. The color for a specific scope is coupled to the ID and must +* be the same over the lifetime of the program. It's an RGB value encoded as an uint32_t: RRGGBB00. +* You can use PERFORMANCEAPI_MAKE_COLOR to create the uint32_t from 3 RGB values in the range of [0, 255]. +* This parameter is optional; use PERFORMANCEAPI_DEFAULT_COLOR as argument to use the default coloring. +* +* All const char* arguments in the API are assumed to be UTF8 encoded strings (i.e. non-ASCII chars are fully supported). +*/ +namespace PerformanceAPI +{ +#if PERFORMANCEAPI_ENABLED + // An InstrumentationScope measures the time of the scope it is contained in; time starts when the constructor is called and ends when the + // destructor is called. + // An ID for the scope must be provided, with optional data and an optional color (see documentation at the top of this file for more info) + // While you can manually use this, it's usually more convenient to use the PERFORMANCEAPI_* macros + struct InstrumentationScope final + { + /** + * @param inID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + */ + InstrumentationScope(const char* inID); + + /** + * @param inID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData The data for this scope as an UTF8 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + */ + InstrumentationScope(const char* inID, const char* inData); + + /** + * @param inID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData The data for this scope as an UTF8 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + * @param inColor The color for this scope. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + */ + InstrumentationScope(const char* inID, const char* inData, uint32_t inColor); + + /** + * @param inID The ID of this scope as an UTF16 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + */ + InstrumentationScope(const wchar_t* inID); + + /** + * @param inID The ID of this scope as an UTF16 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData The data for this scope as an UTF16 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + */ + InstrumentationScope(const wchar_t* inID, const wchar_t* inData); + + /** + * @param inID The ID of this scope as an UTF16 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData The data for this scope as an UTF16 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + * @param inColor The color for this scope. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + */ + InstrumentationScope(const wchar_t* inID, const wchar_t* inData, uint32_t inColor); + + ~InstrumentationScope(); + }; + + // Private helper macros + #define PERFORMANCEAPI_CAT_IMPL(a, b) a##b + #define PERFORMANCEAPI_CAT(a, b) PERFORMANCEAPI_CAT_IMPL(a, b) + #define PERFORMANCEAPI_UNIQUE_IDENTIFIER(a) PERFORMANCEAPI_CAT(a, __LINE__) + + /** + * Creates an InstrumentationScope with the specified ID. + * + * @param InstrumentationID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + */ + #define PERFORMANCEAPI_INSTRUMENT(InstrumentationID) PerformanceAPI::InstrumentationScope PERFORMANCEAPI_UNIQUE_IDENTIFIER(__instrumentation_scope__)((InstrumentationID)); + + /** + * Creates an InstrumentationScope with the specified ID and runtime data + * + * @param InstrumentationID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program. See docs at the top of this file. + * @param InstrumentationData The data for this scope as an UTF8 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + */ + #define PERFORMANCEAPI_INSTRUMENT_DATA(InstrumentationID, InstrumentationData) PerformanceAPI::InstrumentationScope PERFORMANCEAPI_UNIQUE_IDENTIFIER(__instrumentation_scope__)((InstrumentationID), (InstrumentationData)); + + /** + * Creates an InstrumentationScope with the specified ID and color + * + * @param InstrumentationID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program. See docs at the top of this file. + * @param InstrumentationColor The color for this scope. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + */ + #define PERFORMANCEAPI_INSTRUMENT_COLOR(InstrumentationID, InstrumentationColor) PerformanceAPI::InstrumentationScope PERFORMANCEAPI_UNIQUE_IDENTIFIER(__instrumentation_scope__)((InstrumentationID), "", (InstrumentationColor)); + + /** + * Creates an InstrumentationScope with the specified ID, runtime data and color + * + * @param InstrumentationID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program. See docs at the top of this file. + * @param InstrumentationData The data for this scope as an UTF8 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + * @param InstrumentationColor The color for this scope. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + */ + #define PERFORMANCEAPI_INSTRUMENT_DATA_COLOR(InstrumentationID, InstrumentationData, InstrumentationColor) PerformanceAPI::InstrumentationScope PERFORMANCEAPI_UNIQUE_IDENTIFIER(__instrumentation_scope__)((InstrumentationID), (InstrumentationData), (InstrumentationColor)); + + /** + * Convenience wrapper around PERFORMANCEAPI_INSTRUMENT to create an InstrumentationScope with the name of the function as ID + */ + #define PERFORMANCEAPI_INSTRUMENT_FUNCTION() PERFORMANCEAPI_INSTRUMENT(__FUNCTION__) + + /** + * Convenience wrapper around PERFORMANCEAPI_INSTRUMENT_DATA to create an InstrumentationScope with the name of the function as ID + * + * @param InstrumentationData The data for this scope as an UTF8 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + */ + #define PERFORMANCEAPI_INSTRUMENT_FUNCTION_DATA(InstrumentationData) PERFORMANCEAPI_INSTRUMENT_DATA(__FUNCTION__, (InstrumentationData)) + + /** + * Convenience wrapper around PERFORMANCEAPI_INSTRUMENT_COLOR to create an InstrumentationScope with the name of the function as ID + * + * @param InstrumentationColor The color for this scope. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + */ + #define PERFORMANCEAPI_INSTRUMENT_FUNCTION_COLOR(InstrumentationColor) PERFORMANCEAPI_INSTRUMENT_COLOR(__FUNCTION__, (InstrumentationColor)) + + /** + * Convenience wrapper around PERFORMANCEAPI_INSTRUMENT_DATA_COLOR to create an InstrumentationScope with the name of the function as ID + * + * @param InstrumentationData The data for this scope as an UTF8 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + * @param InstrumentationColor The color for this scope. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + */ + #define PERFORMANCEAPI_INSTRUMENT_FUNCTION_DATA_COLOR(InstrumentationData, InstrumentationColor) PERFORMANCEAPI_INSTRUMENT_DATA_COLOR(__FUNCTION__, (InstrumentationData), (InstrumentationColor)) + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Deprecated functions. + // + // These functions remain here only for backwards compatibility. They're no longer maintained and new features will be added to the C interface only. + // The C functions, prefixed with PerformanceAPI_ and found in PerformanceAPI_capi.h, should be used instead. + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Set the name of the current thread to the specified thread name. + * + * @param inThreadName The thread name as an UTF8 encoded string. + */ + inline void SetCurrentThreadName(const char* inThreadName) { PerformanceAPI_SetCurrentThreadName(inThreadName); } + + /** + * Begin an instrumentation event with the specified ID + * + * @param inID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + */ + inline void BeginEvent(const char* inID) { PerformanceAPI_BeginEvent(inID, nullptr, PERFORMANCEAPI_DEFAULT_COLOR); } + + /** + * Begin an instrumentation event with the specified ID + * + * @param inID The ID of this scope as an UTF16 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + */ + inline void BeginEvent(const wchar_t* inID) { PerformanceAPI_BeginEvent_Wide(inID, nullptr, PERFORMANCEAPI_DEFAULT_COLOR); } + + /** + * Begin an instrumentation event with the specified ID and runtime data + * + * @param inID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData The data for this scope as an UTF8 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + */ + inline void BeginEvent(const char* inID, const char* inData) { PerformanceAPI_BeginEvent(inID, inData, PERFORMANCEAPI_DEFAULT_COLOR); } + + /** + * Begin an instrumentation event with the specified ID and runtime data + * + * @param inID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData The data for this scope as an UTF8 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + * @param inColor The color for this event. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + */ + inline void BeginEvent(const char* inID, const char* inData, uint32_t inColor) { PerformanceAPI_BeginEvent(inID, inData, inColor); } + + /** + * Begin an instrumentation event with the specified ID and runtime data + * + * @param inID The ID of this scope as an UTF16 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData The data for this scope as an UTF16 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + */ + inline void BeginEvent(const wchar_t* inID, const wchar_t* inData) { PerformanceAPI_BeginEvent_Wide(inID, inData, PERFORMANCEAPI_DEFAULT_COLOR); } + + /** + * Begin an instrumentation event with the specified ID and runtime data + * + * @param inID The ID of this scope as an UTF16 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData The data for this scope as an UTF16 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + * @param inColor The color for this event. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + */ + inline void BeginEvent(const wchar_t* inID, const wchar_t* inData, uint32_t inColor) { PerformanceAPI_BeginEvent_Wide(inID, inData, inColor); } + + /** + * End an instrumentation event. Must be matched with a call to BeginEvent within the same function + * Note: the return value can be ignored. It is only there to prevent calls to the function from being optimized to jmp instructions as part of tail call optimization. + */ + inline PerformanceAPI_SuppressTailCallOptimization EndEvent() { return PerformanceAPI_EndEvent(); } + +#else + #define PERFORMANCEAPI_INSTRUMENT(InstrumentationID) + #define PERFORMANCEAPI_INSTRUMENT_DATA(InstrumentationID, InstrumentationData) + #define PERFORMANCEAPI_INSTRUMENT_COLOR(InstrumentationID, InstrumentationColor) + #define PERFORMANCEAPI_INSTRUMENT_DATA_COLOR(InstrumentationID, InstrumentationData, InstrumentationColor) + + #define PERFORMANCEAPI_INSTRUMENT_FUNCTION() + #define PERFORMANCEAPI_INSTRUMENT_FUNCTION_DATA(InstrumentationData) + #define PERFORMANCEAPI_INSTRUMENT_FUNCTION_COLOR(InstrumentationColor) + #define PERFORMANCEAPI_INSTRUMENT_FUNCTION_DATA_COLOR(InstrumentationData, InstrumentationColor) + + inline void SetCurrentThreadName(const char* inThreadName) {} + inline void BeginEvent(const char* inID) {} + inline void BeginEvent(const char* inID, const char* inData) {} + inline void BeginEvent(const char* inID, const char* inData, uint32_t inColor) {} + inline void BeginEvent(const wchar_t* inID) {} + inline void BeginEvent(const wchar_t* inID, const wchar_t* inData) {} + inline void BeginEvent(const wchar_t* inID, const wchar_t* inData, uint32_t inColor) {} + inline void EndEvent() {} +#endif +} \ No newline at end of file diff --git a/dep/superluminal/API/include/Superluminal/PerformanceAPI_capi.h b/dep/superluminal/API/include/Superluminal/PerformanceAPI_capi.h new file mode 100644 index 0000000..74bad84 --- /dev/null +++ b/dep/superluminal/API/include/Superluminal/PerformanceAPI_capi.h @@ -0,0 +1,255 @@ +/* +BSD LICENSE + +Copyright (c) 2019-2020 Superluminal. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once + +#include +#include + +// When PERFORMANCEAPI_ENABLED is defined to 0, all calls to the PerformanceAPI (either through macro or direct function calls) will be compiled out. +#ifndef PERFORMANCEAPI_ENABLED + #ifdef _WIN32 + #define PERFORMANCEAPI_ENABLED 1 + #else + #define PERFORMANCEAPI_ENABLED 0 + #endif +#endif + +#define PERFORMANCEAPI_MAJOR_VERSION 3 +#define PERFORMANCEAPI_MINOR_VERSION 0 +#define PERFORMANCEAPI_VERSION ((PERFORMANCEAPI_MAJOR_VERSION << 16) | PERFORMANCEAPI_MINOR_VERSION) + +/** + * This header has been designed to be fully self-contained, which makes it easy to copy this header into your own source tree as needed. + * + * See PerformanceAPI.h for the high level documentation on how to use the API. + * + * Note that this header is split into two sections: + * - The first section defines the static library interface. If you use these functions directly, you need to link against the PerformanceAPI static library. + * - The second section defines the DLL interface. The DLL interface allows you to use the API without linking to a library. Instead, you can load the DLL yourself + * through LoadLibrary, then find the "PerformanceAPI_GetAPI" export through GetProcAddress. PerformanceAPI_GetAPI can be called to get a table of function pointers + * to the API. A convenience function to perform the DLL load & retrieve the API functions is provided for you in a separate header, PerformanceAPI_loader.h. + */ +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Static library interface - if you use these functions, you need to link against the PerformanceAPI library +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Helper struct that is used to prevent calls to EndEvent from being optimized to jmp instructions as part of tail call optimization. + * You don't ever need to do anything with this as user of the API. + */ +typedef struct +{ + int64_t SuppressTailCall[3]; +} PerformanceAPI_SuppressTailCallOptimization; + +#if PERFORMANCEAPI_ENABLED + /** + * Helper function to create an uint32_t color from 3 RGB values. The R, G and B values must be in range [0, 255]. + * The resulting color can be passed to the BeginEvent function. + */ + #define PERFORMANCEAPI_MAKE_COLOR(R, G, B) ((((uint32_t)(R)) << 24) | (((uint32_t)(G)) << 16) | (((uint32_t)(B)) << 8) | (uint32_t)0xFF) + + /** + * Use this define if you don't care about the color of an event and just want to use the default + */ + #define PERFORMANCEAPI_DEFAULT_COLOR 0xFFFFFFFF + + /** + * Set the name of the current thread to the specified thread name. + * + * @param inThreadName The thread name as an UTF8 encoded string. + */ + void PerformanceAPI_SetCurrentThreadName(const char* inThreadName); + + /** + * Set the name of the current thread to the specified thread name. + * + * @param inThreadName The thread name as an UTF8 encoded string. + * @param inThreadNameLength The length of the thread name, in characters, excluding the null terminator. + */ + void PerformanceAPI_SetCurrentThreadName_N(const char* inThreadName, uint16_t inThreadNameLength); + + /** + * Begin an instrumentation event with the specified ID and runtime data + * + * @param inID The ID of this scope as an UTF8 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData [optional] The data for this scope as an UTF8 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + * Set to null if not available. + * @param inColor [optional] The color for this scope. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + * Set to PERFORMANCEAPI_DEFAULT_COLOR to use default coloring. + * + */ + void PerformanceAPI_BeginEvent(const char* inID, const char* inData, uint32_t inColor); + + /** + * Begin an instrumentation event with the specified ID and runtime data, both with an explicit length. + + * It works the same as the regular BeginEvent function (see docs above). The difference is that it allows you to specify the length of both the ID and the data, + * which is useful for languages that do not have null-terminated strings. + * + * Note: both lengths should be specified in the number of characters, not bytes, excluding the null terminator. + */ + void PerformanceAPI_BeginEvent_N(const char* inID, uint16_t inIDLength, const char* inData, uint16_t inDataLength, uint32_t inColor); + + /** + * Begin an instrumentation event with the specified ID and runtime data + * + * @param inID The ID of this scope as an UTF16 encoded string. The ID for a specific scope must be the same over the lifetime of the program (see docs at the top of this file) + * @param inData [optional] The data for this scope as an UTF16 encoded string. The data can vary for each invocation of this scope and is intended to hold information that is only available at runtime. See docs at the top of this file. + Set to null if not available. + * @param inColor [optional] The color for this scope. The color for a specific scope is coupled to the ID and must be the same over the lifetime of the program + * Set to PERFORMANCEAPI_DEFAULT_COLOR to use default coloring. + */ + void PerformanceAPI_BeginEvent_Wide(const wchar_t* inID, const wchar_t* inData, uint32_t inColor); + + /** + * Begin an instrumentation event with the specified ID and runtime data, both with an explicit length. + + * It works the same as the regular BeginEvent_Wide function (see docs above). The difference is that it allows you to specify the length of both the ID and the data, + * which is useful for languages that do not have null-terminated strings. + * + * Note: both lengths should be specified in the number of characters, not bytes, excluding the null terminator. + */ + void PerformanceAPI_BeginEvent_Wide_N(const wchar_t* inID, uint16_t inIDLength, const wchar_t* inData, uint16_t inDataLength, uint32_t inColor); + + /** + * End an instrumentation event. Must be matched with a call to BeginEvent within the same function + * Note: the return value can be ignored. It is only there to prevent calls to the function from being optimized to jmp instructions as part of tail call optimization. + */ + PerformanceAPI_SuppressTailCallOptimization PerformanceAPI_EndEvent(); + + /** + * Call this function when a fiber starts running + * + * @param inFiberID The currently running fiber + */ + void PerformanceAPI_RegisterFiber(uint64_t inFiberID); + + /** + * Call this function before a fiber ends + * + * @param inFiberID The currently running fiber + */ + void PerformanceAPI_UnregisterFiber(uint64_t inFiberID); + + /** + * The call to the Windows SwitchFiber function should be surrounded by BeginFiberSwitch and EndFiberSwitch calls. For example: + * + * PerformanceAPI_BeginFiberSwitch(currentFiber, otherFiber); + * SwitchToFiber(otherFiber); + * PerformanceAPI_EndFiberSwitch(currentFiber); + * + * @param inCurrentFiberID The currently running fiber + * @param inNewFiberID The fiber we're switching to + */ + void PerformanceAPI_BeginFiberSwitch(uint64_t inCurrentFiberID, uint64_t inNewFiberID); + + /** + * The call to the Windows SwitchFiber function should be surrounded by BeginFiberSwitch and EndFiberSwitch calls + * + * PerformanceAPI_BeginFiberSwitch(currentFiber, otherFiber); + * SwitchToFiber(otherFiber); + * PerformanceAPI_EndFiberSwitch(currentFiber); + * + * @param inFiberID The fiber that was running before the call to SwitchFiber (so, the same as inCurrentFiberID in the BeginFiberSwitch call) + */ + void PerformanceAPI_EndFiberSwitch(uint64_t inFiberID); +#else + #define PERFORMANCEAPI_MAKE_COLOR(R, G, B) 0xFFFFFFFF + #define PERFORMANCEAPI_DEFAULT_COLOR 0xFFFFFFFF + + inline void PerformanceAPI_SetCurrentThreadName(const char* inThreadName) {} + inline void PerformanceAPI_SetCurrentThreadName_N(const char* inThreadName, uint16_t inThreadNameLength) {} + inline void PerformanceAPI_BeginEvent(const char* inID, const char* inData, uint32_t inColor) {} + inline void PerformanceAPI_BeginEvent_N(const char* inID, uint16_t inIDLength, const char* inData, uint16_t inDataLength, uint32_t inColor) {} + inline void PerformanceAPI_BeginEvent_Wide(const wchar_t* inID, const wchar_t* inData, uint32_t inColor) {} + inline void PerformanceAPI_BeginEvent_Wide_N(const wchar_t* inID, uint16_t inIDLength, const wchar_t* inData, uint16_t inDataLength, uint32_t inColor) {} + inline void PerformanceAPI_EndEvent() {} + + inline void PerformanceAPI_RegisterFiber(uint64_t inFiberID) {} + inline void PerformanceAPI_UnregisterFiber(uint64_t inFiberID) {} + inline void PerformanceAPI_BeginFiberSwitch(uint64_t inCurrentFiberID, uint64_t inNewFiberID) {} + inline void PerformanceAPI_EndFiberSwitch(uint64_t inFiberID) {} +#endif + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// DLL interface - These functions can be used without linking by loading PerformanceAPI.dll and using GetProcAddress to find the PerformanceAPI_GetAPI function. +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef void (PerformanceAPI_SetCurrentThreadName_Func)(const char* inThreadName); +typedef void (PerformanceAPI_SetCurrentThreadName_N_Func)(const char* inThreadName, uint16_t inThreadNameLength); +typedef void (PerformanceAPI_BeginEvent_Func)(const char* inID, const char* inData, uint32_t inColor); +typedef void (PerformanceAPI_BeginEvent_N_Func)(const char* inID, uint16_t inIDLength, const char* inData, uint16_t inDataLength, uint32_t inColor); +typedef void (PerformanceAPI_BeginEvent_Wide_Func)(const wchar_t* inID, const wchar_t* inData, uint32_t inColor); +typedef void (PerformanceAPI_BeginEvent_Wide_N_Func)(const wchar_t* inID, uint16_t inIDLength, const wchar_t* inData, uint16_t inDataLength, uint32_t inColor); +typedef PerformanceAPI_SuppressTailCallOptimization (PerformanceAPI_EndEvent_Func)(); + +typedef void (PerformanceAPI_RegisterFiber_Func)(uint64_t inFiberID); +typedef void (PerformanceAPI_UnregisterFiber_Func)(uint64_t inFiberID); +typedef void (PerformanceAPI_BeginFiberSwitch_Func)(uint64_t inCurrentFiberID, uint64_t inNewFiberID); +typedef void (PerformanceAPI_EndFiberSwitch_Func)(uint64_t inFiberID); + +typedef struct +{ + PerformanceAPI_SetCurrentThreadName_Func* SetCurrentThreadName; + PerformanceAPI_SetCurrentThreadName_N_Func* SetCurrentThreadNameN; + PerformanceAPI_BeginEvent_Func* BeginEvent; + PerformanceAPI_BeginEvent_N_Func* BeginEventN; + PerformanceAPI_BeginEvent_Wide_Func* BeginEventWide; + PerformanceAPI_BeginEvent_Wide_N_Func* BeginEventWideN; + PerformanceAPI_EndEvent_Func* EndEvent; + + PerformanceAPI_RegisterFiber_Func* RegisterFiber; + PerformanceAPI_UnregisterFiber_Func* UnregisterFiber; + PerformanceAPI_BeginFiberSwitch_Func* BeginFiberSwitch; + PerformanceAPI_EndFiberSwitch_Func* EndFiberSwitch; + +} PerformanceAPI_Functions; + +/** + * Entry point for the PerformanceAPI when used through a DLL. You can get the actual function from the DLL through + * GetProcAddress and then cast it to this function pointer. The name of the function exported from the DLL is "PerformanceAPI_GetAPI". + * + * A convenience function to find & call this function from the PerformanceAPI dll is provided in a separate header, PerformanceAPI_loader.h (PerformanceAPI_LoadFrom) + * + * @param inVersion The version of the header that's used to request the function table. Always specify PERFORMANCEAPI_VERSION for this argument (defined at the top of this file). + * Note: the version of the header and DLL must match exactly; if it doesn't an error will be returned. + * @param outFunctions Pointer to a PerformanceAPI_Functions struct that will be filled with the correct function pointers to use the API + * + * @return 0 if there was an error (version mismatch), 1 on success + */ +typedef int (*PerformanceAPI_GetAPI_Func)(int inVersion, PerformanceAPI_Functions* outFunctions); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/dep/superluminal/API/include/Superluminal/PerformanceAPI_loader.h b/dep/superluminal/API/include/Superluminal/PerformanceAPI_loader.h new file mode 100644 index 0000000..cc476cb --- /dev/null +++ b/dep/superluminal/API/include/Superluminal/PerformanceAPI_loader.h @@ -0,0 +1,106 @@ +/* +BSD LICENSE + +Copyright (c) 2019-2020 Superluminal. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once + +#include "PerformanceAPI_capi.h" + +#ifdef __cplusplus + #define PERFORMANCEAPI_API inline +#else + #define PERFORMANCEAPI_API static inline +#endif + +#if PERFORMANCEAPI_ENABLED + typedef HMODULE PerformanceAPI_ModuleHandle; +#else + typedef void* PerformanceAPI_ModuleHandle; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Load the PerformanceAPI functions from the specified DLL path. If any part of this fails, the output + * outFunctions will be zero-initialized. + * + * @param inPathToDLL The path to the PerformanceAPI DLL. Note: The DLL at the specified path must match the architecture (i.e. x86 or x64) of the program this API is used in. + * @param outFunctions Pointer to a PerformanceAPI_Functions struct that will be filled with the correct function pointers to use the API. Filled with null pointers if the load failed for whatever reason. + * + * @return A handle to the module if the module was successfully loaded and the API retrieved; NULL otherwise. This can be used to free the module through PerformanceAPI_Free if needed. + */ +PERFORMANCEAPI_API PerformanceAPI_ModuleHandle PerformanceAPI_LoadFrom(const wchar_t* inPathToDLL, PerformanceAPI_Functions* outFunctions) +{ + // Zero-initialize functions and copy to the output. This ensures we can return from this function at any point, + // without leaving the user in a state where the output is only partially initialized. + PerformanceAPI_Functions functions = { 0 }; + *outFunctions = functions; + + // If the API is not enabled (i.e. non-Windows or explicitly disabled by the user), we don't try to initialize any of the functions. + // In this case the user will be left with a default (zero) initialized functions struct. +#if PERFORMANCEAPI_ENABLED + HMODULE module = LoadLibraryW(inPathToDLL); + if (module == NULL) + return NULL; + + PerformanceAPI_GetAPI_Func getAPI = (PerformanceAPI_GetAPI_Func)((void*)GetProcAddress(module, "PerformanceAPI_GetAPI")); + if (getAPI == NULL) + { + FreeLibrary(module); + return NULL; + } + + if (getAPI(PERFORMANCEAPI_VERSION, outFunctions) == 0) + { + FreeLibrary(module); + return NULL; + } + + return module; +#else + return NULL; +#endif +} + +/** + * Free the PerformanceAPI module that was previously loaded through PerformanceAPI_LoadFrom. After this function is called, you can no longer use the function pointers + * in the PerformanceAPI_Functions struct that you previously retrieved through PerformanceAPI_LoadFrom. + * + * @param inModule The module to free + */ +PERFORMANCEAPI_API void PerformanceAPI_Free(PerformanceAPI_ModuleHandle* inModule) +{ +#if PERFORMANCEAPI_ENABLED + FreeLibrary(*inModule); +#endif +} + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/dep/superluminal/API/lib/x64/PerformanceAPI_MD.lib b/dep/superluminal/API/lib/x64/PerformanceAPI_MD.lib new file mode 100644 index 0000000..bf1b301 Binary files /dev/null and b/dep/superluminal/API/lib/x64/PerformanceAPI_MD.lib differ diff --git a/dep/superluminal/API/lib/x64/PerformanceAPI_MDd.lib b/dep/superluminal/API/lib/x64/PerformanceAPI_MDd.lib new file mode 100644 index 0000000..46e6349 Binary files /dev/null and b/dep/superluminal/API/lib/x64/PerformanceAPI_MDd.lib differ diff --git a/dep/superluminal/API/lib/x64/PerformanceAPI_MDd_NoIteratorDebug.lib b/dep/superluminal/API/lib/x64/PerformanceAPI_MDd_NoIteratorDebug.lib new file mode 100644 index 0000000..d545a04 Binary files /dev/null and b/dep/superluminal/API/lib/x64/PerformanceAPI_MDd_NoIteratorDebug.lib differ diff --git a/dep/superluminal/API/lib/x64/PerformanceAPI_MT.lib b/dep/superluminal/API/lib/x64/PerformanceAPI_MT.lib new file mode 100644 index 0000000..bd15f23 Binary files /dev/null and b/dep/superluminal/API/lib/x64/PerformanceAPI_MT.lib differ diff --git a/dep/superluminal/API/lib/x64/PerformanceAPI_MTd.lib b/dep/superluminal/API/lib/x64/PerformanceAPI_MTd.lib new file mode 100644 index 0000000..fca8791 Binary files /dev/null and b/dep/superluminal/API/lib/x64/PerformanceAPI_MTd.lib differ diff --git a/dep/superluminal/API/lib/x64/PerformanceAPI_MTd_NoIteratorDebug.lib b/dep/superluminal/API/lib/x64/PerformanceAPI_MTd_NoIteratorDebug.lib new file mode 100644 index 0000000..7eeada6 Binary files /dev/null and b/dep/superluminal/API/lib/x64/PerformanceAPI_MTd_NoIteratorDebug.lib differ diff --git a/engine/common.h b/engine/common.h index a5ebcff..0006b6f 100644 --- a/engine/common.h +++ b/engine/common.h @@ -22,6 +22,8 @@ #include #include +#include + #ifdef _DEBUG #include "common/memtrak3.h" #endif diff --git a/engine/common/base64.c b/engine/common/base64.c index c02a2b6..4518961 100644 --- a/engine/common/base64.c +++ b/engine/common/base64.c @@ -47,7 +47,7 @@ static const unsigned char decodetable[] = 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 }; /* - * Base64Decode() née Curl_base64_decode() + * Base64Decode() [previously Curl_base64_decode()] * * Given a base64 NUL-terminated string at src, decode it and return a * pointer in *outptr to a newly allocated memory area holding decoded @@ -235,7 +235,7 @@ static bool base64_encode(const char* table64, } /* - * Base64Encode() née Curl_base64_encode() + * Base64Encode() [previously Curl_base64_encode()] * * Given a pointer to an input buffer and an input size, encode it and * return a pointer in *outptr to a newly allocated memory area holding @@ -256,7 +256,7 @@ bool Base64Encode(const char* inputbuff, size_t insize, } /* - * Base64UrlEncode() née Curl_base64url_encode() + * Base64UrlEncode() [previously Curl_base64url_encode()] * * Given a pointer to an input buffer and an input size, encode it and * return a pointer in *outptr to a newly allocated memory area holding diff --git a/engine/core/core_compress.cpp b/engine/core/core_compress.cpp index 813de37..4f2d074 100644 --- a/engine/core/core_compress.cpp +++ b/engine/core/core_compress.cpp @@ -12,7 +12,7 @@ std::optional> CompressZstandard(gsl::span sr return dst; } -std::optional> DecompressZstandard(gsl::span src) +std::optional> DecompressZstandard(gsl::span src, std::optional chunkCallback) { const size_t buffOutSize = ZSTD_DStreamOutSize(); std::vector buffOut(buffOutSize); @@ -35,6 +35,10 @@ std::optional> DecompressZstandard(gsl::span } dst.resize(newSize); memcpy(dst.data() + oldSize, output.dst, output.pos); + if (chunkCallback) { + if ((*chunkCallback)(gsl::span(dst))) + chunkCallback.reset(); + } } dst.shrink_to_fit(); diff --git a/engine/core/core_compress.h b/engine/core/core_compress.h index 64e6fe5..c3ca949 100644 --- a/engine/core/core_compress.h +++ b/engine/core/core_compress.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -9,4 +10,5 @@ std::optional> CompressZstandard(gsl::span src, std::optional level = {}); -std::optional> DecompressZstandard(gsl::span src); +using DecompressZstandardChunkCallback = std::function)>; +std::optional> DecompressZstandard(gsl::span src, std::optional chunkCallback = {}); diff --git a/engine/core/core_image.cpp b/engine/core/core_image.cpp index 4362aa3..c9aa4ba 100644 --- a/engine/core/core_image.cpp +++ b/engine/core/core_image.cpp @@ -458,6 +458,21 @@ bool gif_c::Save(std::filesystem::path const& fileName) // DDS Image // ========= +std::optional TryParseDDSSize(gsl::span& partialData) +{ + constexpr std::array FOURCC_DDS{'D', 'D', 'S', ' '}; + using DdsHeaderPrefix = std::array; + if (partialData.size_bytes() < sizeof(FOURCC_DDS) + sizeof(DdsHeaderPrefix)) + return {}; + if (partialData.subspan(0, 4) != gsl::make_span(FOURCC_DDS)) + return {}; + + DdsHeaderPrefix headerPrefix; + std::memcpy(headerPrefix.data(), partialData.subspan(sizeof(FOURCC_DDS), sizeof(DdsHeaderPrefix)).data(), sizeof(DdsHeaderPrefix)); + enum { SizeSlot, FlagsSlot, HeightSlot, WidthSlot }; + return glm::ivec2{headerPrefix[WidthSlot], headerPrefix[HeightSlot]}; +} + bool dds_c::Load(std::filesystem::path const& fileName, std::optional sizeCallback) { // Open file @@ -470,7 +485,18 @@ bool dds_c::Load(std::filesystem::path const& fileName, std::optional= 4 && *(uint32_t*)fileData.data() == 0xFD2FB528) { - auto ret = DecompressZstandard(as_bytes(gsl::span(fileData))); + std::optional chunkCallback; + if (sizeCallback) { + chunkCallback = [&](gsl::span prefix) -> bool { + if (auto size = TryParseDDSSize(prefix)) { + (*sizeCallback)(size->x, size->y); + sizeCallback.reset(); + return true; + } + return false; + }; + } + auto ret = DecompressZstandard(as_bytes(gsl::span(fileData)), chunkCallback); if (!ret.has_value()) return true; fileData.assign(ret->data(), ret->data() + ret->size()); diff --git a/engine/render.h b/engine/render.h index 0bb3681..15c6440 100644 --- a/engine/render.h +++ b/engine/render.h @@ -55,12 +55,10 @@ enum r_blendMode_e { class r_shaderHnd_c { friend class r_renderer_c; public: - ~r_shaderHnd_c(); - std::optional StackCount() const; private: - r_shaderHnd_c(class r_shader_c* sh); - r_shader_c* sh; + r_shaderHnd_c(std::shared_ptr&& sh); + std::shared_ptr sh; }; // ========== diff --git a/engine/render/r_font.cpp b/engine/render/r_font.cpp index 90375b3..7b980a3 100644 --- a/engine/render/r_font.cpp +++ b/engine/render/r_font.cpp @@ -29,7 +29,7 @@ struct f_glyph_s { // Font height info struct f_fontHeight_s { - r_tex_c* tex; + std::shared_ptr tex; int height; int numGlyph; f_glyph_s glyphs[128]; @@ -75,7 +75,8 @@ r_font_c::r_font_c(r_renderer_c* renderer, const char* fontName) fh = new f_fontHeight_s; fontHeights[numFontHeight++] = fh; std::string tgaName = fmt::format("{}.{}.tga", fileNameBase, h); - fh->tex = new r_tex_c(renderer->texMan, tgaName.c_str(), TF_NOMIPMAP); + fh->tex = r_tex_c::CreateFromPath(renderer->texMan, tgaName.c_str(), TF_ASYNC|TF_NOMIPMAP); + fh->tex->WaitOnStatusAtLeast(r_tex_c::SIZE_KNOWN); fh->height = h; if (h > maxHeight) { maxHeight = h; @@ -118,7 +119,7 @@ r_font_c::~r_font_c() { // Delete textures for (int i = 0; i < numFontHeight; i++) { - delete fontHeights[i]->tex; + fontHeights[i]->tex.reset(); delete fontHeights[i]; } delete fontHeightMap; @@ -368,13 +369,13 @@ void r_font_c::DrawTextLine(scp_t pos, int align, int height, col4_t col, std::u // Snap the starting x position to the pixel grid so the leading glyph isn't blurred. x = std::round(x); - r_tex_c* curTex{}; + std::shared_ptr curTex{}; auto drawCodepoint = [this, &curTex, &x, y](f_fontHeight_s* fh, int height, float scale, int yShift, char32_t cp) { float cpY = y + yShift; if (curTex != fh->tex) { curTex = fh->tex; - renderer->curLayer->Bind(fh->tex); + renderer->curLayer->Bind(curTex); } auto& glyph = fh->Glyph((char)(unsigned char)cp); x += glyph.spLeft * scale; diff --git a/engine/render/r_main.cpp b/engine/render/r_main.cpp index 1d46715..43f1eab 100644 --- a/engine/render/r_main.cpp +++ b/engine/render/r_main.cpp @@ -25,6 +25,8 @@ #include #include +#include + static uint64_t MurmurHash64A(void const* data, int len, uint64_t seed); // ======= @@ -46,13 +48,11 @@ class r_shader_c { public: r_renderer_c* renderer; std::string name; - dword nameHash; - int refCount; - r_tex_c* tex; + dword nameHash; + std::shared_ptr tex; r_shader_c(r_renderer_c* renderer, std::string_view shname, int flags); r_shader_c(r_renderer_c* renderer, std::string_view shname, int flags, std::unique_ptr img); - ~r_shader_c(); }; r_shader_c::r_shader_c(r_renderer_c* renderer, std::string_view shname, int flags) @@ -60,8 +60,7 @@ r_shader_c::r_shader_c(r_renderer_c* renderer, std::string_view shname, int flag { name = shname; nameHash = StringHash(name.c_str(), 0xFFFF); - refCount = 0; - tex = new r_tex_c(renderer->texMan, name, flags); + tex = r_tex_c::CreateFromPath(renderer->texMan, name, flags); if (tex->error) { renderer->sys->con->Warning("couldn't load texture '%s'", name.c_str()); } @@ -72,31 +71,16 @@ r_shader_c::r_shader_c(r_renderer_c* renderer, std::string_view shname, int flag { name = shname; nameHash = StringHash(name.c_str(), 0xFFFF); - refCount = 0; - tex = new r_tex_c(renderer->texMan, std::move(img), flags); -} - -r_shader_c::~r_shader_c() -{ - delete tex; + tex = r_tex_c::CreateFromImage(renderer->texMan, std::move(img), flags); } // =================== // Shader Handle Class // =================== -r_shaderHnd_c::r_shaderHnd_c(r_shader_c* sh) +r_shaderHnd_c::r_shaderHnd_c(std::shared_ptr&& sh) : sh(sh) { - sh->refCount++; -} - -r_shaderHnd_c::~r_shaderHnd_c() -{ - sh->refCount--; - if (sh->refCount == 0) { - sh->tex->AbortLoad(); - } } struct Mat4 { @@ -125,8 +109,9 @@ Mat4 OrthoMatrix(double left, double right, double bottom, double top, double ne // Layer queue class // ================= +#pragma pack(push, r_layerCmd, 1) struct r_layerCmd_s { - enum Command { + enum Command : uint8_t { VIEWPORT, BLEND, BIND, @@ -165,6 +150,7 @@ struct r_layerCmdQuad_s { int stackLayer, maskLayer; } quad; }; +#pragma pack(pop, r_layerCmd) r_layer_c::r_layer_c(r_renderer_c* renderer, int layer, int subLayer) : renderer(renderer), layer(layer), subLayer(subLayer) @@ -246,11 +232,13 @@ void r_layer_c::SetBlendMode(int mode) } } -void r_layer_c::Bind(r_tex_c* tex) +void r_layer_c::Bind(const std::shared_ptr& tex) { if (auto* cmd = (r_layerCmdBind_s*)NewCommand(CommandSize(r_layerCmd_s::BIND))) { cmd->cmd = r_layerCmd_s::BIND; - cmd->tex = tex; + cmd->tex = tex.get(); + if (!referencedTextures.count(tex)) + referencedTextures.emplace(tex); } } @@ -418,6 +406,7 @@ struct RenderStrategy { virtual void ProcessCommand(r_layerCmd_s* cmd) = 0; virtual void Flush() = 0; virtual void SetShowStats(bool showStats) { showStats_ = showStats; } + virtual bool UsedIncompleteTextures() const { return false; } protected: bool showStats_{}; @@ -572,6 +561,8 @@ struct AdjacentMergeStrategy : RenderStrategy { } } + bool UsedIncompleteTextures() const override { return usedIncompleteTextures; }; + private: void Dispatch() { glBindBuffer(GL_ARRAY_BUFFER, vbo_); @@ -623,7 +614,10 @@ struct AdjacentMergeStrategy : RenderStrategy { auto tex = textures[i]; tex->Bind(); if (showStats_) { - ImGui::Text("New tex %d (%s)", tex->texId, tex->fileName.c_str()); + ImGui::Text("New tex %d (%s) %d", tex->texId, tex->fileName.c_str(), tex->status.load()); + } + if (!usedIncompleteTextures && tex->status != r_tex_c::Status::DONE) { + usedIncompleteTextures = true; } } else { @@ -655,7 +649,7 @@ struct AdjacentMergeStrategy : RenderStrategy { struct TexturedBatch { explicit TexturedBatch(GLuint prog) : batch(prog) { - textures.reserve(1ull << 20); + textures.reserve(128); } BatchKey key{}; @@ -673,9 +667,11 @@ struct AdjacentMergeStrategy : RenderStrategy { size_t totalVertexCount_ = 0; size_t batchIndex = 0; + + bool usedIncompleteTextures = false; }; -void r_layer_c::Render() +bool r_layer_c::Render() { int const optLevel = renderer->r_layerOptimize->intVal; bool const shuffle = renderer->r_layerShuffle->intVal == 1; @@ -713,12 +709,15 @@ void r_layer_c::Render() if (renderer->glPopGroupMarkerEXT) { renderer->glPopGroupMarkerEXT(); } + + return strat->UsedIncompleteTextures(); } void r_layer_c::Discard() { cmdCursor = 0; numCmd = 0; + referencedTextures.clear(); } // ===================== @@ -947,10 +946,6 @@ void r_renderer_c::Init(r_featureFlag_e features) // Initialise texture manager texMan = r_ITexManager::GetHandle(this); - // Initialise shader array - numShader = 0; - memset(shaderList, 0, sizeof(shaderList)); - GLint maxTextureImageUnits{}; glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits); @@ -1113,9 +1108,7 @@ void r_renderer_c::Shutdown() delete fonts[f]; } - for (int s = 0; s < numShader; s++) { - delete shaderList[s]; - } + shaderList.clear(); for (int l = 0; l < numLayer; l++) { delete layerList[l]; @@ -1150,12 +1143,6 @@ void r_renderer_c::Shutdown() void r_renderer_c::PumpShaders() { texMan->ProcessPendingTextureUploads(); - for (size_t idx = 0; idx < numShader; ++idx) - if (auto* sh = shaderList[idx]) - if (auto tex = sh->tex; tex && tex->status != r_tex_c::DONE) { - inhibitElision = true; - break; - } } void r_renderer_c::BeginFrame() @@ -1344,13 +1331,15 @@ void r_renderer_c::EndFrame() ImGui::Text("Total dense footprint: %sB", BinaryUnitPrefix(totalDenseFootprint).c_str()); size_t totalCmd{}; - if (ImGui::BeginTable("Layer stats", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { + if (ImGui::BeginTable("Layer stats", 8, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) { ImGui::TableSetupColumn("Index"); ImGui::TableSetupColumn("Layer"); ImGui::TableSetupColumn("Sublayer"); ImGui::TableSetupColumn("Command count"); ImGui::TableSetupColumn("Dense"); ImGui::TableSetupColumn("Debug"); + ImGui::TableSetupColumn("XXH3-64"); + ImGui::TableSetupColumn("MH64A"); ImGui::TableHeadersRow(); for (int l = 0; l < numLayer; ++l) { auto layer = layerSort[l]; @@ -1372,6 +1361,22 @@ void r_renderer_c::EndFrame() if (ImGui::Button("Debug")) { layerBreak = { layer->layer, layer->subLayer }; } + + std::chrono::high_resolution_clock::time_point tic; + std::chrono::microseconds dt; + + ImGui::TableNextColumn(); + tic = std::chrono::high_resolution_clock::now(); + volatile auto xxh_hash = XXH3_64bits(layer->cmdStorage.data(), layer->cmdCursor); + dt = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - tic); + ImGui::Text("%d µs", dt.count()); + + ImGui::TableNextColumn(); + tic = std::chrono::high_resolution_clock::now(); + volatile auto mh_hash = MurmurHash64A(layer->cmdStorage.data(), (int)layer->cmdCursor, 0ull); + dt = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - tic); + ImGui::Text("%d µs", dt.count()); + ImGui::PopID(); ImGui::PopID(); } @@ -1383,83 +1388,46 @@ void r_renderer_c::EndFrame() if (inhibitElision || elideFrames != !!r_elideFrames->intVal) { elideFrames = !!r_elideFrames->intVal; - lastFrameHash.clear(); + lastFrameHash = 0; } - std::future>> elidedFrameHashFut; + auto tic = std::chrono::high_resolution_clock::now(); + + uint64_t commandDigest = 0; if (elideFrames) { - elidedFrameHashFut = std::async([&]() -> std::optional> { - std::vector commandDigest; - - for (auto lIdx = 0; lIdx < numLayer; ++lIdx) { - auto layer = layerSort[lIdx]; - uint64_t subHash = MurmurHash64A(layer->cmdStorage.data(), (int)layer->cmdCursor, 0ull); - uint8_t const* p = (uint8_t const*)&subHash; - commandDigest.insert(commandDigest.end(), p, p + sizeof(subHash)); - } + std::shared_ptr hashState(XXH3_createState(), XXH3_freeState); + XXH3_64bits_reset(hashState.get()); - return commandDigest; - }); - } - else { - std::promise>> p; - elidedFrameHashFut = p.get_future(); - p.set_value({}); - } + for (auto lIdx = 0; lIdx < numLayer; ++lIdx) { + auto layer = layerSort[lIdx]; + uint64_t subHash = XXH3_64bits(layer->cmdStorage.data(), (int)layer->cmdCursor); + XXH3_64bits_update(hashState.get(), &subHash, sizeof(subHash)); + } - elidedFrameHashFut.wait(); + commandDigest = XXH3_64bits_digest(hashState.get()); + } ++totalFrames; - bool decideDraw = false; - bool elideDraw = false; + const bool elideDraw = lastFrameHash != 0 && lastFrameHash == commandDigest; + if (!elideDraw) { glBindFramebuffer(GL_FRAMEBUFFER, GetDrawRenderTarget().framebuffer); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - int l{}; - for (l = 0; l < numLayer; l++) { - if (!decideDraw && elidedFrameHashFut.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) { - decideDraw = true; - auto commandDigest = elidedFrameHashFut.get(); - if (commandDigest) { - if (*commandDigest == lastFrameHash) { - elideDraw = true; - break; - } - else { - lastFrameHash = *commandDigest; - } - } - else { - lastFrameHash.clear(); - } - } + for (int l = 0; l < numLayer; l++) { auto& layer = layerSort[l]; if (layerBreak && layerBreak->first == layer->layer && layerBreak->second == layer->subLayer) { #ifdef _WIN32 DebugBreak(); #endif } - layer->Render(); - } - if (!elideDraw) { - presentRtt = 1 - presentRtt; - ++drawnFrames; + inhibitElision = layer->Render() || inhibitElision; } + presentRtt = 1 - presentRtt; + ++drawnFrames; } - if (!decideDraw) { - if (auto commandDigest = elidedFrameHashFut.get()) { - lastFrameHash = *commandDigest; - } - else { - lastFrameHash.clear(); - } - } - - if (inhibitElision) { - // If we explicitly inhibited elision due to things like incomplete textures, make sure that the next frame is drawn. - lastFrameHash.clear(); - } + // If we explicitly inhibited elision due to things like incomplete textures, make sure that the next frame is drawn. + lastFrameHash = inhibitElision ? 0 : commandDigest; for (int l = 0; l < numLayer; ++l) { layerSort[l]->Discard(); @@ -1500,7 +1468,7 @@ void r_renderer_c::EndFrame() if (ImGui::Begin("Hash")) { char* b64{}; size_t b64Len{}; - Base64UrlEncode((char const*)lastFrameHash.data(), lastFrameHash.size(), &b64, &b64Len); + Base64UrlEncode((char const*)&lastFrameHash, sizeof(lastFrameHash), &b64, &b64Len); ImGui::Text("%s", b64); free(b64); } @@ -1566,7 +1534,7 @@ void r_renderer_c::EndFrame() std::optional r_shaderHnd_c::StackCount() const { - if (!sh || sh->tex->status != r_tex_c::Status::DONE) + if (!sh || sh->tex->GetStatus() != r_tex_c::Status::DONE) return {}; return (int)sh->tex->stackLayers; } @@ -1574,12 +1542,7 @@ std::optional r_shaderHnd_c::StackCount() const void r_renderer_c::PurgeShaders() { // Delete released shaders - for (int s = 0; s < numShader; s++) { - if (shaderList[s] && shaderList[s]->refCount == 0 && shaderList[s]->tex->status == r_tex_c::DONE) { - delete shaderList[s]; - shaderList[s] = NULL; - } - } + shaderList.erase(std::remove_if(shaderList.begin(), shaderList.end(), [](const std::weak_ptr& entry) { return entry.expired(); }), shaderList.end()); } r_shaderHnd_c* r_renderer_c::RegisterShader(std::string_view shname, int flags) @@ -1589,61 +1552,43 @@ r_shaderHnd_c* r_renderer_c::RegisterShader(std::string_view shname, int flags) } std::string name(shname); + PERFORMANCEAPI_INSTRUMENT_FUNCTION_DATA(name.c_str()); dword nameHash = StringHash(name, 0xFFFF); int newId = -1; - for (int s = 0; s < numShader; s++) { - if (!shaderList[s]) { - newId = s; - } - else if (shaderList[s]->nameHash == nameHash && _stricmp(name.c_str(), shaderList[s]->name.c_str()) == 0 && shaderList[s]->tex->flags == flags) { - // Shader already exists, return a new handle for it - // Ensure texture is loaded as soon as possible - shaderList[s]->tex->ForceLoad(); - return new r_shaderHnd_c(shaderList[s]); - } + auto found = std::find_if(shaderList.begin(), shaderList.end(), [&name, &nameHash, &flags](const auto& entry) { + if (std::shared_ptr sp = entry.lock()) + return sp->nameHash == nameHash && sp->name == name && sp->tex->flags == flags; + return false; + }); + std::shared_ptr sp; + if (found != shaderList.end()) { + // Shader already exists, return a new handle for it + sp = found->lock(); } - if (newId == -1) { - if (numShader == R_MAXSHADERS) { - sys->con->Warning("shader limit reached"); - return NULL; - } - newId = numShader++; + else { + sp = std::make_shared(this, shname, flags); + shaderList.push_back(sp); } - shaderList[newId] = new r_shader_c(this, shname, flags); - return new r_shaderHnd_c(shaderList[newId]); + return new r_shaderHnd_c(std::move(sp)); } r_shaderHnd_c* r_renderer_c::RegisterShaderFromImage(std::unique_ptr img, int flags) { - int newId = -1; - for (int s = 0; s < numShader; s++) { - if (!shaderList[s]) { - newId = s; - break; - } - } - if (newId == -1) { - if (numShader == R_MAXSHADERS) { - sys->con->Warning("shader limit reached"); - return NULL; - } - newId = numShader++; - } - char shname[32]; - sprintf(shname, "data:%d", newId); - shaderList[newId] = new r_shader_c(this, shname, flags, std::move(img)); - return new r_shaderHnd_c(shaderList[newId]); + std::string shname = fmt::format("data:%d", shaderList.size()); + std::shared_ptr sp = std::make_shared(this, shname, flags, std::move(img)); + shaderList.push_back(sp); + return new r_shaderHnd_c(std::move(sp)); } void r_renderer_c::GetShaderImageSize(r_shaderHnd_c* hnd, int& width, int& height) { - if (hnd) + if (hnd && hnd->sh) { - while (hnd->sh->tex->status < r_tex_c::SIZE_KNOWN) { - Sleep(1); - } - width = hnd->sh->tex->fileWidth; - height = hnd->sh->tex->fileHeight; + PERFORMANCEAPI_INSTRUMENT_FUNCTION_DATA(hnd->sh->name.size() ? hnd->sh->name.c_str() : ""); + auto& tex = *hnd->sh->tex; + tex.WaitOnStatusAtLeast(r_tex_c::SIZE_KNOWN); + width = tex.fileWidth; + height = tex.fileHeight; } else { width = 0; diff --git a/engine/render/r_main.h b/engine/render/r_main.h index f7072fb..f856043 100644 --- a/engine/render/r_main.h +++ b/engine/render/r_main.h @@ -14,6 +14,7 @@ #include #include #include +#include #include // ======= @@ -34,6 +35,7 @@ class r_layer_c { std::vector cmdStorage; size_t cmdCursor{}; size_t numCmd{}; + std::unordered_set< std::shared_ptr< r_tex_c > > referencedTextures; // keeps textures alive for the duration of the frame int layer; int subLayer; @@ -43,10 +45,10 @@ class r_layer_c { void SetViewport(r_viewport_s* viewport); void SetBlendMode(int mode); - void Bind(r_tex_c* tex); + void Bind(const std::shared_ptr& tex); void Color(col4_t col); void Quad(float s0, float t0, float x0, float y0, float s1, float t1, float x1, float y1, float s2, float t2, float x2, float y2, float s3, float t3, float x3, float y3, int stackLayer = 0, int maskLayer = -1); - void Render(); + bool Render(); void Discard(); struct CmdHandle { @@ -150,8 +152,7 @@ class r_renderer_c: public r_IRenderer, public conCmdHandler_c { r_viewport_s curViewport; // Current viewport int curBlendMode = 0; // Current blend mode - int numShader = 0; - class r_shader_c *shaderList[R_MAXSHADERS] = {}; + std::vector> shaderList; int tintedTextureProgram = 0; @@ -180,7 +181,7 @@ class r_renderer_c: public r_IRenderer, public conCmdHandler_c { RenderTarget rttMain[2]; int presentRtt = 0; - std::vector lastFrameHash{}; + uint64_t lastFrameHash{}; uint64_t totalFrames{}; uint64_t drawnFrames{}; diff --git a/engine/render/r_texture.cpp b/engine/render/r_texture.cpp index 07fdda8..34ac910 100644 --- a/engine/render/r_texture.cpp +++ b/engine/render/r_texture.cpp @@ -11,6 +11,7 @@ #include "cmp_core.h" #include "stb_image_resize.h" +#include #include #include @@ -58,24 +59,25 @@ class t_manager_c: public r_ITexManager, public thread_c { r_renderer_c* renderer; - r_tex_c* whiteTex; - r_tex_c* blackTex; + std::shared_ptr whiteTex; + std::shared_ptr blackTex; - bool AsyncAdd(r_tex_c* tex); - bool AsyncRemove(r_tex_c* tex); + bool AsyncAdd(const std::shared_ptr& tex); + bool AsyncRemove(r_tex_c& tex); - void EnqueueTextureUpload(r_tex_c* tex); - void RemovePendingTextureUpload(r_tex_c* tex); + void EnqueueTextureUpload(const std::shared_ptr& tex); + void RemovePendingTextureUpload(const r_tex_c& tex); private: std::atomic doRun; std::atomic runnersRunning; std::vector workers; - std::vector textureQueue; + std::vector> textureQueue; std::mutex mutex; + std::condition_variable workCV; - std::vector uploadQueue; + std::vector> uploadQueue; std::mutex uploadMutex; void ThreadProc() override; @@ -94,8 +96,8 @@ void r_ITexManager::FreeHandle(r_ITexManager* hnd) t_manager_c::t_manager_c(r_renderer_c* renderer) : thread_c(renderer->sys), renderer(renderer) { - whiteTex = new r_tex_c(this, "@white", 0); - blackTex = new r_tex_c(this, "@black", 0); + whiteTex = r_tex_c::CreateFromPath(this, "@white", 0); + blackTex = r_tex_c::CreateFromPath(this, "@black", 0); doRun = true; runnersRunning = 0; @@ -103,11 +105,12 @@ t_manager_c::t_manager_c(r_renderer_c* renderer) for (int i = 0; i < runnersWanted; ++i) { - workers.emplace_back([this] { + workers.emplace_back([this, i] { + PerformanceAPI_SetCurrentThreadName(fmt::format("Tex{}", i).c_str()); ThreadProc(); }); } - //ThreadStart(); + while (runnersRunning < runnersWanted) { renderer->sys->Sleep( 1 ); } @@ -115,15 +118,14 @@ t_manager_c::t_manager_c(r_renderer_c* renderer) t_manager_c::~t_manager_c() { - doRun = false; + { + std::unique_lock lock(mutex); + doRun = false; + } + workCV.notify_all(); for (auto& worker : workers) - worker.join(); - - for (auto tex : textureQueue) - delete tex; - - delete whiteTex; - delete blackTex; + if( worker.joinable()) + worker.join(); } // ===================== @@ -139,74 +141,85 @@ int t_manager_c::GetAsyncCount() void t_manager_c::ProcessPendingTextureUploads() { std::unique_lock lk(uploadMutex); - for (auto tex : uploadQueue) { - r_tex_c::PerformUpload(tex); + for (const auto& tex : uploadQueue) { + tex->PerformUpload(); } uploadQueue.clear(); } -bool t_manager_c::AsyncAdd(r_tex_c* tex) +bool t_manager_c::AsyncAdd(const std::shared_ptr& tex) { - std::lock_guard lock( mutex ); - if ( runnersRunning == 0 ) { - return true; + { + std::lock_guard lock(mutex); + if (runnersRunning == 0) { + return true; + } + textureQueue.push_back(tex); + tex->SetStatus(r_tex_c::IN_QUEUE); } - textureQueue.push_back( tex ); - tex->status = r_tex_c::IN_QUEUE; + workCV.notify_one(); return false; } -bool t_manager_c::AsyncRemove(r_tex_c* tex) +bool t_manager_c::AsyncRemove(r_tex_c& tex) { { std::lock_guard lock( mutex ); - if (tex->status == r_tex_c::IN_QUEUE) { + if (tex.GetStatus() == r_tex_c::IN_QUEUE) { for (auto itr = textureQueue.begin(); itr != textureQueue.end(); ++itr) { - if (*itr == tex) { + if (itr->get() == &tex) { textureQueue.erase( itr ); - tex->status = r_tex_c::INIT; + tex.SetStatus(r_tex_c::INIT); return false; } } } } - while (tex->status == r_tex_c::PROCESSING || tex->status == r_tex_c::SIZE_KNOWN) { - renderer->sys->Sleep( 1 ); + { + std::unique_lock lock(tex.statusMutex); + tex.statusCV.wait(lock, [&tex] { + const auto status = tex.status.load(std::memory_order_relaxed); + return status != r_tex_c::PROCESSING && status != r_tex_c::SIZE_KNOWN; + }); } - if (tex->status == r_tex_c::PENDING_UPLOAD) { + if (tex.GetStatus() == r_tex_c::PENDING_UPLOAD) { RemovePendingTextureUpload(tex); } return true; } -void t_manager_c::EnqueueTextureUpload(r_tex_c* tex) +void t_manager_c::EnqueueTextureUpload(const std::shared_ptr& tex) { std::scoped_lock lk(uploadMutex); uploadQueue.push_back(tex); } -void t_manager_c::RemovePendingTextureUpload(r_tex_c* tex) +void t_manager_c::RemovePendingTextureUpload(const r_tex_c& tex) { std::scoped_lock lk(uploadMutex); - if (auto I = std::find(uploadQueue.begin(), uploadQueue.end(), tex); I != uploadQueue.end()) + if (auto I = std::find_if(uploadQueue.begin(), uploadQueue.end(), [p = &tex](const auto& e) { return e.get() == p; }); I != uploadQueue.end()) uploadQueue.erase(I); } void t_manager_c::ThreadProc() { ++runnersRunning; - while (doRun) { - r_tex_c *doTex = nullptr; + while (true) { + std::shared_ptr doTex; { - std::lock_guard lock( mutex ); + std::unique_lock lock( mutex ); + workCV.wait(lock, [this] { return !doRun || !textureQueue.empty(); }); + + if (!doRun) + break; // Find a texture with the highest loading priority int maxPri = 0; auto doTexItr = textureQueue.end(); for (auto curTexItr = textureQueue.begin(); curTexItr != textureQueue.end(); ++curTexItr) { - auto curTex = *curTexItr; + const auto& curTex = *curTexItr; if (doTexItr == textureQueue.end() || curTex->loadPri > maxPri) { maxPri = curTex->loadPri; doTexItr = curTexItr; @@ -216,17 +229,13 @@ void t_manager_c::ThreadProc() if (doTexItr != textureQueue.end()) { doTex = *doTexItr; textureQueue.erase(doTexItr); - doTex->status = r_tex_c::PROCESSING; + doTex->SetStatus(r_tex_c::PROCESSING); } } if (doTex != nullptr) { // Load this texture doTex->LoadFile(); - doTex = nullptr; - } else { - // Idle - renderer->sys->Sleep(1); } } --runnersRunning; @@ -295,34 +304,40 @@ static void T_ResampleImage(byte* in, dword in_w, dword in_h, int in_comp, byte* // OpenGL Texture Class // ==================== -r_tex_c::r_tex_c(r_ITexManager* manager, std::string_view fileName, int flags) +r_tex_c::r_tex_c(CreateToken, r_ITexManager* manager, std::string_view fileName, int flags) { Init(manager, fileName, flags); - - StartLoad(); - if (status == INIT) { - // Load it now - LoadFile(); - } } -r_tex_c::r_tex_c(r_ITexManager* manager, std::unique_ptr img, int flags) +r_tex_c::r_tex_c(CreateToken, r_ITexManager* manager, std::unique_ptr newImg, int flags) { Init(manager, {}, flags); - // Direct upload - img = BuildMipSet(std::move(img)); - PerformUpload(this); + const auto extent = newImg->tex.extent(); + fileWidth = extent.x; + fileHeight = extent.y; + SetStatus(SIZE_KNOWN); + img = std::move(newImg); } r_tex_c::~r_tex_c() { if (status >= IN_QUEUE && status < DONE) { - manager->AsyncRemove(this); + manager->AsyncRemove(*this); } glDeleteTextures(1, &texId); } +void r_tex_c::Kick() +{ + if (flags & TF_ASYNC) + manager->AsyncAdd(shared_from_this()); + else if (img || fileName.size()) { + // Load it now + LoadFile(); + } +} + void r_tex_c::Init(r_ITexManager* i_manager, std::string_view i_fileName, int i_flags) { manager = (t_manager_c*)i_manager; @@ -335,6 +350,21 @@ void r_tex_c::Init(r_ITexManager* i_manager, std::string_view i_fileName, int i_ fileName = i_fileName; fileWidth = 0; fileHeight = 0; + +} + +std::shared_ptr r_tex_c::CreateFromPath(r_ITexManager* manager, std::string_view fileName, int flags) +{ + auto ptr = std::make_shared(CreateToken{}, manager, fileName, flags); + ptr->Kick(); + return ptr; +} + +std::shared_ptr r_tex_c::CreateFromImage(r_ITexManager* manager, std::unique_ptr img, int flags) +{ + auto ptr = std::make_shared(CreateToken{}, manager, std::move(img), flags); + ptr->Kick(); + return ptr; } void r_tex_c::Bind() @@ -362,25 +392,9 @@ void r_tex_c::Disable() glDisable(GL_TEXTURE_2D); } -void r_tex_c::StartLoad() -{ - if (flags & TF_ASYNC) - manager->AsyncAdd(this); -} - void r_tex_c::AbortLoad() { - manager->AsyncRemove(this); -} - -void r_tex_c::ForceLoad() -{ - if (status == INIT) { - LoadFile(); - } else if (fileWidth == 0) { - // Load not pending, do it now - LoadFile(); - } + manager->AsyncRemove(*this); } std::unique_ptr r_tex_c::BuildMipSet(std::unique_ptr img) @@ -388,7 +402,6 @@ std::unique_ptr r_tex_c::BuildMipSet(std::unique_ptr img) const auto format = img->tex.format(); const bool blockCompressed = is_compressed(format); - const bool isAsync = !!(flags & TF_ASYNC); const bool hasExistingMips = img->tex.layers() > 1; auto extent = img->tex.extent(); @@ -456,7 +469,6 @@ std::unique_ptr r_tex_c::BuildMipSet(std::unique_ptr img) comp, hasAlpha ? 3 : STBIR_ALPHA_CHANNEL_NONE, 0, STBIR_EDGE_CLAMP); } } - //newTex = gli::generate_mipmaps(newTex, gli::FILTER_LINEAR); img->tex = newTex; } } @@ -550,68 +562,117 @@ static gli::texture2d_array TranscodeTexture(gli::texture2d_array src, gli::form return dst; } +struct BuiltinImageSpec +{ + const std::string_view name; + const imageType_s imageType; + const byte* imageData; + const int width; + const int height; +}; + +static const std::array builtinImages{ + BuiltinImageSpec{ "@default", IMGTYPE_GRAY, t_defaultTexture, 8, 8}, + BuiltinImageSpec{ "@white", IMGTYPE_GRAY, t_whiteImage, 8, 8}, + BuiltinImageSpec{ "@black", IMGTYPE_RGBA, t_blackImage, 8, 8}, +}; + void r_tex_c::LoadFile() { - if (_stricmp(fileName.c_str(), "@white") == 0) { - // Upload an 8x8 white image - auto raw = std::make_unique(); - raw->CopyRaw(IMGTYPE_GRAY, 8, 8, t_whiteImage); - Upload(*raw, TF_NOMIPMAP); - status = DONE; - return; - } - else if (_stricmp(fileName.c_str(), "@black") == 0) { - // Upload an 8x8 black image - auto raw = std::make_unique(); - raw->CopyRaw(IMGTYPE_RGBA, 8, 8, t_blackImage); - Upload(*raw, TF_NOMIPMAP); - status = DONE; - return; - } + // Four cases: + // - from existing image data + // - virtual @black or @white + // - from file + // - fallback to gray default texture + + const bool is_async = !!(flags & TF_ASYNC); + const bool no_mipmap = !!(flags & TF_NOMIPMAP); + + auto sizeCallback = [this](int width, int height) { + this->fileWidth = width; + this->fileHeight = height; + SetStatus(SIZE_KNOWN); + }; + + if (!img) { + if (fileName.size() && fileName[0] == '@') { + // Upload a (typically) 8x8 builtin image + auto it = std::find_if(builtinImages.begin(), builtinImages.end(), [name = std::string_view(fileName)](const BuiltinImageSpec& spec) { + return spec.name == name; + }); + if (it == builtinImages.end()) + it = builtinImages.begin(); // fall back to @default - // Try to load image file using appropriate loader - auto path = std::filesystem::u8path(fileName); - img = std::unique_ptr(image_c::LoaderForFile(renderer->sys->con, path)); - if (img) { - auto sizeCallback = [this](int width, int height) { - this->fileWidth = width; - this->fileHeight = height; - this->status = SIZE_KNOWN; - }; - error = img->Load(path, sizeCallback); - if ( !error ) { - const bool useTextureFormatFallback = !renderer->texBC7; - if (useTextureFormatFallback) { - if (img->tex.format() == gli::FORMAT_RGBA_BP_UNORM_BLOCK16) - img->tex = TranscodeTexture(img->tex, gli::FORMAT_RGBA8_UNORM_PACK8, true); - } - stackLayers = img->tex.layers(); - const bool is_async = !!(flags & TF_ASYNC); - img = BuildMipSet(std::move(img)); - - status = PENDING_UPLOAD; - if (is_async) { - // Post a main thread task to create and fill GPU textures. - manager->EnqueueTextureUpload(this); - } - else { - PerformUpload(this); + flags |= TF_NOMIPMAP; + img = std::make_unique(); + img->CopyRaw(it->imageType, it->width, it->height, it->imageData); + sizeCallback(it->width, it->height); + } + else { + // Try to load image file using appropriate loader + const auto path = std::filesystem::u8path(fileName); + img = std::unique_ptr(image_c::LoaderForFile(renderer->sys->con, path)); + if (img && img->Load(path, sizeCallback)) + img.reset(); + + // Fallback to gray default texture + if( !img ) { + img = std::make_unique(); + img->CopyRaw(IMGTYPE_GRAY, 8, 8, t_defaultTexture); + flags |= TF_NOMIPMAP; + sizeCallback(8, 8); } - return; } } - auto raw = std::make_unique(); - raw->CopyRaw(IMGTYPE_GRAY, 8, 8, t_defaultTexture); - Upload(*raw, TF_NOMIPMAP); - status = DONE; + const bool useTextureFormatFallback = !renderer->texBC7; + if (useTextureFormatFallback) { + if (img->tex.format() == gli::FORMAT_RGBA_BP_UNORM_BLOCK16) + img->tex = TranscodeTexture(img->tex, gli::FORMAT_RGBA8_UNORM_PACK8, true); + } + stackLayers = img->tex.layers(); + if (!no_mipmap) + img = BuildMipSet(std::move(img)); + + SetStatus(PENDING_UPLOAD); + if (is_async) { + // Post a main thread task to create and fill GPU textures. + manager->EnqueueTextureUpload(shared_from_this()); + } + else { + PerformUpload(); + } + return; +} + +r_tex_c::Status r_tex_c::GetStatus() const noexcept +{ + std::unique_lock statusLock(statusMutex); + return status.load(std::memory_order_acquire); +} + +void r_tex_c::SetStatus(Status newStatus) +{ + { + std::unique_lock statusLock(statusMutex); + status.store(newStatus, std::memory_order_release); + } + statusCV.notify_all(); +} + +void r_tex_c::WaitOnStatusAtLeast(Status bound) const noexcept +{ + std::unique_lock statusLock(statusMutex); + statusCV.wait(statusLock, [this, bound] { + return this->status.load(std::memory_order_relaxed) >= bound; + }); } -void r_tex_c::PerformUpload(r_tex_c* tex) +void r_tex_c::PerformUpload() { - tex->Upload(*tex->img, tex->flags); - tex->img = {}; - tex->status = DONE; + Upload(*img, flags); + img.reset(); + SetStatus(DONE); } static std::atomic inputBytes = 0; diff --git a/engine/render/r_texture.h b/engine/render/r_texture.h index c20c8bf..a49d369 100644 --- a/engine/render/r_texture.h +++ b/engine/render/r_texture.h @@ -10,14 +10,23 @@ #include #include +#include #include class image_c; class mip_set_c; // Texture -class r_tex_c { +class r_tex_c : public std::enable_shared_from_this { + struct CreateToken {}; + + void Kick(); + public: + r_tex_c(CreateToken, class r_ITexManager* manager, std::string_view fileName, int flags); + r_tex_c(CreateToken, class r_ITexManager* manager, std::unique_ptr img, int flags); + ~r_tex_c(); + int error; enum Status { @@ -28,6 +37,8 @@ class r_tex_c { PENDING_UPLOAD, DONE, }; + mutable std::mutex statusMutex; + mutable std::condition_variable statusCV; std::atomic status; std::atomic loadPri; dword texId; @@ -39,20 +50,22 @@ class r_tex_c { GLenum target{}; size_t stackLayers = 1; - r_tex_c(class r_ITexManager* manager, std::string_view fileName, int flags); - r_tex_c(class r_ITexManager* manager, std::unique_ptr img, int flags); - ~r_tex_c(); + static std::shared_ptr CreateFromPath(class r_ITexManager* manager, std::string_view fileName, int flags); + static std::shared_ptr CreateFromImage(class r_ITexManager* manager, std::unique_ptr img, int flags); + + void Bind(); + void Unbind(); + void Enable(); + void Disable(); + + void AbortLoad(); + void LoadFile(); - void Bind(); - void Unbind(); - void Enable(); - void Disable(); - void StartLoad(); - void AbortLoad(); - void ForceLoad(); - void LoadFile(); + [[nodiscard]] Status GetStatus() const noexcept; + void SetStatus(Status newStatus); + void WaitOnStatusAtLeast(Status bound) const noexcept; - static void PerformUpload(r_tex_c*); + void PerformUpload(); private: class t_manager_c* manager; diff --git a/engine/system/win/sys_console.cpp b/engine/system/win/sys_console.cpp index 88b6de8..d6828f2 100644 --- a/engine/system/win/sys_console.cpp +++ b/engine/system/win/sys_console.cpp @@ -42,8 +42,9 @@ class sys_console_c: public sys_IConsole, public conPrintHook_c, public thread_c static LRESULT __stdcall WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); - volatile bool doRun; - volatile bool isRunning; + HANDLE threadStartedEvent{}; + HANDLE threadShouldStopEvent{}; + HANDLE threadExitedEvent{}; void RunMessages(HWND hwnd = nullptr); void ThreadProc(); @@ -68,11 +69,12 @@ void sys_IConsole::FreeHandle(sys_IConsole* hnd) sys_console_c::sys_console_c(sys_IMain* sysHnd) : conPrintHook_c(sysHnd->con), sys((sys_main_c*)sysHnd), thread_c(sysHnd) { - isRunning = false; - doRun = true; + threadStartedEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + threadShouldStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + threadExitedEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); ThreadStart(true); - while ( !isRunning ); + WaitForSingleObject(threadStartedEvent, INFINITE); } void sys_console_c::RunMessages(HWND hwnd) @@ -90,6 +92,7 @@ void sys_console_c::RunMessages(HWND hwnd) void sys_console_c::ThreadProc() { + PerformanceAPI_SetCurrentThreadName("SysConsole"); // Get info of the monitor containing the mouse cursor POINT curPos; GetCursorPos(&curPos); @@ -154,10 +157,9 @@ void sys_console_c::ThreadProc() InstallPrintHook(); - isRunning = true; - while (doRun) { + SetEvent(threadStartedEvent); + while (WAIT_OBJECT_0 != MsgWaitForMultipleObjects(1, &threadShouldStopEvent, FALSE, INFINITE, QS_ALLINPUT)) { RunMessages(hwMain); - sys->Sleep(1); } RemovePrintHook(); @@ -170,16 +172,19 @@ void sys_console_c::ThreadProc() DestroyWindow(hwMain); UnregisterClass(CFG_SCON_TITLE " Class", sys->hinst); - isRunning = false; - // Flush windowless messages (Like WM_QUIT) RunMessages(); + + SetEvent(threadExitedEvent); } sys_console_c::~sys_console_c() { - doRun = false; - while (isRunning); + SetEvent(threadShouldStopEvent); + WaitForSingleObject(threadExitedEvent, INFINITE); + CloseHandle(threadStartedEvent); + CloseHandle(threadShouldStopEvent); + CloseHandle(threadExitedEvent); } // ======================== @@ -204,7 +209,7 @@ LRESULT __stdcall sys_console_c::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPA } case WM_CLOSE: // Quit - conWin->doRun = false; + SetEvent(conWin->threadShouldStopEvent); PostQuitMessage(0); return FALSE; } diff --git a/ui_api.cpp b/ui_api.cpp index 6008350..05cf10b 100644 --- a/ui_api.cpp +++ b/ui_api.cpp @@ -404,6 +404,7 @@ SG_LUA_CPP_FUN_BEGIN(imgHandleLoad) fileName = ui->scriptWorkDir / fileName; } delete imgHandle->hnd; + imgHandle->hnd = nullptr; int flags = TF_NOMIPMAP; for (int f = 2; f <= n; f++) { if (!lua_isstring(L, f)) { @@ -1909,6 +1910,7 @@ SG_LUA_CPP_FUN_BEGIN(LoadModule) if (!fileName.has_extension()) { fileName.replace_extension(".lua"); } + PERFORMANCEAPI_INSTRUMENT_DATA("[API]LoadModule", fileName.generic_string().c_str()); ui->sys->SetWorkDir(ui->scriptPath); auto fileStr = fileName.generic_u8string(); @@ -1932,6 +1934,7 @@ SG_LUA_CPP_FUN_BEGIN(PLoadModule) if (!fileName.has_extension()) { fileName.replace_extension(".lua"); } + PERFORMANCEAPI_INSTRUMENT_DATA("[API]PLoadModule", fileName.generic_string().c_str()); ui->sys->SetWorkDir(ui->scriptPath); int err = luaL_loadfile(L, fileName.generic_u8string().c_str()); @@ -2116,10 +2119,6 @@ static int l_OpenURL(lua_State* L) static int l_SetProfiling(lua_State* L) { - ui_main_c* ui = GetUIPtr(L); - int n = lua_gettop(L); - ui->LAssert(L, n >= 1, "Usage: SetProfiling(isEnabled)"); - ui->debug->SetProfiling(lua_toboolean(L, 1) == 1); return 0; } diff --git a/ui_debug.cpp b/ui_debug.cpp deleted file mode 100644 index 73eba47..0000000 --- a/ui_debug.cpp +++ /dev/null @@ -1,299 +0,0 @@ -// DyLua: SimpleGraphic -// (c) David Gowor, 2014 -// -// Module: UI Debug -// - -#include "ui_local.h" - -// ======= -// Classes -// ======= - -struct d_lineHit_s { - char* source; - char* name; - int line; - int count; -}; - -struct d_callHit_s { - char* source; - char* name; - int count; - int lineHitNum; - int lineHitSz; - d_lineHit_s* lineHits; -}; - -// =================== -// ui_IDebug Interface -// =================== - -class ui_debug_c : public ui_IDebug, public thread_c { -public: - // Interface - void SetProfiling(bool enable) override; - void ToggleProfiling() override; - - // Encapsulated - ui_debug_c(ui_main_c* ui); - ~ui_debug_c(); - - ui_main_c* ui = nullptr; - - volatile bool doRun = false; - volatile bool isRunning = false; - - volatile bool profiling = false; - - volatile bool hookHold = false; - volatile bool hookHolding = false; - - volatile int lineHitNum = 0; - int lineHitSz = 0; - d_lineHit_s* lineHits = nullptr; - - volatile int callHitNum = 0; - int callHitSz = 0; - int callHitInitCount = 0; - d_callHit_s* callHits = nullptr; - - void ThreadProc(); -}; - -ui_IDebug* ui_IDebug::GetHandle(ui_main_c* ui) -{ - return new ui_debug_c(ui); -} - -void ui_IDebug::FreeHandle(ui_IDebug* hnd) -{ - delete (ui_debug_c*)hnd; -} - -ui_debug_c::ui_debug_c(ui_main_c* ui) - : thread_c(ui->sys), ui(ui) -{ - profiling = false; - - hookHold = false; - hookHolding = false; - - lineHitNum = 0; - lineHitSz = 16; - lineHits = new d_lineHit_s[lineHitSz]; - - callHitNum = 0; - callHitSz = 16; - callHitInitCount = 0; - callHits = new d_callHit_s[callHitSz]; - - doRun = true; - ThreadStart(); -} - -ui_debug_c::~ui_debug_c() -{ - profiling = false; - while (lineHitNum || callHitNum); - doRun = false; - while (isRunning); - delete lineHits; - for (int i = 0; i < callHitInitCount; i++) { - delete callHits[i].lineHits; - } - delete callHits; -} - -// ============== -// UI Debug Class -// ============== - -// Grab UI main pointer from the registry -static ui_debug_c* GetDebugPtr(lua_State* L) -{ - lua_rawgeti(L, LUA_REGISTRYINDEX, 0); - ui_main_c* ui = (ui_main_c*)lua_touserdata(L, -1); - lua_pop(L, 1); - return (ui_debug_c*)ui->debug; -} - -static void debugHook(lua_State* L, lua_Debug* ar) -{ - ui_debug_c* d = GetDebugPtr(L); - d->hookHolding = true; - while (d->hookHold); - d->hookHolding = false; -} - -static int lineComp(const void* aVoid, const void* bVoid) -{ - d_lineHit_s* a = (d_lineHit_s*)aVoid; - d_lineHit_s* b = (d_lineHit_s*)bVoid; - if (a->count == b->count) { - return 0; - } - else { - return a->count > b->count ? -1 : 1; - } -} - -static int callComp(const void* aVoid, const void* bVoid) -{ - d_callHit_s* a = (d_callHit_s*)aVoid; - d_callHit_s* b = (d_callHit_s*)bVoid; - if (a->count == b->count) { - return 0; - } - else { - return a->count > b->count ? -1 : 1; - } -} - -void ui_debug_c::ThreadProc() -{ - isRunning = true; - while (doRun) { - ui->sys->Sleep(1); - - if (profiling) { - if (!ui->inLua) { - continue; - } - hookHold = true; - lua_sethook(ui->L, &debugHook, LUA_MASKLINE, 0); - while (profiling && !hookHolding); - lua_sethook(ui->L, &debugHook, 0, 0); - if (!profiling) { - hookHold = false; - continue; - } - lua_Debug dbg; - memset(&dbg, 0, sizeof(dbg)); - if (lua_getstack(ui->L, 0, &dbg) && lua_getinfo(ui->L, "Sln", &dbg) && dbg.source) { - int l; - for (l = 0; l < lineHitNum; l++) { - if (dbg.currentline == lineHits[l].line && !strcmp(dbg.source, lineHits[l].source)) { - if (dbg.name && !lineHits[l].name) { - lineHits[l].name = AllocString(dbg.name); - } - lineHits[l].count++; - break; - } - } - if (l == lineHitNum) { - if (lineHitNum == lineHitSz) { - lineHitSz <<= 1; - trealloc(lineHits, lineHitSz); - } - lineHits[l].source = AllocString(dbg.source); - lineHits[l].name = AllocString(dbg.name); - lineHits[l].line = dbg.currentline; - lineHits[l].count = 1; - lineHitNum++; - } - const char* funcSource = dbg.source; - const char* funcName = dbg.name; - if (funcName && lua_getstack(ui->L, 1, &dbg) && lua_getinfo(ui->L, "Sln", &dbg) && dbg.source) { - int c; - for (c = 0; c < callHitNum; c++) { - if (!strcmp(funcSource, callHits[c].source) && !strcmp(funcName, callHits[c].name)) { - callHits[c].count++; - break; - } - } - if (c == callHitNum) { - if (callHitNum == callHitSz) { - callHitSz <<= 1; - trealloc(callHits, callHitSz); - } - if (callHitNum == callHitInitCount) { - callHits[c].lineHitSz = 16; - callHits[c].lineHits = new d_lineHit_s[16]; - callHitInitCount++; - } - callHits[c].source = AllocString(funcSource); - callHits[c].name = AllocString(funcName); - callHits[c].count = 1; - callHits[c].lineHitNum = 0; - callHitNum++; - } - d_callHit_s* call = callHits + c; - int l; - for (l = 0; l < call->lineHitNum; l++) { - if (dbg.currentline == call->lineHits[l].line && !strcmp(dbg.source, call->lineHits[l].source)) { - if (dbg.name && !call->lineHits[l].name) { - call->lineHits[l].name = AllocString(dbg.name); - } - call->lineHits[l].count++; - break; - } - } - if (l == call->lineHitNum) { - if (call->lineHitNum == call->lineHitSz) { - call->lineHitSz <<= 1; - trealloc(call->lineHits, call->lineHitSz); - } - call->lineHits[l].source = AllocString(dbg.source); - call->lineHits[l].name = AllocString(dbg.name); - call->lineHits[l].line = dbg.currentline; - call->lineHits[l].count = 1; - call->lineHitNum++; - } - } - } - hookHold = false; - while (hookHolding); - } - else if (lineHitNum) { - ui->sys->con->Printf("Hot lines:\n"); - qsort(lineHits, lineHitNum, sizeof(d_lineHit_s), lineComp); - for (int l = 0; l < lineHitNum; l++) { - if (l < 20) { - ui->sys->con->Printf("%s(%d) in '%s': %d\n", lineHits[l].source, lineHits[l].line, lineHits[l].name ? lineHits[l].name : "?", lineHits[l].count); - } - delete lineHits[l].source; - delete lineHits[l].name; - } - lineHitNum = 0; - ui->sys->con->Printf("Hot calls:\n"); - qsort(callHits, callHitNum, sizeof(d_callHit_s), callComp); - for (int c = 0; c < callHitNum; c++) { - qsort(callHits[c].lineHits, callHits[c].lineHitNum, sizeof(d_lineHit_s), lineComp); - if (c < 10) { - ui->sys->con->Printf("%s in '%s': %d\n", callHits[c].source, callHits[c].name, callHits[c].count); - } - for (int l = 0; l < callHits[c].lineHitNum; l++) { - if (c < 10 && l < 5) { - ui->sys->con->Printf("\t%s(%d) in '%s': %d\n", callHits[c].lineHits[l].source, callHits[c].lineHits[l].line, callHits[c].lineHits[l].name ? callHits[c].lineHits[l].name : "?", callHits[c].lineHits[l].count); - } - delete callHits[c].lineHits[l].source; - delete callHits[c].lineHits[l].name; - } - delete callHits[c].source; - delete callHits[c].name; - } - callHitNum = 0; - } - } - isRunning = false; -} - -void ui_debug_c::SetProfiling(bool enable) -{ - if (enable) { - ui->sys->con->Printf("Profiling enabled.\n"); - profiling = true; - } - else { - ui->sys->con->Printf("Profiling finished:\n"); - profiling = false; - while (lineHitNum || callHitNum); - } -} - -void ui_debug_c::ToggleProfiling() -{ - SetProfiling(!profiling); -} diff --git a/ui_debug.h b/ui_debug.h deleted file mode 100644 index 6c1cfca..0000000 --- a/ui_debug.h +++ /dev/null @@ -1,19 +0,0 @@ -// DyLua: SimpleGraphic -// (c) David Gowor, 2014 -// -// UI Debug Header -// - -// ========== -// Interfaces -// ========== - -// UI Debug Handler -class ui_IDebug { -public: - static ui_IDebug* GetHandle(class ui_main_c*); - static void FreeHandle(ui_IDebug*); - - virtual void SetProfiling(bool enable) = 0; - virtual void ToggleProfiling() = 0; -}; \ No newline at end of file diff --git a/ui_local.h b/ui_local.h index 5925ed7..780dbe7 100644 --- a/ui_local.h +++ b/ui_local.h @@ -16,7 +16,6 @@ #include #include "ui_console.h" -#include "ui_debug.h" #include "ui_subscript.h" #include "ui_main.h" \ No newline at end of file diff --git a/ui_main.cpp b/ui_main.cpp index 09e59be..7adfe19 100644 --- a/ui_main.cpp +++ b/ui_main.cpp @@ -317,9 +317,6 @@ void ui_main_c::ScriptInit() if (err) sys->Error("Error initialising Lua environment: \n%s\n", lua_tostring(L, -1)); lua_gc(L, LUA_GCRESTART, -1); - // Setup debug system - debug = ui_IDebug::GetHandle(this); - // Setup subscript system subScriptSize = 16; subScriptList = new ui_ISubScript*[subScriptSize]; @@ -453,14 +450,13 @@ void ui_main_c::ScriptShutdown() PCall(extraArgs, 0); } - // Shutdown subscript and debug systems + // Shutdown subscript system for (dword i = 0; i < subScriptSize; i++) { if (subScriptList[i]) { ui_ISubScript::FreeHandle(subScriptList[i]); } } delete subScriptList; - ui_IDebug::FreeHandle(debug); // Shutdown Lua L = NULL; @@ -532,11 +528,6 @@ void ui_main_c::KeyEvent(int key, int type) case KEY_F10: renderer->ToggleDebugImGui(); break; - case KEY_PAUSE: - if (sys->IsKeyDown(KEY_SHIFT)) { - debug->ToggleProfiling(); - break; - } default: CallKeyHandler("OnKeyUp", key, false); break; diff --git a/ui_main.h b/ui_main.h index 02ec907..32d8331 100644 --- a/ui_main.h +++ b/ui_main.h @@ -29,7 +29,6 @@ class ui_main_c: public ui_IMain { r_IRenderer* renderer = nullptr; ui_IConsole* conUI = nullptr; - ui_IDebug* debug = nullptr; dword subScriptSize = 0; ui_ISubScript** subScriptList = nullptr; diff --git a/ui_subscript.cpp b/ui_subscript.cpp index befba5c..0d0bcdb 100644 --- a/ui_subscript.cpp +++ b/ui_subscript.cpp @@ -372,6 +372,7 @@ void ui_subscript_c::Stop() void ui_subscript_c::ThreadProc() { + PerformanceAPI_SetCurrentThreadName("Subscript"); int numarg = (int)lua_tointeger(L, -1); lua_pop(L, 1); if (lua_pcall(L, numarg, LUA_MULTRET, 1)) { diff --git a/vcpkg.json b/vcpkg.json index 2e4efdd..5bdc5d3 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -13,6 +13,7 @@ "pkgconf", "re2", "sol2", + "xxhash", "zstd", "zlib" ] diff --git a/win/entry.cpp b/win/entry.cpp index af02f1f..582d9b5 100644 --- a/win/entry.cpp +++ b/win/entry.cpp @@ -77,6 +77,7 @@ extern "C" SIMPLEGRAPHIC_DLL_PUBLIC int RunLuaFileAsWin(int argc, char** argv) { + PerformanceAPI_SetCurrentThreadName("Main"); #ifdef _MEMTRAK_H strcpy_s(_memTrak_reportName, 512, "SimpleGraphic/memtrak.log"); #endif