diff options
| author | Miles Macklin <[email protected]> | 2017-03-10 14:51:31 +1300 |
|---|---|---|
| committer | Miles Macklin <[email protected]> | 2017-03-10 14:51:31 +1300 |
| commit | ad3d90fafe5ee79964bdfe1f1e0704c3ffcdfd5f (patch) | |
| tree | 4cc6f3288363889d7342f7f8407c0251e6904819 /src | |
| download | flex-ad3d90fafe5ee79964bdfe1f1e0704c3ffcdfd5f.tar.xz flex-ad3d90fafe5ee79964bdfe1f1e0704c3ffcdfd5f.zip | |
Initial 1.1.0 binary release
Diffstat (limited to 'src')
| -rw-r--r-- | src/dx/context/Context.h | 410 | ||||
| -rw-r--r-- | src/dx/context/Device.h | 75 | ||||
| -rw-r--r-- | src/dx/context/Format.h | 22 | ||||
| -rw-r--r-- | src/dx/context/NvFlexTypes.h | 88 | ||||
| -rw-r--r-- | src/dx/context/README.md | 7 | ||||
| -rw-r--r-- | src/dx/context/Vector.h | 207 |
6 files changed, 809 insertions, 0 deletions
diff --git a/src/dx/context/Context.h b/src/dx/context/Context.h new file mode 100644 index 0000000..31ddb95 --- /dev/null +++ b/src/dx/context/Context.h @@ -0,0 +1,410 @@ +/* + * 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 "NvFlexTypes.h" +#include "Vector.h" +#include <Windows.h> +#include <vector> +#include <atomic> +#include <map> + + // NV shader extensions +#include "../../../external/nvapi/include/nvShaderExtnEnums.h" + + +#define ENABLE_AMD_AGS 1 // enable AMD AGS shader extensions, used for warp shuffle based reductions +#if ENABLE_AMD_AGS + // AMD shader extensions +#include <external/ags_lib/inc/amd_ags.h> +#endif + +#define USE_GPUBB 0 //Used for D3D12 shader debugging +#define ENABLE_D3D12 1 + +enum GpuVendorId +{ + VENDOR_ID_NVIDIA = 0x10DE, + VENDOR_ID_AMD = 0x1002, + VENDOR_ID_OTHERS = 0x00EE, +}; + +inline void ThrowIfFailed(HRESULT hr) +{ + if (FAILED(hr)) + { + throw; + } +} + +//! graphics API dependent descriptions +struct NvFlexContextDesc; + +//! export interop buffers +struct NvFlexBuffer; +struct NvFlexTexture3D; + +//! graphics API dependent descriptions +struct NvFlexBufferViewDesc; +struct NvFlexTexture3DViewDesc; + +/// common object type +struct NvFlexObject +{ + virtual NvFlexUint addRef() = 0; + virtual NvFlexUint release() = 0; +}; + +//struct NvFlexBuffer : public NvFlexObject {}; +//struct NvFlexTexture3D : public NvFlexObject {}; + +namespace NvFlex +{ + struct Context; + + struct Allocable + { + void* operator new(size_t size); + void* operator new[](size_t size); + void operator delete(void* ptr); + void operator delete[](void* ptr); + }; + + struct Object : public Allocable + { + virtual NvFlexUint addRefInternal(); + + virtual NvFlexUint releaseInternal(); + + virtual ~Object(); + + private: + std::atomic_uint32_t m_refCount = 1u; + }; + + #define NV_FLEX_OBJECT_IMPL \ + virtual NvFlexUint addRef() { return Object::addRefInternal(); } \ + virtual NvFlexUint release() { return Object::releaseInternal(); } + + #define NV_FLEX_DISPATCH_MAX_READ_TEXTURES ( 30u ) + #define NV_FLEX_DISPATCH_MAX_WRITE_TEXTURES ( 8u ) + + #define NV_FLEX_DRAW_MAX_READ_TEXTURES ( 8u ) + #define NV_FLEX_DRAW_MAX_WRITE_TEXTURES ( 1u ) + + struct ConstantBufferDesc + { + NvFlexUint stride; + NvFlexUint dim; + bool uploadAccess; +#if defined(_DEBUG) + const char* name; +#endif + }; + + enum BufferTypes + { + eBuffer = 1 << 0, + eUAV_SRV = 1 << 1, + eStage = 1 << 2, + eRaw = 1 << 3, + eStructured = 1 << 4, + eIndirect = 1 << 5, + eShared = 1 << 6 + }; + + struct BufferDesc + { + NvFlexFormat format; + NvFlexUint dim; + NvFlexUint stride; + unsigned int bufferType; + const void* data; +#if defined(_DEBUG) + const char* name; +#endif + }; + + struct BufferViewDesc + { + NvFlexFormat format; + }; + + struct Texture3DDesc + { + NvFlexFormat format; + NvFlexDim dim; + bool uploadAccess; + bool downloadAccess; + const void* data; +#if defined(_DEBUG) + const char* name; +#endif + }; + + struct ComputeShaderDesc + { + ComputeShaderDesc() : cs(nullptr), cs_length(0), label(L""), NvAPI_Slot(~0u) {} + ComputeShaderDesc(void* shaderByteCode, NvFlexUint64 byteCodeLength, wchar_t* shaderLabel = L"", NvFlexUint nvApiSlot = ~0u): cs(shaderByteCode), cs_length (byteCodeLength), label(shaderLabel), NvAPI_Slot(nvApiSlot){} + const void* cs; + NvFlexUint64 cs_length; + const wchar_t* label; + NvFlexUint NvAPI_Slot; + }; + + struct InputElementDesc + { + const char* semanticName; + NvFlexFormat format; + }; + + /// A basic map-discard constant buffer + struct ConstantBuffer : public NvFlexObject + { + virtual ~ConstantBuffer() {} + + ConstantBufferDesc m_desc; + }; + + /// A GPU resource that supports read operations + struct Resource : public NvFlexObject {}; + + /// A GPU resource that supports read/write operations + /// includes a Resource + struct ResourceRW : public NvFlexObject + { + virtual Resource* getResource() = 0; + }; + + + /// Context created resources + /// includes Resource, ResourceRW + struct Buffer : public NvFlexObject + { + virtual Resource* getResource() = 0; + virtual ResourceRW* getResourceRW() = 0; + virtual ~Buffer() {} + BufferDesc m_desc; + }; + + /// Staging resource + struct Stage : public NvFlexObject + { + virtual ~Stage() {} + BufferDesc m_desc; + }; + + /// includes Resource, ResourceRW + struct Texture3D : public NvFlexObject + { + virtual Resource* getResource() = 0; + virtual ResourceRW* getResourceRW() = 0; + virtual ~Texture3D() {} + Texture3DDesc m_desc; + }; + + struct ComputeShader : public NvFlexObject + { + virtual ~ComputeShader() {} + ComputeShaderDesc m_desc; + }; + + struct Timer : public NvFlexObject + { + virtual ~Timer() {} + }; + + struct Fence : public NvFlexObject + { + virtual ~Fence() {} + }; + + struct DispatchParams + { + ComputeShader* shader; + NvFlexDim gridDim; + ConstantBuffer* rootConstantBuffer; + ConstantBuffer* secondConstantBuffer; + Resource* readOnly[NV_FLEX_DISPATCH_MAX_READ_TEXTURES]; + ResourceRW* readWrite[NV_FLEX_DISPATCH_MAX_WRITE_TEXTURES]; + Buffer * IndirectLaunchArgs; + }; + + struct MappedData + { + void* data; + NvFlexUint rowPitch; + NvFlexUint depthPitch; + }; + + // D3D events are expensive to create and destroy, so this pool enables + // DxRangeTimers to be reused. + struct TimerPool + { + virtual ~TimerPool() {} + // We initialize new elements instead of std::vector doing it, to avoid + // having to allocate D3D11Query in a copy constructor + //returns an index to the timer in the pool + //virtual int alloc() = 0; + + virtual int begin() = 0; + virtual void end(int index) = 0; + + // synchronize timers between GPU and CPU + virtual void resolve() = 0; + // Copy timers to cpu + virtual void sync() = 0; + + virtual void clear() = 0; + + virtual float get(int index) = 0; + // Set the number of timers in the pool + // In D3D12 this results in a reallocation of the timer heap + virtual void reserve(size_t size) = 0; + static const int m_initPoolSize = 200; + size_t m_timerPoolSize = 0; //The size of the heap + size_t m_usedElemCount = 0; //How many elements are used + size_t m_requestedElemCount = 0; //How many timers were requested + }; + + // String comparison function for timer name to timer index map + struct ltstr + { + bool operator()(const char* s1, const char* s2) const + { + return strcmp(s1, s2) < 0; + } + }; + + + // WString comparison function for timer name to timer index map + struct ltwstr + { + bool operator()(const wchar_t* s1, const wchar_t* s2) const + { + return wcscmp(s1, s2) < 0; + } + }; + + // Each named range can occur more than once, for example once per iteration + // So the map from name to timer has to be a multimap + + typedef std::multimap<const char*, int, ltstr> NameToTimerMap; + typedef NameToTimerMap::iterator NameToTimerMapIter; + + typedef std::multimap<const wchar_t*, int, ltwstr> NameToTimerMapW; + typedef NameToTimerMapW::iterator NameToTimerMapWIter; + + struct Context + { + // ************ public interface ***************** + + virtual void updateContext(const NvFlexContextDesc* desc) = 0; + + //virtual void updateBufferViewDesc(NvFlexBuffer* buffer, NvFlexBufferViewDesc* desc) = 0; + + //virtual void updateTexture3DViewDesc(NvFlexTexture3D* buffer, NvFlexTexture3DViewDesc* desc) = 0; + + // ************ internal resources *************** + + virtual ConstantBuffer* createConstantBuffer(const ConstantBufferDesc* desc) = 0; + + virtual void* map(ConstantBuffer* buffer) = 0; + + virtual void unmap(ConstantBuffer* buffer) = 0; + + virtual void copy(ConstantBuffer* dst, Buffer* src) = 0; + + virtual Buffer* createBuffer(const BufferDesc* desc, void* ptr = 0) = 0; + + virtual Buffer* createBufferView(Buffer* buffer, const BufferViewDesc* desc) = 0; + + virtual void* map(Buffer* buffer) = 0; + + virtual void* mapUpload(Buffer* buffer) = 0; + + virtual void unmap(Buffer* buffer) = 0; + + virtual void upload(Buffer* buffer) = 0; + + virtual void upload(Buffer* buffer, NvFlexUint offset, NvFlexUint numBytes) = 0; + + virtual void download(Buffer* buffer) = 0; + + virtual void download(Buffer* buffer, NvFlexUint offset, NvFlexUint numBytes) = 0; + + virtual void copy(Buffer* dst, unsigned dstByteOffset, Buffer* src, NvFlexUint srcByteOffset, NvFlexUint numBytes) = 0; + + virtual void copyToNative(void* dst, unsigned dstByteOffset, Buffer* src, NvFlexUint srcByteOffset, NvFlexUint numBytes) = 0; + + virtual void copyToDevice(Buffer* dst, unsigned dstByteOffset, Buffer* src, NvFlexUint srcByteOffset, NvFlexUint numBytes) = 0; + + virtual void copyToHost(Buffer* dst, unsigned dstByteOffset, Buffer* src, NvFlexUint srcByteOffset, NvFlexUint numBytes) = 0; + + virtual void clearUnorderedAccessViewUint(Buffer* buffer, const NvFlexUint * values) = 0; + + virtual void copyResourceState(Buffer* bufferFrom, Buffer* bufferTo) = 0; + + //virtual void updateSubresource(Buffer* bufferIn, const NvFlexUint * box, const void * data) = 0; + + virtual Texture3D* createTexture3D(const Texture3DDesc* desc) = 0; + + virtual MappedData map(Texture3D* buffer) = 0; + + virtual void unmap(Texture3D* buffer) = 0; + + virtual void download(Texture3D* buffer) = 0; + + virtual MappedData mapDownload(Texture3D* buffer) = 0; + + virtual void unmapDownload(Texture3D* buffer) = 0; + + virtual void copy(Texture3D* dst, Texture3D* srv) = 0; + + virtual ComputeShader* createComputeShader(const ComputeShaderDesc* desc) = 0; + + // ***************** Operations **************** + + virtual void dispatch(const DispatchParams* params) = 0; + + virtual void dispatchIndirect(const DispatchParams* params) = 0; + + virtual TimerPool * createTimerPool() = 0; + + virtual Fence* createFence() = 0; + + virtual void fenceSet(Fence * fence) = 0; + + virtual void fenceWait(Fence * fence) = 0; + + virtual void startFrame() = 0; + + virtual void flush() = 0; + + virtual void clearState() = 0; + + // ***************** Profiling **************** + + virtual void eventMarker(const wchar_t* label) = 0; + + virtual void contextPush() = 0; + + virtual void contextPop() = 0; + + bool mEnableTimers; + + NameToTimerMapW mNameToInternalTimerMap; + NameToTimerMap mNameToExternalTimerMap; + + Context(); + virtual ~Context(); + }; +}
\ No newline at end of file diff --git a/src/dx/context/Device.h b/src/dx/context/Device.h new file mode 100644 index 0000000..c3685a9 --- /dev/null +++ b/src/dx/context/Device.h @@ -0,0 +1,75 @@ +#pragma once + +#include "Context.h" + +#include <d3d11.h> +#include <d3d11_1.h> + +#if ENABLE_D3D12 +#include <d3d12.h> +#endif + +#include <external/nvapi/include/nvapi.h> + +#include <dxgi.h> +#include <string> +#include <vector> + +#define ENABLE_LIVE_DEVICE_OBJECTS_REPORTING 0 // Provides detailed report of D3D object reference counts +#define USE_NVAPI_DEVICE 0 // Enable this for D3D11 SCG + +struct FlexDeviceDesc +{ + FlexDeviceDesc() : mDeviceNumber(-1), mDevice(nullptr), mCommandQueue(nullptr), useComputeQueue(false), enablePresent(false) {} + virtual ~FlexDeviceDesc() {} + unsigned int mDeviceNumber; //!< Compute device number + void * mDevice; //!< pointer to existing device + void * mCommandQueue; //!< pointer to existing command queue for DX12 + bool useComputeQueue; //!< Use the compute queue instead of the graphics queue for DX12 + bool enablePresent; //!< Present after each frame to that APIC and PB dump work +}; + +namespace NvFlex +{ + + struct Device : public NvFlexObject + { + Device(); + virtual ~Device(); + + virtual Context* createContext() = 0; + + virtual NvAPI_Status NvAPI_IsNvShaderExtnOpCodeSupported(NvU32 opCode, bool * pSupported) = 0; + +#if ENABLE_AMD_AGS + virtual AGSReturnCode AGS_DriverExtensions_Init(AGSContext* agsContext, unsigned int agsApiSlot, unsigned int *extensionsSupported) = 0; + virtual void AGS_DeInit(AGSContext* agsContext) = 0; +#endif + + virtual DXGI_ADAPTER_DESC1 getDXGIAdapter(void* d3ddevice, int & deviceNumber) = 0; + virtual DXGI_ADAPTER_DESC1 getDeviceNumber(const LUID adapterID, int & deviceNumber); + virtual std::vector<IDXGIAdapter1 *> enumerateDXGIAdapters(); + virtual void queryDeviceCapabilities(void* d3ddevice); + + virtual void reportLiveDeviceObjects() {} + +//#if APIC_CAPTURE + virtual void createWindow(); + virtual void present() = 0; + + WNDCLASSEX mWindowDesc; + RECT mWinRect; + HWND m_hWnd; + IDXGISwapChain* m_swapChain; +//#endif + + IDXGIFactory1* m_pFactory; + + NvFlexUint mSMCount; + std::string mDeviceName; + // Gpu Vendor Id + GpuVendorId mGpuVendorId; + bool mD3D12Device; + Context* m_context; + }; +} diff --git a/src/dx/context/Format.h b/src/dx/context/Format.h new file mode 100644 index 0000000..389b3a8 --- /dev/null +++ b/src/dx/context/Format.h @@ -0,0 +1,22 @@ +/* + * 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 "NvFlexTypes.h" + +namespace NvFlex +{ + DXGI_FORMAT convertToDXGI(NvFlexFormat format); + + NvFlexFormat convertToNvFlex(DXGI_FORMAT format); + + NvFlexUint getFormatSizeInBytes(NvFlexFormat format); +}
\ No newline at end of file diff --git a/src/dx/context/NvFlexTypes.h b/src/dx/context/NvFlexTypes.h new file mode 100644 index 0000000..46e8269 --- /dev/null +++ b/src/dx/context/NvFlexTypes.h @@ -0,0 +1,88 @@ +/* + * 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 + + +enum NvFlexResult +{ + eNvFlexSuccess = 0, + eNvFlexFail = 1 +}; + +typedef int NvFlexInt; +typedef unsigned int NvFlexUint; +typedef unsigned long long NvFlexUint64; + +struct NvFlexDim +{ + NvFlexUint x, y, z; +}; + +struct NvFlexUint3 +{ + NvFlexUint x, y, z; +}; + +struct NvFlexUint4 +{ + NvFlexUint x, y, z, w; +}; + +struct NvFlexFloat3 +{ + float x, y, z; +}; + +struct NvFlexFloat4 +{ + float x, y, z, w; +}; + +struct NvFlexFloat4x4 +{ + NvFlexFloat4 x, y, z, w; +}; + +enum NvFlexFormat +{ + eNvFlexFormat_unknown = 0, + + eNvFlexFormat_r32_typeless = 1, + + eNvFlexFormat_r32_float = 2, + eNvFlexFormat_r32g32_float = 3, + eNvFlexFormat_r32g32b32a32_float = 4, + + eNvFlexFormat_r16_float = 5, + eNvFlexFormat_r16g16_float = 6, + eNvFlexFormat_r16g16b16a16_float = 7, + + eNvFlexFormat_r32_uint = 8, + eNvFlexFormat_r32g32_uint = 9, + eNvFlexFormat_r32g32b32a32_uint = 10, + + eNvFlexFormat_r8_unorm = 11, + eNvFlexFormat_r8g8_unorm = 12, + eNvFlexFormat_r8g8b8a8_unorm = 13, + + eNvFlexFormat_r16_unorm = 14, + eNvFlexFormat_r16g16_unorm = 15, + eNvFlexFormat_r16g16b16a16_unorm = 16, + + eNvFlexFormat_d32_float = 17, + eNvFlexFormat_d24_unorm_s8_uint = 18, + + eNvFlexFormat_r8_snorm = 19, + eNvFlexFormat_r8g8_snorm = 20, + eNvFlexFormat_r8g8b8a8_snorm = 21, + + eNvFlexFormat_max +};
\ No newline at end of file diff --git a/src/dx/context/README.md b/src/dx/context/README.md new file mode 100644 index 0000000..7b45ecd --- /dev/null +++ b/src/dx/context/README.md @@ -0,0 +1,7 @@ +The interfaces in this folder define a thin wrapper around D3D11 and D3D12 APIs that +are used internally by the Flex solver. Some parts of the Flex API (e.g.: solver callbacks) +will return pointers to these structures. + +See the extensions forcefield API for an example of how these wrappers may be used to implement +Flex solver callbacks. Note that this code should not be modified or recompiled to ensure binary +compatibility with the main Flex libraries. diff --git a/src/dx/context/Vector.h b/src/dx/context/Vector.h new file mode 100644 index 0000000..780faf6 --- /dev/null +++ b/src/dx/context/Vector.h @@ -0,0 +1,207 @@ +/* + * 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 "NvFlexTypes.h" + +#include <new> + +namespace NvFlex +{ + + template<class T, NvFlexUint staticCapacity> + struct VectorCached : public Allocable + { + static const NvFlexUint s_staticCapacity = staticCapacity; + + T* m_data; + NvFlexUint m_capacity; + T m_cache[staticCapacity]; + NvFlexUint m_size = 0u; + + T* allocate(NvFlexUint capacity) + { + T* ptr = (T*)operator new[](capacity*sizeof(T)); + for (NvFlexUint i = 0; i < capacity; i++) + { + new(ptr + i) T; + } + return ptr; + } + + void cleanup(T* data, NvFlexUint capacity) + { + if (m_cache != data) + { + for (NvFlexUint i = 0; i < capacity; i++) + { + data[i].~T(); + } + operator delete[](data); + } + } + + VectorCached(NvFlexUint capacity) + { + m_capacity = capacity; + if (capacity > staticCapacity) + { + m_data = allocate(capacity); + } + else + { + m_data = m_cache; + } + } + + ~VectorCached() + { + if (m_capacity > staticCapacity) + { + cleanup(m_data, m_capacity); + } + m_data = nullptr; + } + + T& operator[](unsigned int idx) + { + return m_data[idx]; + } + + NvFlexUint allocateBack() + { + // resize if needed + if (m_size + 1 > m_capacity) + { + NvFlexUint capacity = 2 * m_capacity; + T* newData = allocate(capacity); + // copy to new + for (NvFlexUint i = 0; i < m_size; i++) + { + new(&newData[i]) T(m_data[i]); + } + // cleanup old + cleanup(m_data, m_capacity); + // commit new + m_data = newData; + m_capacity = capacity; + } + m_size++; + return m_size - 1; + } + + void reserve(NvFlexUint capacity) + { + if (capacity > m_capacity) + { + T* newData = allocate(capacity); + // copy to new + for (NvFlexUint i = 0; i < m_size; i++) + { + new(&newData[i]) T(m_data[i]); + } + // cleanup old + cleanup(m_data, m_capacity); + // commit new + m_data = newData; + m_capacity = capacity; + } + } + }; + + template <class T> + struct Image3D : public Allocable + { + T* m_data = nullptr; + NvFlexDim m_dim = { 0u, 0u, 0u }; + NvFlexUint m_capacity = 0u; + + Image3D() {} + + void init(NvFlexDim dim) + { + m_dim = dim; + m_capacity = m_dim.x * m_dim.y * m_dim.z; + + T* ptr = (T*)operator new[](m_capacity*sizeof(T)); + for (NvFlexUint i = 0; i < m_capacity; i++) + { + new(ptr + i) T; + } + m_data = ptr; + } + + ~Image3D() + { + if (m_data) + { + for (NvFlexUint i = 0; i < m_capacity; i++) + { + m_data[i].~T(); + } + operator delete[](m_data); + m_data = nullptr; + } + } + + T& operator[](unsigned int idx) + { + return m_data[idx]; + } + + T& operator()(NvFlexUint i, NvFlexUint j, NvFlexUint k) + { + return m_data[(k*m_dim.y + j) * m_dim.x + i]; + } + }; + + template <class T> + struct Image1D : public Allocable + { + T* m_data = nullptr; + NvFlexUint m_dim = 0u; + NvFlexUint m_capacity = 0u; + + Image1D() {} + + void init(NvFlexUint dim) + { + m_dim = dim; + m_capacity = m_dim; + + T* ptr = (T*)operator new[](m_capacity*sizeof(T)); + for (NvFlexUint i = 0; i < m_capacity; i++) + { + new(ptr + i) T; + } + m_data = ptr; + } + + ~Image1D() + { + if (m_data) + { + for (NvFlexUint i = 0; i < m_capacity; i++) + { + m_data[i].~T(); + } + operator delete[](m_data); + m_data = nullptr; + } + } + + T& operator[](unsigned int idx) + { + return m_data[idx]; + } + }; + +}
\ No newline at end of file |