Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NGFX CMake Nabla build system integration #789

Merged
merged 2 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions 3rdparty/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ NBL_ADD_GIT_TRACKING_META_LIBRARY(nabla "${NBL_ROOT_PATH}")
NBL_ADD_GIT_TRACKING_META_LIBRARY(dxc "${CMAKE_CURRENT_SOURCE_DIR}/dxc/dxc")
NBL_GENERATE_GIT_TRACKING_META()

# NGFX
include(ngfx/ngfx.cmake)

if(NBL_BUILD_IMGUI)
set(NBL_IMGUI_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/imgui")
set(NBL_IMGUI_TEST_ENGINE_PROJECT_ROOT "${THIRD_PARTY_SOURCE_DIR}/imgui_test_engine")
Expand Down
67 changes: 67 additions & 0 deletions 3rdparty/ngfx/ngfx.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
option(NBL_BUILD_WITH_NGFX "Enable NGFX build" OFF)

# NOTE: on windows default installation path is:
# "C:/Program Files/NVIDIA Corporation/Nsight Graphics <version>/SDKs/NsightGraphicsSDK" <- define as "NGFX_SDK" environment variable
# then you can pick SDK version with "NGFX_SDK_VERSION" cache variable (CMake GUI list supported)

if(NBL_BUILD_WITH_NGFX)
if(NOT DEFINED ENV{NGFX_SDK})
message(FATAL_ERROR "\"NGFX_SDK\" environment variable must be defined to build with NBL_BUILD_WITH_NGFX enabled!")
endif()

set(NGFX_SDK "$ENV{NGFX_SDK}")
cmake_path(NORMAL_PATH NGFX_SDK OUTPUT_VARIABLE NGFX_SDK)

if(NOT EXISTS "${NGFX_SDK}")
message(FATAL_ERROR "Found \"NGFX_SDK\" environment variable but it is invalid, env:NGFX_SDK=\"${NGFX_SDK}\" doesn't exist!")
endif()

file(GLOB ENTRIES "${NGFX_SDK}/*")

set(NGFX_VERSIONS "")
foreach(ENTRY ${ENTRIES})
if(IS_DIRECTORY ${ENTRY})
list(APPEND NGFX_VERSIONS ${ENTRY})
endif()
endforeach()

if(NOT NGFX_VERSIONS)
message(FATAL_ERROR "Could not find any NGFX SDK Version!")
endif()

list(TRANSFORM NGFX_VERSIONS REPLACE "${NGFX_SDK}/" "")
list(SORT NGFX_VERSIONS)
list(GET NGFX_VERSIONS -1 LATEST_NGFX_VERSION)

# on the cache variable init pick the latest version, then let user pick from list
set(NGFX_SDK_VERSION "${LATEST_NGFX_VERSION}" CACHE STRING "NGFX SDK Version")
set_property(CACHE NGFX_SDK_VERSION PROPERTY STRINGS ${NGFX_VERSIONS})

set(NGFX_SDK_VERSION "$CACHE{NGFX_SDK_VERSION}")
set(NGFX_SDK_BASE "${NGFX_SDK}/${NGFX_SDK_VERSION}")

# TODO: wanna support more *host* platforms? (*)
# NOTE: also I'm hardcoding windows x64 library requests till I know the answer for (*)
find_file(NBL_NGFX_INJECTION_HEADER NGFX_Injection.h PATHS ${NGFX_SDK_BASE}/include)
find_file(NBL_NGFX_INJECTION_DLL NGFX_Injection.dll PATHS ${NGFX_SDK_BASE}/lib/x64)
find_file(NBL_NGFX_INJECTION_IMPORT_LIBRARY NGFX_Injection.lib PATHS ${NGFX_SDK_BASE}/lib/x64)

if(NBL_NGFX_INJECTION_HEADER AND NBL_NGFX_INJECTION_DLL AND NBL_NGFX_INJECTION_IMPORT_LIBRARY)
message(STATUS "Enabled build with NVIDIA Nsight Graphics SDK ${NGFX_SDK_VERSION}\nlocated in: \"${NGFX_SDK_BASE}\"")
else()
message(STATUS "Could not enable build with NVIDIA Nsight Graphics SDK ${NGFX_SDK_VERSION} - invalid components!")
message(STATUS "Located in: \"${NGFX_SDK_BASE}\"")
message(STATUS "NBL_NGFX_INJECTION_HEADER=\"${NBL_NGFX_INJECTION_HEADER}\"")
message(STATUS "NBL_NGFX_INJECTION_DLL=\"${NBL_NGFX_INJECTION_DLL}\"")
message(STATUS "NBL_NGFX_INJECTION_IMPORT_LIBRARY=\"${NBL_NGFX_INJECTION_IMPORT_LIBRARY}\"")
message(FATAL_ERROR "You installation may be corupted, please fix it and re-run CMake or disable NBL_BUILD_WITH_NGFX!")
endif()

add_library(ngfx INTERFACE)
target_sources(ngfx INTERFACE "${NBL_NGFX_INJECTION_HEADER}")
target_include_directories(ngfx INTERFACE "${NGFX_SDK_BASE}/include")
target_link_libraries(ngfx INTERFACE "${NBL_NGFX_INJECTION_IMPORT_LIBRARY}")
target_link_options(ngfx INTERFACE "/DELAYLOAD:NGFX_Injection.dll")
target_compile_definitions(ngfx INTERFACE NGFX_INJECTION_DLL_DIR="${NGFX_SDK_BASE}/lib/x64")
target_compile_definitions(ngfx INTERFACE NGFX_VERSION="${NGFX_SDK_VERSION}")
endif()
10 changes: 8 additions & 2 deletions include/nbl/video/IAPIConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,17 @@ class NBL_API2 IAPIConnection : public core::IReferenceCounted
SDebuggerType m_debuggerType;
renderdoc_api_t* m_rdoc_api;

struct SNGFXIntegration {
bool useNGFX;
struct SNGFXIntegration
{
SNGFXIntegration();

bool useNGFX = false;

bool injectNGFXToProcess();
bool executeNGFXCommand();
inline bool isAPILoaded() { return m_loaded; }
private:
const bool m_loaded;
};
using ngfx_api_t = SNGFXIntegration;
ngfx_api_t m_ngfx_api;
Expand Down
15 changes: 15 additions & 0 deletions src/nbl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,21 @@ write_source_definitions("${_NBL_DEFINE_FILE_WRAPPER_}" "${_NBL_SOURCE_DEFINITIO
# git version tracking
target_link_libraries(Nabla PUBLIC gtml)

# NGFX
if(TARGET ngfx)
if(NBL_STATIC_BUILD)
target_link_libraries(Nabla INTERFACE ngfx)
else()
target_link_libraries(Nabla PRIVATE ngfx)
endif()

target_include_directories(Nabla PRIVATE $<TARGET_PROPERTY:ngfx,INTERFACE_INCLUDE_DIRECTORIES>)
target_compile_definitions(Nabla
PRIVATE NBL_BUILD_WITH_NGFX
PRIVATE $<TARGET_PROPERTY:ngfx,INTERFACE_COMPILE_DEFINITIONS>
)
endif()
Comment on lines +662 to +675

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AnastaZIuk why is there an ngfx target cmake knows about?

we dont do this for Vulkan, CUDA, Optix, we delay load from DLL and are not supposed to crash if not found

Copy link
Member Author

@AnastaZIuk AnastaZIuk Dec 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AnastaZIuk why is there an ngfx target cmake knows about?

because I introduce modern cmake there as I do on newBuildSystem branch, interface targets don't have build rules but you can interact with them

we dont do this for Vulkan, CUDA, Optix

it's actually not quite true, for example when you call find_package(Vulkan) you get imported targets if found which are similar to interface targets and because there is no existing ngfx module which I can request I wrote a simple one myself just to embed whole logic into single logical target (it could also be imported target just like vulkan targets - doesn't matter too much). The only difference is the module is integral part of Nabla's cmake build system and we don't explicitly call the find_package on it but have an option to make it available for configure scope if possible.

If the target is available it means:

  • someone enabled NBL_BUILD_WITH_NGFX
  • the SDK with the injection header has been found and passed a simple validation

we delay load from DLL and are not supposed to crash if not found

and what makes you think it will crash? We do delay load from DLL here as well & we have m_loaded member to make sure we can call the API only if we first loaded the DLL

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok thanks for explaining it to me

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but whats the deal with target_link_libraries and the ngfx being an INTERFACE instead of PRIVATE?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but whats the deal with target_link_libraries and the ngfx being an INTERFACE instead of PRIVATE?

the reason for

if(NBL_STATIC_BUILD)
  target_link_libraries(Nabla INTERFACE ngfx)
else()
  target_link_libraries(Nabla PRIVATE ngfx)
endif()

is that in static build mode each executable must link against the import library - using the INTERFACE qualifier ensures this linkage is propagated.

On the other hand when building Nabla as a DLL the PRIVATE qualifier is used to prevent any inheritance of ngfx linkage for targets linking Nabla since the import library is already linked directly to the DLL.


#on MSVC it won't compile without this option!
if (MSVC)
target_compile_options(Nabla PUBLIC /bigobj)
Expand Down
171 changes: 118 additions & 53 deletions src/nbl/video/IAPIConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

#include "nbl/video/IPhysicalDevice.h"
#include "nbl/video/utilities/renderdoc.h"
#include "nbl/video/utilities/ngfx.h"

// TODO: temporary hopefully
#include "C:\Program Files\NVIDIA Corporation\Nsight Graphics 2024.1.0\SDKs\NsightGraphicsSDK\0.8.0\include\NGFX_Injection.h"
#include "nbl/system/CSystemWin32.h"

#ifdef NBL_BUILD_WITH_NGFX
#include "NGFX_Injection.h"
#endif

#if defined(_NBL_POSIX_API_)
#include <dlfcn.h>
Expand All @@ -14,7 +16,6 @@
namespace nbl::video
{


std::span<IPhysicalDevice* const> IAPIConnection::getPhysicalDevices() const
{
static_assert(sizeof(std::unique_ptr<IPhysicalDevice>) == sizeof(void*));
Expand Down Expand Up @@ -59,70 +60,134 @@ IAPIConnection::IAPIConnection(const SFeatures& enabledFeatures)

bool IAPIConnection::SNGFXIntegration::injectNGFXToProcess()
{
uint32_t numInstallations = 0;
auto result = NGFX_Injection_EnumerateInstallations(&numInstallations, nullptr);
if (numInstallations == 0 || NGFX_INJECTION_RESULT_OK != result)
#ifdef NBL_BUILD_WITH_NGFX
if (m_loaded) //! this check is mandatory!
{
useNGFX = false;
return false;
}
uint32_t numInstallations = 0;
auto result = NGFX_Injection_EnumerateInstallations(&numInstallations, nullptr);
if (numInstallations == 0 || NGFX_INJECTION_RESULT_OK != result)
{
useNGFX = false;
return false;
}

std::vector<NGFX_Injection_InstallationInfo> installations(numInstallations);
result = NGFX_Injection_EnumerateInstallations(&numInstallations, installations.data());
if (numInstallations == 0 || NGFX_INJECTION_RESULT_OK != result)
{
useNGFX = false;
return false;
}
std::vector<NGFX_Injection_InstallationInfo> installations(numInstallations);
result = NGFX_Injection_EnumerateInstallations(&numInstallations, installations.data());
if (numInstallations == 0 || NGFX_INJECTION_RESULT_OK != result)
{
useNGFX = false;
return false;
}

// get latest installation
NGFX_Injection_InstallationInfo versionInfo = installations.back();
// get latest installation
NGFX_Injection_InstallationInfo versionInfo = installations.back();

uint32_t numActivities = 0;
result = NGFX_Injection_EnumerateActivities(&versionInfo, &numActivities, nullptr);
if (numActivities == 0 || NGFX_INJECTION_RESULT_OK != result)
{
useNGFX = false;
return false;
}
uint32_t numActivities = 0;
result = NGFX_Injection_EnumerateActivities(&versionInfo, &numActivities, nullptr);
if (numActivities == 0 || NGFX_INJECTION_RESULT_OK != result)
{
useNGFX = false;
return false;
}

std::vector<NGFX_Injection_Activity> activities(numActivities);
result = NGFX_Injection_EnumerateActivities(&versionInfo, &numActivities, activities.data());
if (NGFX_INJECTION_RESULT_OK != result)
{
useNGFX = false;
return false;
}
std::vector<NGFX_Injection_Activity> activities(numActivities);
result = NGFX_Injection_EnumerateActivities(&versionInfo, &numActivities, activities.data());
if (NGFX_INJECTION_RESULT_OK != result)
{
useNGFX = false;
return false;
}

const NGFX_Injection_Activity* pActivityToInject = nullptr;
for (const NGFX_Injection_Activity& activity : activities)
{
if (activity.type == NGFX_INJECTION_ACTIVITY_FRAME_DEBUGGER) // only want frame debugger
const NGFX_Injection_Activity* pActivityToInject = nullptr;
for (const NGFX_Injection_Activity& activity : activities)
{
pActivityToInject = &activity;
break;
if (activity.type == NGFX_INJECTION_ACTIVITY_FRAME_DEBUGGER) // only want frame debugger
{
pActivityToInject = &activity;
break;
}
}
}

if (!pActivityToInject) {
useNGFX = false;
return false;
}
if (!pActivityToInject) {
useNGFX = false;
return false;
}

result = NGFX_Injection_InjectToProcess(&versionInfo, pActivityToInject);
if (NGFX_INJECTION_RESULT_OK != result)
{
useNGFX = false;
return false;
}
result = NGFX_Injection_InjectToProcess(&versionInfo, pActivityToInject);
if (NGFX_INJECTION_RESULT_OK != result)
{
useNGFX = false;
return false;
}

useNGFX = true;

return true;
} // optional TOOD: could log on "else"
#endif // NBL_BUILD_WITH_NGFX

useNGFX = true;
return true;
return false;
}

bool IAPIConnection::SNGFXIntegration::executeNGFXCommand()
{
return NGFX_Injection_ExecuteActivityCommand() == NGFX_INJECTION_RESULT_OK;
#ifdef NBL_BUILD_WITH_NGFX
if(m_loaded) //! this check is mandatory!
return NGFX_Injection_ExecuteActivityCommand() == NGFX_INJECTION_RESULT_OK; // optional TOOD: could log on "else"
#endif // NBL_BUILD_WITH_NGFX

return false;
}

IAPIConnection::SNGFXIntegration::SNGFXIntegration()
: useNGFX(false /*??*/), m_loaded([]() -> bool
{
#ifdef NBL_BUILD_WITH_NGFX
//! absolute path to official install NGFX SDK runtime directory
auto getOfficialRuntimeDirectory = []()
{
const char* sdk = std::getenv("NGFX_SDK");
const char* version = std::getenv("NGFX_VERSION");
const bool composed = sdk && version;

if (composed)
{
const auto directory = system::path(sdk) / system::path(version) / "lib" / "x64";

if (std::filesystem::exists(directory))
return directory;
}

return system::path("");
};

//! batch request with priority order & custom Nabla runtime search, I'm assuming we are loading the runtime from official SDK not custom location
//! one question is if we should have any constraints for min/max version, maybe force the "version"
//! to match the "NGFX_VERSION" define so to "what we built with", or don't have any - just like now

#if defined(_NBL_PLATFORM_WINDOWS_)
static constexpr std::string_view NGFXMODULE = "NGFX_Injection.dll";
HMODULE isAlreadyLoaded = GetModuleHandleA(NGFXMODULE.data());

if (!isAlreadyLoaded)
{
const auto dll = getOfficialRuntimeDirectory() / NGFXMODULE.data();
const HRESULT hook = system::CSystemWin32::delayLoadDLL(NGFXMODULE.data(), { NGFX_INJECTION_DLL_DIR, dll.parent_path() });

//! don't be scared if you see "No symbols loaded" - you will not hit "false" in this case, the DLL will get loaded if found,
//! proc addresses will be resolved correctly but status will scream "FAILED" because we don't have any PDB to load
if (FAILED(hook))
return false;
}
#else
#error "TODO!"
#endif

return true;
#else
return false; // no NGFX build -> no API to load
#endif
}())
{}

}