Skip to content
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: 2 additions & 1 deletion tools/render-test/render-test-main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1584,7 +1584,8 @@ static SlangResult _innerMain(
//
// We also don't want to output the 'Unable to create renderer' error, as this isn't
// an error.
SlangResult res = SLANG_E_NOT_AVAILABLE; // Default to not available if device creation fails
SlangResult res =
SLANG_E_NOT_AVAILABLE; // Default to not available if device creation fails
if (!options.onlyStartup)
{
fprintf(stderr, "Unable to create renderer %s\n", rendererName.getBuffer());
Expand Down
43 changes: 23 additions & 20 deletions tools/render-test/slang-test-device-cache.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <algorithm>
#include "slang-test-device-cache.h"

#include <algorithm>

// Static member accessor functions (Meyer's singleton pattern)
// This ensures proper destruction order - function-local statics are destroyed
// in reverse order of first access, avoiding the static destruction order fiasco
Expand All @@ -10,7 +11,11 @@ std::mutex& DeviceCache::getMutex()
return instance;
}

std::unordered_map<DeviceCache::DeviceCacheKey, DeviceCache::CachedDevice, DeviceCache::DeviceCacheKeyHash>& DeviceCache::getDeviceCache()
std::unordered_map<
DeviceCache::DeviceCacheKey,
DeviceCache::CachedDevice,
DeviceCache::DeviceCacheKeyHash>&
DeviceCache::getDeviceCache()
{
static std::unordered_map<DeviceCacheKey, CachedDevice, DeviceCacheKeyHash> instance;
return instance;
Expand All @@ -24,11 +29,9 @@ uint64_t& DeviceCache::getNextCreationOrder()

bool DeviceCache::DeviceCacheKey::operator==(const DeviceCacheKey& other) const
{
return deviceType == other.deviceType &&
enableValidation == other.enableValidation &&
return deviceType == other.deviceType && enableValidation == other.enableValidation &&
enableRayTracingValidation == other.enableRayTracingValidation &&
profileName == other.profileName &&
requiredFeatures == other.requiredFeatures;
profileName == other.profileName && requiredFeatures == other.requiredFeatures;
}

std::size_t DeviceCache::DeviceCacheKeyHash::operator()(const DeviceCacheKey& key) const
Expand All @@ -37,17 +40,17 @@ std::size_t DeviceCache::DeviceCacheKeyHash::operator()(const DeviceCacheKey& ke
std::size_t h2 = std::hash<bool>{}(key.enableValidation);
std::size_t h3 = std::hash<bool>{}(key.enableRayTracingValidation);
std::size_t h4 = std::hash<std::string>{}(key.profileName);

std::size_t h5 = 0;
for (const auto& feature : key.requiredFeatures)
{
h5 ^= std::hash<std::string>{}(feature) + 0x9e3779b9 + (h5 << 6) + (h5 >> 2);
}

return h1 ^ (h2 << 1) ^ (h3 << 2) ^ (h4 << 3) ^ (h5 << 4);
}

DeviceCache::CachedDevice::CachedDevice()
DeviceCache::CachedDevice::CachedDevice()
: creationOrder(0)
{
}
Expand All @@ -57,11 +60,11 @@ void DeviceCache::evictOldestDeviceIfNeeded()
auto& deviceCache = getDeviceCache();
if (deviceCache.size() < MAX_CACHED_DEVICES)
return;

// Find the oldest device to evict
auto oldestIt = deviceCache.end();
uint64_t oldestCreationOrder = UINT64_MAX;

for (auto it = deviceCache.begin(); it != deviceCache.end(); ++it)
{
if (it->second.creationOrder < oldestCreationOrder)
Expand All @@ -70,7 +73,7 @@ void DeviceCache::evictOldestDeviceIfNeeded()
oldestIt = it;
}
}

// Remove the oldest device - ComPtr will handle the actual device release
if (oldestIt != deviceCache.end())
{
Expand All @@ -89,49 +92,49 @@ Slang::ComPtr<rhi::IDevice> DeviceCache::acquireDevice(const rhi::DeviceDesc& de
return device;
return nullptr;
}

std::lock_guard<std::mutex> lock(getMutex());
auto& deviceCache = getDeviceCache();
auto& nextCreationOrder = getNextCreationOrder();

// Create cache key
DeviceCacheKey key;
key.deviceType = desc.deviceType;
key.enableValidation = desc.enableValidation;
key.enableRayTracingValidation = desc.enableRayTracingValidation;
key.profileName = desc.slang.targetProfile ? desc.slang.targetProfile : "";

// Add required features to key
for (int i = 0; i < desc.requiredFeatureCount; ++i)
{
key.requiredFeatures.push_back(desc.requiredFeatures[i]);
}
std::sort(key.requiredFeatures.begin(), key.requiredFeatures.end());

// Evict oldest device if we've reached the limit
evictOldestDeviceIfNeeded();

// Check if we have a cached device
auto it = deviceCache.find(key);
if (it != deviceCache.end())
{
// Return the cached device - COM reference counting handles the references
return it->second.device;
}

// Create new device
Slang::ComPtr<rhi::IDevice> device;
auto result = rhi::getRHI()->createDevice(desc, device.writeRef());
if (SLANG_FAILED(result))
{
return nullptr;
}

// Cache the device
CachedDevice& cached = deviceCache[key];
cached.device = device;
cached.creationOrder = nextCreationOrder++;

return device;
}

Expand Down
44 changes: 23 additions & 21 deletions tools/render-test/slang-test-device-cache.h
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
#pragma once

#include <slang-rhi.h>
#include <chrono>
#include <mutex>
#include <slang-rhi.h>
#include <string>
#include <unordered_map>
#include <vector>
#include <string>

// Device Cache for preventing NVIDIA Tegra driver state corruption
// This cache reuses Vulkan instances and devices to avoid the VK_ERROR_INCOMPATIBLE_DRIVER
// issue that occurs after ~19 device creation/destruction cycles on Tegra platforms.
// Uses ComPtr for automatic device lifecycle management - devices are released when removed from cache.
// Uses ComPtr for automatic device lifecycle management - devices are released when removed from
// cache.
class DeviceCache
{
public:
Expand All @@ -21,33 +22,33 @@ class DeviceCache
bool enableRayTracingValidation;
std::string profileName;
std::vector<std::string> requiredFeatures;

bool operator==(const DeviceCacheKey& other) const;
};

struct DeviceCacheKeyHash
{
std::size_t operator()(const DeviceCacheKey& key) const;
};

struct CachedDevice
{
Slang::ComPtr<rhi::IDevice> device;
uint64_t creationOrder;

CachedDevice();
};

private:
static constexpr int MAX_CACHED_DEVICES = 10;

// Use function-local statics to control destruction order (Meyer's singleton pattern)
static std::mutex& getMutex();
static std::unordered_map<DeviceCacheKey, CachedDevice, DeviceCacheKeyHash>& getDeviceCache();
static uint64_t& getNextCreationOrder();

static void evictOldestDeviceIfNeeded();

public:
static Slang::ComPtr<rhi::IDevice> acquireDevice(const rhi::DeviceDesc& desc);
static void cleanCache();
Expand All @@ -58,22 +59,23 @@ class CachedDeviceWrapper
{
private:
Slang::ComPtr<rhi::IDevice> m_device;

public:
CachedDeviceWrapper() = default;

CachedDeviceWrapper(Slang::ComPtr<rhi::IDevice> device) : m_device(device) {}

~CachedDeviceWrapper()

CachedDeviceWrapper(Slang::ComPtr<rhi::IDevice> device)
: m_device(device)
{
}


~CachedDeviceWrapper() {}

// Move constructor
CachedDeviceWrapper(CachedDeviceWrapper&& other) noexcept
: m_device(std::move(other.m_device))
{
}

// Move assignment
CachedDeviceWrapper& operator=(CachedDeviceWrapper&& other) noexcept
{
Expand All @@ -83,14 +85,14 @@ class CachedDeviceWrapper
}
return *this;
}

// Delete copy constructor and assignment
CachedDeviceWrapper(const CachedDeviceWrapper&) = delete;
CachedDeviceWrapper& operator=(const CachedDeviceWrapper&) = delete;

rhi::IDevice* get() const { return m_device.get(); }
rhi::IDevice* operator->() const { return m_device.get(); }
operator bool() const { return m_device != nullptr; }

Slang::ComPtr<rhi::IDevice>& getComPtr() { return m_device; }
};
13 changes: 6 additions & 7 deletions tools/slang-test/slang-test-main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5451,14 +5451,13 @@ SlangResult innerMain(int argc, char** argv)

int main(int argc, char** argv)
{
// Fallback: run without cleanup if context initialization fails
SlangResult res = innerMain(argc, argv);
slang::shutdown();
Slang::RttiInfo::deallocateAll();
// Fallback: run without cleanup if context initialization fails
SlangResult res = innerMain(argc, argv);
slang::shutdown();
Slang::RttiInfo::deallocateAll();

#ifdef _MSC_VER
_CrtDumpMemoryLeaks();
_CrtDumpMemoryLeaks();
#endif
return SLANG_SUCCEEDED(res) ? 0 : 1;

return SLANG_SUCCEEDED(res) ? 0 : 1;
}
6 changes: 4 additions & 2 deletions tools/slang-test/test-context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ TestContext::InnerMainFunc TestContext::getInnerMainFunc(const String& dirPath,
loader->loadPlatformSharedLibrary(path.begin(), tool.m_sharedLibrary.writeRef())))
{
tool.m_func = (InnerMainFunc)tool.m_sharedLibrary->findFuncByName("innerMain");
tool.m_cleanDeviceCacheFunc = (CleanDeviceCacheFunc)tool.m_sharedLibrary->findFuncByName("cleanDeviceCache");
tool.m_cleanDeviceCacheFunc =
(CleanDeviceCacheFunc)tool.m_sharedLibrary->findFuncByName("cleanDeviceCache");
}

m_sharedLibTools.add(name, tool);
Expand All @@ -156,7 +157,8 @@ void TestContext::setInnerMainFunc(const String& name, InnerMainFunc func)
TestContext::CleanDeviceCacheFunc TestContext::getCleanDeviceCacheFunc(const String& name)
{
SharedLibraryTool* tool = m_sharedLibTools.tryGetValue(name);
if (tool) {
if (tool)
{
return tool->m_cleanDeviceCacheFunc;
}

Expand Down
2 changes: 1 addition & 1 deletion tools/slang-test/test-context.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class TestContext
InnerMainFunc getInnerMainFunc(const Slang::String& dirPath, const Slang::String& name);
/// Set the function for the shared library
void setInnerMainFunc(const Slang::String& name, InnerMainFunc func);

/// Get the device cache cleanup function (from shared library)
CleanDeviceCacheFunc getCleanDeviceCacheFunc(const Slang::String& name);

Expand Down