diff --git a/.gitmodules b/.gitmodules index e2d9e1c..92d6c4e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "Src/ThirdParty/Amd"] path = Src/ThirdParty/Amd url = https://github.com/GPUOpen-LibrariesAndSDKs/ADLX +[submodule "Src/com.nthompson.gpu.sdPlugin/libs"] + path = Src/com.nthompson.gpu.sdPlugin/libs + url = https://github.com/elgatosf/streamdeck-javascript-sdk.git diff --git a/CMakeLists.txt b/CMakeLists.txt index dfe5bf4..f72c7d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,12 +22,14 @@ add_executable(gpu Src/Windows/Amd/AmdGpuUsage.cpp Src/Windows/Amd/AmdGpuUsage.h Src/Windows/IGpuUsage.h + Src/GpuAbstraction.h ) target_include_directories(gpu PUBLIC Src/ThirdParty/Amd) add_library(adlx STATIC ${THIRD_PARTY_AMD_INCLUDE} ${THIRD_PARTY_AMD_SRC} ${THIRD_PARTY_AMD_SRC_WIN}) +set_property(TARGET gpu PROPERTY LINK_FLAGS "/DELAYLOAD:nvml.dll") + target_link_libraries(gpu PRIVATE StreamDeckSDK CUDA::nvml adlx d3d12.lib dxgi.lib) -set_property(TARGET gpu PROPERTY LINK_FLAGS "/DELAYLOAD:nvml.dll") \ No newline at end of file diff --git a/Src/GpuAbstraction.h b/Src/GpuAbstraction.h new file mode 100644 index 0000000..f023091 --- /dev/null +++ b/Src/GpuAbstraction.h @@ -0,0 +1,26 @@ +// +// Created by Noah Thompson on 8/7/2024. +// + +#pragma once +#include +#include + + +namespace nthompson { + + enum class GpuVendor : int32_t { + Nvidia, + Amd, + Unknown + }; + + struct Gpu { + GpuVendor vendor; + std::string name; + uint32_t index; + }; + + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Gpu, vendor, name, index); +} + diff --git a/Src/GpuPlugin.cpp b/Src/GpuPlugin.cpp index 5ee0c3d..a2c2615 100644 --- a/Src/GpuPlugin.cpp +++ b/Src/GpuPlugin.cpp @@ -6,8 +6,7 @@ namespace nthompson { // Convert a wide Unicode string to an UTF8 string - std::string ConvToString(const std::wstring &wStr) - { + std::string ConvToString(const std::wstring &wStr) { if(wStr.empty()) return {}; int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, &wStr[0], (int)wStr.size(), nullptr, 0, nullptr, nullptr); std::string strTo(sizeNeeded, 0); @@ -15,12 +14,12 @@ namespace nthompson { return strTo; } - void Timer::Start(int32_t interval, const std::function& func) { + void Timer::Start(const int32_t& interval, const std::function& func) { if (running_) return; running_ = true; - thread_ = std::thread([this, &interval, func]() { + thread_ = std::thread([this, interval, func]() { + std::scoped_lock lock(mutex_); while (running_) { - std::scoped_lock lock(mutex_); func(); std::this_thread::sleep_for(std::chrono::milliseconds(interval)); } @@ -35,47 +34,7 @@ namespace nthompson { } GpuPlugin::GpuPlugin() { - IDXGIFactory* factory = nullptr; - - HRESULT result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory); - - if (FAILED(result)) { - ESDLog("Failed to create factory object."); - return; - } - - std::string amd = "amd", advancedMicroDevices = "advanced micro devices", nvidia = "nvidia"; - - UINT index = 0; - IDXGIAdapter* adapter = nullptr; - - nlohmann::json payload; - - while (factory->EnumAdapters(index, &adapter) != DXGI_ERROR_NOT_FOUND) { - DXGI_ADAPTER_DESC desc; - adapter->GetDesc(&desc); - std::wstring wDescription = desc.Description; - - std::string description = ConvToString(wDescription); - - std::transform(description.begin(), description.end(), description.begin(), - [](char c) { return std::tolower(c); }); - - if (description.find(nvidia) != std::string::npos) { - usage_ = std::make_unique(); - break; - } - else if (description.find(amd) != std::string::npos || description.find(advancedMicroDevices) != std::string::npos) { - usage_ = std::make_unique(); - break; - } else { - ESDLog("Found unsupported display adapter"); - usage_ = nullptr; - } - - ++index; - } - + FindAvailableGpus(); timer_ = std::make_unique(); @@ -86,9 +45,6 @@ namespace nthompson { void GpuPlugin::Update() { - if (mConnectionManager == nullptr) return; - std::scoped_lock lock(mutex_); - if (usage_ == nullptr) { SetActionText("?"); return; @@ -100,7 +56,6 @@ namespace nthompson { stream << std::to_string(utilization) << "%"; SetActionText(stream.str()); - } GpuPlugin::~GpuPlugin() { @@ -119,10 +74,88 @@ namespace nthompson { contexts_.erase(inContext); } - void GpuPlugin::SetActionText(std::string text) { + void GpuPlugin::SetActionText(const std::string& text) { for (const std::string& context : contexts_) { mConnectionManager->SetTitle(text, context, kESDSDKTarget_HardwareAndSoftware); } } + void GpuPlugin::FindAvailableGpus() { + IDXGIFactory* factory = nullptr; + + HRESULT result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory); + + if (FAILED(result)) { + ESDLog("Failed to create factory object."); + return; + } + + std::string amd = "amd", advancedMicroDevices = "advanced micro devices", nvidia = "nvidia"; + + UINT index = 0; + IDXGIAdapter* adapter = nullptr; + + nlohmann::json payload; + + while (factory->EnumAdapters(index, &adapter) != DXGI_ERROR_NOT_FOUND) { + DXGI_ADAPTER_DESC desc; + adapter->GetDesc(&desc); + std::wstring wDescription = desc.Description; + + std::string description = ConvToString(wDescription); + std::string gpuName = description; + + std::transform(description.begin(), description.end(), description.begin(), + [](char c) { return std::tolower(c); }); + + if (description.find(nvidia) != std::string::npos) { + gpus_.emplace_back(Gpu{GpuVendor::Nvidia, gpuName, index}); + std::string gpuLog = gpuName + " found"; + ESDLog(gpuLog); + } + else if (description.find(amd) != std::string::npos || description.find(advancedMicroDevices) != std::string::npos) { + gpus_.emplace_back(Gpu{GpuVendor::Amd, gpuName, index}); + std::string gpuLog = gpuName + " found"; + ESDLog(gpuLog); + } else { + gpus_.emplace_back(Gpu{GpuVendor::Unknown, gpuName, index}); + ESDLog("Found unsupported display adapter"); + } + + ++index; + } + } + + void + GpuPlugin::SendToPlugin(const std::string &inAction, const std::string &inContext, const nlohmann::json &inPayload, + const std::string &inDeviceID) { + nlohmann::json payload; + if (inPayload.at("propertyInspectorLoaded").get()) { + payload["type"] = "availableGpus"; + payload["gpus"] = gpus_; + payload["selected"] = selectedGpu_; + mConnectionManager->SendToPropertyInspector(inAction, inContext, payload); + } + + if (inPayload.at("receiveSelection").get()) { + Gpu gpu = inPayload["gpuInfo"]; + HandleSelectedGpu(gpu); + mConnectionManager->SendToPropertyInspector(inAction, inContext, payload); + } + } + + void GpuPlugin::HandleSelectedGpu(const Gpu &gpu) { + switch (gpu.vendor) { + case GpuVendor::Nvidia: + usage_ = std::make_unique(gpu.index); + break; + case GpuVendor::Amd: + usage_ = std::make_unique(gpu.index); + break; + case GpuVendor::Unknown: + usage_ = nullptr; + break; + } + selectedGpu_ = gpu.index; + } } \ No newline at end of file diff --git a/Src/GpuPlugin.h b/Src/GpuPlugin.h index 5d0d98f..9e12fa9 100644 --- a/Src/GpuPlugin.h +++ b/Src/GpuPlugin.h @@ -15,12 +15,13 @@ #include #include "Windows/Nvidia/NvidiaGpuUsage.h" #include "Windows/Amd/AmdGpuUsage.h" +#include "GpuAbstraction.h" namespace nthompson { class Timer { public: - void Start(int32_t interval, const std::function& func); + void Start(const int32_t& interval, const std::function& func); void Stop(); private: std::thread thread_; @@ -35,7 +36,7 @@ namespace nthompson { void Update(); - void SetActionText(std::string text); + void SetActionText(const std::string& text); void WillAppearForAction(const std::string& inAction, const std::string& inContext, @@ -47,11 +48,20 @@ namespace nthompson { const std::string& inContext, const nlohmann::json& inPayload, const std::string& inDeviceID) override; + + void SendToPlugin(const std::string &inAction, + const std::string &inContext, + const nlohmann::json &inPayload, + const std::string &inDeviceID) override; private: + void FindAvailableGpus(); + std::vector gpus_; std::unique_ptr usage_ = nullptr; std::unique_ptr timer_; std::mutex mutex_; std::set contexts_; + int32_t selectedGpu_{0}; + void HandleSelectedGpu(const Gpu &gpu); }; } // nthompson diff --git a/Src/Windows/Amd/AmdGpuUsage.cpp b/Src/Windows/Amd/AmdGpuUsage.cpp index ebca490..58839e8 100644 --- a/Src/Windows/Amd/AmdGpuUsage.cpp +++ b/Src/Windows/Amd/AmdGpuUsage.cpp @@ -5,7 +5,7 @@ #include "AmdGpuUsage.h" namespace nthompson { - AmdGpuUsage::AmdGpuUsage() { + AmdGpuUsage::AmdGpuUsage(int32_t index) { ADLX_RESULT result = helper_.Initialize(); if (!ADLX_SUCCEEDED(result)) { ESDLog("Failed to initialize ADLX"); @@ -28,7 +28,7 @@ namespace nthompson { return; } - result = gpus->At(gpus->Begin(), &gpu_); + result = gpus->At(index, &gpu_); if (!ADLX_SUCCEEDED(result)) { ESDLog("Failed to get GPU"); diff --git a/Src/Windows/Amd/AmdGpuUsage.h b/Src/Windows/Amd/AmdGpuUsage.h index f18802f..a432baf 100644 --- a/Src/Windows/Amd/AmdGpuUsage.h +++ b/Src/Windows/Amd/AmdGpuUsage.h @@ -13,7 +13,7 @@ namespace nthompson { class AmdGpuUsage : public IGpuUsage { public: - AmdGpuUsage(); + AmdGpuUsage(int32_t index = 0); ~AmdGpuUsage() override; uint32_t GetGpuUsage() override; private: diff --git a/Src/Windows/Nvidia/NvidiaGpuUsage.cpp b/Src/Windows/Nvidia/NvidiaGpuUsage.cpp index 9b6736e..7b94654 100644 --- a/Src/Windows/Nvidia/NvidiaGpuUsage.cpp +++ b/Src/Windows/Nvidia/NvidiaGpuUsage.cpp @@ -7,7 +7,7 @@ namespace nthompson { - NvidiaGpuUsage::NvidiaGpuUsage() { + NvidiaGpuUsage::NvidiaGpuUsage(int32_t index) { nvmlReturn_t status = nvmlInit(); if (status != NVML_SUCCESS) { @@ -17,7 +17,7 @@ namespace nthompson { return; } - if (status = nvmlDeviceGetHandleByIndex(0, &device_); status != NVML_SUCCESS) { + if (status = nvmlDeviceGetHandleByIndex(index, &device_); status != NVML_SUCCESS) { std::stringstream error_status; error_status << "Failed to query device. Error: " << nvmlErrorString(status); ESDLog(error_status.str()); diff --git a/Src/Windows/Nvidia/NvidiaGpuUsage.h b/Src/Windows/Nvidia/NvidiaGpuUsage.h index c23891b..8e5408e 100644 --- a/Src/Windows/Nvidia/NvidiaGpuUsage.h +++ b/Src/Windows/Nvidia/NvidiaGpuUsage.h @@ -13,7 +13,7 @@ namespace nthompson { class NvidiaGpuUsage : public IGpuUsage { public: - NvidiaGpuUsage(); + explicit NvidiaGpuUsage(int32_t index = 0); ~NvidiaGpuUsage() override; uint32_t GetGpuUsage() override; private: diff --git a/Src/com.nthompson.gpu.sdPlugin/index.html b/Src/com.nthompson.gpu.sdPlugin/index.html new file mode 100644 index 0000000..7aabadc --- /dev/null +++ b/Src/com.nthompson.gpu.sdPlugin/index.html @@ -0,0 +1,27 @@ + + + + + GPU + + + + + + + +
+
+
Select GPU
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/Src/com.nthompson.gpu.sdPlugin/js/index.js b/Src/com.nthompson.gpu.sdPlugin/js/index.js new file mode 100644 index 0000000..cbec408 --- /dev/null +++ b/Src/com.nthompson.gpu.sdPlugin/js/index.js @@ -0,0 +1,70 @@ +const select = document.getElementById('gpuSelect'); + +select.addEventListener('change', e => { + sendValueToPlugin(e.target.value, 'gpuInfo'); +}) + +let socket; +let context; +let uuid; +let info; + +window.connectElgatoStreamDeckSocket = (inPort, inPropertyInspectorUUID, inRegisterEvent, inInfo, inActionInfo) => { + info = JSON.parse(inActionInfo); + uuid = inPropertyInspectorUUID; + socket = new WebSocket('ws://127.0.0.1:' + inPort); + + console.log('Connected to Elgato.'); + + socket.addEventListener('open', () => { + const json = { + event: inRegisterEvent, + uuid, + } + socket.send(JSON.stringify(json)); + + const message = { + action: info['action'], + event: 'sendToPlugin', + context: uuid, + payload: { + propertyInspectorLoaded: true, + receiveSelection: false + }, + } + socket.send(JSON.stringify(message)); + }); + + socket.addEventListener('message', (e) => { + const info = JSON.parse(e.data); + + const payload = info['payload']; + + const {gpus, selected} = payload; + + for (let i = 0; i < gpus?.length; i++) { + select.add(new Option(gpus[i].name, JSON.stringify(gpus[i]), false, selected > 0 ? i === selected : i === 0)); + } + }) + + socket.addEventListener('close', () => { + console.log('Connection closed'); + }) +} + + +const sendValueToPlugin = (data, param) => { + const json = { + action: info['action'], + event: 'sendToPlugin', + context: uuid, + payload: { + receiveSelection: true, + propertyInspectorLoaded: false, + [param]: JSON.parse(data) + }, + } + + socket.send(JSON.stringify(json)); +} + diff --git a/Src/com.nthompson.gpu.sdPlugin/libs b/Src/com.nthompson.gpu.sdPlugin/libs new file mode 160000 index 0000000..70f866e --- /dev/null +++ b/Src/com.nthompson.gpu.sdPlugin/libs @@ -0,0 +1 @@ +Subproject commit 70f866ee3adbb0634977ef9b8acbd44474c9d6fc diff --git a/Src/com.nthompson.gpu.sdPlugin/manifest.json b/Src/com.nthompson.gpu.sdPlugin/manifest.json index 0d92098..3f7d619 100644 --- a/Src/com.nthompson.gpu.sdPlugin/manifest.json +++ b/Src/com.nthompson.gpu.sdPlugin/manifest.json @@ -18,12 +18,13 @@ "SDKVersion": 2, "Author": "Noah Thompson", "CodePathWin": "gpu.exe", + "PropertyInspectorPath" : "index.html", "Category": "GPU Metrics", "CategoryIcon": "pluginIcon", "Description": "Displays the current GPU usage.", "Name": "GPU", "Icon": "pluginIcon", - "Version": "1.1.1", + "Version": "1.2.0", "URL": "https://github.com/thompsonnoahe/StreamDeckGpu", "OS": [ {