Implicit surface rendering via ray tracing
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

654 lines
20 KiB

/*
* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include "nvpfilesystem.hpp"
#include <unordered_map>
#include <array>
#include <cassert>
#include <string.h>
using namespace nvp;
#if defined(_WIN32)
#include <locale>
#include <filesystem>
#include <Windows.h>
void logLastWindowError(std::string context)
{
LPVOID messageBuffer;
DWORD dw = GetLastError();
DWORD numChars = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&messageBuffer, 0, NULL);
assert(numChars);
#ifndef UNICODE
std::string errorStr((char*)messageBuffer);
#else
#error Not implemented
#endif
LOGE("%s, error %lu: %s\n", context.c_str(), dw, errorStr.c_str());
LocalFree(messageBuffer);
}
struct PathKey
{
std::string path;
uint32_t eventMask;
bool operator==(const PathKey& other) const { return path == other.path && eventMask == other.eventMask; }
};
template <class A, class B>
size_t combine_hash(const A& a, const B& b)
{
return std::hash<A>()(a) ^ (std::hash<B>()(b) << 1);
}
template <class A, class B, class C>
size_t combine_hash(const A& a, const B& b, const C& c)
{
return (combine_hash(a, b) >> 1) ^ std::hash<C>()(c);
}
namespace std {
template <>
struct hash<PathKey>
{
std::size_t operator()(const PathKey& k) const { return ::combine_hash(k.path, k.eventMask); }
};
} // namespace std
// Converts a UTF-8 string to a UTF-16 string. Avoids using <codecvt>, due to
// https://github.com/microsoft/STL/issues/443.
std::wstring utf8ToWideString(std::string utf8String)
{
if(utf8String.size() > std::numeric_limits<int>::max())
{
assert(!"Too many characters for UTF8-to-UTF16 API!");
return L"";
}
const int utf8Bytes = static_cast<int>(utf8String.size());
const int utf16Characters = MultiByteToWideChar(CP_UTF8, 0, utf8String.data(), utf8Bytes, nullptr, 0);
if(utf16Characters < 0)
{
assert(!"Error counting UTF-16 characters!");
return L"";
}
std::wstring result(utf16Characters, 0);
(void)MultiByteToWideChar(CP_UTF8, 0, utf8String.data(), utf8Bytes, result.data(), utf16Characters);
return result;
}
// Converts a UTF-16 string to a UTF-8 string. Avoids using <codecvt>, due to
// https://github.com/microsoft/STL/issues/443.
std::string wideToUTF8String(std::wstring utf16String)
{
if(utf16String.size() > std::numeric_limits<int>::max())
{
assert(!"Too many characters for UTF16-to-UTF8 API!");
return "";
}
const int utf16Characters = static_cast<int>(utf16String.size());
const int utf8Bytes = WideCharToMultiByte(CP_UTF8, 0, utf16String.data(), utf16Characters, nullptr, 0, nullptr, nullptr);
if(utf8Bytes < 0)
{
assert(!"Error counting UTF-8 bytes!");
return "";
}
std::string result(utf8Bytes, 0);
(void)WideCharToMultiByte(CP_UTF8, 0, utf16String.data(), utf16Characters, result.data(), utf8Bytes, nullptr, nullptr);
return result;
}
struct PathInstance
{
FileSystemMonitor::PathID id;
void* userPtr;
bool operator==(const FileSystemMonitor::PathID& pathID) const { return id == pathID; }
};
/** Class to handle receiving per-directory filesystem events
*
* This class is reused to provide per-file events and distribute events to multiple listeners.
* Each WindowsPathMonitor can only be a file monitor (filtered events) or a directory monitor (all events), not both.
*
* Structure:
* - WindowsPathMonitor - monitored directory, receiving OS events
* - Instances (PathID + userData) for multiple directory listeners of per-directory monitoring
* - Sub-paths for per-file monitoring
* - Instances (PathID + userData) for multiple file listeners
*/
struct WindowsPathMonitor : PathKey
{
WindowsPathMonitor(PathKey key)
: PathKey{key}
, m_overlapped{}
, m_eventsRequested{false}
{
// Translate the event mask
m_winEventFilter = 0;
if(eventMask & (FileSystemMonitor::FSM_CREATE | FileSystemMonitor::FSM_DELETE))
m_winEventFilter |= FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME;
if(eventMask & FileSystemMonitor::FSM_MODIFY)
m_winEventFilter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
// Open the path to receive events from it
std::wstring pathW = utf8ToWideString(path);
DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
DWORD flags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED;
m_dirHandle = CreateFileW(pathW.c_str(), GENERIC_READ, shareMode, NULL, OPEN_EXISTING, flags, NULL);
if(m_dirHandle == INVALID_HANDLE_VALUE)
logLastWindowError(std::string("FileSystemMonitor: Error in CreateFileW for path ") + path);
}
~WindowsPathMonitor()
{
if(m_eventsRequested)
cancelAsync();
if(m_dirHandle != INVALID_HANDLE_VALUE)
CloseHandle(m_dirHandle);
}
bool getEventsAsync()
{
m_eventsRequested = true;
BOOL result = ReadDirectoryChangesExW(m_dirHandle, m_eventBuffer.data(), (DWORD)m_eventBuffer.size(), TRUE, m_winEventFilter,
NULL, &m_overlapped, NULL, ReadDirectoryNotifyExtendedInformation);
if(result == FALSE)
logLastWindowError(std::string("FileSystemMonitor: Error in ReadDirectoryChangesW for path ") + path);
return result == TRUE;
}
void cancelAsync()
{
assert(m_eventsRequested);
m_eventsRequested = false;
CancelIoEx(m_dirHandle, &m_overlapped);
}
// Avoid throwing an exception in the constructor with an is-valid method.
bool isValid() { return m_dirHandle != INVALID_HANDLE_VALUE; }
HANDLE m_dirHandle;
DWORD m_winEventFilter;
std::array<uint8_t, 63 * 1024> m_eventBuffer;
OVERLAPPED m_overlapped;
bool m_eventsRequested;
std::unordered_map<std::string, std::vector<PathInstance>> m_fileInstances;
std::vector<PathInstance> m_directoryInstances;
};
class FileSystemMonitorWindows : public FileSystemMonitor
{
friend class FileSystemMonitor;
virtual PathID add(const std::string& path, uint32_t eventMask, void* userPtr) override
{
std::string monitorDir{path};
// Workaround monitoring individual files by monitoring the entire directory
std::filesystem::path pathObj{path};
bool isDir = std::filesystem::is_directory(pathObj);
if(!isDir)
{
monitorDir = pathObj.parent_path().string();
}
// Track the path if it isn't tracked already
WindowsPathMonitor* monitor;
auto key = PathKey{monitorDir, eventMask};
auto it = m_paths.find(key);
if(it != m_paths.end())
{
monitor = it->second.get();
}
else
{
auto pathMonitor = std::make_unique<WindowsPathMonitor>(key);
if(!pathMonitor->isValid())
return false;
// Get async events for this path on the main port
ULONG_PTR objectPtr = reinterpret_cast<ULONG_PTR>(pathMonitor.get());
if(CreateIoCompletionPort(pathMonitor->m_dirHandle, m_ioCompletionPort, objectPtr, 1) != m_ioCompletionPort)
{
logLastWindowError("CreateIoCompletionPort in path monitoring");
return false;
}
if(!pathMonitor->getEventsAsync())
return false;
monitor = pathMonitor.get();
m_paths[key] = std::move(pathMonitor);
}
auto id = nextPathID();
PathInstance instance{id, userPtr};
if(isDir)
monitor->m_directoryInstances.push_back(instance);
else
monitor->m_fileInstances[pathObj.filename().string()].push_back(instance);
m_idToMonitor[id] = monitor;
return id;
}
virtual void remove(const PathID& pathID) override
{
auto& monitor = *m_idToMonitor[pathID];
bool isDirMonitor = monitor.m_fileInstances.empty();
bool instancesEmpty;
// TODO: lots of slow linear searching here, although both monitoring many files and remove() is expected to be uncommon.
if(isDirMonitor)
{
// Find and remove the pathID instance in m_directoryInstances.
auto it = std::find(monitor.m_directoryInstances.begin(), monitor.m_directoryInstances.end(), pathID);
monitor.m_directoryInstances.erase(it);
instancesEmpty = monitor.m_directoryInstances.empty();
}
else
{
// Find and remove the pathID instance in m_fileInstances. It could be inside any one of the monitored files (sub-paths).
for(auto& file : monitor.m_fileInstances)
{
auto& instances = file.second;
auto it = std::find(instances.begin(), instances.end(), pathID);
if(it == instances.end())
continue;
instances.erase(it);
if(instances.empty())
monitor.m_fileInstances.erase(file.first);
break;
}
instancesEmpty = monitor.m_fileInstances.empty();
}
// If there are no instances left, remove the monitor.
if(instancesEmpty)
m_paths.erase((PathKey)monitor);
m_idToMonitor.erase(pathID);
}
void handleEvent(WindowsPathMonitor* pathMonitor, const FILE_NOTIFY_EXTENDED_INFORMATION* notifyInfo, const Callback& callback)
{
std::wstring subPathW(notifyInfo->FileName, notifyInfo->FileNameLength / sizeof(WCHAR));
std::string subPath = wideToUTF8String(subPathW);
bool isDirMonitor = pathMonitor->m_fileInstances.empty();
std::vector<PathInstance>* instances;
if(!isDirMonitor)
{
// Filter out files not monitored in this directoriy
auto it = pathMonitor->m_fileInstances.find(subPath);
if(it == pathMonitor->m_fileInstances.end())
{
return;
}
instances = &it->second;
}
else
instances = &pathMonitor->m_directoryInstances;
std::filesystem::path fullPath = pathMonitor->path;
fullPath /= subPath; // "/" adds the paths
LOGI("FileSystemMonitor %p event (mask %lx) for '%s'\n", m_ioCompletionPort, notifyInfo->Action, fullPath.string().c_str());
for(auto& instance : *instances)
{
switch(notifyInfo->Action)
{
case FILE_ACTION_ADDED:
case FILE_ACTION_RENAMED_NEW_NAME:
if(pathMonitor->eventMask & FSM_CREATE)
callback(EventData{FSM_CREATE, fullPath.string(), instance.userPtr});
break;
case FILE_ACTION_REMOVED:
case FILE_ACTION_RENAMED_OLD_NAME:
if(pathMonitor->eventMask & FSM_DELETE)
callback(EventData{FSM_DELETE, fullPath.string(), instance.userPtr});
break;
case FILE_ACTION_MODIFIED:
if(pathMonitor->eventMask & FSM_MODIFY)
callback(EventData{FSM_MODIFY, fullPath.string(), instance.userPtr});
break;
default:
// TODO: don't throw away free information
break;
}
}
}
virtual bool checkEvents(const Callback& callback) override
{
DWORD bytesTransferred = 0; // FILE_NOTIFY_EXTENDED_INFORMATION struct size
ULONG_PTR userObject = 0;
OVERLAPPED* overlapped = NULL;
if(GetQueuedCompletionStatus(m_ioCompletionPort, &bytesTransferred, &userObject, &overlapped, INFINITE) == FALSE)
{
return true;
}
// Handle the case that cancel() unblocked the call
if(reinterpret_cast<ULONG_PTR>(this) == userObject)
{
bool cancelling = m_cancelling;
m_cancelling = false;
return !cancelling;
}
auto pathMonitor = reinterpret_cast<WindowsPathMonitor*>(userObject);
const auto& buffer = pathMonitor->m_eventBuffer;
size_t offset = 0;
for(;;)
{
auto notifyInfo = reinterpret_cast<const FILE_NOTIFY_EXTENDED_INFORMATION*>(buffer.data() + offset);
handleEvent(pathMonitor, notifyInfo, callback);
// Re-arm the event
if(!pathMonitor->getEventsAsync())
{
return false;
}
if(!notifyInfo->NextEntryOffset)
break;
offset += notifyInfo->NextEntryOffset;
}
return true;
}
virtual void cancel() override
{
ULONG_PTR objectPtr = reinterpret_cast<ULONG_PTR>(this);
m_cancelling = true;
PostQueuedCompletionStatus(m_ioCompletionPort, 0, objectPtr, NULL);
}
FileSystemMonitorWindows()
{
m_ioCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
assert(m_ioCompletionPort != NULL);
}
virtual ~FileSystemMonitorWindows() override
{
assert(!m_cancelling);
CloseHandle(m_ioCompletionPort);
}
HANDLE m_ioCompletionPort;
bool m_cancelling = false;
std::unordered_map<PathKey, std::unique_ptr<WindowsPathMonitor>> m_paths;
// Reuse directory monitors that are created to monitor specific files
std::unordered_map<PathID, WindowsPathMonitor*> m_idToMonitor;
};
#endif
#if defined(LINUX)
#include <unistd.h>
#include <poll.h>
#include <sys/inotify.h>
#include <sys/eventfd.h>
#include <linux/limits.h>
/** Object to track monitored paths
*
* inotify will not create multiple watches if the same path is added twice.
* To handle multiple FileSystemMonitor clients, this is faked with a list of
* instances per path.
*/
struct InotifyPath
{
/** File or directory name */
std::string path;
/** Union of events that all instances request */
uint32_t inotifyMaskAll;
/** Per-instance structure requesting events for this path */
struct MonitorInstance
{
void* userPtr;
uint32_t inotifyMask;
};
std::unordered_map<FileSystemMonitor::PathID, MonitorInstance> instances;
};
class FileSystemMonitorInotify : public FileSystemMonitor
{
friend class FileSystemMonitor;
virtual int add(const std::string& path, uint32_t eventMask, void* userPtr) override
{
// Translate the event mask
uint32_t inotifyMask = 0;
if(eventMask & FSM_CREATE)
inotifyMask |= IN_CREATE;
if(eventMask & FSM_MODIFY)
inotifyMask |= IN_MODIFY;
if(eventMask & FSM_DELETE)
inotifyMask |= IN_DELETE;
assert(inotifyMask);
if(!inotifyMask)
return INVALID_PATH_ID;
int watchDescriptor = inotify_add_watch(m_inotifyFd, path.c_str(), inotifyMask);
if(watchDescriptor == -1)
return INVALID_PATH_ID;
auto id = nextPathID();
//LOGI("FileSystemMonitor %i added %i %s\n", m_inotifyFd, id, path.c_str());
auto it = m_paths.find(watchDescriptor);
if(it != m_paths.end())
{
// Path has already been added before. May need to combine the mask and re-add.
auto allBits = it->second.inotifyMaskAll | inotifyMask;
if(allBits != inotifyMask)
(void)inotify_add_watch(m_inotifyFd, path.c_str(), allBits);
it->second.inotifyMaskAll = allBits;
it->second.instances[id] = {userPtr, inotifyMask};
}
else
{
m_paths[watchDescriptor] = InotifyPath{path, inotifyMask, {{id, InotifyPath::MonitorInstance{userPtr, inotifyMask}}}};
}
m_idToWatchDescriptor[id] = watchDescriptor;
return id;
}
virtual void remove(const PathID& pathID) override
{
int watchDescriptor = m_idToWatchDescriptor[pathID];
auto& path = m_paths[watchDescriptor];
//LOGI("FileSystemMonitor %i removed %i %s\n", m_inotifyFd, pathID, path.path.c_str());
m_idToWatchDescriptor.erase(pathID);
path.instances.erase(pathID);
if(path.instances.empty())
{
//LOGI("FileSystemMonitor %i removing inotify watch\n", m_inotifyFd);
int result = inotify_rm_watch(m_inotifyFd, watchDescriptor);
assert(result == 0);
m_paths.erase(watchDescriptor);
}
}
virtual bool checkEvents(const Callback& callback) override
{
struct pollfd fds[]
{
{m_inotifyFd, POLLIN}, {m_cancelFd, POLLIN},
};
const auto nfds = sizeof(fds) / sizeof(fds[0]);
// Block until there are inotify events, or cancel() is called.
//LOGI("FileSystemMonitor %i poll enter\n", m_inotifyFd);
int fdsReady = poll(fds, nfds, -1);
//LOGI("FileSystemMonitor %i poll exit\n", m_inotifyFd);
assert(fdsReady >= 0);
if(fdsReady == 0)
{
return true;
}
if(fdsReady == -1)
{
if(errno == EINTR || errno == EAGAIN)
{
return true;
}
// Stop checking events because some error happened
LOGE("Error in poll(inotify-fd)\n");
return false;
}
if(fds[1].revents & POLLIN)
{
uint64_t val = 0;
const ssize_t numBytes = read(m_cancelFd, &val, sizeof(val));
assert(val == 1);
assert(numBytes == sizeof(val));
// Stop checking events because cancel() was called
return false;
}
// There was no error and m_cancelFd wasn't set. Must be an inotify event.
assert(fds[0].revents & POLLIN);
// Read event data
auto readFrom = m_eventBuffer.data() + m_eventBufferBytes;
auto bytesLeft = m_eventBuffer.size() - m_eventBufferBytes;
size_t bytesRead = read(m_inotifyFd, readFrom, bytesLeft);
m_eventBufferBytes += bytesRead;
// Process whole events in the buffer
size_t offset = 0;
while(offset + sizeof(inotify_event) <= m_eventBufferBytes)
{
const auto& event = *reinterpret_cast<inotify_event*>(m_eventBuffer.data() + offset);
size_t eventSize = sizeof(inotify_event) + event.len;
if(offset + eventSize > m_eventBufferBytes)
{
// Incomplete event read() into buffer
break;
}
// If remove() is called, there may still be queued events. Ignore any for
// unknown watch descriptors. IN_Q_OVERFLOW can also generate wd == -1.
auto pathIt = m_paths.find(event.wd);
if(pathIt != m_paths.end())
{
const auto& path = pathIt->second;
for(const auto& instanceIt : path.instances)
{
const auto& instance = instanceIt.second;
auto reportMask = event.mask & instance.inotifyMask;
// inotify only gives a name when watching directories, not files.
auto filename = event.len ? std::string(event.name) : path.path;
LOGI("FileSystemMonitor %i event (mask %x) for '%s'\n", m_inotifyFd, reportMask, filename.c_str());
if(reportMask & IN_CREATE)
callback(EventData{FSM_CREATE, filename, instance.userPtr});
if(reportMask & IN_MODIFY)
callback(EventData{FSM_MODIFY, filename, instance.userPtr});
if(reportMask & IN_DELETE)
callback(EventData{FSM_DELETE, filename, instance.userPtr});
}
}
offset += eventSize;
}
// Shift any remainder to the start of the buffer
m_eventBufferBytes -= offset;
if(offset && m_eventBufferBytes)
{
memmove(m_eventBuffer.data(), m_eventBuffer.data() + offset, m_eventBufferBytes);
}
return true;
}
virtual void cancel() override
{
uint64_t val = 1;
const ssize_t numBytes = write(m_cancelFd, &val, sizeof(val));
assert(numBytes == sizeof(val));
}
FileSystemMonitorInotify()
{
m_inotifyFd = inotify_init();
m_cancelFd = eventfd(0, 0);
}
virtual ~FileSystemMonitorInotify() override
{
close(m_cancelFd);
close(m_inotifyFd);
}
int m_inotifyFd;
int m_cancelFd;
/** inotify provides a stream of event data. This buffer is here to hold incomplete reads. Sized to hold at least one
* event for the max path length.
*/
std::array<uint8_t, sizeof(inotify_event) + PATH_MAX> m_eventBuffer;
size_t m_eventBufferBytes = 0;
/** Monitored paths, indexed by the inotify watch descriptor */
std::unordered_map<int, InotifyPath> m_paths;
/* Path instance lookup, to provide multiple events for the same path */
std::unordered_map<PathID, int> m_idToWatchDescriptor;
};
#endif
FileSystemMonitor* FileSystemMonitor::create()
{
#if defined(_WIN32)
return new FileSystemMonitorWindows();
#elif defined(LINUX)
return new FileSystemMonitorInotify();
#else
return nullptr;
#endif
}
void FileSystemMonitor::destroy(FileSystemMonitor* monitor)
{
delete monitor;
}