aboutsummaryrefslogtreecommitdiff
path: root/NvCloth/samples/SampleBase/utils
diff options
context:
space:
mode:
authormtamis <[email protected]>2017-02-28 18:24:59 +0100
committermtamis <[email protected]>2017-02-28 18:24:59 +0100
commit5581909a4d19db97304449f66404ff99a0429d3f (patch)
treea90f7eb85c095a8aba45cf5e909c82c1cdbed77d /NvCloth/samples/SampleBase/utils
parentFix cmake visual studio project generation (locate_gw_root.bat) (diff)
downloadnvcloth-5581909a4d19db97304449f66404ff99a0429d3f.tar.xz
nvcloth-5581909a4d19db97304449f66404ff99a0429d3f.zip
Add visual samples.
Diffstat (limited to 'NvCloth/samples/SampleBase/utils')
-rw-r--r--NvCloth/samples/SampleBase/utils/CallbackImplementations.cpp110
-rw-r--r--NvCloth/samples/SampleBase/utils/CallbackImplementations.h230
-rw-r--r--NvCloth/samples/SampleBase/utils/ClothMeshGenerator.cpp193
-rw-r--r--NvCloth/samples/SampleBase/utils/ClothMeshGenerator.h54
-rw-r--r--NvCloth/samples/SampleBase/utils/JobManager.cpp136
-rw-r--r--NvCloth/samples/SampleBase/utils/JobManager.h175
-rw-r--r--NvCloth/samples/SampleBase/utils/SampleProfiler.cpp223
-rw-r--r--NvCloth/samples/SampleBase/utils/SampleProfiler.h79
-rw-r--r--NvCloth/samples/SampleBase/utils/SampleTime.h58
-rw-r--r--NvCloth/samples/SampleBase/utils/UIHelpers.h56
-rw-r--r--NvCloth/samples/SampleBase/utils/Utils.cpp13
-rw-r--r--NvCloth/samples/SampleBase/utils/Utils.h89
12 files changed, 1416 insertions, 0 deletions
diff --git a/NvCloth/samples/SampleBase/utils/CallbackImplementations.cpp b/NvCloth/samples/SampleBase/utils/CallbackImplementations.cpp
new file mode 100644
index 0000000..0a257ba
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/CallbackImplementations.cpp
@@ -0,0 +1,110 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#include "CallbackImplementations.h"
+#include "JobManager.h"
+#include <iostream>
+
+//#if USE_DX11
+#include <d3d11.h>
+//#endif
+
+#include <PsThread.h>
+
+NvClothEnvironment* NvClothEnvironment::sEnv = nullptr;
+
+
+void ErrorCallback::reportError(physx::PxErrorCode::Enum code, const char* message, const char* file, int line)
+{
+ const char* codeName = "???";
+ switch(code)
+ {
+#define CASE(x) case physx::PxErrorCode::Enum::x : codeName = #x; break;
+ CASE(eNO_ERROR)
+ CASE(eDEBUG_INFO)
+ CASE(eDEBUG_WARNING)
+ CASE(eINVALID_PARAMETER)
+ CASE(eINVALID_OPERATION)
+ CASE(eOUT_OF_MEMORY)
+ CASE(eINTERNAL_ERROR)
+ CASE(eABORT)
+ CASE(ePERF_WARNING)
+ default:
+ ;
+#undef CASE
+ }
+
+ std::cout << "Log " << codeName << " from file:" << file << ":" << line << "\n MSG:" << message << std::endl;
+}
+
+//#if USE_DX11
+DxContextManagerCallbackImpl::DxContextManagerCallbackImpl(ID3D11Device* device, bool synchronizeResources)
+ :
+ mDevice(device),
+ mSynchronizeResources(synchronizeResources)
+{
+ mDevice->AddRef();
+ mDevice->GetImmediateContext(&mContext);
+#ifdef _DEBUG
+ mLockCountTls = physx::shdfnd::TlsAlloc();
+#endif
+}
+DxContextManagerCallbackImpl::~DxContextManagerCallbackImpl()
+{
+ mContext->Release();
+
+#if _DEBUG
+ ID3D11Debug* debugDevice;
+ mDevice->QueryInterface(&debugDevice);
+ if(debugDevice)
+ {
+ debugDevice->ReportLiveDeviceObjects(D3D11_RLDO_DETAIL);
+ debugDevice->Release();
+ }
+#endif
+
+ mDevice->Release();
+
+#if _DEBUG
+ physx::shdfnd::TlsFree(mLockCountTls);
+#endif
+}
+
+void DxContextManagerCallbackImpl::acquireContext()
+{
+
+ mMutex.lock();
+#if _DEBUG
+ physx::shdfnd::TlsSet(mLockCountTls, reinterpret_cast<void*>(reinterpret_cast<intptr_t>(physx::shdfnd::TlsGet(mLockCountTls)) + 1));
+#endif
+}
+void DxContextManagerCallbackImpl::releaseContext()
+{
+#if _DEBUG
+ physx::shdfnd::TlsSet(mLockCountTls, reinterpret_cast<void*>(reinterpret_cast<intptr_t>(physx::shdfnd::TlsGet(mLockCountTls)) - 1));
+#endif
+ mMutex.unlock();
+}
+ID3D11Device* DxContextManagerCallbackImpl::getDevice() const
+{
+ return mDevice;
+}
+ID3D11DeviceContext* DxContextManagerCallbackImpl::getContext() const
+{
+#if _DEBUG
+ assert(reinterpret_cast<intptr_t>(physx::shdfnd::TlsGet(mLockCountTls)) > 0);
+#endif
+ return mContext;
+}
+bool DxContextManagerCallbackImpl::synchronizeResources() const
+{
+ return mSynchronizeResources;
+}
+//#endif
diff --git a/NvCloth/samples/SampleBase/utils/CallbackImplementations.h b/NvCloth/samples/SampleBase/utils/CallbackImplementations.h
new file mode 100644
index 0000000..38a4b17
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/CallbackImplementations.h
@@ -0,0 +1,230 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#pragma once
+#include <NvCloth/Callbacks.h>
+#include <foundation/PxAllocatorCallback.h>
+#include <foundation/PxErrorCallback.h>
+#include <foundation/PxAssert.h>
+#include <foundation/PxProfiler.h>
+
+#include <vector>
+#include <map>
+
+#if USE_CUDA
+#include <cuda.h>
+#endif
+
+#include <string>
+#include <sstream>
+#include <assert.h>
+#include <mutex>
+
+#include <NvCloth/DxContextManagerCallback.h>
+
+#ifdef _MSC_VER
+#include <Windows.h>
+#endif
+
+class Allocator : public physx::PxAllocatorCallback
+{
+ public:
+ Allocator()
+ {
+ mEnableLeakDetection = false;
+ }
+ virtual void* allocate(size_t size, const char* typeName, const char* filename, int line)
+ {
+ #ifdef _MSC_VER
+ void* ptr = _aligned_malloc(size, 16);
+ #else
+ void* ptr;
+ if(posix_memalign(&ptr, 16, size)) ptr = 0;
+ #endif
+ if (mEnableLeakDetection)
+ {
+ std::lock_guard<std::mutex> lock(mAllocationsMapLock);
+ mAllocations[ptr] = Allocation(size, typeName, filename, line);
+ }
+ return ptr;
+ }
+ virtual void deallocate(void* ptr)
+ {
+ if (mEnableLeakDetection && ptr)
+ {
+ std::lock_guard<std::mutex> lock(mAllocationsMapLock);
+ auto i = mAllocations.find(ptr);
+ if (i == mAllocations.end())
+ {
+ printf("Tried to deallocate %p which was not allocated with this allocator callback.",ptr);
+ }
+ else
+ {
+ mAllocations.erase(i);
+ }
+ }
+ #ifdef _MSC_VER
+ _aligned_free(ptr);
+ #else
+ free(ptr);
+ #endif
+ }
+
+ void StartTrackingLeaks()
+ {
+ std::lock_guard<std::mutex> lock(mAllocationsMapLock);
+ mAllocations.clear();
+ mEnableLeakDetection = true;
+ }
+
+ void StopTrackingLeaksAndReport()
+ {
+ std::lock_guard<std::mutex> lock(mAllocationsMapLock);
+ mEnableLeakDetection = false;
+
+ size_t totalBytes = 0;
+ std::stringstream message;
+ message << "Memory leaks detected:\n";
+ for (auto it = mAllocations.begin(); it != mAllocations.end(); ++it)
+ {
+ const Allocation& alloc = it->second;
+ message << "* Allocated ptr " << it->first << " of " << alloc.mSize << "bytes (type=" << alloc.mTypeName << ") at " << alloc.mFileName << ":" << alloc.mLine<<"\n";
+ totalBytes += alloc.mSize;
+ }
+ if (mAllocations.size()>0)
+ {
+ message << "=====Total of " << totalBytes << " bytes in " << mAllocations.size() << " allocations leaked=====";
+ const std::string& tmp = message.str();
+#ifdef _MSC_VER
+// OutputDebugString(tmp.c_str()); //Write to visual studio output so we can see it after the application closes
+#endif
+ printf("%s\n", tmp.c_str());
+ }
+
+ mAllocations.clear();
+ }
+ private:
+ bool mEnableLeakDetection;
+ struct Allocation
+ {
+ Allocation(){}
+ Allocation(size_t size, const char* typeName, const char* filename, int line)
+ : mSize(size), mTypeName(typeName), mFileName(filename), mLine(line)
+ {
+
+ }
+ size_t mSize;
+ std::string mTypeName;
+ std::string mFileName;
+ int mLine;
+ };
+ std::map<void*,Allocation> mAllocations;
+ std::mutex mAllocationsMapLock;
+};
+
+class ErrorCallback : public physx::PxErrorCallback
+{
+ public:
+ ErrorCallback(){}
+ virtual void reportError(physx::PxErrorCode::Enum code, const char* message, const char* file, int line);
+};
+
+
+class AssertHandler : public physx::PxAssertHandler
+{
+ public:
+ virtual void operator()(const char* exp, const char* file, int line, bool& ignore)
+ {
+ PX_UNUSED(ignore);
+ printf("NV_CLOTH_ASSERT(%s) from file:%s:%d Failed\n", exp, file, line);
+ assert(("Assertion failed, see log/console for more info.",0));
+ }
+};
+
+
+class NvClothEnvironment
+{
+ NvClothEnvironment()
+ {
+ SetUp();
+ }
+ virtual ~NvClothEnvironment()
+ {
+ TearDown();
+ }
+
+ static NvClothEnvironment* sEnv;
+
+ public:
+ static void AllocateEnv()
+ {
+ sEnv = new NvClothEnvironment;
+ }
+ static void FreeEnv(){ delete sEnv; sEnv = nullptr; }
+ static void ReportEnvFreed(){ sEnv = nullptr; } //google test will free it for us, so we just reset the value
+ static NvClothEnvironment* GetEnv(){ return sEnv; }
+
+ virtual void SetUp()
+ {
+ mAllocator = new Allocator;
+ mAllocator->StartTrackingLeaks();
+ mFoundationAllocator = new Allocator;
+ mFoundationAllocator->StartTrackingLeaks();
+ mErrorCallback = new ErrorCallback;
+ mAssertHandler = new AssertHandler;
+ nv::cloth::InitializeNvCloth(mAllocator, mErrorCallback, mAssertHandler, nullptr);
+#if USE_CUDA
+ cuInit(0);
+#endif
+ }
+ virtual void TearDown()
+ {
+ mAllocator->StopTrackingLeaksAndReport();
+ mFoundationAllocator->StopTrackingLeaksAndReport();
+ delete mErrorCallback;
+ delete mFoundationAllocator;
+ delete mAllocator;
+ delete mAssertHandler;
+ }
+
+ Allocator* GetAllocator(){ return mAllocator; }
+ Allocator* GetFoundationAllocator(){ return mFoundationAllocator; }
+ ErrorCallback* GetErrorCallback(){ return mErrorCallback; }
+ AssertHandler* GetAssertHandler(){ return mAssertHandler; }
+
+ private:
+ Allocator* mAllocator;
+ Allocator* mFoundationAllocator;
+ ErrorCallback* mErrorCallback;
+ AssertHandler* mAssertHandler;
+};
+
+//#if USE_DX11
+class DxContextManagerCallbackImpl : public nv::cloth::DxContextManagerCallback
+{
+public:
+ DxContextManagerCallbackImpl(ID3D11Device* device, bool synchronizeResources = false);
+ ~DxContextManagerCallbackImpl();
+ virtual void acquireContext() override;
+ virtual void releaseContext() override;
+ virtual ID3D11Device* getDevice() const override;
+ virtual ID3D11DeviceContext* getContext() const override;
+ virtual bool synchronizeResources() const override;
+
+private:
+ std::recursive_mutex mMutex;
+ ID3D11Device* mDevice;
+ ID3D11DeviceContext* mContext;
+ bool mSynchronizeResources;
+#ifdef _DEBUG
+ uint32_t mLockCountTls;
+#endif
+};
+//#endif
diff --git a/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.cpp b/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.cpp
new file mode 100644
index 0000000..d75bb25
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.cpp
@@ -0,0 +1,193 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#include "ClothMeshGenerator.h"
+
+void ClothMeshData::Clear()
+{
+ mVertices.clear();
+ mTriangles.clear();
+ mQuads.clear();
+}
+
+void ClothMeshData::GeneratePlaneCloth(float width, float height, int segmentsX, int segmentsY, bool createQuads, physx::PxMat44 transform, bool alternatingDiagonals)
+{
+
+/*
+GeneratePlaneCloth(x,y,2,2) generates:
+
+ v0______v1_____v2 v0______v1_____v2
+ | | | |\ |\ |
+ | Q0 | Q1 | | \t0 | \t2 |
+ | | | | t1 \ | t3 \ |
+ v3------v4-----v5 v3-----\v4----\v5
+ | | | | \ | \ |
+ | Q2 | Q3 | | \t4| \t6|
+ |______|______| |_t5_\_|_t7__\|
+ v6 v7 v8 v6 v7 v8
+*/
+
+
+// Submesh submesh;
+
+ Clear();
+ mVertices.resize((segmentsX + 1) * (segmentsY + 1));
+ mInvMasses.resize((segmentsX + 1) * (segmentsY + 1));
+ mTriangles.resize(segmentsX * segmentsY * 2);
+ if (createQuads)
+ mQuads.resize(segmentsX * segmentsY);
+
+ mMesh.vertices.resize(mVertices.size());
+ mMesh.indices.resize(3 * mTriangles.size());
+
+ physx::PxVec3 topLeft(-width * 0.5f, 0.f, -height * 0.5f);
+// vec3 topLeftGLM(-width * 0.5f, translateUp, -height * 0.5f);
+
+ //calculate uv scale and offset to keep texture aspect ratio 1:1
+ float uvSx = width > height ? 1.0f : width / height;
+ float uvSy = width > height ? height / width : 1.0f;
+ float uvOx = 0.5f * (1.0f - uvSx);
+ float uvOy = 0.5f * (1.0f - uvSy);
+
+ // Vertices
+ for (int y = 0; y < segmentsY + 1; y++)
+ {
+ for(int x = 0; x < segmentsX + 1; x++)
+ {
+ mVertices[x + y * (segmentsX + 1)] = transform.transform(topLeft + physx::PxVec3( ((float)x / (float)segmentsX) * width,
+ 0.f,
+ ((float)y / (float)segmentsY) * height));
+ mInvMasses[x + y * (segmentsX + 1)] = 1.0f;
+
+ mMesh.vertices[x + y * (segmentsX + 1)].position = transform.transform(topLeft + physx::PxVec3(((float)x / (float)segmentsX) * width,
+ 0.f,
+ ((float)y / (float)segmentsY) * height));
+ mMesh.vertices[x + y * (segmentsX + 1)].normal = transform.transform(physx::PxVec3(0.f, 1.f, 0.f));
+
+ mMesh.vertices[x + y * (segmentsX + 1)].uv = physx::PxVec2(uvOx + uvSx*(float)x / (float)segmentsX, uvOy + uvSy*(1.0f - (float)y / (float)segmentsY));
+ }
+ }
+
+ if (createQuads)
+ {
+ // Quads
+ for (int y = 0; y < segmentsY; y++)
+ {
+ for (int x = 0; x < segmentsX; x++)
+ {
+ mQuads[(x + y * segmentsX)] = Quad((uint32_t)(x + 0) + (y + 0) * (segmentsX + 1),
+ (uint32_t)(x + 1) + (y + 0) * (segmentsX + 1),
+ (uint32_t)(x + 1) + (y + 1) * (segmentsX + 1),
+ (uint32_t)(x + 0) + (y + 1) * (segmentsX + 1));
+ }
+ }
+ }
+
+ // Triangles
+ for (int y = 0; y < segmentsY; y++)
+ {
+ for(int x = 0; x < segmentsX; x++)
+ {
+ if(alternatingDiagonals && (x^y)&1)
+ {
+ //Top right to bottom left
+ mTriangles[(x + y * segmentsX) * 2 + 0] = Triangle( (uint32_t)(x + 0) + (y + 0) * (segmentsX + 1),
+ (uint32_t)(x + 1) + (y + 0) * (segmentsX + 1),
+ (uint32_t)(x + 0) + (y + 1) * (segmentsX + 1));
+
+ mTriangles[(x + y * segmentsX) * 2 + 1] = Triangle( (uint32_t)(x + 1) + (y + 0) * (segmentsX + 1),
+ (uint32_t)(x + 1) + (y + 1) * (segmentsX + 1),
+ (uint32_t)(x + 0) + (y + 1) * (segmentsX + 1));
+ }
+ else
+ {
+ //Top left to bottom right
+ mTriangles[(x + y * segmentsX) * 2 + 0] = Triangle( (uint32_t)(x + 0) + (y + 0) * (segmentsX + 1),
+ (uint32_t)(x + 1) + (y + 0) * (segmentsX + 1),
+ (uint32_t)(x + 1) + (y + 1) * (segmentsX + 1));
+
+ mTriangles[(x + y * segmentsX) * 2 + 1] = Triangle( (uint32_t)(x + 0) + (y + 0) * (segmentsX + 1),
+ (uint32_t)(x + 1) + (y + 1) * (segmentsX + 1),
+ (uint32_t)(x + 0) + (y + 1) * (segmentsX + 1));
+ }
+ }
+ }
+
+ for (int i = 0; i < (int)mTriangles.size(); i++)
+ {
+ mMesh.indices[3 * i] = mTriangles[i].a;
+ mMesh.indices[3 * i + 1] = mTriangles[i].b;
+ mMesh.indices[3 * i + 2] = mTriangles[i].c;
+ }
+}
+
+void ClothMeshData::AttachClothPlaneByAngles(int segmentsX, int segmentsY, bool attachByWidth)
+{
+ for (int y = 0; y < segmentsY + 1; y++)
+ for (int x = 0; x < segmentsX + 1; x++)
+ if ((attachByWidth && y == 0) || (!attachByWidth && x == 0))
+ if (x == 0 || x == segmentsX)
+ mInvMasses[x + y * (segmentsX + 1)] = 0.0f;
+}
+
+void ClothMeshData::AttachClothPlaneBySide(int segmentsX, int segmentsY, bool attachByWidth)
+{
+ for (int y = 0; y < segmentsY + 1; y++)
+ for (int x = 0; x < segmentsX + 1; x++)
+ if ((attachByWidth && y == 0) || (!attachByWidth && x == 0))
+ mInvMasses[x + y * (segmentsX + 1)] = 0.0f;
+}
+
+void ClothMeshData::SetInvMasses(float invMass)
+{
+ // Doesn't modify attached vertices
+ for (int i = 0; i < (int)mInvMasses.size(); ++i)
+ if (mInvMasses[i] > 1e-6f)
+ mInvMasses[i] = invMass;
+}
+
+void ClothMeshData::SetInvMassesFromDensity(float density)
+{
+ // Tempt code, should take into account triangle's areas
+ // Doesn't modify attached vertices
+ for (int i = 0; i < (int)mInvMasses.size(); ++i)
+ if (mInvMasses[i] > 1e-6f)
+ mInvMasses[i] = 1.f / density;
+}
+
+template <typename T>
+nv::cloth::BoundedData ToBoundedData(T& vector)
+{
+ nv::cloth::BoundedData d;
+ d.data = &vector[0];
+ d.stride = sizeof(vector[0]);
+ d.count = (physx::PxU32)vector.size();
+
+ return d;
+}
+
+nv::cloth::ClothMeshDesc ClothMeshData::GetClothMeshDesc()
+{
+ nv::cloth::ClothMeshDesc d;
+ d.setToDefault();
+ d.points = ToBoundedData(mVertices);
+ if (mQuads.size() != 0)
+ d.quads = ToBoundedData(mQuads);
+ if (mTriangles.size() != 0)
+ d.triangles = ToBoundedData(mTriangles);
+ d.invMasses = ToBoundedData(mInvMasses);
+
+ return d;
+}
+
+SimpleMesh ClothMeshData::GetRenderMesh()
+{
+ return mMesh;
+} \ No newline at end of file
diff --git a/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.h b/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.h
new file mode 100644
index 0000000..0f57230
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/ClothMeshGenerator.h
@@ -0,0 +1,54 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#pragma once
+#include <vector>
+#include <foundation/PxVec3.h>
+#include <foundation/PxVec2.h>
+#include "NvClothExt/ClothFabricCooker.h"
+#include <foundation/PxMat44.h>
+#include "Mesh.h"
+
+struct ClothMeshData
+{
+ struct Triangle
+ {
+ Triangle(){}
+ Triangle(uint32_t _a, uint32_t _b, uint32_t _c) :
+ a(_a), b(_b), c(_c){}
+ uint32_t a, b, c;
+ };
+ struct Quad
+ {
+ Quad(){}
+ Quad(uint32_t _a, uint32_t _b, uint32_t _c, uint32_t _d) :
+ a(_a), b(_b), c(_c), d(_d){}
+ uint32_t a, b, c, d;
+ };
+ std::vector<physx::PxVec3> mVertices;
+ std::vector<physx::PxVec2> mUvs;
+ std::vector<Triangle> mTriangles;
+ std::vector<Quad> mQuads;
+ std::vector<physx::PxReal> mInvMasses;
+
+ SimpleMesh mMesh;
+
+ void Clear();
+ void GeneratePlaneCloth(float width, float height, int segmentsX, int segmentsY, bool createQuads = false, physx::PxMat44 transform = physx::PxIdentity, bool alternatingDiagonals = true);
+
+ void AttachClothPlaneByAngles(int segmentsX, int segmentsY, bool attachByWidth = true);
+ void AttachClothPlaneBySide(int segmentsX, int segmentsY, bool attachByWidth = true);
+
+ void SetInvMasses(float invMass);
+ void SetInvMassesFromDensity(float density); // Todo
+
+ nv::cloth::ClothMeshDesc GetClothMeshDesc();
+ SimpleMesh GetRenderMesh();
+};
diff --git a/NvCloth/samples/SampleBase/utils/JobManager.cpp b/NvCloth/samples/SampleBase/utils/JobManager.cpp
new file mode 100644
index 0000000..093db60
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/JobManager.cpp
@@ -0,0 +1,136 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#include "JobManager.h"
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <NvCloth/Solver.h>
+
+void Job::Initialize(JobManager* parent, std::function<void(Job*)> function, int refcount)
+{
+ mFunction = function;
+ mParent = parent;
+ Reset(refcount);
+}
+
+Job::Job(const Job& job)
+{
+ mFunction = job.mFunction;
+ mParent = job.mParent;
+ mRefCount.store(job.mRefCount);
+ mFinished = job.mFinished;
+}
+
+void Job::Reset(int refcount)
+{
+ mRefCount = refcount;
+ mFinished = false;
+}
+
+void Job::Execute()
+{
+ if (mFunction)
+ mFunction(this);
+ else
+ ExecuteInternal();
+
+ mFinishedLock.lock();
+ mFinished = true;
+ mFinishedLock.unlock();
+ mFinishedEvent.notify_one();
+}
+
+void Job::AddReference()
+{
+ mRefCount++;
+}
+void Job::RemoveReference()
+{
+ if (0 == --mRefCount)
+ {
+ mParent->Submit(this);
+ }
+}
+
+void Job::Wait()
+{
+ std::unique_lock<std::mutex> lock(mFinishedLock);
+ mFinishedEvent.wait(lock, [this](){return mFinished;} );
+ return;
+}
+
+void JobManager::WorkerEntryPoint(JobManager* parrent)
+{
+ while (true)
+ {
+ Job* job;
+ {
+ std::unique_lock<std::mutex> lock(parrent->mJobQueueLock);
+ while (parrent->mJobQueue.size() == 0 && !parrent->mQuit)
+ parrent->mJobQueueEvent.wait(lock);
+
+ if (parrent->mQuit)
+ return;
+
+ job = parrent->mJobQueue.front();
+ parrent->mJobQueue.pop();
+ }
+ job->Execute();
+ }
+}
+
+void JobManager::Submit(Job* job)
+{
+ mJobQueueLock.lock();
+ mJobQueue.push(job);
+ mJobQueueLock.unlock();
+ mJobQueueEvent.notify_one();
+}
+
+void MultithreadedSolverHelper::Initialize(nv::cloth::Solver* solver, JobManager* jobManager)
+{
+ mSolver = solver;
+ mJobManager = jobManager;
+ mEndSimulationJob.Initialize(mJobManager, [this](Job*) {
+ mSolver->endSimulation();
+ });
+
+ mStartSimulationJob.Initialize(mJobManager, [this](Job*) {
+ mSolver->beginSimulation(mDt);
+ for(int j = 0; j < mSolver->getSimulationChunkCount(); j++)
+ mSimulationChunkJobs[j].RemoveReference();
+ });
+}
+
+void MultithreadedSolverHelper::StartSimulation(float dt)
+{
+ mDt = dt;
+
+ if (mSolver->getSimulationChunkCount() != mSimulationChunkJobs.size())
+ {
+ mSimulationChunkJobs.resize(mSolver->getSimulationChunkCount(), Job());
+ for (int j = 0; j < mSolver->getSimulationChunkCount(); j++)
+ mSimulationChunkJobs[j].Initialize(mJobManager, [this, j](Job*) {mSolver->simulateChunk(j); mEndSimulationJob.RemoveReference(); });
+ }
+ else
+ {
+ for (int j = 0; j < mSolver->getSimulationChunkCount(); j++)
+ mSimulationChunkJobs[j].Reset();
+ }
+ mStartSimulationJob.Reset();
+ mEndSimulationJob.Reset(mSolver->getSimulationChunkCount());
+ mStartSimulationJob.RemoveReference();
+
+}
+
+void MultithreadedSolverHelper::WaitForSimulation()
+{
+ mEndSimulationJob.Wait();
+} \ No newline at end of file
diff --git a/NvCloth/samples/SampleBase/utils/JobManager.h b/NvCloth/samples/SampleBase/utils/JobManager.h
new file mode 100644
index 0000000..648fa33
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/JobManager.h
@@ -0,0 +1,175 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#pragma once
+
+#include <foundation/PxErrorCallback.h>
+#include <regex>
+#include "CallbackImplementations.h"
+#include <mutex>
+#include <thread>
+#include <condition_variable>
+
+#include <foundation/PxVec4.h>
+#include <foundation/PxVec3.h>
+#include <vector>
+#include <queue>
+#include <atomic>
+
+#include <task/PxTaskManager.h>
+#include <task/PxTask.h>
+
+namespace nv
+{
+namespace cloth
+{
+class Solver;
+}
+}
+
+///Dummy task that can be used as end node in a task graph.
+class DummyTask : public physx::PxTask
+{
+public:
+ DummyTask() { mFinished = false; mTm = nullptr; }
+ DummyTask(physx::PxTaskManager* tm) { mFinished = false; mTm = tm; mTm->submitUnnamedTask(*this); }
+ ~DummyTask() { mTm = nullptr; } //Way to catch race conditions. Will usually crash on nullptr if the task gets deleted before the taskmanager is done with it.
+
+ virtual void run() override { }
+ virtual void release() override { physx::PxTask::release(); mFinished = true; mWaitEvent.notify_all(); }
+ virtual const char* getName() const override { return "DummyTask"; }
+
+ void Reset(physx::PxTaskManager* tm) { mFinished = false; mTm = tm; mTm->submitUnnamedTask(*this); }
+
+ ///Use Wait to block the calling thread until this task is finished and save to delete
+ void Wait()
+ {
+ std::mutex eventLock;
+ std::unique_lock<std::mutex> lock(eventLock);
+ while (!mFinished) { mWaitEvent.wait(lock); }
+ }
+
+private:
+ std::condition_variable mWaitEvent;
+ bool mFinished;
+};
+
+class CpuDispatcher : public physx::PxCpuDispatcher
+{
+ virtual void submitTask(physx::PxBaseTask& task)
+ {
+ task.run();
+ task.release();
+ }
+ virtual uint32_t getWorkerCount() const { return 1; }
+};
+
+
+class JobManager;
+class Job
+{
+public:
+ Job() = default;
+ Job(const Job&);
+ void Initialize(JobManager* parent, std::function<void(Job*)> function = std::function<void(Job*)>(), int refcount = 1);
+ void Reset(int refcount = 1); //Call this before reusing a job that doesn't need to be reinitialized
+ void Execute();
+ void AddReference();
+ void RemoveReference();
+ void Wait(); //Block until job is finished
+private:
+ virtual void ExecuteInternal() {}
+
+ std::function<void(Job*)> mFunction;
+ JobManager* mParent;
+ std::atomic_int mRefCount;
+
+ bool mFinished;
+ std::mutex mFinishedLock;
+ std::condition_variable mFinishedEvent;
+};
+
+class JobManager
+{
+public:
+ JobManager()
+ {
+ mWorkerCount = 8;
+ mWorkerThreads = new std::thread[mWorkerCount];
+ mQuit = false;
+
+ for(int i = 0; i < mWorkerCount; i++)
+ mWorkerThreads[i] = std::thread(JobManager::WorkerEntryPoint, this);
+ }
+ ~JobManager()
+ {
+ if(!mQuit)
+ Quit();
+ }
+
+ void Quit()
+ {
+ std::unique_lock<std::mutex> lock(mJobQueueLock);
+ mQuit = true;
+ lock.unlock();
+ mJobQueueEvent.notify_all();
+ for(int i = 0; i < mWorkerCount; i++)
+ {
+ mWorkerThreads[i].join();
+ }
+ delete[] mWorkerThreads;
+ }
+
+ template <int count, typename F>
+ void ParallelLoop(F const& function)
+ {
+ /*for(int i = 0; i < count; i++)
+ function(i);*/
+ Job finalJob;
+ finalJob.Initialize(this, std::function<void(Job*)>(), count);
+ Job jobs[count];
+ for(int j = 0; j < count; j++)
+ {
+ jobs[j].Initialize(this, [j, &finalJob, function](Job*) {function(j); finalJob.RemoveReference(); });
+ jobs[j].RemoveReference();
+ }
+ finalJob.Wait();
+ }
+
+ static void WorkerEntryPoint(JobManager* parrent);
+private:
+ friend class Job;
+ void Submit(Job* job);
+
+ int mWorkerCount;
+ std::thread* mWorkerThreads;
+
+ std::mutex mJobQueueLock;
+ std::queue<Job*> mJobQueue;
+ std::condition_variable mJobQueueEvent;
+ bool mQuit;
+};
+
+class MultithreadedSolverHelper
+{
+public:
+ void Initialize(nv::cloth::Solver* solver, JobManager* jobManager);
+ void StartSimulation(float dt);
+ void WaitForSimulation();
+private:
+ Job mStartSimulationJob;
+ Job mEndSimulationJob;
+ std::vector<Job> mSimulationChunkJobs;
+
+ float mDt;
+
+ nv::cloth::Solver* mSolver;
+ JobManager* mJobManager;
+};
diff --git a/NvCloth/samples/SampleBase/utils/SampleProfiler.cpp b/NvCloth/samples/SampleBase/utils/SampleProfiler.cpp
new file mode 100644
index 0000000..0438b06
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/SampleProfiler.cpp
@@ -0,0 +1,223 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+
+#include "SampleProfiler.h"
+#include <map>
+#include <iostream>
+#include <fstream>
+#include <stack>
+
+using namespace std::chrono;
+
+struct ProfileData
+{
+ steady_clock::time_point start;
+
+ microseconds time;
+ microseconds prevTime;
+ microseconds maxTime;
+ uint32_t calls;
+ uint32_t prevCalls;
+
+ ProfileData() : time(0), prevTime(0), maxTime(0), calls(0), prevCalls(0)
+ {}
+};
+
+struct Node
+{
+ ProfileData data;
+ std::map<const char*, Node> childs;
+ Node* parent;
+};
+
+static std::map<const char*, Node> s_roots;
+static Node* s_currentNode;
+static bool s_beginEndMismatch;
+static microseconds s_overhead;
+static microseconds s_prevOverhead;
+
+void SampleProfilerInit()
+{
+ s_roots.clear();
+ s_currentNode = nullptr;
+ s_beginEndMismatch = false;
+ s_overhead = microseconds();
+}
+
+void SampleProfilerBegin(const char* name)
+{
+ auto start = steady_clock::now();
+ {
+ Node* parent = s_currentNode;
+ if (s_currentNode == nullptr)
+ {
+ s_currentNode = &s_roots[name];
+ }
+ else
+ {
+ s_currentNode = &s_currentNode->childs[name];
+ }
+ s_currentNode->parent = parent;
+ s_currentNode->data.calls++;
+ s_currentNode->data.start = steady_clock::now();
+ }
+ s_overhead += duration_cast<microseconds>(steady_clock::now() - start);
+}
+
+void SampleProfilerEnd()
+{
+ auto start = steady_clock::now();
+ {
+ if (s_currentNode)
+ {
+ auto& data = s_currentNode->data;
+ data.time += duration_cast<microseconds>(steady_clock::now() - data.start);
+ data.maxTime = data.time > data.maxTime ? data.time : data.maxTime;
+ s_currentNode = s_currentNode->parent;
+ }
+ else
+ {
+ s_beginEndMismatch = true;
+ }
+ }
+ s_overhead += duration_cast<microseconds>(steady_clock::now() - start);
+}
+
+struct SampleProfilerTreeIteratorImpl final : public SampleProfilerTreeIterator
+{
+ struct StackNode
+ {
+ Node* node;
+ const char* name;
+ };
+
+ SampleProfilerTreeIteratorImpl(std::map<const char*, Node>& roots)
+ {
+ for (auto& root : roots)
+ {
+ m_stack.emplace(StackNode { &root.second, root.first });
+ }
+
+ next();
+ }
+
+ virtual const Data* data() const override
+ {
+ return m_valid ? &m_data : nullptr;
+ }
+
+ Node* node()
+ {
+ return m_node;
+ }
+
+ virtual bool isDone() const
+ {
+ return !m_valid;
+ }
+
+ virtual void next()
+ {
+ if (!m_stack.empty())
+ {
+ auto& e = m_stack.top();
+ m_stack.pop();
+ m_node = e.node;
+ m_data.depth = 0;
+ m_data.hash = (uint64_t)m_node;
+ for (const Node* p = m_node; p != nullptr; p = p->parent)
+ {
+ m_data.depth++;
+ }
+ m_data.name = e.name;
+ m_data.calls = m_node->data.prevCalls;
+ m_data.time = m_node->data.prevTime;
+ m_data.maxTime = m_node->data.maxTime;
+ m_data.hasChilds = !m_node->childs.empty();
+
+ for (auto it = m_node->childs.rbegin(); it != m_node->childs.rend(); ++it)
+ {
+ m_stack.emplace(StackNode { &(*it).second, (*it).first });
+ }
+ m_valid = true;
+ }
+ else
+ {
+ m_valid = false;
+ }
+ }
+
+ virtual void release()
+ {
+ delete this;
+ }
+
+ bool m_valid;
+ Data m_data;
+ Node* m_node;
+ std::stack<StackNode > m_stack;
+};
+
+void SampleProfilerReset()
+{
+ for (SampleProfilerTreeIteratorImpl it(s_roots); !it.isDone(); it.next())
+ {
+ auto& data = it.node()->data;
+ data.prevTime = data.time;
+ data.prevCalls = data.calls;
+ data.time = microseconds();
+ data.calls = 0;
+ }
+ s_currentNode = nullptr;
+ s_beginEndMismatch = false;
+ s_prevOverhead = s_overhead;
+ s_overhead = microseconds();
+}
+
+bool SampleProfilerIsValid()
+{
+ return !s_beginEndMismatch;
+}
+
+microseconds SampleProfilerGetOverhead()
+{
+ return s_prevOverhead;
+}
+
+SampleProfilerTreeIterator* SampleProfilerCreateTreeIterator()
+{
+ return SampleProfilerIsValid() ? new SampleProfilerTreeIteratorImpl(s_roots) : nullptr;
+}
+
+void SampleProfilerDumpToFile(const char* path)
+{
+ std::ofstream myfile(path, std::ios_base::out);
+ if (myfile.is_open())
+ {
+ if (s_beginEndMismatch)
+ {
+ myfile << "Error: Begin/End Mismatch.\n";
+ }
+ else
+ {
+ myfile << "[Root]\n";
+ for(SampleProfilerTreeIteratorImpl it(s_roots); !it.isDone(); it.next())
+ {
+ auto data = it.data();
+ for (uint32_t i = 0; i < data->depth; ++i)
+ myfile << "\t";
+ myfile << data->name << " --> calls: " << data->calls << ", total: " << data->time.count() * 0.001 << "ms\n";
+ }
+ }
+
+ myfile.close();
+ }
+}
diff --git a/NvCloth/samples/SampleBase/utils/SampleProfiler.h b/NvCloth/samples/SampleBase/utils/SampleProfiler.h
new file mode 100644
index 0000000..af0002c
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/SampleProfiler.h
@@ -0,0 +1,79 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#ifndef SAMPLEPROFILER_H
+#define SAMPLEPROFILER_H
+
+#include <chrono>
+
+#if NV_PROFILE
+
+void SampleProfilerInit();
+void SampleProfilerBegin(const char* name);
+void SampleProfilerEnd();
+void SampleProfilerReset();
+
+struct SampleProfilerScoped
+{
+ SampleProfilerScoped(const char* name)
+ {
+ SampleProfilerBegin(name);
+ }
+
+ ~SampleProfilerScoped()
+ {
+ SampleProfilerEnd();
+ }
+};
+
+#define PROFILER_INIT() SampleProfilerInit()
+#define PROFILER_BEGIN(x) SampleProfilerBegin(x)
+#define PROFILER_END() SampleProfilerEnd()
+#define PROFILER_SCOPED(x) SampleProfilerScoped __scopedProfiler__(x)
+#define PROFILER_SCOPED_FUNCTION() SampleProfilerScoped __scopedProfiler__(__FUNCTION__)
+#define PROFILER_RESET() SampleProfilerReset()
+
+#else
+
+#define PROFILER_INIT()
+#define PROFILER_BEGIN(x)
+#define PROFILER_END()
+#define PROFILER_SCOPED(x)
+#define PROFILER_SCOPED_FUNCTION()
+#define PROFILER_RESET()
+
+#endif
+
+void SampleProfilerDumpToFile(const char* path);
+bool SampleProfilerIsValid();
+std::chrono::microseconds SampleProfilerGetOverhead();
+
+struct SampleProfilerTreeIterator
+{
+ struct Data
+ {
+ uint64_t hash;
+ const char* name;
+ bool hasChilds;
+ uint32_t depth;
+ std::chrono::microseconds time;
+ std::chrono::microseconds maxTime;
+ uint32_t calls;
+ };
+
+ virtual const Data* data() const = 0;
+ virtual bool isDone() const = 0;
+ virtual void next() = 0;
+ virtual void release() = 0;
+};
+
+SampleProfilerTreeIterator* SampleProfilerCreateTreeIterator();
+
+#endif //SAMPLEPROFILER_H \ No newline at end of file
diff --git a/NvCloth/samples/SampleBase/utils/SampleTime.h b/NvCloth/samples/SampleBase/utils/SampleTime.h
new file mode 100644
index 0000000..eac895d
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/SampleTime.h
@@ -0,0 +1,58 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#ifndef SAMPLE_TIME_H
+#define SAMPLE_TIME_H
+
+#include <stdint.h>
+
+class Time
+{
+public:
+ Time() : m_lastTickCount(getTimeTicks()) {}
+
+ double Time::getElapsedSeconds()
+ {
+ const int64_t lastTickCount = m_lastTickCount;
+ m_lastTickCount = getTimeTicks();
+ return (m_lastTickCount - lastTickCount) * s_secondsPerTick;
+ }
+
+ double Time::peekElapsedSeconds() const
+ {
+ return (getTimeTicks() - m_lastTickCount) * s_secondsPerTick;
+ }
+
+ double Time::getLastTime() const
+ {
+ return m_lastTickCount * s_secondsPerTick;
+ }
+
+private:
+ static double getTickDuration()
+ {
+ LARGE_INTEGER a;
+ QueryPerformanceFrequency(&a);
+ return 1.0 / (double)a.QuadPart;
+ }
+
+ int64_t getTimeTicks() const
+ {
+ LARGE_INTEGER a;
+ QueryPerformanceCounter(&a);
+ return a.QuadPart;
+ }
+
+ int64_t m_lastTickCount;
+ static const double s_secondsPerTick;
+};
+
+
+#endif \ No newline at end of file
diff --git a/NvCloth/samples/SampleBase/utils/UIHelpers.h b/NvCloth/samples/SampleBase/utils/UIHelpers.h
new file mode 100644
index 0000000..5c4dfee
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/UIHelpers.h
@@ -0,0 +1,56 @@
+/*
+* Copyright (c) 2008-2017, NVIDIA CORPORATION. All rights reserved.
+*
+* NVIDIA CORPORATION and its licensors retain all intellectual property
+* and proprietary rights in and to this software, related documentation
+* and any modifications thereto. Any use, reproduction, disclosure or
+* distribution of this software and related documentation without an express
+* license agreement from NVIDIA CORPORATION is strictly prohibited.
+*/
+
+#ifndef UI_HELPERS_H
+#define UI_HELPERS_H
+
+#include "imgui.h"
+#include "PxVec3.h"
+
+
+static void ImGui_DragFloat3Dir(const char* label, float v[3])
+{
+ if (ImGui::Button("Normalize"))
+ {
+ ((physx::PxVec3*)v)->normalize();
+ }
+ ImGui::SameLine();
+ ImGui::DragFloat3(label, v);
+};
+
+
+#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR)/sizeof(*_ARR)))
+
+template<int valuesCount = 90>
+class PlotLinesInstance
+{
+public:
+ PlotLinesInstance()
+ {
+ memset(m_values, 0, sizeof(float) * valuesCount);
+ }
+
+ void plot(const char* label, float newValue, const char* overlay_text, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 80))
+ {
+ for (; ImGui::GetTime() > m_time + 1.0f / 60.0f; m_time += 1.0f / 60.0f)
+ {
+ m_values[m_offset] = newValue;
+ m_offset = (m_offset + 1) % valuesCount;
+ }
+ ImGui::PlotLines(label, m_values, valuesCount, m_offset, overlay_text, scale_min, scale_max, graph_size);
+ }
+
+private:
+ float m_values[valuesCount];
+ int m_offset;
+ float m_time = ImGui::GetTime();
+};
+
+#endif //UI_HELPERS_H \ No newline at end of file
diff --git a/NvCloth/samples/SampleBase/utils/Utils.cpp b/NvCloth/samples/SampleBase/utils/Utils.cpp
new file mode 100644
index 0000000..a271137
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/Utils.cpp
@@ -0,0 +1,13 @@
+#include "Utils.h"
+
+#include <string>
+#include <cstdarg>
+
+HRESULT messagebox_printf(const char* caption, UINT mb_type, const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ char formatted_text[512];
+ _vsnprintf(formatted_text, 512, format, args);
+ return MessageBoxA(nullptr, formatted_text, caption, mb_type);
+}
diff --git a/NvCloth/samples/SampleBase/utils/Utils.h b/NvCloth/samples/SampleBase/utils/Utils.h
new file mode 100644
index 0000000..5e84e8e
--- /dev/null
+++ b/NvCloth/samples/SampleBase/utils/Utils.h
@@ -0,0 +1,89 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <DeviceManager.h>
+#include <assert.h>
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// MACROS
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifndef V_RETURN
+#define V_RETURN(x) \
+ { \
+ hr = (x); \
+ if(FAILED(hr)) \
+ { \
+ return hr; \
+ } \
+ }
+#endif
+
+#ifndef V
+#define V(x) \
+ { \
+ HRESULT hr = (x); \
+ _ASSERT(SUCCEEDED(hr)); \
+ }
+#endif
+
+#ifndef SAFE_RELEASE
+#define SAFE_RELEASE(p) \
+ { \
+ if(p) \
+ { \
+ (p)->Release(); \
+ (p) = NULL; \
+ } \
+ }
+#endif
+
+#ifndef SAFE_DELETE
+#define SAFE_DELETE(p) \
+ { \
+ if(p) \
+ { \
+ delete (p); \
+ (p) = NULL; \
+ } \
+ }
+#endif
+
+#define ASSERT_PRINT(cond, format, ...) \
+ if(!(cond)) \
+ { \
+ messagebox_printf("Assertion Failed!", MB_OK | MB_ICONERROR, #cond "\n" format, __VA_ARGS__); \
+ assert(cond); \
+ }
+
+HRESULT messagebox_printf(const char* caption, UINT mb_type, const char* format, ...);
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static const char* strext(const char* str)
+{
+ const char* ext = NULL; // by default no extension found!
+ while (str)
+ {
+ str = strchr(str, '.');
+ if (str)
+ {
+ str++;
+ ext = str;
+ }
+ }
+ return ext;
+}
+
+static inline float lerp(float a, float b, float t) { return a + (b - a) * t; }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#endif \ No newline at end of file