cleanup and refactoring

This commit is contained in:
CDaut 2024-05-25 11:53:25 +02:00
parent 2302158928
commit 76f6bf62a4
Signed by: clara
GPG key ID: 223391B52FAD4463
1285 changed files with 757994 additions and 8 deletions

View file

@ -0,0 +1,540 @@
/*
* Copyright (c) 2018-2021, 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) 2018-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
//--------------------------------------------------------------------
/// @DOC_SKIP
#ifndef NV_FOUNDATION_H
#define NV_FOUNDATION_H
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#ifdef _MSC_VER
#ifndef _INTPTR
#define _INTPTR 0
#endif
#endif
#include <stdint.h>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning( disable : 4985 ) // 'symbol name': attributes not present on previous declaration
#endif
#include <math.h>
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#include <float.h>
#include <assert.h>
#define NV_ASSERT(exp) (assert(exp))
#define NV_ALWAYS_ASSERT() NV_ASSERT(0)
//***************************************
// FILE: NvVersionNumber.h
//***************************************
/*
VersionNumbers: The combination of these
numbers uniquely identifies the API, and should
be incremented when the SDK API changes. This may
include changes to file formats.
This header is included in the main SDK header files
so that the entire SDK and everything that builds on it
is completely rebuilt when this file changes. Thus,
this file is not to include a frequently changing
build number. See BuildNumber.h for that.
Each of these three values should stay below 255 because
sometimes they are stored in a byte.
*/
/** \addtogroup foundation
@{
*/
#define NV_FOUNDATION_VERSION_MAJOR 1
#define NV_FOUNDATION_VERSION_MINOR 1
#define NV_FOUNDATION_VERSION_BUGFIX 0
/**
The constant NV_FOUNDATION_VERSION is used to confirm the version of the foundation headers.
This is to ensure that the application is using the same header version as the library was built with.
*/
#define NV_FOUNDATION_VERSION ((NV_FOUNDATION_VERSION_MAJOR<<24) + (NV_FOUNDATION_VERSION_MINOR<<16) + (NV_FOUNDATION_VERSION_BUGFIX<<8) + 0)
//***************************************
// FILE: NvPreprocessor.h
//***************************************
/**
List of preprocessor defines used to configure the SDK
- NV_DEBUG: enable asserts (exactly one needs to be defined)
- NV_CHECKED: enable run time checks, mostly unused or equiv. to NV_DEBUG
- NV_SUPPORT_VISUAL_DEBUGGER: ...
- AG_PERFMON: ... (Deprecated)
*/
/**
Compiler define
*/
#ifdef _MSC_VER
# define NV_VC
# if _MSC_VER >= 1700
# define NV_VC11
# elif _MSC_VER >= 1600
# define NV_VC10
# elif _MSC_VER >= 1500
# define NV_VC9
# elif _MSC_VER >= 1400
# define NV_VC8
# elif _MSC_VER >= 1300
# define NV_VC7
# else
# define NV_VC6
# endif
#elif defined(__ghs__)
# define NV_GHS
#elif __GNUC__ || __SNC__
# define NV_GNUC
#else
# error "Unknown compiler"
#endif
/**
Platform define
*/
#ifdef NV_VC
# ifdef XBOXONE
# define NV_XBOXONE
# define NV_X64
# elif defined(_M_IX86)
# define NV_X86
# define NV_WINDOWS
# elif defined(_M_X64)
# define NV_X64
# define NV_WINDOWS
# elif defined(_M_PPC)
# define NV_PPC
# define NV_X360
# define NV_VMX
# elif defined(_M_ARM)
# define NV_ARM
# define NV_ARM_NEON
# else
# error "Unknown platform"
# endif
# if defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_PARTITION_APP
# define NV_WINMODERN
# endif
#elif defined NV_GNUC
# ifdef __CELLOS_LV2__
# define NV_PS3
# define NV_VMX
# elif defined(__arm__) || defined(__aarch64__)
# define NV_ARM
# if defined(__SNC__)
# define NV_PSP2
# endif
# if defined(__ARM_NEON__)
# define NV_ARM_NEON
# endif
# elif defined(__i386__)
# define NV_X86
# define NV_VMX
# elif defined(__x86_64__)
# ifdef __PS4__
# define NV_PS4
# define NV_X64
# else
# define NV_X64
# endif
# elif defined(__ppc__)
# define NV_PPC
# elif defined(__ppc64__)
# define NV_PPC
# define NV_PPC64
# else
# error "Unknown platform"
# endif
# if defined(ANDROID)
# define NV_ANDROID
# define NV_UNIX
# elif defined(__linux__)
# define NV_LINUX
# define NV_UNIX
# elif defined(__APPLE__)
# define NV_APPLE
# define NV_UNIX
# if defined(__arm__)
# define NV_APPLE_IOS
# else
# define NV_OSX
# endif
# elif defined(__CYGWIN__)
# define NV_CYGWIN
# define NV_LINUX
# define NV_UNIX
# endif
#elif defined NV_GHS
# define NV_WIIU
#endif
/**
DLL export macros
*/
#if !defined(NV_C_EXPORT)
# if defined(NV_WINDOWS) || defined(NV_WINMODERN)
# define NV_C_EXPORT extern "C"
# else
# define NV_C_EXPORT
# endif
#endif
/**
Calling convention
*/
#ifndef NV_CALL_CONV
# if defined NV_WINDOWS
# define NV_CALL_CONV __cdecl
# else
# define NV_CALL_CONV
# endif
#endif
/**
Pack macros - disabled on SPU because they are not supported
*/
#if defined(NV_VC)
# define NV_PUSH_PACK_DEFAULT __pragma( pack(push, 8) )
# define NV_POP_PACK __pragma( pack(pop) )
#elif (defined(NV_GNUC) && !defined(__SPU__)) || defined(NV_GHS)
# define NV_PUSH_PACK_DEFAULT _Pragma("pack(push, 8)")
# define NV_POP_PACK _Pragma("pack(pop)")
#else
# define NV_PUSH_PACK_DEFAULT
# define NV_POP_PACK
#endif
/**
Inline macro
*/
#if defined(NV_WINDOWS) || defined(NV_X360) || defined(NV_WINMODERN) || defined(NV_XBOXONE)
# define NV_INLINE inline
# pragma inline_depth( 255 )
#else
# define NV_INLINE inline
#endif
/**
Force inline macro
*/
#if defined(NV_VC)
#define NV_FORCE_INLINE __forceinline
#elif defined(NV_LINUX) // Workaround; Fedora Core 3 do not agree with force inline and NvcPool
#define NV_FORCE_INLINE inline
#elif defined(NV_GNUC) || defined(NV_GHS)
#define NV_FORCE_INLINE inline __attribute__((always_inline))
#else
#define NV_FORCE_INLINE inline
#endif
/**
Noinline macro
*/
#if defined NV_WINDOWS || defined NV_XBOXONE
# define NV_NOINLINE __declspec(noinline)
#elif defined(NV_GNUC) || defined(NV_GHS)
# define NV_NOINLINE __attribute__ ((noinline))
#else
# define NV_NOINLINE
#endif
/*! restrict macro */
#if defined(__CUDACC__)
# define NV_RESTRICT __restrict__
#elif (defined(NV_GNUC) || defined(NV_VC) || defined(NV_GHS)) && !defined(NV_PS4) // ps4 doesn't like restricted functions
# define NV_RESTRICT __restrict
#else
# define NV_RESTRICT
#endif
#if defined(NV_WINDOWS) || defined(NV_X360) || defined(NV_WINMODERN) || defined(NV_XBOXONE)
#define NV_NOALIAS __declspec(noalias)
#else
#define NV_NOALIAS
#endif
/**
Alignment macros
NV_ALIGN_PREFIX and NV_ALIGN_SUFFIX can be used for type alignment instead of aligning individual variables as follows:
NV_ALIGN_PREFIX(16)
struct A {
...
} NV_ALIGN_SUFFIX(16);
This declaration style is parsed correctly by Visual Assist.
*/
#ifndef NV_ALIGN
#if defined(NV_VC)
#define NV_ALIGN(alignment, decl) __declspec(align(alignment)) decl
#define NV_ALIGN_PREFIX(alignment) __declspec(align(alignment))
#define NV_ALIGN_SUFFIX(alignment)
#elif defined(NV_GNUC) || defined(NV_GHS) || defined(NV_APPLE_IOS)
#define NV_ALIGN(alignment, decl) decl __attribute__ ((aligned(alignment)))
#define NV_ALIGN_PREFIX(alignment)
#define NV_ALIGN_SUFFIX(alignment) __attribute__ ((aligned(alignment)))
#else
#define NV_ALIGN(alignment, decl)
#define NV_ALIGN_PREFIX(alignment)
#define NV_ALIGN_SUFFIX(alignment)
#endif
#endif
/**
Deprecated macro
- To deprecate a function: Place NV_DEPRECATED at the start of the function header (leftmost word).
- To deprecate a 'typdef', a 'struct' or a 'class': Place NV_DEPRECATED directly after the keywords ('typdef', 'struct', 'class').
*/
#if 0 // set to 1 to create warnings for deprecated functions
# define NV_DEPRECATED __declspec(deprecated)
#else
# define NV_DEPRECATED
#endif
// VC6 no '__FUNCTION__' workaround
#if defined NV_VC6 && !defined __FUNCTION__
# define __FUNCTION__ "Undefined"
#endif
/**
General defines
*/
// static assert
#define NV_COMPILE_TIME_ASSERT(exp) typedef char NvCompileTimeAssert_Dummy[(exp) ? 1 : -1]
#if defined(NV_GNUC)
#define NV_OFFSET_OF(X, Y) __builtin_offsetof(X, Y)
#else
#define NV_OFFSET_OF(X, Y) offsetof(X, Y)
#endif
#define NV_SIZE_OF(Class, Member) sizeof(((Class*)0)->Member)
#define NV_ARRAY_SIZE(X) (sizeof((X))/sizeof((X)[0]))
#define NV_PAD_POW2(value, pad) (((value) + ((pad)-1)) & (~((pad)-1)))
// _DEBUG is provided only by MSVC, but not GCC.
// NDEBUG is the canonical platform agnostic way to detect debug/release builds
#if !defined(_DEBUG) && !defined(NDEBUG)
#define _DEBUG 1
#endif
// check that exactly one of NDEBUG and _DEBUG is defined
#if !(defined NDEBUG ^ defined _DEBUG)
#error "NDEBUG and _DEBUG are mutually exclusive"
#endif
// make sure NV_CHECKED is defined in all _DEBUG configurations as well
#if !defined(NV_CHECKED) && defined _DEBUG
#define NV_CHECKED
#endif
#ifdef __CUDACC__
#define NV_CUDA_CALLABLE __host__ __device__
#else
#define NV_CUDA_CALLABLE
#endif
// avoid unreferenced parameter warning (why not just disable it?)
// PT: or why not just omit the parameter's name from the declaration????
#if defined(__cplusplus__)
template <class T> NV_CUDA_CALLABLE NV_INLINE void NV_UNUSED(T const&) {}
#else
# define NV_UNUSED(var) (void)(var)
#endif
// Ensure that the application hasn't tweaked the pack value to less than 8, which would break
// matching between the API headers and the binaries
// This assert works on win32/win64/360/ps3, but may need further specialization on other platforms.
// Some GCC compilers need the compiler flag -malign-double to be set.
// Apparently the apple-clang-llvm compiler doesn't support malign-double.
typedef struct NvPackValidation { char _; long long a; } NvPackValidation;
#if !defined(NV_APPLE)
NV_COMPILE_TIME_ASSERT(NV_OFFSET_OF(NvPackValidation, a) == 8);
#endif
// use in a cpp file to suppress LNK4221
#if defined(NV_VC)
#define NV_DUMMY_SYMBOL namespace { char NvDummySymbol; }
#else
#define NV_DUMMY_SYMBOL
#endif
#ifdef __SPU__
#define NV_IS_SPU 1
#else
#define NV_IS_SPU 0
#endif
#ifdef NV_X64
#define NV_IS_X64 1
#else
#define NV_IS_X64 0
#endif
#ifdef NV_WINDOWS
#define NV_IS_WINDOWS 1
#else
#define NV_IS_WINDOWS 0
#endif
#ifdef NV_X86
#define NV_IS_X86 1
#else
#define NV_IS_X86 0
#endif
#ifdef NV_X64
#define NV_IS_X64 1
#else
#define NV_IS_X64 0
#endif
#if defined(NV_X86) || defined(NV_X64)
#define NV_IS_INTEL 1
#else
#define NV_IS_INTEL 0
#endif
#ifdef NV_X360
#define NV_IS_X360 1
#else
#define NV_IS_X360 0
#endif
#ifdef NV_PS3
#define NV_IS_PS3 1
#else
#define NV_IS_PS3 0
#endif
#define NV_IS_PPU (NV_IS_PS3 && !NV_IS_SPU) // PS3 PPU
#ifdef NV_GNUC
#define NV_WEAK_SYMBOL __attribute__((weak)) // this is to support SIMD constant merging in template specialization
#else
#define NV_WEAK_SYMBOL
#endif
// Type ranges
#define NV_MAX_I8 127 //maximum possible sbyte value, 0x7f
#define NV_MIN_I8 (-128) //minimum possible sbyte value, 0x80
#define NV_MAX_U8 255U //maximum possible ubyte value, 0xff
#define NV_MIN_U8 0 //minimum possible ubyte value, 0x00
#define NV_MAX_I16 32767 //maximum possible sword value, 0x7fff
#define NV_MIN_I16 (-32768) //minimum possible sword value, 0x8000
#define NV_MAX_U16 65535U //maximum possible uword value, 0xffff
#define NV_MIN_U16 0 //minimum possible uword value, 0x0000
#define NV_MAX_I32 2147483647 //maximum possible sdword value, 0x7fffffff
#define NV_MIN_I32 (-2147483647 - 1) //minimum possible sdword value, 0x80000000
#define NV_MAX_U32 4294967295U //maximum possible udword value, 0xffffffff
#define NV_MIN_U32 0 //minimum possible udword value, 0x00000000
#define NV_MAX_F32 3.4028234663852885981170418348452e+38F
//maximum possible float value
#define NV_MAX_F64 DBL_MAX //maximum possible double value
#define NV_EPS_F32 FLT_EPSILON //maximum relative error of float rounding
#define NV_EPS_F64 DBL_EPSILON //maximum relative error of double rounding
#define NV_MAX_REAL NV_MAX_F32
#define NV_EPS_REAL NV_EPS_F32
#define NV_NORMALIZATION_EPSILON float(1e-20f)
/** enum for empty constructor tag*/
enum NvEMPTY { NvEmpty };
/** Basic struct data type for float2 Vectors */
typedef struct
{
float x,y;
} NV_float2;
/** Basic struct data type for float3 Vectors */
typedef struct
{
float x,y,z;
} NV_float3;
/** Basic struct data type for float4 Vector or quaternion */
typedef struct
{
float x,y,z,w;
} NV_float4;
/** Basic struct data type for 7 floats, typically used to represent a 'pose' comprised of a quaternion rotation (x,y,z,w) followed by a position (x,y,z) */
typedef struct
{
NV_float4 q;
NV_float3 p;
} NV_float7;
/** Basic struct data type for 9 floats, typically a 3x3 matrix */
typedef struct
{
float transform[9];
} NV_float9;
/** Basic struct data type for 12 floats, typically a 3x4 matrix */
typedef struct
{
float transform[12];
} NV_float12;
/** Basic struct data type for 16 floats, typically a 4x4 matrix */
typedef struct
{
float transform[16];
} NV_float16;
/** Basic struct data to for a 3d bounding box */
typedef struct
{
float minimum[3];
float maximum[3];
} NV_bounds3;
/** @} */
#endif // NV_FOUNDATION_H

View file

@ -0,0 +1,83 @@
## Table of Contents
- [nvpfilesystem.hpp](#nvpfilesystemhpp)
- [nvpsystem.hpp](#nvpsystemhpp)
- [nvpwindow.hpp](#nvpwindowhpp)
## nvpfilesystem.hpp
### class nvp::FileSystemMonitor
Monitors files and/or directories for changes
This cross-platform wrapper does not create any threads, but is designed for
it. checkEvents() will block until either an event is generated, or cancel()
is called. See ModifiedFilesMonitor for an example.
### class nvp::FSMRunner
Adds a thread to nvp::FileSystemMonitor that repeatedly calls
nvp::FileSystemMonitor::checkEvents().
### class nvp::FSMCallbacks
Utility class to get per-path callbacks.
Make sure PathSetCallback objects returned by add() do not outlive the FSMCallbacks.
Be careful not to destroy a PathCallback during a callback.
Example:
```cpp
FSMCallbacks callbacks;
auto callbackFile1 = callbacks.add(std::vector<std::string>{"file1.txt"}, nvp::FileSystemMonitor::FSM_MODIFY,
[this](nvp::FileSystemMonitor::EventData ev) {
// Do something with file1.txt
});
auto callbackFile2 = callbacks.add(std::vector<std::string>{"file2.txt"}, nvp::FileSystemMonitor::FSM_MODIFY,
[this](nvp::FileSystemMonitor::EventData ev) {
// Do something with file2.txt
});
// When callbackFile1 goes out of scope, file1.txt stops being monitored
callbackFile1.reset()
```
### class nvp::ModifiedFilesMonitor
Monitors files and/or directories for changes.
Starts a thread at creation. Be careful to keep it in scope while events are needed.
This cross-platform wrapper does not create any threads, but is designed to be
used with one. checkEvents() will block until either an event is generated, or
cancel() is called.
Example:
```cpp
std::vector<std::string> dirs = {"shaders_bin"};
nvp::FileSystemMonitor::Callback callback = [](nvp::FileSystemMonitor::EventData ev){
g_reloadShaders = true;
};
auto fileMonitor = std::make_unique<nvp::ModifiedFilesMonitor>(dirs, callback);
```
## nvpsystem.hpp
### class NVPSystem
> NVPSystem is a utility class to handle some basic system
functionality that all projects likely make use of.
It does not require any window to be opened.
Typical usage is calling init right after main and deinit
in the end, or use the NVPSystem object for that.
init
- calls glfwInit and registers the error callback for it
- sets up and log filename based on projectName via nvprintSetLogFileName
## nvpwindow.hpp
### class NVPWindow
> base class for a window, to catch events
Using and deriving of NVPWindow base-class is optional.
However one must always make use of the NVPSystem
That takes care of glfwInit/terminate as well.

View file

@ -0,0 +1,21 @@
/*
* Copyright (c) 2018-2021, 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) 2018-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
//--------------------------------------------------------------------
/// @DOC_SKIP
#include <nvgl/extensions_gl.hpp>

View file

@ -0,0 +1,862 @@
//
// Courtesy from Sam Hocevar <sam@hocevar.net>
//
/// @DOC_SKIP (keyword to exclude this file from automatic README.md generation)
#pragma once
#ifndef _POSIX_C_SOURCE
# define _POSIX_C_SOURCE 2 // for popen()
#endif
#include <cstdio> // popen()
#include <cstdlib> // std::getenv()
#include <fcntl.h> // fcntl()
#include <unistd.h> // read(), pipe(), dup2()
#include <csignal> // ::kill, std::signal
#include <sys/wait.h> // waitpid()
#include <string> // std::string
#include <memory> // std::shared_ptr
#include <iostream> // std::ostream
#include <map> // std::map
#include <set> // std::set
#include <regex> // std::regex
#include <thread> // std::mutex, std::this_thread
#include <chrono> // std::chrono
enum class button
{
cancel = -1,
ok,
yes,
no,
abort,
retry,
ignore,
};
enum class choice
{
ok = 0,
ok_cancel,
yes_no,
yes_no_cancel,
retry_cancel,
abort_retry_ignore,
};
enum class icon
{
info = 0,
warning,
error,
question,
};
// Additional option flags for various dialog constructors
enum class opt : uint8_t
{
none = 0,
// For file open, allow multiselect.
multiselect = 0x1,
// For file save, force overwrite and disable the confirmation dialog.
force_overwrite = 0x2,
// For folder select, force path to be the provided argument instead
// of the last opened directory, which is the Microsoft-recommended,
// user-friendly behaviour.
force_path = 0x4,
};
inline opt operator |(opt a, opt b) { return opt(uint8_t(a) | uint8_t(b)); }
inline bool operator &(opt a, opt b) { return bool(uint8_t(a) & uint8_t(b)); }
// The settings class, only exposing to the user a way to set verbose mode
// and to force a rescan of installed desktop helpers (zenity, kdialog…).
class settings
{
public:
static bool available();
static void verbose(bool value);
static void rescan();
protected:
explicit settings(bool resync = false);
bool check_program(std::string const &program);
inline bool is_zenity() const;
inline bool is_kdialog() const;
enum class flag
{
is_scanned = 0,
is_verbose,
has_zenity,
has_matedialog,
has_qarma,
has_kdialog,
is_vista,
max_flag,
};
// Static array of flags for internal state
bool const &flags(flag in_flag) const;
// Non-const getter for the static array of flags
bool &flags(flag in_flag);
};
// Internal classes, not to be used by client applications
namespace internal
{
// Process wait timeout, in milliseconds
static int const default_wait_timeout = 20;
class executor
{
friend class dialog;
public:
// High level function to get the result of a command
std::string result(int *exit_code = nullptr);
// High level function to abort
bool kill();
void start_process(std::vector<std::string> const &command);
~executor();
protected:
bool ready(int timeout = default_wait_timeout);
void stop();
private:
bool m_running = false;
std::string m_stdout;
int m_exit_code = -1;
pid_t m_pid = 0;
int m_fd = -1;
};
class dialog : protected settings
{
public:
bool ready(int timeout = default_wait_timeout) const;
bool kill() const;
protected:
explicit dialog();
std::vector<std::string> desktop_helper() const;
static std::string buttons_to_name(choice _choice);
static std::string get_icon_name(icon _icon);
std::string powershell_quote(std::string const &str) const;
std::string shell_quote(std::string const &str) const;
// Keep handle to executing command
std::shared_ptr<executor> m_async;
};
class file_dialog : public dialog
{
protected:
enum type
{
open,
save,
folder,
};
file_dialog(type in_type,
std::string const &title,
std::string const &default_path = "",
std::vector<std::string> const &filters = {},
opt options = opt::none);
protected:
std::string string_result();
std::vector<std::string> vector_result();
};
} // namespace internal
//
// The notify widget
//
class notify : public internal::dialog
{
public:
notify(std::string const &title,
std::string const &message,
icon _icon = icon::info);
};
//
// The message widget
//
class message : public internal::dialog
{
public:
message(std::string const &title,
std::string const &text,
choice _choice = choice::ok_cancel,
icon _icon = icon::info);
button result();
private:
// Some extra logic to map the exit code to button number
std::map<int, button> m_mappings;
};
//
// The open_file, save_file, and open_folder widgets
//
class open_file : public internal::file_dialog
{
public:
open_file(std::string const &title,
std::string const &default_path = "",
std::vector<std::string> const &filters = { "All Files", "*" },
opt options = opt::none);
#if defined(__has_cpp_attribute)
#if __has_cpp_attribute(deprecated)
// Backwards compatibility
[[deprecated("Use opt::multiselect instead of allow_multiselect")]]
#endif
#endif
open_file(std::string const &title,
std::string const &default_path,
std::vector<std::string> const &filters,
bool allow_multiselect);
std::vector<std::string> result();
};
class save_file : public internal::file_dialog
{
public:
save_file(std::string const &title,
std::string const &default_path = "",
std::vector<std::string> const &filters = { "All Files", "*" },
opt options = opt::none);
#if defined(__has_cpp_attribute)
#if __has_cpp_attribute(deprecated)
// Backwards compatibility
[[deprecated("Use opt::force_overwrite instead of confirm_overwrite")]]
#endif
#endif
save_file(std::string const &title,
std::string const &default_path,
std::vector<std::string> const &filters,
bool confirm_overwrite);
std::string result();
};
class select_folder : public internal::file_dialog
{
public:
select_folder(std::string const &title,
std::string const &default_path = "",
opt options = opt::none);
std::string result();
};
//
// Below this are all the method implementations. You may choose to define the
// macro SKIP_IMPLEMENTATION everywhere before including this header except
// in one place. This may reduce compilation times.
//
#if !defined SKIP_IMPLEMENTATION
// internal free functions implementations
namespace internal
{
// This is necessary until C++20 which will have std::string::ends_with() etc.
static inline bool ends_with(std::string const &str, std::string const &suffix)
{
return suffix.size() <= str.size() &&
str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
}
static inline bool starts_with(std::string const &str, std::string const &prefix)
{
return prefix.size() <= str.size() &&
str.compare(0, prefix.size(), prefix) == 0;
}
} // namespace internal
// settings implementation
inline settings::settings(bool resync)
{
flags(flag::is_scanned) &= !resync;
if (flags(flag::is_scanned))
return;
flags(flag::has_zenity) = check_program("zenity");
flags(flag::has_matedialog) = check_program("matedialog");
flags(flag::has_qarma) = check_program("qarma");
flags(flag::has_kdialog) = check_program("kdialog");
// If multiple helpers are available, try to default to the best one
if (flags(flag::has_zenity) && flags(flag::has_kdialog))
{
auto desktop_name = std::getenv("XDG_SESSION_DESKTOP");
if (desktop_name && desktop_name == std::string("gnome"))
flags(flag::has_kdialog) = false;
else if (desktop_name && desktop_name == std::string("KDE"))
flags(flag::has_zenity) = false;
}
flags(flag::is_scanned) = true;
}
inline bool settings::available()
{
settings tmp;
return tmp.flags(flag::has_zenity) ||
tmp.flags(flag::has_matedialog) ||
tmp.flags(flag::has_qarma) ||
tmp.flags(flag::has_kdialog);
}
inline void settings::verbose(bool value)
{
settings().flags(flag::is_verbose) = value;
}
inline void settings::rescan()
{
settings(/* resync = */ true);
}
// Check whether a program is present using “which”.
inline bool settings::check_program(std::string const &program)
{
int exit_code = -1;
internal::executor async;
async.start_process({"/bin/sh", "-c", "which " + program});
async.result(&exit_code);
return exit_code == 0;
}
inline bool settings::is_zenity() const
{
return flags(flag::has_zenity) ||
flags(flag::has_matedialog) ||
flags(flag::has_qarma);
}
inline bool settings::is_kdialog() const
{
return flags(flag::has_kdialog);
}
inline bool const &settings::flags(flag in_flag) const
{
static bool flags[size_t(flag::max_flag)];
return flags[size_t(in_flag)];
}
inline bool &settings::flags(flag in_flag)
{
return const_cast<bool &>(static_cast<settings const *>(this)->flags(in_flag));
}
// executor implementation
inline std::string internal::executor::result(int *exit_code /* = nullptr */)
{
stop();
if (exit_code)
*exit_code = m_exit_code;
return m_stdout;
}
inline bool internal::executor::kill()
{
::kill(m_pid, SIGKILL);
stop();
return true;
}
inline void internal::executor::start_process(std::vector<std::string> const &command)
{
stop();
m_stdout.clear();
m_exit_code = -1;
int in[2], out[2];
if (pipe(in) != 0 || pipe(out) != 0)
return;
m_pid = fork();
if (m_pid < 0)
return;
close(in[m_pid ? 0 : 1]);
close(out[m_pid ? 1 : 0]);
if (m_pid == 0)
{
dup2(in[0], STDIN_FILENO);
dup2(out[1], STDOUT_FILENO);
// Ignore stderr so that it doesnt pollute the console (e.g. GTK+ errors from zenity)
int fd = open("/dev/null", O_WRONLY);
dup2(fd, STDERR_FILENO);
close(fd);
std::vector<char *> args;
std::transform(command.cbegin(), command.cend(), std::back_inserter(args),
[](std::string const &s) { return const_cast<char *>(s.c_str()); });
args.push_back(nullptr); // null-terminate argv[]
execvp(args[0], args.data());
exit(1);
}
close(in[1]);
m_fd = out[0];
auto flags = fcntl(m_fd, F_GETFL);
fcntl(m_fd, F_SETFL, flags | O_NONBLOCK);
m_running = true;
}
inline internal::executor::~executor()
{
stop();
}
inline bool internal::executor::ready(int timeout /* = default_wait_timeout */)
{
if (!m_running)
return true;
char buf[BUFSIZ];
ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore
if (received > 0)
{
m_stdout += std::string(buf, received);
return false;
}
// Reap child process if it is dead. It is possible that the system has already reaped it
// (this happens when the calling application handles or ignores SIG_CHLD) and results in
// waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for
// a little while.
int status;
pid_t child = waitpid(m_pid, &status, WNOHANG);
if (child != m_pid && (child >= 0 || errno != ECHILD))
{
// FIXME: this happens almost always at first iteration
std::this_thread::sleep_for(std::chrono::milliseconds(timeout));
return false;
}
close(m_fd);
m_exit_code = WEXITSTATUS(status);
m_running = false;
return true;
}
inline void internal::executor::stop()
{
// Loop until the user closes the dialog
while (!ready())
;
}
// dialog implementation
inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const
{
return m_async->ready(timeout);
}
inline bool internal::dialog::kill() const
{
return m_async->kill();
}
inline internal::dialog::dialog()
: m_async(std::make_shared<executor>())
{
}
inline std::vector<std::string> internal::dialog::desktop_helper() const
{
return { flags(flag::has_zenity) ? "zenity"
: flags(flag::has_matedialog) ? "matedialog"
: flags(flag::has_qarma) ? "qarma"
: flags(flag::has_kdialog) ? "kdialog"
: "echo" };
}
inline std::string internal::dialog::buttons_to_name(choice _choice)
{
switch (_choice)
{
case choice::ok_cancel: return "okcancel";
case choice::yes_no: return "yesno";
case choice::yes_no_cancel: return "yesnocancel";
case choice::retry_cancel: return "retrycancel";
case choice::abort_retry_ignore: return "abortretryignore";
/* case choice::ok: */ default: return "ok";
}
}
inline std::string internal::dialog::get_icon_name(icon _icon)
{
switch (_icon)
{
case icon::warning: return "warning";
case icon::error: return "error";
case icon::question: return "question";
// Zenity wants "information" but WinForms wants "info"
/* case icon::info: */ default:
return "information";
}
}
// THis is only used for debugging purposes
inline std::ostream& operator <<(std::ostream &s, std::vector<std::string> const &v)
{
int not_first = 0;
for (auto &e : v)
s << (not_first++ ? " " : "") << e;
return s;
}
// Properly quote a string for Powershell: replace ' or " with '' or ""
// FIXME: we should probably get rid of newlines!
// FIXME: the \" sequence seems unsafe, too!
// XXX: this is no longer used but I would like to keep it around just in case
inline std::string internal::dialog::powershell_quote(std::string const &str) const
{
return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'";
}
// Properly quote a string for the shell: just replace ' with '\''
// XXX: this is no longer used but I would like to keep it around just in case
inline std::string internal::dialog::shell_quote(std::string const &str) const
{
return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'";
}
// file_dialog implementation
inline internal::file_dialog::file_dialog(type in_type,
std::string const &title,
std::string const &default_path /* = "" */,
std::vector<std::string> const &filters /* = {} */,
opt options /* = opt::none */)
{
auto command = desktop_helper();
if (is_zenity())
{
command.push_back("--file-selection");
command.push_back("--filename=" + default_path);
command.push_back("--title");
command.push_back(title);
command.push_back("--separator=\n");
for (size_t i = 0; i < filters.size() / 2; ++i)
{
command.push_back("--file-filter");
command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]);
}
if (in_type == type::save)
command.push_back("--save");
if (in_type == type::folder)
command.push_back("--directory");
if (!(options & opt::force_overwrite))
command.push_back("--confirm-overwrite");
if (options & opt::multiselect)
command.push_back("--multiple");
}
else if (is_kdialog())
{
switch (in_type)
{
case type::save: command.push_back("--getsavefilename"); break;
case type::open: command.push_back("--getopenfilename"); break;
case type::folder: command.push_back("--getexistingdirectory"); break;
}
if (options & opt::multiselect)
command.push_back(" --multiple");
command.push_back(default_path);
std::string filter;
for (size_t i = 0; i < filters.size() / 2; ++i)
filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")";
command.push_back(filter);
command.push_back("--title");
command.push_back(title);
}
if (flags(flag::is_verbose))
std::cerr << command << std::endl;
m_async->start_process(command);
}
inline std::string internal::file_dialog::string_result()
{
auto ret = m_async->result();
// Strip potential trailing newline (zenity). Also strip trailing slash.
while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/'))
ret.pop_back();
return ret;
}
inline std::vector<std::string> internal::file_dialog::vector_result()
{
std::vector<std::string> ret;
auto result = m_async->result();
for (;;)
{
// Split result along newline characters
auto i = result.find('\n');
if (i == 0 || i == std::string::npos)
break;
ret.push_back(result.substr(0, i));
result = result.substr(i + 1, result.size());
}
return ret;
}
// notify implementation
inline notify::notify(std::string const &title,
std::string const &message,
icon _icon /* = icon::info */)
{
if (_icon == icon::question) // Not supported by notifications
_icon = icon::info;
auto command = desktop_helper();
if (is_zenity())
{
command.push_back("--notification");
command.push_back("--window-icon");
command.push_back(get_icon_name(_icon));
command.push_back("--text");
command.push_back(title + "\n" + message);
}
else if (is_kdialog())
{
command.push_back("--icon");
command.push_back(get_icon_name(_icon));
command.push_back("--title");
command.push_back(title);
command.push_back("--passivepopup");
command.push_back(message);
command.push_back("5");
}
if (flags(flag::is_verbose))
std::cerr << command << std::endl;
m_async->start_process(command);
}
// message implementation
inline message::message(std::string const &title,
std::string const &text,
choice _choice /* = choice::ok_cancel */,
icon _icon /* = icon::info */)
{
auto command = desktop_helper();
if (is_zenity())
{
switch (_choice)
{
case choice::ok_cancel:
command.insert(command.end(), { "--question", "--cancel-label=Cancel", "--ok-label=OK" }); break;
case choice::yes_no:
// Do not use standard --question because it causes “No” to return -1,
// which is inconsistent with the “Yes/No/Cancel” mode below.
command.insert(command.end(), { "--question", "--switch", "--extra-button=No", "--extra-button=Yes" }); break;
case choice::yes_no_cancel:
command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=No", "--extra-button=Yes" }); break;
case choice::retry_cancel:
command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry" }); break;
case choice::abort_retry_ignore:
command.insert(command.end(), { "--question", "--switch", "--extra-button=Ignore", "--extra-button=Abort", "--extra-button=Retry" }); break;
case choice::ok:
default:
switch (_icon)
{
case icon::error: command.push_back("--error"); break;
case icon::warning: command.push_back("--warning"); break;
default: command.push_back("--info"); break;
}
}
command.insert(command.end(), { "--title", title,
"--width=300", "--height=0", // sensible defaults
"--text", text,
"--icon-name=dialog-" + get_icon_name(_icon) });
}
else if (is_kdialog())
{
if (_choice == choice::ok)
{
switch (_icon)
{
case icon::error: command.push_back("--error"); break;
case icon::warning: command.push_back("--sorry"); break;
default: command.push_back("--msgbox"); break;
}
}
else
{
std::string flag = "--";
if (_icon == icon::warning || _icon == icon::error)
flag += "warning";
flag += "yesno";
if (_choice == choice::yes_no_cancel)
flag += "cancel";
command.push_back(flag);
if (_choice == choice::yes_no || _choice == choice::yes_no_cancel)
{
m_mappings[0] = button::yes;
m_mappings[256] = button::no;
}
}
command.push_back(text);
command.push_back("--title");
command.push_back(title);
// Must be after the above part
if (_choice == choice::ok_cancel)
command.insert(command.end(), { "--yes-label", "OK", "--no-label", "Cancel" });
}
if (flags(flag::is_verbose))
std::cerr << command << std::endl;
m_async->start_process(command);
}
inline button message::result()
{
int exit_code;
auto ret = m_async->result(&exit_code);
if (exit_code < 0 || // this means cancel
internal::ends_with(ret, "Cancel\n"))
return button::cancel;
if (internal::ends_with(ret, "OK\n"))
return button::ok;
if (internal::ends_with(ret, "Yes\n"))
return button::yes;
if (internal::ends_with(ret, "No\n"))
return button::no;
if (internal::ends_with(ret, "Abort\n"))
return button::abort;
if (internal::ends_with(ret, "Retry\n"))
return button::retry;
if (internal::ends_with(ret, "Ignore\n"))
return button::ignore;
if (m_mappings.count(exit_code) != 0)
return m_mappings[exit_code];
return exit_code == 0 ? button::ok : button::cancel;
}
// open_file implementation
inline open_file::open_file(std::string const &title,
std::string const &default_path /* = "" */,
std::vector<std::string> const &filters /* = { "All Files", "*" } */,
opt options /* = opt::none */)
: file_dialog(type::open, title, default_path, filters, options)
{
}
inline open_file::open_file(std::string const &title,
std::string const &default_path,
std::vector<std::string> const &filters,
bool allow_multiselect)
: open_file(title, default_path, filters,
(allow_multiselect ? opt::multiselect : opt::none))
{
}
inline std::vector<std::string> open_file::result()
{
return vector_result();
}
// save_file implementation
inline save_file::save_file(std::string const &title,
std::string const &default_path /* = "" */,
std::vector<std::string> const &filters /* = { "All Files", "*" } */,
opt options /* = opt::none */)
: file_dialog(type::save, title, default_path, filters, options)
{
}
inline save_file::save_file(std::string const &title,
std::string const &default_path,
std::vector<std::string> const &filters,
bool confirm_overwrite)
: save_file(title, default_path, filters,
(confirm_overwrite ? opt::none : opt::force_overwrite))
{
}
inline std::string save_file::result()
{
return string_result();
}
// select_folder implementation
inline select_folder::select_folder(std::string const &title,
std::string const &default_path /* = "" */,
opt options /* = opt::none */)
: file_dialog(type::folder, title, default_path, {}, options)
{
}
inline std::string select_folder::result()
{
return string_result();
}
#endif // SKIP_IMPLEMENTATION

View file

@ -0,0 +1,659 @@
/*
* 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()
{
BOOL result = FALSE;
#if(_WIN32_WINNT >= 0x0400)
#if(NTDDI_VERSION >= NTDDI_WIN10_RS3)
m_eventsRequested = true;
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);
#endif
#endif
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;
}

View file

@ -0,0 +1,305 @@
/*
* 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
*/
#pragma once
#include <cassert>
#include <nvh/nvprint.hpp>
#include <nvh/threading.hpp>
#include <functional>
#include <thread>
#include <mutex>
#include <string>
#include <memory>
namespace nvp {
/** @DOC_START
# class nvp::FileSystemMonitor
Monitors files and/or directories for changes
This cross-platform wrapper does not create any threads, but is designed for
it. checkEvents() will block until either an event is generated, or cancel()
is called. See ModifiedFilesMonitor for an example.
@DOC_END */
class FileSystemMonitor
{
public:
/** File events.
*
* \note Visual studio saves files to temporary filenames and then swaps them with renames. Renames/moves are not
* exposed explicitly yet.
*/
enum Event
{
FSM_CREATE = (1 << 0),
FSM_DELETE = (1 << 1),
FSM_MODIFY = (1 << 2),
};
struct EventData
{
Event event;
const std::string path;
void* userPtr;
};
using CallbackType = void(const EventData&);
using Callback = std::function<CallbackType>;
using PathID = int;
static const PathID INVALID_PATH_ID = 0;
static FileSystemMonitor* create();
static void destroy(FileSystemMonitor* monitor);
/** Add a file or directory to be monitored for events
*
* It is not safe to call add() at the same time as checkEvents(). Call cancel() first.
*
* \return PathID that can be passed to remove() on success, 0 on failure.
*/
virtual PathID add(const std::string& path, uint32_t eventMask, void* userPtr = nullptr) = 0;
/** Remove a file or directory from being monitored
*
* It is not safe to call remove() at the same time as checkEvents(). Call cancel() first. */
virtual void remove(const PathID& pathID) = 0;
/** Process the event queue, blocking until there is another event or cancel() is called.
*
* \return True if the thread should keep looping, i.e. there are no errors and cancel() has not been called. It is
* expected this is called in a loop by an event thread.
*/
virtual bool checkEvents(const Callback& callback) = 0;
/** Abort checkEvents, if it is currently being called. */
virtual void cancel() = 0;
protected:
FileSystemMonitor() = default;
virtual ~FileSystemMonitor() = default;
PathID nextPathID()
{
while(++m_nextPathID == INVALID_PATH_ID)
;
return m_nextPathID;
}
private:
PathID m_nextPathID = 0;
};
/** @DOC_START
# class nvp::FSMRunner
Adds a thread to nvp::FileSystemMonitor that repeatedly calls
nvp::FileSystemMonitor::checkEvents().
@DOC_END */
class FSMRunner
{
public:
FSMRunner(const FileSystemMonitor::Callback& callback)
: m_callback(callback)
{
m_monitor = FileSystemMonitor::create();
}
~FSMRunner()
{
stop();
FileSystemMonitor::destroy(m_monitor);
}
protected:
void start()
{
assert(!m_thread.joinable());
m_thread = std::thread(&FSMRunner::entrypoint, this);
}
void stop()
{
if(m_thread.joinable())
{
m_monitor->cancel();
m_thread.join();
}
}
FileSystemMonitor* m_monitor;
private:
void entrypoint()
{
for(;;)
{
if(!m_monitor->checkEvents(m_callback))
break;
}
}
std::thread m_thread;
FileSystemMonitor::Callback m_callback;
};
/** @DOC_START
# class nvp::FSMCallbacks
Utility class to get per-path callbacks.
Make sure PathSetCallback objects returned by add() do not outlive the FSMCallbacks.
Be careful not to destroy a PathCallback during a callback.
Example:
```cpp
FSMCallbacks callbacks;
auto callbackFile1 = callbacks.add(std::vector<std::string>{"file1.txt"}, nvp::FileSystemMonitor::FSM_MODIFY,
[this](nvp::FileSystemMonitor::EventData ev) {
// Do something with file1.txt
});
auto callbackFile2 = callbacks.add(std::vector<std::string>{"file2.txt"}, nvp::FileSystemMonitor::FSM_MODIFY,
[this](nvp::FileSystemMonitor::EventData ev) {
// Do something with file2.txt
});
// When callbackFile1 goes out of scope, file1.txt stops being monitored
callbackFile1.reset()
```
@DOC_END */
class FSMCallbacks : public FSMRunner
{
public:
using Clock = nvh::DefaultDelayClock;
using Duration = nvh::DefaultDelayDuration;
struct PathSetCallbackData
{
FSMCallbacks& owner;
std::vector<FileSystemMonitor::PathID> ids;
FileSystemMonitor::Callback callback;
const Duration consolidateDelay;
nvh::delayed_call<Clock, Duration> delayedCall;
~PathSetCallbackData()
{
if(!ids.empty())
{
std::lock_guard<std::mutex> lock(owner.m_monitorMutex);
owner.stop();
for(const auto& id : ids)
owner.m_monitor->remove(id);
owner.start();
}
}
};
using PathSetCallback = std::shared_ptr<PathSetCallbackData>;
FSMCallbacks()
: FSMRunner(&FSMCallbacks::callback)
{
}
template <class List>
PathSetCallback add(const List& pathList,
uint32_t eventMask,
const FileSystemMonitor::Callback& callback,
const Duration& consolidateDelay = Duration::zero())
{
std::lock_guard<std::mutex> lock(m_monitorMutex);
stop();
PathSetCallback pathSetCallback{new PathSetCallbackData{*this, {}, callback, consolidateDelay}};
for(const auto& path : pathList)
{
auto pathID = m_monitor->add(path, eventMask, pathSetCallback.get());
if(pathID == FileSystemMonitor::INVALID_PATH_ID)
{
LOGE("Failed to watch '%s' for changes\n", path.c_str());
}
else
{
pathSetCallback->ids.push_back(pathID);
}
}
start();
return pathSetCallback;
}
private:
static void callback(const nvp::FileSystemMonitor::EventData& ev)
{
auto cbData = reinterpret_cast<PathSetCallbackData*>(ev.userPtr);
if(cbData->consolidateDelay != Duration::zero())
{
// This may block if the previous delayed call started and is still running. Rather than do anything clever here,
// it is left to the user to layer on functionality and make sure this callback loop is not blocked.
if(!cbData->delayedCall.delay_for(cbData->consolidateDelay))
cbData->delayedCall = nvh::delay_noreturn_for<Clock>(cbData->consolidateDelay, cbData->callback, ev);
}
else
cbData->callback(ev);
}
// Protect against various threads racing FSMRunner::stop()/start() calls during add() and ~PathSetCallbackData().
std::mutex m_monitorMutex;
};
/** @DOC_START
# class nvp::ModifiedFilesMonitor
Monitors files and/or directories for changes.
Starts a thread at creation. Be careful to keep it in scope while events are needed.
This cross-platform wrapper does not create any threads, but is designed to be
used with one. checkEvents() will block until either an event is generated, or
cancel() is called.
Example:
```cpp
std::vector<std::string> dirs = {"shaders_bin"};
nvp::FileSystemMonitor::Callback callback = [](nvp::FileSystemMonitor::EventData ev){
g_reloadShaders = true;
};
auto fileMonitor = std::make_unique<nvp::ModifiedFilesMonitor>(dirs, callback);
```
@DOC_END */
class ModifiedFilesMonitor : public FSMRunner
{
public:
template <class List>
ModifiedFilesMonitor(const List& paths, const FileSystemMonitor::Callback& callback)
: FSMRunner(callback)
{
for(const auto& path : paths)
{
if(m_monitor->add(path, FileSystemMonitor::FSM_MODIFY | FileSystemMonitor::FSM_CREATE) == FileSystemMonitor::INVALID_PATH_ID)
{
LOGE("Failed to watch '%s' for changes\n", path.c_str());
}
}
start();
}
};
} // namespace nvp

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2019-2021, 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 "nvpsystem.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#ifdef NVP_SUPPORTS_SOCKETS
#include "socketSampleMessages.h"
#endif
#include <algorithm>
static bool s_sysInit = false;
static void cb_errorfun(int, const char* str)
{
LOGE("%s\n", str);
}
//---------------------------------------------------------------------------
// Message pump
void NVPSystem::pollEvents()
{
#ifdef NVP_SUPPORTS_SOCKETS
// check the stack of messages from remote connection, first
processRemoteMessages();
#endif
glfwPollEvents();
}
void NVPSystem::waitEvents()
{
glfwWaitEvents();
}
void NVPSystem::postTiming(float ms, int fps, const char* details)
{
#ifdef NVP_SUPPORTS_SOCKETS
::postTiming(ms, fps, details);
#endif
}
double NVPSystem::getTime()
{
return glfwGetTime();
}
void NVPSystem::init(const char* projectName)
{
std::string logfile = std::string("log_") + std::string(projectName) + std::string(".txt");
nvprintSetLogFileName(logfile.c_str());
int result = glfwInit();
if(!result)
{
LOGE("could not init glfw\n");
exit(-1);
}
glfwSetErrorCallback(cb_errorfun);
//initNSight();
#ifdef NVP_SUPPORTS_SOCKETS
//
// Socket init if needed
//
startSocketServer(/*port*/ 1056);
#endif
s_sysInit = true;
platformInit();
}
void NVPSystem::deinit()
{
platformDeinit();
glfwTerminate();
}
bool NVPSystem::isInited()
{
return s_sysInit;
}

View file

@ -0,0 +1,102 @@
/*
* Copyright (c) 2019-2021, 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
*/
//--------------------------------------------------------------------
#ifndef __NVPSYSTEM_H__
#define __NVPSYSTEM_H__
#include "platform.h"
#include <stdio.h>
#include <stdlib.h>
#include <string>
#if defined(WIN32) && defined(MEMORY_LEAKS_CHECK)
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#include <stdlib.h>
inline void* operator new(size_t size, const char* file, int line)
{
return ::operator new(size, 1, file, line);
}
inline void __cdecl operator delete(void* ptr, const char* file, int line)
{
::operator delete(ptr, _NORMAL_BLOCK, file, line);
}
#define DEBUG_NEW new(__FILE__, __LINE__)
#define MALLOC_DBG(x) _malloc_dbg(x, 1, __FILE__, __LINE__);
#else
#define DEBUG_NEW new
#define MALLOC_DBG malloc
#endif // #if defined(WIN32) && defined(MEMORY_LEAKS_CHECK)
#include <nvh/nvprint.hpp>
/* @DOC_START
# class NVPSystem
> NVPSystem is a utility class to handle some basic system
functionality that all projects likely make use of.
It does not require any window to be opened.
Typical usage is calling init right after main and deinit
in the end, or use the NVPSystem object for that.
init
- calls glfwInit and registers the error callback for it
- sets up and log filename based on projectName via nvprintSetLogFileName
@DOC_END */
class NVPSystem
{
public:
static void init(const char* projectName);
static void deinit();
static void pollEvents(); ///< polls events. Non blicking
static void waitEvents(); ///< wait for events. Will return when at least 1 event happened
static void postTiming(float ms, int fps, const char* details = NULL);
static double getTime(); ///< returns time in seconds
static void sleep(double seconds);
static std::string exePath(); ///< exePath() can be called without init called before
static bool isInited();
/// for sake of debugging/automated testing
static void windowScreenshot(struct GLFWwindow* glfwin, const char* filename);
static void windowClear(struct GLFWwindow* glfwin, uint32_t r, uint32_t g, uint32_t b);
/// \namesimple modal dialog
/// @{
static std::string windowOpenFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts);
static std::string windowSaveFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts);
/// @}
NVPSystem(const char* projectName) { init(projectName); }
~NVPSystem() { deinit(); }
private:
static void platformInit();
static void platformDeinit();
};
#endif

View file

@ -0,0 +1,362 @@
/*
* Copyright (c) 2019-2021, 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 "nvpsystem.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_X11
#include <GLFW/glfw3native.h>
#include <vector>
#include <algorithm>
#include <unistd.h>
#include <stdio.h>
#include <limits.h>
#include <string>
#include <assert.h>
#include <memory>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
// Samples include their own definitions of stb_image. Use STB_IMAGE_WRITE_STATIC to avoid issues with multiple
// definitions in the nvpro_core static lib at the cost of having the code exist multiple times.
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#include <stb_image_write.h>
#include "linux_file_dialog.h"
union Pixel
{
uint32_t data;
struct
{
uint8_t r, g, b, a;
} channels;
};
// Object to allocate and hold a shared memory XImage and a shared memory segment
// This allows reading/writing an XImage in one IPC call
// Check XShmQueryExtension() before using
class XShmImage
{
public:
XShmImage(Display* display, int width, int height)
: m_display{display}
{
// Allocate a shared XImage
int screen = XDefaultScreen(m_display);
Visual* visual = XDefaultVisual(m_display, screen);
int depth = DefaultDepth(m_display, screen);
m_image = XShmCreateImage(m_display, visual, depth, ZPixmap, nullptr, &m_shmSegmentInfo, width, height);
if(!m_image)
{
LOGE("Error: XShmCreateImage() failed\n");
return;
}
// Create the shared memory, used by XShmGetImage()
int permissions = 0600;
m_shmID = shmget(IPC_PRIVATE, height * m_image->bytes_per_line, IPC_CREAT | permissions);
if(m_shmID == -1)
{
LOGE("Error: shmget() failed\n");
return;
}
// Map the shared memory segment into the address space of this process
m_shmAddr = shmat(m_shmID, 0, 0);
if(reinterpret_cast<intptr_t>(m_shmAddr) == -1)
{
LOGE("Error: shmat() failed\n");
return;
}
// Use the allocated shared memory for the XImage
m_shmSegmentInfo.shmid = m_shmID;
m_shmSegmentInfo.shmaddr = reinterpret_cast<char*>(m_shmAddr);
m_shmSegmentInfo.readOnly = false;
m_image->data = m_shmSegmentInfo.shmaddr;
// Get the X server to attach the shared memory segment on its side and sync
if(!XShmAttach(m_display, &m_shmSegmentInfo))
LOGE("Error: XShmAttach() failed\n");
if(!XSync(m_display, false))
LOGE("Error: XSync() failed\n");
return;
}
~XShmImage()
{
if(m_image)
{
if(!XShmDetach(m_display, &m_shmSegmentInfo))
LOGE("Error: XShmDetach() failed\n");
if(!XDestroyImage(m_image))
LOGE("Error: XDestroyImage() failed\n");
}
if(reinterpret_cast<intptr_t>(m_shmAddr) != -1 && shmdt(m_shmAddr) == -1)
LOGE("Error: shmdt() failed\n");
if(m_shmID != -1 && shmctl(m_shmID, IPC_RMID, nullptr) == -1)
LOGE("Error: shmctl(IPC_RMID) failed\n");
}
// Get the X server to copy the window contents into the shared memory
bool read(Window window) { return XShmGetImage(m_display, window, m_image, 0, 0, AllPlanes); }
// In lieu of exceptions, call this after constructing to see if the constructor failed
bool valid() const { return m_shmID != -1 && reinterpret_cast<intptr_t>(m_shmAddr) != -1; }
// Returns the XImage object to be accessed after calling read()
XImage* image() const { return m_image; }
private:
int m_shmID{-1};
void* m_shmAddr{reinterpret_cast<void*>(-1)};
XShmSegmentInfo m_shmSegmentInfo{};
Display* m_display{};
XImage* m_image{};
};
void NVPSystem::windowScreenshot(struct GLFWwindow* glfwin, const char* filename)
{
const int bytesPerPixel = sizeof(Pixel);
int width{};
int height{};
std::unique_ptr<XShmImage> shmImage;
std::vector<Pixel> imageData;
XImage* fallbackXImage{};
Display* display = glfwGetX11Display();
Window window = glfwGetX11Window(glfwin);
glfwGetWindowSize(glfwin, &width, &height);
if(XShmQueryExtension(display))
{
// Use the shared memory extension if it is supported to avoid expensive XGetPixel calls
// Shared memory allows X11 to copy all the image data at once
shmImage = std::make_unique<XShmImage>(display, width, height);
if(shmImage->valid() && bytesPerPixel * 8 == shmImage->image()->bits_per_pixel)
{
if(shmImage->read(window))
{
XImage* ximg = shmImage->image();
imageData.reserve(width * height);
for(int y = 0; y < ximg->height; ++y)
{
Pixel* ximgData = reinterpret_cast<Pixel*>(ximg->data + ximg->bytes_per_line * y);
imageData.insert(imageData.end(), ximgData, ximgData + ximg->width);
}
// bgr to rgb
for(Pixel& pixel : imageData)
{
std::swap(pixel.channels.r, pixel.channels.b);
}
}
else
{
LOGE("Error: Failed to get window contents for screenshot. Falling back to XGetPixel()\n");
}
}
else
{
LOGE("Error: Failed to create XShm Image for screenshot. Falling back to XGetPixel()\n");
}
}
if(imageData.empty())
{
fallbackXImage = XGetImage(display, window, 0, 0, width, height, AllPlanes, ZPixmap);
if(!fallbackXImage)
{
LOGE("Error: XGetImage() failed to get window contents for screenshot\n");
return;
}
if(bytesPerPixel * 8 != fallbackXImage->bits_per_pixel)
{
LOGE("Error: XGetImage() returned an image with %i bits per pixel but only %i is supported\n",
fallbackXImage->bits_per_pixel, bytesPerPixel * 8);
return;
}
imageData.reserve(width * height);
for(int y = 0; y < height; ++y)
{
for(int x = 0; x < width; ++x)
{
Pixel pixel;
pixel.data = static_cast<uint32_t>(XGetPixel(fallbackXImage, x, y));
std::swap(pixel.channels.r, pixel.channels.b); // bgr to rgb
pixel.channels.a = 0xff; // set full alpha
imageData.push_back(pixel);
}
}
}
if(!stbi_write_png(filename, width, height, 4, imageData.data(), width * bytesPerPixel))
{
LOGE("Error: Writing %s failed\n", filename);
}
if(fallbackXImage && !XDestroyImage(fallbackXImage))
LOGE("Error: XDestroyImage() failed\n");
}
void NVPSystem::windowClear(struct GLFWwindow* glfwin, uint32_t r, uint32_t g, uint32_t b)
{
Window hwnd = glfwGetX11Window(glfwin);
assert(0 && "not yet implemented");
}
static void fixSingleFilter(std::string* pFilter);
static std::vector<std::string> toFilterArgs(const char* exts)
{
// Convert exts list to filter format recognized by portable-file-dialogs
// Try to match implemented nvpsystem on Windows behavior:
// Alternate between human-readable descriptions and filter strings.
// | separates strings
// ; separates filters e.g. .png|.gif
// Case-insensitive e.g. .png = .PNG = .pNg
// Split strings by |
std::vector<std::string> filterArgs(1);
for(const char* pC = exts; pC != nullptr && *pC != '\0'; ++pC)
{
char c = *pC;
if(c == '|')
filterArgs.emplace_back();
else
filterArgs.back().push_back(c);
}
// Default arguments
if(filterArgs.size() < 2)
{
filterArgs = {"All files", "*"};
}
// Split filters by ; and fix those filters.
for(size_t i = 1; i < filterArgs.size(); i += 2)
{
std::string& arg = filterArgs[i];
std::string newArg;
std::string singleFilter;
for(char c : arg)
{
if(c == ';')
{
fixSingleFilter(&singleFilter);
newArg += std::move(singleFilter);
singleFilter.clear();
newArg += ' '; // portable-file-dialogs uses spaces to separate... win32 wants no spaces in filters at all so presumably this is fine.
}
else
{
singleFilter.push_back(c);
}
}
fixSingleFilter(&singleFilter);
newArg += std::move(singleFilter);
arg = std::move(newArg);
}
return filterArgs;
}
static void fixSingleFilter(std::string* pFilter)
{
// Make case insensitive.
std::string newFilter;
for(char c : *pFilter)
{
if(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))
{
// replace c with [cC] to make case-insensitive. TODO: Unicode support, not sure how to implement for multibyte utf-8 characters.
newFilter.push_back('[');
newFilter.push_back(c);
newFilter.push_back(char(c ^ 32));
newFilter.push_back(']');
}
else
{
newFilter.push_back(c);
}
}
*pFilter = std::move(newFilter);
}
std::string NVPSystem::windowOpenFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts)
{
// Not sure yet how to use this; maybe make as a child window somehow?
[[maybe_unused]] Window hwnd = glfwGetX11Window(glfwin);
std::vector<std::string> filterArgs = toFilterArgs(exts);
std::vector<std::string> resultVector = open_file(title, ".", filterArgs).result();
assert(resultVector.size() <= 1);
return resultVector.empty() ? "" : std::move(resultVector[0]);
}
std::string NVPSystem::windowSaveFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts)
{
// Not sure yet how to use this; maybe make as a child window somehow?
[[maybe_unused]] Window hwnd = glfwGetX11Window(glfwin);
std::vector<std::string> filterArgs = toFilterArgs(exts);
return save_file(title, ".", filterArgs).result();
}
void NVPSystem::sleep(double seconds)
{
::sleep(seconds);
}
void NVPSystem::platformInit()
{
}
void NVPSystem::platformDeinit()
{
}
static bool s_exePathInit = false;
std::string NVPSystem::exePath()
{
static std::string s_exePath;
if(!s_exePathInit)
{
char modulePath[PATH_MAX];
ssize_t modulePathLength = readlink( "/proc/self/exe", modulePath, PATH_MAX );
s_exePath = std::string(modulePath, modulePathLength > 0 ? modulePathLength : 0);
size_t last = s_exePath.rfind('/');
if(last != std::string::npos)
{
s_exePath = s_exePath.substr(0, last) + std::string("/");
}
s_exePathInit = true;
}
return s_exePath;
}

View file

@ -0,0 +1,325 @@
/*
* Copyright (c) 2019-2021, 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 "nvpsystem.hpp"
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>
#include <commdlg.h>
#include <windows.h>
#include <windowsx.h>
#include "resources.h"
#include <algorithm>
#include <io.h>
#include <stdio.h>
#include <string>
#include <vector>
// Samples include their own definitions of stb_image. Use STB_IMAGE_WRITE_STATIC to avoid issues with multiple
// definitions in the nvpro_core static lib at the cost of having the code exist multiple times.
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#include <stb_image_write.h>
#include <memory>
// Executables (but not DLLs) exporting this symbol with this value will be
// automatically directed to the high-performance GPU on Nvidia Optimus systems
// with up-to-date drivers
//
extern "C" {
__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
}
// from https://docs.microsoft.com/en-us/windows/desktop/gdi/capturing-an-image
static int CaptureAnImage(HWND hWnd, const char* filename)
{
struct LocalResources
{
LocalResources(HWND _hWnd)
: hWnd(_hWnd)
{
}
~LocalResources()
{
if(hMemoryDC != nullptr && hOldBitmap != nullptr)
SelectObject(hMemoryDC, hOldBitmap);
if(hBitmap != nullptr)
DeleteObject(hBitmap);
if(hMemoryDC != nullptr)
DeleteDC(hMemoryDC);
if(hWnd != nullptr && hWindowDC != nullptr)
ReleaseDC(hWnd, hWindowDC);
}
HDC hMemoryDC = nullptr;
HDC hWindowDC = nullptr;
HBITMAP hOldBitmap = nullptr;
HBITMAP hBitmap = nullptr;
HWND hWnd = nullptr;
};
std::unique_ptr<LocalResources> res = std::make_unique<LocalResources>(hWnd);
// Get the window's device context
res->hWindowDC = GetDC(hWnd);
if(res->hWindowDC == nullptr)
{
LOGE("Failed to Create Compatible Bitmap");
return -1;
}
// Get the window's width and height
RECT rect;
if(!GetClientRect(res->hWnd, &rect))
{
LOGE("Failed to retrieves a handle to a device context");
return -1;
}
// Create a compatible device context
res->hMemoryDC = CreateCompatibleDC(res->hWindowDC);
if(res->hMemoryDC == nullptr)
{
LOGE("Failed to creates a memory device context");
return -1;
}
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
// Create a bitmap and select it into the device context
res->hBitmap = CreateCompatibleBitmap(res->hWindowDC, width, height);
if(res->hBitmap == nullptr)
{
LOGE("Failed to creates a bitmap compatible with the device");
return -1;
}
res->hOldBitmap = (HBITMAP)SelectObject(res->hMemoryDC, res->hBitmap);
if(res->hOldBitmap == nullptr)
{
LOGE("Failed to selects an object into the specified device context");
return -1;
}
// Copy the window's device context to the bitmap
if(BitBlt(res->hMemoryDC, 0, 0, width, height, res->hWindowDC, 0, 0, SRCCOPY) == 0)
{
LOGE("Failed to bit-block transfer");
return -1;
}
// Prepare the bitmap info header
BITMAPINFOHEADER bi = {};
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = width;
bi.biHeight = -height; // Negative height to ensure top-down orientation
bi.biPlanes = 1;
bi.biBitCount = 24; // 24 bits per pixel (RGB)
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
// Allocate memory for the bitmap data
std::vector<uint8_t> pixels(width * height * 3);
// Copy the bitmap data into the pixel buffer
if(GetDIBits(res->hMemoryDC, res->hBitmap, 0, height, pixels.data(), (BITMAPINFO*)&bi, DIB_RGB_COLORS) == 0)
{
LOGE("Failed to retrieves the bits of the specified compatible bitmap");
return -1;
}
// Convert BGR to RGB
for(int i = 0; i < width * height; ++i)
{
uint8_t temp = pixels[i * 3];
pixels[i * 3] = pixels[i * 3 + 2];
pixels[i * 3 + 2] = temp;
}
// Save the bitmap as a PNG file using stb_image
if(stbi_write_png(filename, width, height, 3, pixels.data(), width * 3) == 0)
{
LOGE("Failed to write: %s", filename);
return -1;
}
return 0;
}
void NVPSystem::windowScreenshot(struct GLFWwindow* glfwin, const char* filename)
{
if(!glfwin)
{
assert(!"Attempted to fall windowScreenshot() on null window!");
return;
}
CaptureAnImage(glfwGetWin32Window(glfwin), filename);
}
void NVPSystem::windowClear(struct GLFWwindow* glfwin, uint32_t r, uint32_t g, uint32_t b)
{
if(!glfwin)
{
assert(!"Attempted to fall windowClear() on null window!");
return;
}
HWND hwnd = glfwGetWin32Window(glfwin);
HDC hdcWindow = GetDC(hwnd);
RECT rcClient;
GetClientRect(hwnd, &rcClient);
HBRUSH hbr = CreateSolidBrush(RGB(r, g, b));
FillRect(hdcWindow, &rcClient, hbr);
ReleaseDC(hwnd, hdcWindow);
DeleteBrush(hbr);
}
static std::string fileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts, bool openToLoad)
{
if(!glfwin)
{
assert(!"Attempted to fall fileDialog() on null window!");
return std::string();
}
HWND hwnd = glfwGetWin32Window(glfwin);
std::vector<char> extsfixed;
for(size_t i = 0; i < strlen(exts); i++)
{
if(exts[i] == '|')
{
extsfixed.push_back(0);
}
else
{
extsfixed.push_back(exts[i]);
}
}
extsfixed.push_back(0);
extsfixed.push_back(0);
OPENFILENAME ofn; // common dialog box structure
char szFile[1024]{}; // buffer for file name
// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = szFile;
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
// use the contents of szFile to initialize itself.
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = extsfixed.data();
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST;
ofn.lpstrTitle = title;
// Display the Open dialog box.
if(openToLoad)
{
ofn.Flags |= OFN_FILEMUSTEXIST;
if(GetOpenFileNameA(&ofn) == TRUE)
{
return ofn.lpstrFile;
}
}
else
{
ofn.Flags |= OFN_OVERWRITEPROMPT;
if(GetSaveFileNameA(&ofn) == TRUE)
{
return ofn.lpstrFile;
}
}
return std::string();
}
std::string NVPSystem::windowOpenFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts)
{
return fileDialog(glfwin, title, exts, true);
}
std::string NVPSystem::windowSaveFileDialog(struct GLFWwindow* glfwin, const char* title, const char* exts)
{
return fileDialog(glfwin, title, exts, false);
}
void NVPSystem::sleep(double seconds)
{
::Sleep(DWORD(seconds * 1000.0));
}
void NVPSystem::platformInit()
{
#ifdef MEMORY_LEAKS_CHECK
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_WNDW);
#endif
}
void NVPSystem::platformDeinit()
{
// Because we set _CRTDBG_LEAK_CHECK_DF in platformInit(), we don't need to
// call _CrtDumpMemoryLeaks() in platformDeinit(). If we did, then we might
// see allocations for static objects that haven't been destroyed yet.
}
static std::string s_exePath;
static bool s_exePathInit = false;
std::string NVPSystem::exePath()
{
if(!s_exePathInit)
{
char modulePath[MAX_PATH];
size_t modulePathLength = GetModuleFileNameA(NULL, modulePath, MAX_PATH);
s_exePath = std::string(modulePath, modulePathLength);
std::replace(s_exePath.begin(), s_exePath.end(), '\\', '/');
size_t last = s_exePath.rfind('/');
if(last != std::string::npos)
{
s_exePath = s_exePath.substr(0, last) + std::string("/");
}
s_exePathInit = true;
}
return s_exePath;
}

View file

@ -0,0 +1,405 @@
/*
* Copyright (c) 2018-2021, 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) 2018-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
//--------------------------------------------------------------------
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include "nvpwindow.hpp"
#include <algorithm>
#include <stdio.h>
#include <string>
static_assert(NVPWindow::BUTTON_RELEASE == GLFW_RELEASE, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::BUTTON_PRESS == GLFW_PRESS, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::BUTTON_REPEAT == GLFW_REPEAT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::MOUSE_BUTTON_LEFT == GLFW_MOUSE_BUTTON_LEFT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::MOUSE_BUTTON_RIGHT == GLFW_MOUSE_BUTTON_RIGHT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::MOUSE_BUTTON_MIDDLE == GLFW_MOUSE_BUTTON_MIDDLE, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KMOD_SHIFT == GLFW_MOD_SHIFT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KMOD_CONTROL == GLFW_MOD_CONTROL, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KMOD_ALT == GLFW_MOD_ALT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KMOD_SUPER == GLFW_MOD_SUPER, "glfw/nvpwindow mismatch");
/*
for key in keysheader:gmatch("#define ([%w_]+)") do
print("static_assert(NVPWindow::"..key:sub(6,-1).." == "..key..", \"glfw/nvpwindow mismatch\");")
end
*/
static_assert(NVPWindow::KEY_UNKNOWN == GLFW_KEY_UNKNOWN, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_SPACE == GLFW_KEY_SPACE, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_APOSTROPHE == GLFW_KEY_APOSTROPHE, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_COMMA == GLFW_KEY_COMMA, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_MINUS == GLFW_KEY_MINUS, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_PERIOD == GLFW_KEY_PERIOD, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_SLASH == GLFW_KEY_SLASH, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_0 == GLFW_KEY_0, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_1 == GLFW_KEY_1, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_2 == GLFW_KEY_2, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_3 == GLFW_KEY_3, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_4 == GLFW_KEY_4, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_5 == GLFW_KEY_5, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_6 == GLFW_KEY_6, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_7 == GLFW_KEY_7, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_8 == GLFW_KEY_8, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_9 == GLFW_KEY_9, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_SEMICOLON == GLFW_KEY_SEMICOLON, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_EQUAL == GLFW_KEY_EQUAL, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_A == GLFW_KEY_A, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_B == GLFW_KEY_B, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_C == GLFW_KEY_C, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_D == GLFW_KEY_D, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_E == GLFW_KEY_E, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F == GLFW_KEY_F, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_G == GLFW_KEY_G, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_H == GLFW_KEY_H, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_I == GLFW_KEY_I, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_J == GLFW_KEY_J, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_K == GLFW_KEY_K, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_L == GLFW_KEY_L, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_M == GLFW_KEY_M, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_N == GLFW_KEY_N, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_O == GLFW_KEY_O, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_P == GLFW_KEY_P, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_Q == GLFW_KEY_Q, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_R == GLFW_KEY_R, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_S == GLFW_KEY_S, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_T == GLFW_KEY_T, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_U == GLFW_KEY_U, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_V == GLFW_KEY_V, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_W == GLFW_KEY_W, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_X == GLFW_KEY_X, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_Y == GLFW_KEY_Y, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_Z == GLFW_KEY_Z, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_LEFT_BRACKET == GLFW_KEY_LEFT_BRACKET, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_BACKSLASH == GLFW_KEY_BACKSLASH, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_RIGHT_BRACKET == GLFW_KEY_RIGHT_BRACKET, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_GRAVE_ACCENT == GLFW_KEY_GRAVE_ACCENT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_WORLD_1 == GLFW_KEY_WORLD_1, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_WORLD_2 == GLFW_KEY_WORLD_2, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_ESCAPE == GLFW_KEY_ESCAPE, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_ENTER == GLFW_KEY_ENTER, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_TAB == GLFW_KEY_TAB, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_BACKSPACE == GLFW_KEY_BACKSPACE, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_INSERT == GLFW_KEY_INSERT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_DELETE == GLFW_KEY_DELETE, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_RIGHT == GLFW_KEY_RIGHT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_LEFT == GLFW_KEY_LEFT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_DOWN == GLFW_KEY_DOWN, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_UP == GLFW_KEY_UP, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_PAGE_UP == GLFW_KEY_PAGE_UP, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_PAGE_DOWN == GLFW_KEY_PAGE_DOWN, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_HOME == GLFW_KEY_HOME, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_END == GLFW_KEY_END, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_CAPS_LOCK == GLFW_KEY_CAPS_LOCK, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_SCROLL_LOCK == GLFW_KEY_SCROLL_LOCK, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_NUM_LOCK == GLFW_KEY_NUM_LOCK, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_PRINT_SCREEN == GLFW_KEY_PRINT_SCREEN, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_PAUSE == GLFW_KEY_PAUSE, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F1 == GLFW_KEY_F1, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F2 == GLFW_KEY_F2, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F3 == GLFW_KEY_F3, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F4 == GLFW_KEY_F4, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F5 == GLFW_KEY_F5, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F6 == GLFW_KEY_F6, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F7 == GLFW_KEY_F7, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F8 == GLFW_KEY_F8, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F9 == GLFW_KEY_F9, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F10 == GLFW_KEY_F10, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F11 == GLFW_KEY_F11, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F12 == GLFW_KEY_F12, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F13 == GLFW_KEY_F13, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F14 == GLFW_KEY_F14, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F15 == GLFW_KEY_F15, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F16 == GLFW_KEY_F16, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F17 == GLFW_KEY_F17, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F18 == GLFW_KEY_F18, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F19 == GLFW_KEY_F19, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F20 == GLFW_KEY_F20, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F21 == GLFW_KEY_F21, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F22 == GLFW_KEY_F22, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F23 == GLFW_KEY_F23, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F24 == GLFW_KEY_F24, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_F25 == GLFW_KEY_F25, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_0 == GLFW_KEY_KP_0, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_1 == GLFW_KEY_KP_1, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_2 == GLFW_KEY_KP_2, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_3 == GLFW_KEY_KP_3, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_4 == GLFW_KEY_KP_4, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_5 == GLFW_KEY_KP_5, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_6 == GLFW_KEY_KP_6, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_7 == GLFW_KEY_KP_7, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_8 == GLFW_KEY_KP_8, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_9 == GLFW_KEY_KP_9, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_DECIMAL == GLFW_KEY_KP_DECIMAL, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_DIVIDE == GLFW_KEY_KP_DIVIDE, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_MULTIPLY == GLFW_KEY_KP_MULTIPLY, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_SUBTRACT == GLFW_KEY_KP_SUBTRACT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_ADD == GLFW_KEY_KP_ADD, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_ENTER == GLFW_KEY_KP_ENTER, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_KP_EQUAL == GLFW_KEY_KP_EQUAL, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_LEFT_SHIFT == GLFW_KEY_LEFT_SHIFT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_LEFT_CONTROL == GLFW_KEY_LEFT_CONTROL, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_LEFT_ALT == GLFW_KEY_LEFT_ALT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_LEFT_SUPER == GLFW_KEY_LEFT_SUPER, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_RIGHT_SHIFT == GLFW_KEY_RIGHT_SHIFT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_RIGHT_CONTROL == GLFW_KEY_RIGHT_CONTROL, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_RIGHT_ALT == GLFW_KEY_RIGHT_ALT, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_RIGHT_SUPER == GLFW_KEY_RIGHT_SUPER, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_MENU == GLFW_KEY_MENU, "glfw/nvpwindow mismatch");
static_assert(NVPWindow::KEY_LAST == GLFW_KEY_LAST, "glfw/nvpwindow mismatch");
void NVPWindow::cb_windowrefreshfun(GLFWwindow* glfwwin)
{
NVPWindow* win = (NVPWindow*)glfwGetWindowUserPointer(glfwwin);
if(win->isClosing())
return;
win->onWindowRefresh();
}
void NVPWindow::cb_windowsizefun(GLFWwindow* glfwwin, int w, int h)
{
NVPWindow* win = (NVPWindow*)glfwGetWindowUserPointer(glfwwin);
if(win->isClosing())
return;
win->m_windowSize[0] = w;
win->m_windowSize[1] = h;
win->onWindowResize(w, h);
}
void NVPWindow::cb_windowclosefun(GLFWwindow* glfwwin)
{
NVPWindow* win = (NVPWindow*)glfwGetWindowUserPointer(glfwwin);
win->m_isClosing = true;
win->onWindowClose();
}
void NVPWindow::cb_mousebuttonfun(GLFWwindow* glfwwin, int button, int action, int mods)
{
double x, y;
glfwGetCursorPos(glfwwin, &x, &y);
NVPWindow* win = (NVPWindow*)glfwGetWindowUserPointer(glfwwin);
if(win->isClosing())
return;
win->m_keyModifiers = mods;
win->m_mouseX = int(x);
win->m_mouseY = int(y);
win->onMouseButton((NVPWindow::MouseButton)button, (NVPWindow::ButtonAction)action, mods, win->m_mouseX, win->m_mouseY);
}
void NVPWindow::cb_cursorposfun(GLFWwindow* glfwwin, double x, double y)
{
NVPWindow* win = (NVPWindow*)glfwGetWindowUserPointer(glfwwin);
if(win->isClosing())
return;
win->m_mouseX = int(x);
win->m_mouseY = int(y);
win->onMouseMotion(win->m_mouseX, win->m_mouseY);
}
void NVPWindow::cb_scrollfun(GLFWwindow* glfwwin, double x, double y)
{
NVPWindow* win = (NVPWindow*)glfwGetWindowUserPointer(glfwwin);
if(win->isClosing())
return;
win->m_mouseWheel += int(y);
win->onMouseWheel(int(y));
}
void NVPWindow::cb_keyfun(GLFWwindow* glfwwin, int key, int scancode, int action, int mods)
{
NVPWindow* win = (NVPWindow*)glfwGetWindowUserPointer(glfwwin);
if(win->isClosing())
return;
win->m_keyModifiers = mods;
win->onKeyboard((NVPWindow::KeyCode)key, (NVPWindow::ButtonAction)action, mods, win->m_mouseX, win->m_mouseY);
}
void NVPWindow::cb_charfun(GLFWwindow* glfwwin, unsigned int codepoint)
{
NVPWindow* win = (NVPWindow*)glfwGetWindowUserPointer(glfwwin);
if(win->isClosing())
return;
win->onKeyboardChar(codepoint, win->m_keyModifiers, win->m_mouseX, win->m_mouseY);
}
void NVPWindow::cb_dropfun(GLFWwindow* glfwwin, int count, const char** paths)
{
NVPWindow* win = (NVPWindow*)glfwGetWindowUserPointer(glfwwin);
if(win->isClosing())
return;
win->onDragDrop(count, paths);
}
bool NVPWindow::isClosing() const
{
return m_isClosing || glfwWindowShouldClose(m_internal);
}
bool NVPWindow::isOpen() const
{
return glfwGetWindowAttrib(m_internal, GLFW_VISIBLE) == GLFW_TRUE
&& glfwGetWindowAttrib(m_internal, GLFW_ICONIFIED) == GLFW_FALSE && !isClosing();
}
bool NVPWindow::open(int posX, int posY, int width, int height, const char* title, bool requireGLContext)
{
NV_ASSERT(NVPSystem::isInited() && "NVPSystem::Init not called");
m_windowSize[0] = width;
m_windowSize[1] = height;
m_windowName = title ? title : "Sample";
#ifdef _WIN32
(void)requireGLContext;
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
#else
if(!requireGLContext)
{
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
}
else
{
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
// Some samples make use of compatibility profile features
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
#ifndef NDEBUG
#ifdef GLFW_CONTEXT_DEBUG // Since GLFW_CONTEXT_DEBUG is new in GLFW 3.4
glfwWindowHint(GLFW_CONTEXT_DEBUG, 1);
#else
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, 1);
#endif
#endif
}
#endif
m_internal = glfwCreateWindow(width, height, title, nullptr, nullptr);
if(!m_internal)
{
return false;
}
if(posX != 0 || posY != 0)
{
glfwSetWindowPos(m_internal, posX, posY);
}
glfwSetWindowUserPointer(m_internal, this);
glfwSetWindowRefreshCallback(m_internal, cb_windowrefreshfun);
glfwSetWindowCloseCallback(m_internal, cb_windowclosefun);
glfwSetCursorPosCallback(m_internal, cb_cursorposfun);
glfwSetMouseButtonCallback(m_internal, cb_mousebuttonfun);
glfwSetKeyCallback(m_internal, cb_keyfun);
glfwSetScrollCallback(m_internal, cb_scrollfun);
glfwSetCharCallback(m_internal, cb_charfun);
glfwSetWindowSizeCallback(m_internal, cb_windowsizefun);
glfwSetDropCallback(m_internal, cb_dropfun);
return true;
}
void NVPWindow::deinit()
{
glfwDestroyWindow(m_internal);
m_internal = nullptr;
m_windowSize[0] = 0;
m_windowSize[1] = 0;
m_windowName = std::string();
}
void NVPWindow::close()
{
glfwSetWindowShouldClose(m_internal, GLFW_TRUE);
}
void NVPWindow::setTitle(const char* title)
{
glfwSetWindowTitle(m_internal, title);
}
void NVPWindow::maximize()
{
glfwMaximizeWindow(m_internal);
}
void NVPWindow::restore()
{
glfwRestoreWindow(m_internal);
}
void NVPWindow::minimize()
{
glfwIconifyWindow(m_internal);
}
void NVPWindow::setWindowPos(int x, int y)
{
glfwSetWindowPos(m_internal, x, y);
}
void NVPWindow::setWindowSize(int w, int h)
{
glfwSetWindowSize(m_internal, w, h);
}
std::string NVPWindow::openFileDialog(const char* title, const char* exts)
{
return NVPSystem::windowOpenFileDialog(m_internal, title, exts);
}
std::string NVPWindow::saveFileDialog(const char* title, const char* exts)
{
return NVPSystem::windowSaveFileDialog(m_internal, title, exts);
}
void NVPWindow::screenshot(const char* filename)
{
NVPSystem::windowScreenshot(m_internal, filename);
}
void NVPWindow::clear(uint32_t r, uint32_t g, uint32_t b)
{
NVPSystem::windowClear(m_internal, r, g, b);
}
void NVPWindow::setFullScreen(bool bYes)
{
if(bYes == m_isFullScreen)
return;
GLFWmonitor* monitor = glfwGetWindowMonitor(m_internal);
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
if(bYes)
{
glfwGetWindowPos(m_internal, &m_preFullScreenPos[0], &m_preFullScreenPos[1]);
glfwGetWindowSize(m_internal, &m_preFullScreenSize[0], &m_preFullScreenSize[1]);
glfwSetWindowMonitor(m_internal, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
glfwSetWindowAttrib(m_internal, GLFW_RESIZABLE, GLFW_FALSE);
glfwSetWindowAttrib(m_internal, GLFW_DECORATED, GLFW_FALSE);
}
else
{
glfwSetWindowMonitor(m_internal, nullptr, m_preFullScreenPos[0], m_preFullScreenPos[1], m_preFullScreenSize[0],
m_preFullScreenSize[1], 0);
glfwSetWindowAttrib(m_internal, GLFW_RESIZABLE, GLFW_TRUE);
glfwSetWindowAttrib(m_internal, GLFW_DECORATED, GLFW_TRUE);
}
m_isFullScreen = bYes;
}

View file

@ -0,0 +1,297 @@
/*
* Copyright (c) 2018-2021, 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) 2018-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
//--------------------------------------------------------------------
#ifndef __NVPWINDOW_H__
#define __NVPWINDOW_H__
#include "nvpsystem.hpp"
/* @DOC_START
# class NVPWindow
> base class for a window, to catch events
Using and deriving of NVPWindow base-class is optional.
However one must always make use of the NVPSystem
That takes care of glfwInit/terminate as well.
@DOC_END */
class NVPWindow
{
public:
// these are taken from GLFW3 and must be kept in a matching state
enum ButtonAction
{
BUTTON_RELEASE = 0,
BUTTON_PRESS = 1,
BUTTON_REPEAT = 2,
};
enum MouseButton
{
MOUSE_BUTTON_LEFT = 0,
MOUSE_BUTTON_RIGHT = 1,
MOUSE_BUTTON_MIDDLE = 2,
NUM_MOUSE_BUTTONIDX,
};
enum MouseButtonFlag
{
MOUSE_BUTTONFLAG_NONE = 0,
MOUSE_BUTTONFLAG_LEFT = (1 << MOUSE_BUTTON_LEFT),
MOUSE_BUTTONFLAG_RIGHT = (1 << MOUSE_BUTTON_RIGHT),
MOUSE_BUTTONFLAG_MIDDLE = (1 << MOUSE_BUTTON_MIDDLE)
};
enum KeyCode
{
KEY_UNKNOWN = -1,
KEY_SPACE = 32,
KEY_APOSTROPHE = 39, /* ' */
KEY_LEFT_PARENTHESIS = 40, /* ( */
KEY_RIGHT_PARENTHESIS = 41, /* ) */
KEY_ASTERISK = 42, /* * */
KEY_PLUS = 43, /* + */
KEY_COMMA = 44, /* , */
KEY_MINUS = 45, /* - */
KEY_PERIOD = 46, /* . */
KEY_SLASH = 47, /* / */
KEY_0 = 48,
KEY_1 = 49,
KEY_2 = 50,
KEY_3 = 51,
KEY_4 = 52,
KEY_5 = 53,
KEY_6 = 54,
KEY_7 = 55,
KEY_8 = 56,
KEY_9 = 57,
KEY_SEMICOLON = 59, /* ; */
KEY_EQUAL = 61, /* = */
KEY_A = 65,
KEY_B = 66,
KEY_C = 67,
KEY_D = 68,
KEY_E = 69,
KEY_F = 70,
KEY_G = 71,
KEY_H = 72,
KEY_I = 73,
KEY_J = 74,
KEY_K = 75,
KEY_L = 76,
KEY_M = 77,
KEY_N = 78,
KEY_O = 79,
KEY_P = 80,
KEY_Q = 81,
KEY_R = 82,
KEY_S = 83,
KEY_T = 84,
KEY_U = 85,
KEY_V = 86,
KEY_W = 87,
KEY_X = 88,
KEY_Y = 89,
KEY_Z = 90,
KEY_LEFT_BRACKET = 91, /* [ */
KEY_BACKSLASH = 92, /* \ */
KEY_RIGHT_BRACKET = 93, /* ] */
KEY_GRAVE_ACCENT = 96, /* ` */
KEY_WORLD_1 = 161, /* non-US #1 */
KEY_WORLD_2 = 162, /* non-US #2 */
/* Function keys */
KEY_ESCAPE = 256,
KEY_ENTER = 257,
KEY_TAB = 258,
KEY_BACKSPACE = 259,
KEY_INSERT = 260,
KEY_DELETE = 261,
KEY_RIGHT = 262,
KEY_LEFT = 263,
KEY_DOWN = 264,
KEY_UP = 265,
KEY_PAGE_UP = 266,
KEY_PAGE_DOWN = 267,
KEY_HOME = 268,
KEY_END = 269,
KEY_CAPS_LOCK = 280,
KEY_SCROLL_LOCK = 281,
KEY_NUM_LOCK = 282,
KEY_PRINT_SCREEN = 283,
KEY_PAUSE = 284,
KEY_F1 = 290,
KEY_F2 = 291,
KEY_F3 = 292,
KEY_F4 = 293,
KEY_F5 = 294,
KEY_F6 = 295,
KEY_F7 = 296,
KEY_F8 = 297,
KEY_F9 = 298,
KEY_F10 = 299,
KEY_F11 = 300,
KEY_F12 = 301,
KEY_F13 = 302,
KEY_F14 = 303,
KEY_F15 = 304,
KEY_F16 = 305,
KEY_F17 = 306,
KEY_F18 = 307,
KEY_F19 = 308,
KEY_F20 = 309,
KEY_F21 = 310,
KEY_F22 = 311,
KEY_F23 = 312,
KEY_F24 = 313,
KEY_F25 = 314,
KEY_KP_0 = 320,
KEY_KP_1 = 321,
KEY_KP_2 = 322,
KEY_KP_3 = 323,
KEY_KP_4 = 324,
KEY_KP_5 = 325,
KEY_KP_6 = 326,
KEY_KP_7 = 327,
KEY_KP_8 = 328,
KEY_KP_9 = 329,
KEY_KP_DECIMAL = 330,
KEY_KP_DIVIDE = 331,
KEY_KP_MULTIPLY = 332,
KEY_KP_SUBTRACT = 333,
KEY_KP_ADD = 334,
KEY_KP_ENTER = 335,
KEY_KP_EQUAL = 336,
KEY_LEFT_SHIFT = 340,
KEY_LEFT_CONTROL = 341,
KEY_LEFT_ALT = 342,
KEY_LEFT_SUPER = 343,
KEY_RIGHT_SHIFT = 344,
KEY_RIGHT_CONTROL = 345,
KEY_RIGHT_ALT = 346,
KEY_RIGHT_SUPER = 347,
KEY_MENU = 348,
KEY_LAST = KEY_MENU,
};
enum KeyModifiers
{
KMOD_SHIFT = 1,
KMOD_CONTROL = 2,
KMOD_ALT = 4,
KMOD_SUPER = 8,
};
//////////////////////////////////////////////////////////////////////////
struct GLFWwindow* m_internal = nullptr; ///< internal delegate to GLFWwindow
std::string m_windowName;
inline bool pollEvents() // returns false on exit, can do while(pollEvents()){ ... }
{
NVPSystem::pollEvents();
return !isClosing();
}
inline void waitEvents() { NVPSystem::waitEvents(); }
inline double getTime() { return NVPSystem::getTime(); }
inline std::string exePath() { return NVPSystem::exePath(); }
// Accessors
inline int getWidth() const { return m_windowSize[0]; }
inline int getHeight() const { return m_windowSize[1]; }
inline int getMouseWheel() const { return m_mouseWheel; }
inline int getKeyModifiers() const { return m_keyModifiers; }
inline int getMouseX() const { return m_mouseX; }
inline int getMouseY() const { return m_mouseY; }
void setTitle(const char* title);
void setFullScreen(bool bYes);
void setWindowPos(int x, int y);
void setWindowSize(int w, int h);
inline void setKeyModifiers(int m) { m_keyModifiers = m; }
inline void setMouse(int x, int y)
{
m_mouseX = x;
m_mouseY = y;
}
inline bool isFullScreen() const { return m_isFullScreen; }
bool isClosing() const;
bool isOpen() const;
virtual bool open(int posX, int posY, int width, int height, const char* title, bool requireGLContext); ///< creates internal window and opens it
void deinit(); ///< destroys internal window
void close(); ///< triggers closing event, still needs deinit for final cleanup
void maximize();
void restore();
void minimize();
// uses operating system specific code for sake of debugging/automated testing
void screenshot(const char* filename);
void clear(uint32_t r, uint32_t g, uint32_t b);
/// \defgroup dialog
/// simple modal file dialog, uses OS basic api
/// the exts string must be a | separated list that has two items per possible extension
/// "extension descripton|*.ext"
/// @{
std::string openFileDialog(const char* title, const char* exts);
std::string saveFileDialog(const char* title, const char* exts);
/// @}
/// \name derived windows/apps should override to handle events
/// @{
virtual void onWindowClose() {}
virtual void onWindowResize(int w, int h) {}
virtual void onWindowRefresh() {}
virtual void onMouseMotion(int x, int y) {}
virtual void onMouseWheel(int delta) {}
virtual void onMouseButton(MouseButton button, ButtonAction action, int mods, int x, int y) {}
virtual void onKeyboard(KeyCode key, ButtonAction action, int mods, int x, int y) {}
virtual void onKeyboardChar(unsigned char key, int mods, int x, int y) {}
virtual void onDragDrop(int num, const char** paths) {}
/// @}
private:
int m_mouseX = 0;
int m_mouseY = 0;
int m_mouseWheel = 0;
int m_windowSize[2] = {0, 0};
int m_keyModifiers = 0;
bool m_isFullScreen = false;
bool m_isClosing = false;
int m_preFullScreenPos[2] = {0, 0};
int m_preFullScreenSize[2] = {0, 0};
/// \name Callbacks
/// @{
static void cb_windowrefreshfun(GLFWwindow* glfwwin);
static void cb_windowsizefun(GLFWwindow* glfwwin, int w, int h);
static void cb_windowclosefun(GLFWwindow* glfwwin);
static void cb_mousebuttonfun(GLFWwindow* glfwwin, int button, int action, int mods);
static void cb_cursorposfun(GLFWwindow* glfwwin, double x, double y);
static void cb_scrollfun(GLFWwindow* glfwwin, double x, double y);
static void cb_keyfun(GLFWwindow* glfwwin, int key, int scancode, int action, int mods);
static void cb_charfun(GLFWwindow* glfwwin, unsigned int codepoint);
static void cb_dropfun(GLFWwindow* glfwwin, int count, const char** paths);
/// @}
};
#endif

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2014-2021, 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) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
#include <nvp/perproject_globals.hpp>
std::string getProjectName()
{
return PROJECT_NAME;
}
bool isAftermathAvailable()
{
#ifdef NVVK_SUPPORTS_AFTERMATH
return true;
#else
return false;
#endif
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (c) 2014-2021, 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) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
/// @DOC_SKIP
#pragma once
#include <string>
extern std::string getProjectName();
extern bool isAftermathAvailable();

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2014-2021, 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) 2014-2021 NVIDIA CORPORATION
* SPDX-License-Identifier: Apache-2.0
*/
/// @DOC_SKIP
#include "NvFoundation.h"
#ifndef NVP_PLATFORM_H__
#define NVP_PLATFORM_H__
#if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ >= 3 && __GNUC_MINOR__ >= 4))
#define NV_NOOP(...)
#define NV_BARRIER() __sync_synchronize()
/*
// maybe better than __sync_synchronize?
#if defined(__i386__ ) || defined(__x64__)
#define NVP_BARRIER() __asm__ __volatile__ ("mfence" ::: "memory")
#endif
#if defined(__arm__)
#define NVP_BARRIER() __asm__ __volatile__ ("dmb" :::"memory")
#endif
*/
#elif defined(__MSC__) || defined(_MSC_VER)
#include <emmintrin.h>
#pragma warning(disable:4142) // redefinition of same type
#if (_MSC_VER >= 1400) // VC8+
#pragma warning(disable : 4996) // Either disable all deprecation warnings,
#endif // VC8+
#define NV_NOOP __noop
#define NV_BARRIER() _mm_mfence()
#else
#error "compiler unkown"
#endif
#endif

View file

@ -0,0 +1,30 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Bak3dView.rc
/// @DOC_SKIP
#define IDC_MYICON 2
#define IDD_OPENGL_DIALOG 102
#define IDD_ABOUTBOX 103
#define IDS_APP_TITLE 103
#define IDM_ABOUT 104
#define IDM_EXIT 105
#define IDS_HELLO 106
#define IDI_OPENGL_ICON 107
#define IDC_OPENGL_MENU 108
#define IDC_OPENGL_ACCELERATOR 109
#define IDC_OPENGL 110
#define IDR_MAINFRAME 128
#define IDR_CGFX1 129
#define IDR_DEFAULTEFFECT 130
#define IDC_STATIC -1
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 131
#define _APS_NEXT_COMMAND_VALUE 32771
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 110
#endif
#endif

View file

@ -0,0 +1,137 @@
// Microsoft Visual C++ generated resource script.
//
#include "resources.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#define APSTUDIO_HIDDEN_SYMBOLS
#include "windows.h"
#undef APSTUDIO_HIDDEN_SYMBOLS
#include "resources.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_OPENGL_ICON ICON "resources/opengl.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Menu
//
IDC_OPENGL_MENU MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "E&xit", IDM_EXIT
END
POPUP "&Help"
BEGIN
MENUITEM "&About ...", IDM_ABOUT
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Accelerator
//
IDC_OPENGL_ACCELERATOR ACCELERATORS
BEGIN
"?", IDM_ABOUT, ASCII, ALT
"/", IDM_ABOUT, ASCII, ALT
END
/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//
IDD_ABOUTBOX DIALOG 22, 17, 230, 75
STYLE DS_SETFONT | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
CAPTION "About"
FONT 8, "System"
BEGIN
ICON IDI_OPENGL_ICON,IDC_MYICON,14,9,20,20
LTEXT "OpenGL Sample v1.0",IDC_STATIC,49,10,119,8,SS_NOPREFIX
LTEXT "Copyright (C) ",IDC_STATIC,49,20,119,8
DEFPUSHBUTTON "OK",IDOK,195,6,30,11,WS_GROUP
END
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
2 TEXTINCLUDE
BEGIN
"#define APSTUDIO_HIDDEN_SYMBOLS\r\n"
"#include ""windows.h""\r\n"
"#undef APSTUDIO_HIDDEN_SYMBOLS\r\n"
"#include ""resource.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_APP_TITLE "opengl"
IDC_OPENGL "OPENGL"
END
#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED