diff --git a/CMakeLists.txt b/CMakeLists.txt index 8821282..4e0c108 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,14 +177,17 @@ add_library(NintendoSDK OBJECT include/nn/util/util_BinTypes.h include/nn/util/util_BitPack.h include/nn/util/util_BytePtr.h + include/nn/util/util_IntrusiveList.h include/nn/util/util_BitUtil.h include/nn/util/util_ResDic.h include/nn/util/util_StringView.h include/nn/ui2d/detail/TexCoordArray.h include/nn/ui2d/Layout.h - include/nn/ui2d/Parts.h - include/nn/ui2d/Pane.h include/nn/ui2d/Material.h + include/nn/ui2d/Pane.h + include/nn/ui2d/Parts.h + include/nn/ui2d/Types.h + include/nn/ui2d/Util.h include/nn/g3d/BindFuncTable.h include/nn/g3d/ModelObj.h include/nn/g3d/ResMaterialAnim.h diff --git a/include/nn/gfx/gfx_Device.h b/include/nn/gfx/gfx_Device.h index 1586a8f..188e05b 100644 --- a/include/nn/gfx/gfx_Device.h +++ b/include/nn/gfx/gfx_Device.h @@ -5,9 +5,20 @@ #include namespace nn::gfx { - class DeviceInfo; +template +class TCommandBuffer; + +template +class ApiVariation; + +template +class ApiType; + +template +class ApiVersion; + template class TDevice : public detail::DeviceImpl, private detail::RequiredMemory> { @@ -26,4 +37,7 @@ class TDevice : public detail::DeviceImpl, const void* GetUserPtr() const; }; -} // namespace nn::gfx \ No newline at end of file +typedef TDevice, ApiVersion<8>>> Device; +typedef TCommandBuffer, gfx::ApiVersion<8>>> CommandBuffer; + +} // namespace nn::gfx diff --git a/include/nn/ui2d/Layout.h b/include/nn/ui2d/Layout.h index 5acabf6..d1034e7 100644 --- a/include/nn/ui2d/Layout.h +++ b/include/nn/ui2d/Layout.h @@ -33,10 +33,15 @@ class Layout { static void AllocateMemory(size_t); static void FreeMemory(void* src); + Pane* GetPane() const { return mPane; } + +private: + u64 _8; u64 _10; - u64 _18; + Pane* mPane; u64 _20; - u64 _28; + f32 _28; + f32 _2c; u64 _30; u64 _40; diff --git a/include/nn/ui2d/Pane.h b/include/nn/ui2d/Pane.h index 5e7aa95..21397f4 100644 --- a/include/nn/ui2d/Pane.h +++ b/include/nn/ui2d/Pane.h @@ -6,73 +6,183 @@ #pragma once #include +#include #include +#include +#include +#include +#include + +namespace nn::util { +struct Unorm8x4; +} + +namespace nn::ui2d ::detail { + +class PaneBase { + NN_NO_COPY(PaneBase); + +public: + PaneBase(); + virtual ~PaneBase(); + + util::IntrusiveListNode m_Link; +}; + +} // namespace nn::ui2d::detail namespace nn::ui2d { class AnimTransform; class Layout; +class DrawInfo; +class ResPane; +struct BuildArgSet; +class Material; +struct ResExtUserDataList; -class Pane { +class Pane : public detail::PaneBase { public: NN_RUNTIME_TYPEINFO_BASE(); + struct CalculateContext; + + typedef util::IntrusiveList> + PaneList; + Pane(); + Pane(const ResPane*, const BuildArgSet&); Pane(const Pane&); virtual ~Pane(); - + virtual void Finalize(gfx::Device*); virtual s32 GetVertexColor(s32); + virtual void SetVertexColor(s32, util::Unorm8x4 const&); virtual u8 GetColorElement(s32); virtual void SetColorElement(u32, u8); virtual u8 GetVertexColorElement(s32); virtual void SetVertexColorElement(u32, u8); virtual u32 GetMaterialCount() const; - virtual u64* GetMaterial(s32) const; - + virtual Material* GetMaterial(s32) const; + virtual Pane* FindPaneByName(char const*, bool); + virtual const Pane* FindPaneByName(char const*, bool) const; + virtual Material* FindMaterialByName(char const*, bool); + virtual const Material* FindMaterialByName(char const*, bool) const; virtual void BindAnimation(AnimTransform*, bool, bool); virtual void UnbindAnimation(AnimTransform*, bool); + virtual void UnbindAnimationSelf(AnimTransform*); + virtual void Calculate(DrawInfo&, CalculateContext&, bool); + virtual void Draw(DrawInfo&, gfx::CommandBuffer&); + virtual void DrawSelf(DrawInfo&, gfx::CommandBuffer&); - void Initialize(); void SetName(const char*); void SetUserData(const char*); + Material* GetMaterial() const; void AppendChild(Pane*); void PrependChild(Pane*); void InsertChild(Pane*, Pane*); void RemoveChild(Pane*); - void GetVertexPos() const; + + void Show() { SetVisible(true); } + void Hide() { SetVisible(false); } + + bool IsVisible() const { return detail::TestBit(mFlags, PaneFlag_Visible); } + bool IsInfluencedAlpha() const { return detail::TestBit(mFlags, PaneFlag_InfluencedAlpha); } + bool IsLocationAdjust() const { return detail::TestBit(mFlags, PaneFlag_LocationAdjust); } + bool IsUserAllocated() const { return detail::TestBit(mFlags, PaneFlag_UserAllocated); } + bool IsGlobalMatrixDirty() const { + return detail::TestBit(mFlags, PaneFlag_IsGlobalMatrixDirty); + } + bool IsUserMatrix() const { return detail::TestBit(mFlags, PaneFlag_UserMatrix); } + bool IsUserGlobalMatrix() const { return detail::TestBit(mFlags, PaneFlag_UserGlobalMatrix); } + bool IsConstantBufferReady() const { + return detail::TestBit(mFlags, PaneFlag_IsConstantBufferReady); + } + bool IsMaxPanelFlag() const { return detail::TestBit(mFlags, PaneFlag_MaxPaneFlag); } + + const util::Float3& GetPosition() const { return mPosition; } + void SetPosition(const util::Float3& position) { + mPosition = position; + SetGlobalMatrixDirty(true); + } + + const util::Float3& GetRotation() const { return mRotation; } + void SetRotation(const util::Float3& rotation) { + mRotation = rotation; + SetGlobalMatrixDirty(true); + } + + const util::Float2& GetScale() const { return mScale; } + void SetScale(const util::Float2& scale) { + mScale = scale; + SetGlobalMatrixDirty(true); + } + + const Size& GetSize() const { return mSize; } + void SetSize(const Size& size) { + mSize.Set(size.width, size.height); + SetGlobalMatrixDirty(true); + } + + void SetAlpha(u8 alpha) { mAlpha = alpha; } + + u8 GetGlobalAlpha() const { return mGlobalAlpha; } + + const util::MatrixT4x3fType& GetMtx() const { return mMtx; } + +protected: + virtual void LoadMtx(DrawInfo&); + virtual Pane* FindPaneByNameRecursive(const char*); + virtual const Pane* FindPaneByNameRecursive(const char*) const; + virtual Material* FindMaterialByNameRecursive(const char*); + virtual const Material* FindMaterialByNameRecursive(const char*) const; + + const util::Float2& GetVertexPos() const; + + void SetVisible(bool state) { detail::SetBit(&mFlags, PaneFlag_Visible, state); } + void SetInfluencedAlpha(bool state) { + detail::SetBit(&mFlags, PaneFlag_InfluencedAlpha, state); + } + void SetLocationAdjus(bool state) { detail::SetBit(&mFlags, PaneFlag_LocationAdjust, state); } + void SetUserAllocated(bool state) { detail::SetBit(&mFlags, PaneFlag_UserAllocated, state); } + void SetGlobalMatrixDirty(bool state) { + detail::SetBit(&mFlags, PaneFlag_IsGlobalMatrixDirty, state); + } + void SetUserMatrix(bool state) { detail::SetBit(&mFlags, PaneFlag_UserMatrix, state); } + void SetUserGlobalMatrix(bool state) { + detail::SetBit(&mFlags, PaneFlag_UserGlobalMatrix, state); + } + void setConstantBufferReady(bool state) { + detail::SetBit(&mFlags, PaneFlag_IsConstantBufferReady, state); + } + void setMaxPanelFlag(bool state) { detail::SetBit(&mFlags, PaneFlag_MaxPaneFlag, state); } + +private: + void Initialize(); + const Pane& operator=(const Pane&); + void CalculateScaleFromPartsRoot(util::Float2*, Pane*) const; + void AllocateAndCopyAnimatedExtUserData(const ResExtUserDataList*); + void CalculateGlobalMatrixSelf(CalculateContext&); Pane* mParent; - u64 _10; - u64 _18; - u64 _20; - u64 _28; - float mPositionX; - float mPositionY; - float mPositionZ; - float mRotationX; - float mRotationY; - float mRotationZ; - float mScaleX; - float mScaleY; - float mSizeX; - float mSizeY; + PaneList mChildList; + util::Float3 mPosition; + util::Float3 mRotation; + util::Float2 mScale; + Size mSize; u8 mFlags; u8 mAlpha; - u8 mAlphaInfluence; - u8 mOriginFlags; - u32 _5C; - u64 _60; + u8 mGlobalAlpha; + u8 mBasePosition; + u8 mFlagEx; + u32 mSystemDataFlags; Layout* mLayout; - u128 _70; - u128 _80; - u128 _90; - u64 _A0; - u64 _A8; + util::MatrixT4x3fType mMtx; + const util::MatrixT4x3fType* mUserMtx; + const ResExtUserDataList* mExtUserDataList; void* mAnimExtUserData; - char mPanelName[0x18]; - u8 _D0; - char mUserData[8]; - u8 _D9; + char mPanelName[25]; + char mUserData[9]; u16 _DA; u32 _DC; }; diff --git a/include/nn/ui2d/Types.h b/include/nn/ui2d/Types.h new file mode 100644 index 0000000..8913e54 --- /dev/null +++ b/include/nn/ui2d/Types.h @@ -0,0 +1,55 @@ + +namespace nn::ui2d { + +enum PaneFlag { + PaneFlag_Visible, + PaneFlag_InfluencedAlpha, + PaneFlag_LocationAdjust, + PaneFlag_UserAllocated, + PaneFlag_IsGlobalMatrixDirty, + PaneFlag_UserMatrix, + PaneFlag_UserGlobalMatrix, + PaneFlag_IsConstantBufferReady, + PaneFlag_MaxPaneFlag +}; + +enum PaneFlagEx { + PaneFlagEx_IgnorePartsMagnify, + PaneFlagEx_PartsMagnifyAdjustToPartsBound, + PaneFlagEx_ExtUserDataAnimationEnabled, + PaneFlagEx_ViewerInvisible, + PaneFlagEx_IsConstantBufferReadySelf, + PaneFlagEx_IsCalculationFinishedSelf, + PaneFlagEx_DynamicExtUserDataEnabled, + PaneFlagEx_MaxPaneFlagEx +}; + +struct Size { + static Size Create(f32, f32); + + void Set(f32 aWidth, f32 aHeight) { + width = aWidth; + height = aHeight; + } + + f32 width; + f32 height; +}; +} // namespace nn::ui2d + +namespace nn::ui2d::detail { + +template +void SetBit(T* pBits, s32 pos, bool val) { + const T mask = static_cast(1 << pos); + *pBits &= ~mask; + *pBits |= mask * val; +} + +template +bool TestBit(T bits, s32 pos) { + const T mask = static_cast(1 << pos); + return bits & mask; +} + +} // namespace nn::ui2d::detail diff --git a/include/nn/ui2d/Util.h b/include/nn/ui2d/Util.h new file mode 100644 index 0000000..de57073 --- /dev/null +++ b/include/nn/ui2d/Util.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +namespace nn::gfx { +class MemoryPool; +} // namespace nn::gfx + +namespace nn::ui2d { + +class AnimTransform; +class DrawInfo; +class Group; +class Layout; +class Pane; +class Material; +struct ResHermiteKey; +class ResourceTextureInfo; +class ShaderInfo; +struct ResExtUserDataList; +class ResExtUserData; + +bool LoadTexture(ResourceTextureInfo*, gfx::Device*, const void*); +void LoadArchiveShader(ShaderInfo*, gfx::Device*, void*, const void*, gfx::MemoryPool*, s64, u64); +void FreeArchiveShader(gfx::Device*, ShaderInfo*); +void ConvertBlendsToArchiveShaderName(char*, s32, s32); +bool ConvertArchiveShaderNameToBlends(s32*, s32*, const char*); +s32 SearchShaderVariationIndexFromTable(const void*, s32, s32); +void BindAnimation(AnimTransform*, Group*, bool); +void UnbindAnimation(AnimTransform*, Group*); +bool IsContain(const Pane*, const util::Float2&); +Pane* FindHitPane(Pane*, const util::Float2&); +const Pane* FindHitPane(const Pane*, const util::Float2&); +Pane* FindHitPane(Layout*, const util::Float2&); +const Pane* FindHitPane(const Layout*, const util::Float2&); +Pane* GetNextPane(Pane*); +Pane* ClonePaneTree(const Pane*, gfx::Device*); +Pane* ClonePaneTreeWithPartsLayout(const Pane*, Layout*, gfx::Device*, Layout*); +bool ComparePaneTreeTest(const Pane*, const Pane*); +void DrawNullAndBoundingPane(gfx::CommandBuffer&, DrawInfo&, Material&, const Pane*, + const util::Unorm8x4&, const util::Unorm8x4&); +float GetHermiteCurveValue(float, const ResHermiteKey*, s32); +const ResExtUserData* GetExtUserData(const ResExtUserDataList*, const char*); +u64 GetAlignedBufferSize(gfx::Device*, gfx::GpuAccess, u64); +void SetDefaultShaderId(Material*, s32); + +} // namespace nn::ui2d diff --git a/include/nn/util/MathTypes.h b/include/nn/util/MathTypes.h index 92e320e..c5d8fc9 100644 --- a/include/nn/util/MathTypes.h +++ b/include/nn/util/MathTypes.h @@ -2,45 +2,65 @@ #include +namespace nn::util::neon { + +struct MatrixRowMajor4x3fType { + f32 m[4][3]; +}; + +struct MatrixColumnMajor4x3fType { + f32 m[3][4]; +}; + +struct MatrixRowMajor4x4fType { + f32 m[4][4]; +}; + +struct MatrixColumnMajor4x4fType { + f32 m[4][4]; +}; + +} // namespace nn::util::neon + namespace nn::util { typedef uint32_t AngleIndex; struct Float2 { union { - float v[2]; + f32 v[2]; struct { - float x; - float y; + f32 x; + f32 y; }; }; }; struct Float3 { union { - float v[3]; + f32 v[3]; struct { - float x; - float y; - float z; + f32 x; + f32 y; + f32 z; }; }; }; struct Float4 { union { - float v[4]; + f32 v[4]; struct { - float x; - float y; - float z; - float w; + f32 x; + f32 y; + f32 z; + f32 w; }; }; }; struct FloatColumnMajor4x3 { - float m[3][4]; + f32 m[3][4]; }; struct Unorm8x4 { @@ -51,4 +71,9 @@ struct Unorm8x4 { typedef Unorm8x4 Color4u8Type; +typedef neon::MatrixRowMajor4x3fType Matrix4x3fType; +typedef neon::MatrixRowMajor4x4fType Matrix4x4fType; +typedef neon::MatrixColumnMajor4x3fType MatrixT4x3fType; +typedef neon::MatrixColumnMajor4x4fType MatrixT4x4fType; + } // namespace nn::util diff --git a/include/nn/util/util_IntrusiveList.h b/include/nn/util/util_IntrusiveList.h new file mode 100644 index 0000000..a0d7b42 --- /dev/null +++ b/include/nn/util/util_IntrusiveList.h @@ -0,0 +1,354 @@ +#pragma once + +#include +#include + +namespace nn::util { + +namespace detail { + +class IntrusiveListImplementation; + +} + +class IntrusiveListNode { + NN_NO_COPY(IntrusiveListNode); + +public: + IntrusiveListNode() : m_Prev(this), m_Next(this) {} + + bool IsLinked() const { return m_Next != this; } + +private: + friend class detail::IntrusiveListImplementation; + + IntrusiveListNode* GetPrev() { return m_Prev; } + const IntrusiveListNode* GetPrev() const { return m_Prev; } + IntrusiveListNode* GetNext() { return m_Next; } + const IntrusiveListNode* GetNext() const { return m_Next; } + + void LinkPrev(IntrusiveListNode* node) { LinkPrev(node, node); } + + void LinkPrev(IntrusiveListNode* first, IntrusiveListNode* last) { + IntrusiveListNode* node = last->m_Prev; + first->m_Prev = m_Prev; + node->m_Next = this; + m_Prev->m_Next = first; + m_Prev = node; + } + + void LinkNext(IntrusiveListNode*); + void LinkNext(IntrusiveListNode*, IntrusiveListNode*); + + void Unlink() { Unlink(m_Next); } + + void Unlink(IntrusiveListNode* last) { + IntrusiveListNode* node = last->m_Prev; + m_Prev->m_Next = last; + last->m_Prev = m_Prev; + node->m_Next = this; + m_Prev = node; + } + + IntrusiveListNode* m_Prev; + IntrusiveListNode* m_Next; +}; + +namespace detail { + +class IntrusiveListImplementation { + NN_NO_COPY(IntrusiveListImplementation); + +public: + typedef IntrusiveListNode value_type; + typedef int difference_type; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef int size_type; + + class const_iterator { + public: + typedef const value_type value_type; + typedef difference_type difference_type; + typedef value_type* pointer; + typedef value_type& reference; + + const_iterator(pointer p) : m_Node(p) {} + + reference operator*() const { return *m_Node; } + + pointer operator->() const; + const_iterator& operator++(); + const_iterator operator++(int); + const_iterator& operator--(); + const_iterator operator--(int); + bool operator==(const const_iterator&) const; + bool operator!=(const const_iterator& ci) const { return m_Node != ci.m_Node; } + + private: + pointer m_Node; + }; + + class iterator { + public: + typedef value_type value_type; + typedef value_type* pointer; + typedef value_type& reference; + + iterator(pointer p) : m_Node(p) {} + + operator const_iterator() const { return m_Node; } + + reference operator*() const { return *m_Node; } + pointer operator->() const { return m_Node; } + + iterator& operator++() { + m_Node = m_Node->GetNext(); + return *this; + } + + iterator operator++(int) { + iterator temporary(*this); + ++(*this); + return temporary; + } + + iterator& operator--(); + iterator operator--(int); + bool operator==(const iterator&) const; + + bool operator!=(const iterator& i) const { return m_Node != i.m_Node; } + + private: + pointer m_Node; + }; + + IntrusiveListImplementation() : m_Root() {} + + void push_back(reference node) { m_Root.LinkPrev(&node); } + + void push_front(reference); + void pop_back(); + + void pop_front() { m_Root.GetNext()->Unlink(); } + + reference back(); + reference back() const; + reference front(); + reference front() const; + + iterator begin() { return m_Root.GetNext(); } + const_iterator begin() const { return m_Root.GetNext(); } + + iterator end() { return &m_Root; } + const_iterator end() const { return &m_Root; } + + iterator iterator_to(reference value) { return iterator(&value); } + const_iterator iterator_to(reference value) const { return iterator(&value); } + + size_type size() const; + + bool empty() const { return !m_Root.IsLinked(); } + + iterator erase(const_iterator position) { + iterator temporary(ToMutable(position)); + if (temporary != end()) { + (temporary++)->Unlink(); + } + return temporary; + } + + void clear() { + while (!empty()) { + pop_front(); + } + } + + iterator insert(const_iterator position, reference node) { + ToMutable(position)->LinkPrev(&node); + return iterator(&node); + } + + void splice(const_iterator, IntrusiveListImplementation&); + void splice(const_iterator, IntrusiveListImplementation&, const_iterator); + void splice(const_iterator, IntrusiveListImplementation&, const_iterator, const_iterator); + +private: + void SpliceImplementation(const_iterator, const_iterator, const_iterator); + + iterator ToMutable(const_iterator i) const { return const_cast(&*i); } + + value_type m_Root; +}; + +} // namespace detail + +template +class IntrusiveList { + NN_NO_COPY(IntrusiveList); + +public: + class iterator; + class const_iterator; + + typedef T value_type; + typedef value_type& reference; + typedef const value_type& const_reference; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + typedef int size_type; + + class const_iterator { + public: + typedef T value_type; + typedef value_type* pointer; + typedef value_type& reference; + + reference operator*() const; + pointer operator->() const; + const_iterator& operator++(); + const_iterator operator++(int); + const_iterator& operator--(); + const_iterator operator--(int); + bool operator==(const const_iterator&) const; + bool operator!=(const const_iterator&) const; + + private: + friend class IntrusiveList; + + const_iterator(detail::IntrusiveListImplementation::const_iterator ci) : m_Iterator(ci) {} + + detail::IntrusiveListImplementation::const_iterator GetImplementationIterator() const { + return m_Iterator; + } + + detail::IntrusiveListImplementation::const_iterator m_Iterator; + }; + + class iterator { + public: + typedef T value_type; + typedef value_type* pointer; + typedef value_type& reference; + + operator const_iterator() const { + return static_cast(m_Iterator); + } + + reference operator*() const { return NodeTraits::GetItem(*m_Iterator); } + + pointer operator->() const { return &NodeTraits::GetItem(*m_Iterator); } + + iterator& operator++() { + ++m_Iterator; + return *this; + } + + iterator operator++(int) { + iterator temporary(*this); + ++m_Iterator; + return temporary; + } + + iterator& operator--(); + iterator operator--(int); + bool operator==(const iterator&) const; + + bool operator!=(const iterator& i) const { return m_Iterator != i.m_Iterator; } + + private: + friend class IntrusiveList; + + iterator(detail::IntrusiveListImplementation::iterator iter) : m_Iterator(iter) {} + + detail::IntrusiveListImplementation::iterator GetImplementationIterator() const; + + detail::IntrusiveListImplementation::iterator m_Iterator; + }; + + IntrusiveList() : m_Implementation() {} + + void push_back(reference value) { m_Implementation.push_back(ToNode(value)); } + + void push_front(reference); + void pop_back(); + void pop_front(); + reference front(); + reference front() const; + reference back(); + reference back() const; + + iterator begin() { return m_Implementation.begin(); } + const_iterator begin() const { return m_Implementation.begin(); } + + const_iterator cbegin() const; + + iterator end() { return m_Implementation.end(); } + const_iterator end() const { return m_Implementation.end(); } + + const_iterator cend() const; + reverse_iterator rbegin(); + const_reverse_iterator rbegin() const; + const_reverse_iterator crbegin() const; + reverse_iterator rend(); + const_reverse_iterator rend() const; + const_reverse_iterator crend() const; + + iterator iterator_to(reference value) { return m_Implementation.iterator_to(ToNode(value)); } + const_iterator iterator_to(const_reference value) const { + return m_Implementation.iterator_to(ToNode(value)); + } + + size_type size() const; + bool empty() const; + + iterator erase(const_iterator position) { + detail::IntrusiveListImplementation::iterator result = + m_Implementation.erase(position.GetImplementationIterator()); + return result; + } + + void clear() { m_Implementation.clear(); } + + iterator insert(const_iterator position, reference value) { + detail::IntrusiveListImplementation::iterator result = + m_Implementation.insert(position.GetImplementationIterator(), ToNode(value)); + return result; + } + + void splice(const_iterator, IntrusiveList&); + void splice(const_iterator, IntrusiveList&, const_iterator); + void splice(const_iterator, IntrusiveList&, const_iterator, const_iterator); + +private: + IntrusiveListNode& ToNode(reference ref) const { return NodeTraits::GetNode(ref); } + + const IntrusiveListNode& ToNode(const_reference) const; + reference ToReference(IntrusiveListNode&) const; + const_reference ToReference(const IntrusiveListNode&) const; + + detail::IntrusiveListImplementation m_Implementation; +}; + +template +class IntrusiveListMemberNodeTraits { + friend class IntrusiveList; + + static IntrusiveListNode& GetNode(T& ref) { return ref.*Member; } + + static const IntrusiveListNode& GetNode(const T& ref) { return ref.*Member; } + + static T& GetItem(IntrusiveListNode& node) { + return *reinterpret_cast(reinterpret_cast(&node) - GetOffset()); + } + + static const T& GetItem(const IntrusiveListNode& node) { + return *reinterpret_cast(reinterpret_cast(&node) - GetOffset()); + } + + static uintptr_t GetOffset() { + return reinterpret_cast(&(reinterpret_cast(0)->*Member)); + } +}; + +} // namespace nn::util