//-------------------------------------------------------------------------------------- // File: D3DXGlobal.h // // Direct3D 11 Effects helper defines and data structures // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // http://go.microsoft.com/fwlink/p/?LinkId=271568 //-------------------------------------------------------------------------------------- #pragma once #include #include namespace D3DX11Debug { // Helper sets a D3D resource name string (used by PIX and debug layer leak reporting). inline void SetDebugObjectName(_In_ ID3D11DeviceChild* resource, _In_z_ const char *name ) { #if !defined(NO_D3D11_DEBUG_NAME) && ( defined(_DEBUG) || defined(PROFILE) ) resource->SetPrivateData( WKPDID_D3DDebugObjectName, static_cast(strlen(name)), name ); #else UNREFERENCED_PARAMETER(resource); UNREFERENCED_PARAMETER(name); #endif } template inline void SetDebugObjectName(_In_ ID3D11DeviceChild* resource, _In_z_ const char (&name)[TNameLength]) { #if !defined(NO_D3D11_DEBUG_NAME) && ( defined(_DEBUG) || defined(PROFILE) ) resource->SetPrivateData(WKPDID_D3DDebugObjectName, TNameLength - 1, name); #else UNREFERENCED_PARAMETER(resource); UNREFERENCED_PARAMETER(name); #endif } } using namespace D3DX11Debug; #define SAFE_RELEASE(p) { if (p) { (p)->Release(); (p) = nullptr; } } #define SAFE_ADDREF(p) { if (p) { (p)->AddRef(); } } #define SAFE_DELETE_ARRAY(p) { delete [](p); p = nullptr; } #define SAFE_DELETE(p) { delete (p); p = nullptr; } #if FXDEBUG #define __BREAK_ON_FAIL { __debugbreak(); } #else #define __BREAK_ON_FAIL #endif #define VA(x, action) { hr = (x); if (FAILED(hr)) { action; __BREAK_ON_FAIL; return hr; } } #define VNA(x,action) { if (!(x)) { action; __BREAK_ON_FAIL; hr = E_OUTOFMEMORY; goto lExit; } } #define VBA(x,action) { if (!(x)) { action; __BREAK_ON_FAIL; hr = E_FAIL; goto lExit; } } #define VHA(x,action) { hr = (x); if (FAILED(hr)) { action; __BREAK_ON_FAIL; goto lExit; } } #define V(x) { VA (x, 0) } #define VN(x) { VNA(x, 0) } #define VB(x) { VBA(x, 0) } #define VH(x) { VHA(x, 0) } #define VBD(x,str) { VBA(x, DPF(1,str)) } #define VHD(x,str) { VHA(x, DPF(1,str)) } #define VEASSERT(x) { hr = (x); if (FAILED(hr)) { __BREAK_ON_FAIL; assert(!#x); goto lExit; } } #define VNASSERT(x) { if (!(x)) { __BREAK_ON_FAIL; assert(!#x); hr = E_OUTOFMEMORY; goto lExit; } } #define D3DX11FLTASSIGN(a,b) { *reinterpret_cast< UINT32* >(&(a)) = *reinterpret_cast< UINT32* >(&(b)); } // Preferred data alignment -- must be a power of 2! static const uint32_t c_DataAlignment = sizeof(UINT_PTR); inline uint32_t AlignToPowerOf2(uint32_t Value, uint32_t Alignment) { assert((Alignment & (Alignment - 1)) == 0); // to align to 2^N, add 2^N - 1 and AND with all but lowest N bits set _Analysis_assume_(Alignment > 0 && Value < MAXDWORD - Alignment); return (Value + Alignment - 1) & (~(Alignment - 1)); } inline void * AlignToPowerOf2(void *pValue, UINT_PTR Alignment) { assert((Alignment & (Alignment - 1)) == 0); // to align to 2^N, add 2^N - 1 and AND with all but lowest N bits set return (void *)(((UINT_PTR)pValue + Alignment - 1) & (~((UINT_PTR)Alignment - 1))); } namespace D3DX11Core { ////////////////////////////////////////////////////////////////////////// // CMemoryStream - A class to simplify reading binary data ////////////////////////////////////////////////////////////////////////// class CMemoryStream { uint8_t *m_pData; size_t m_cbData; size_t m_readPtr; public: HRESULT SetData(_In_reads_bytes_(size) const void *pData, _In_ size_t size); HRESULT Read(_Out_ uint32_t *pUint); HRESULT Read(_Outptr_result_buffer_(size) void **ppData, _In_ size_t size); HRESULT Read(_Outptr_ LPCSTR *ppString); HRESULT ReadAtOffset(_In_ size_t offset, _In_ size_t size, _Outptr_result_buffer_(size) void **ppData); HRESULT ReadAtOffset(_In_ size_t offset, _Outptr_result_z_ LPCSTR *ppString); size_t GetPosition(); HRESULT Seek(_In_ size_t offset); CMemoryStream(); ~CMemoryStream(); }; } #if defined(_DEBUG) && !defined(_M_X64) namespace D3DX11Debug { // This variable indicates how many ticks to go before rolling over // all of the timer variables in the FX system. // It is read from the system registry in debug builds; in retail the high bit is simply tested. _declspec(selectany) unsigned int g_TimerRolloverCount = 0x80000000; } #endif // _DEBUG && !_M_X64 ////////////////////////////////////////////////////////////////////////// // CEffectVector - A vector implementation ////////////////////////////////////////////////////////////////////////// template class CEffectVector { protected: #if _DEBUG T *m_pCastData; // makes debugging easier to have a casted version of the data #endif // _DEBUG uint8_t *m_pData; uint32_t m_MaxSize; uint32_t m_CurSize; HRESULT Grow() { return Reserve(m_CurSize + 1); } HRESULT Reserve(_In_ uint32_t DesiredSize) { if (DesiredSize > m_MaxSize) { uint8_t *pNewData; uint32_t newSize = std::max(m_MaxSize * 2, DesiredSize); if (newSize < 16) newSize = 16; if ((newSize < m_MaxSize) || (newSize < m_CurSize) || (newSize >= UINT_MAX / sizeof(T))) { m_hLastError = E_OUTOFMEMORY; return m_hLastError; } pNewData = new uint8_t[newSize * sizeof(T)]; if (pNewData == nullptr) { m_hLastError = E_OUTOFMEMORY; return m_hLastError; } if (m_pData) { memcpy(pNewData, m_pData, m_CurSize * sizeof(T)); delete []m_pData; } m_pData = pNewData; m_MaxSize = newSize; } #if _DEBUG m_pCastData = (T*) m_pData; #endif // _DEBUG return S_OK; } public: HRESULT m_hLastError; CEffectVector() : m_hLastError(S_OK), m_pData(nullptr), m_CurSize(0), m_MaxSize(0) { #if _DEBUG m_pCastData = nullptr; #endif // _DEBUG } ~CEffectVector() { Clear(); } // cleanly swaps two vectors -- useful for when you want // to reallocate a vector and copy data over, then swap them back void SwapVector(_Out_ CEffectVector &vOther) { uint8_t tempData[sizeof(*this)]; memcpy(tempData, this, sizeof(*this)); memcpy(this, &vOther, sizeof(*this)); memcpy(&vOther, tempData, sizeof(*this)); } HRESULT CopyFrom(_In_ const CEffectVector &vOther) { HRESULT hr = S_OK; Clear(); VN( m_pData = new uint8_t[vOther.m_MaxSize * sizeof(T)] ); m_CurSize = vOther.m_CurSize; m_MaxSize = vOther.m_MaxSize; m_hLastError = vOther.m_hLastError; for (size_t i = 0; i < m_CurSize; ++ i) { ((T*)m_pData)[i] = ((T*)vOther.m_pData)[i]; } lExit: #if _DEBUG m_pCastData = (T*) m_pData; #endif // _DEBUG return hr; } void Clear() { Empty(); SAFE_DELETE_ARRAY(m_pData); m_MaxSize = 0; #if _DEBUG m_pCastData = nullptr; #endif // _DEBUG } void ClearWithoutDestructor() { m_CurSize = 0; m_hLastError = S_OK; SAFE_DELETE_ARRAY(m_pData); m_MaxSize = 0; #if _DEBUG m_pCastData = nullptr; #endif // _DEBUG } void Empty() { // manually invoke destructor on all elements for (size_t i = 0; i < m_CurSize; ++ i) { ((T*)m_pData + i)->~T(); } m_CurSize = 0; m_hLastError = S_OK; } T* Add() { if (FAILED(Grow())) return nullptr; // placement new return new((T*)m_pData + (m_CurSize ++)) T; } T* AddRange(_In_ uint32_t count) { if (m_CurSize + count < m_CurSize) { m_hLastError = E_OUTOFMEMORY; return nullptr; } if (FAILED(Reserve(m_CurSize + count))) return nullptr; T *pData = (T*)m_pData + m_CurSize; for (size_t i = 0; i < count; ++ i) { new(pData + i) T; } m_CurSize += count; return pData; } HRESULT Add(_In_ const T& var) { if (FAILED(Grow())) return m_hLastError; memcpy((T*)m_pData + m_CurSize, &var, sizeof(T)); m_CurSize++; return S_OK; } HRESULT AddRange(_In_reads_(count) const T *pVar, _In_ uint32_t count) { if (m_CurSize + count < m_CurSize) { m_hLastError = E_OUTOFMEMORY; return m_hLastError; } if (FAILED(Reserve(m_CurSize + count))) return m_hLastError; memcpy((T*)m_pData + m_CurSize, pVar, count * sizeof(T)); m_CurSize += count; return S_OK; } HRESULT Insert(_In_ const T& var, _In_ uint32_t index) { assert(index < m_CurSize); if (FAILED(Grow())) return m_hLastError; memmove((T*)m_pData + index + 1, (T*)m_pData + index, (m_CurSize - index) * sizeof(T)); memcpy((T*)m_pData + index, &var, sizeof(T)); m_CurSize++; return S_OK; } HRESULT InsertRange(_In_reads_(count) const T *pVar, _In_ uint32_t index, _In_ uint32_t count) { assert(index < m_CurSize); if (m_CurSize + count < m_CurSize) { m_hLastError = E_OUTOFMEMORY; return m_hLastError; } if (FAILED(Reserve(m_CurSize + count))) return m_hLastError; memmove((T*)m_pData + index + count, (T*)m_pData + index, (m_CurSize - index) * sizeof(T)); memcpy((T*)m_pData + index, pVar, count * sizeof(T)); m_CurSize += count; return S_OK; } inline T& operator[](_In_ size_t index) { assert(index < m_CurSize); return ((T*)m_pData)[index]; } // Deletes element at index and shifts all other values down void Delete(_In_ uint32_t index) { assert(index < m_CurSize); -- m_CurSize; memmove((T*)m_pData + index, (T*)m_pData + index + 1, (m_CurSize - index) * sizeof(T)); } // Deletes element at index and moves the last element into its place void QuickDelete(_In_ uint32_t index) { assert(index < m_CurSize); -- m_CurSize; memcpy((T*)m_pData + index, (T*)m_pData + m_CurSize, sizeof(T)); } inline uint32_t GetSize() const { return m_CurSize; } inline T* GetData() const { return (T*)m_pData; } uint32_t FindIndexOf(_In_ const void *pEntry) const { for (size_t i = 0; i < m_CurSize; ++ i) { if (((T*)m_pData + i) == pEntry) return i; } return -1; } void Sort(int (__cdecl *pfnCompare)(const void *pElem1, const void *pElem2)) { qsort(m_pData, m_CurSize, sizeof(T), pfnCompare); } }; ////////////////////////////////////////////////////////////////////////// // CEffectVectorOwner - implements a vector of ptrs to objects. The vector owns the objects. ////////////////////////////////////////////////////////////////////////// template class CEffectVectorOwner : public CEffectVector { public: ~CEffectVectorOwner() { Clear(); for (size_t i=0; i::Delete(index); } }; ////////////////////////////////////////////////////////////////////////// // Checked uint32_t, uint64_t // Use CheckedNumber only with uint32_t and uint64_t ////////////////////////////////////////////////////////////////////////// template class CheckedNumber { T m_Value; bool m_bValid; public: CheckedNumber() : m_Value(0), m_bValid(true) { } CheckedNumber(const T &value) : m_Value(value), m_bValid(true) { } CheckedNumber(const CheckedNumber &value) : m_bValid(value.m_bValid), m_Value(value.m_Value) { } CheckedNumber &operator+(const CheckedNumber &other) { CheckedNumber Res(*this); return Res+=other; } CheckedNumber &operator+=(const CheckedNumber &other) { if (!other.m_bValid) { m_bValid = false; } else { m_Value += other.m_Value; if (m_Value < other.m_Value) m_bValid = false; } return *this; } CheckedNumber &operator*(const CheckedNumber &other) { CheckedNumber Res(*this); return Res*=other; } CheckedNumber &operator*=(const CheckedNumber &other) { if (!other.m_bValid) { m_bValid = false; } else { if (other.m_Value != 0) { if (m_Value > MaxValue / other.m_Value) { m_bValid = false; } } m_Value *= other.m_Value; } return *this; } HRESULT GetValue(_Out_ T *pValue) { if (!m_bValid) { *pValue = uint32_t(-1); return E_FAIL; } *pValue = m_Value; return S_OK; } }; typedef CheckedNumber CCheckedDword; typedef CheckedNumber CCheckedDword64; ////////////////////////////////////////////////////////////////////////// // Data Block Store - A linked list of allocations ////////////////////////////////////////////////////////////////////////// class CDataBlock { protected: uint32_t m_size; uint32_t m_maxSize; uint8_t *m_pData; CDataBlock *m_pNext; bool m_IsAligned; // Whether or not to align the data to c_DataAlignment public: // AddData appends an existing use buffer to the data block HRESULT AddData(_In_reads_bytes_(bufferSize) const void *pNewData, _In_ uint32_t bufferSize, _Outptr_ CDataBlock **ppBlock); // Allocate reserves bufferSize bytes of contiguous memory and returns a pointer to the user _Success_(return != nullptr) void* Allocate(_In_ uint32_t bufferSize, _Outptr_ CDataBlock **ppBlock); void EnableAlignment(); CDataBlock(); ~CDataBlock(); friend class CDataBlockStore; }; class CDataBlockStore { protected: CDataBlock *m_pFirst; CDataBlock *m_pLast; uint32_t m_Size; uint32_t m_Offset; // m_Offset gets added to offsets returned from AddData & AddString. Use this to set a global for the entire string block bool m_IsAligned; // Whether or not to align the data to c_DataAlignment public: #if _DEBUG uint32_t m_cAllocations; #endif public: HRESULT AddString(_In_z_ LPCSTR pString, _Inout_ uint32_t *pOffset); // Writes a null-terminated string to buffer HRESULT AddData(_In_reads_bytes_(bufferSize) const void *pNewData, _In_ uint32_t bufferSize, _Inout_ uint32_t *pOffset); // Writes data block to buffer // Memory allocator support void* Allocate(_In_ uint32_t bufferSize); uint32_t GetSize(); void EnableAlignment(); CDataBlockStore(); ~CDataBlockStore(); }; // Custom allocator that uses CDataBlockStore // The trick is that we never free, so we don't have to keep as much state around // Use PRIVATENEW in CEffectLoader inline void* __cdecl operator new(_In_ size_t s, _In_ CDataBlockStore &pAllocator) { #ifdef _M_X64 assert( s <= 0xffffffff ); #endif return pAllocator.Allocate( (uint32_t)s ); } inline void __cdecl operator delete(_In_opt_ void* p, _In_ CDataBlockStore &pAllocator) { UNREFERENCED_PARAMETER(p); UNREFERENCED_PARAMETER(pAllocator); } ////////////////////////////////////////////////////////////////////////// // Hash table ////////////////////////////////////////////////////////////////////////// #define HASH_MIX(a,b,c) \ { \ a -= b; a -= c; a ^= (c>>13); \ b -= c; b -= a; b ^= (a<<8); \ c -= a; c -= b; c ^= (b>>13); \ a -= b; a -= c; a ^= (c>>12); \ b -= c; b -= a; b ^= (a<<16); \ c -= a; c -= b; c ^= (b>>5); \ a -= b; a -= c; a ^= (c>>3); \ b -= c; b -= a; b ^= (a<<10); \ c -= a; c -= b; c ^= (b>>15); \ } static uint32_t ComputeHash(_In_reads_bytes_(cbToHash) const uint8_t *pb, _In_ uint32_t cbToHash) { uint32_t cbLeft = cbToHash; uint32_t a; uint32_t b; a = b = 0x9e3779b9; // the golden ratio; an arbitrary value uint32_t c = 0; while (cbLeft >= 12) { const uint32_t *pdw = reinterpret_cast(pb); a += pdw[0]; b += pdw[1]; c += pdw[2]; HASH_MIX(a,b,c); pb += 12; cbLeft -= 12; } c += cbToHash; switch(cbLeft) // all the case statements fall through { case 11: c+=((uint32_t) pb[10] << 24); case 10: c+=((uint32_t) pb[9] << 16); case 9 : c+=((uint32_t) pb[8] << 8); // the first byte of c is reserved for the length case 8 : b+=((uint32_t) pb[7] << 24); case 7 : b+=((uint32_t) pb[6] << 16); case 6 : b+=((uint32_t) pb[5] << 8); case 5 : b+=pb[4]; case 4 : a+=((uint32_t) pb[3] << 24); case 3 : a+=((uint32_t) pb[2] << 16); case 2 : a+=((uint32_t) pb[1] << 8); case 1 : a+=pb[0]; } HASH_MIX(a,b,c); return c; } static uint32_t ComputeHashLower(_In_reads_bytes_(cbToHash) const uint8_t *pb, _In_ uint32_t cbToHash) { uint32_t cbLeft = cbToHash; uint32_t a; uint32_t b; a = b = 0x9e3779b9; // the golden ratio; an arbitrary value uint32_t c = 0; while (cbLeft >= 12) { uint8_t pbT[12]; for( size_t i = 0; i < 12; i++ ) pbT[i] = (uint8_t)tolower(pb[i]); uint32_t *pdw = reinterpret_cast(pbT); a += pdw[0]; b += pdw[1]; c += pdw[2]; HASH_MIX(a,b,c); pb += 12; cbLeft -= 12; } c += cbToHash; uint8_t pbT[12]; for( size_t i = 0; i < cbLeft; i++ ) pbT[i] = (uint8_t)tolower(pb[i]); switch(cbLeft) // all the case statements fall through { case 11: c+=((uint32_t) pbT[10] << 24); case 10: c+=((uint32_t) pbT[9] << 16); case 9 : c+=((uint32_t) pbT[8] << 8); // the first byte of c is reserved for the length case 8 : b+=((uint32_t) pbT[7] << 24); case 7 : b+=((uint32_t) pbT[6] << 16); case 6 : b+=((uint32_t) pbT[5] << 8); case 5 : b+=pbT[4]; case 4 : a+=((uint32_t) pbT[3] << 24); case 3 : a+=((uint32_t) pbT[2] << 16); case 2 : a+=((uint32_t) pbT[1] << 8); case 1 : a+=pbT[0]; } HASH_MIX(a,b,c); return c; } static uint32_t ComputeHash(_In_z_ LPCSTR pString) { return ComputeHash(reinterpret_cast(pString), (uint32_t)strlen(pString)); } // 1) these numbers are prime // 2) each is slightly less than double the last // 4) each is roughly in between two powers of 2; // (2^n hash table sizes are VERY BAD; they effectively truncate your // precision down to the n least significant bits of the hash) static const uint32_t c_PrimeSizes[] = { 11, 23, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, 100663319, 201326611, 402653189, 805306457, 1610612741, }; template class CEffectHashTable { protected: struct SHashEntry { uint32_t Hash; T Data; SHashEntry *pNext; }; // Array of hash entries SHashEntry **m_rgpHashEntries; uint32_t m_NumHashSlots; uint32_t m_NumEntries; bool m_bOwnHashEntryArray; public: class CIterator { friend class CEffectHashTable; protected: SHashEntry **ppHashSlot; SHashEntry *pHashEntry; public: T GetData() { assert(pHashEntry != 0); _Analysis_assume_(pHashEntry != 0); return pHashEntry->Data; } uint32_t GetHash() { assert(pHashEntry != 0); _Analysis_assume_(pHashEntry != 0); return pHashEntry->Hash; } }; CEffectHashTable() : m_rgpHashEntries(nullptr), m_NumHashSlots(0), m_NumEntries(0), m_bOwnHashEntryArray(false) { } HRESULT Initialize(_In_ const CEffectHashTable *pOther) { HRESULT hr = S_OK; SHashEntry **rgpNewHashEntries = nullptr; uint32_t valuesMigrated = 0; uint32_t actualSize; Cleanup(); actualSize = pOther->m_NumHashSlots; VN( rgpNewHashEntries = new SHashEntry*[actualSize] ); ZeroMemory(rgpNewHashEntries, sizeof(SHashEntry*) * actualSize); // Expensive operation: rebuild the hash table CIterator iter, nextIter; pOther->GetFirstEntry(&iter); while (!pOther->PastEnd(&iter)) { uint32_t index = iter.GetHash() % actualSize; // we need to advance to the next element // before we seize control of this element and move // it to the new table nextIter = iter; pOther->GetNextEntry(&nextIter); // seize this hash entry, migrate it to the new table SHashEntry *pNewEntry; VN( pNewEntry = new SHashEntry ); pNewEntry->pNext = rgpNewHashEntries[index]; pNewEntry->Data = iter.pHashEntry->Data; pNewEntry->Hash = iter.pHashEntry->Hash; rgpNewHashEntries[index] = pNewEntry; iter = nextIter; ++ valuesMigrated; } assert(valuesMigrated == pOther->m_NumEntries); m_rgpHashEntries = rgpNewHashEntries; m_NumHashSlots = actualSize; m_NumEntries = pOther->m_NumEntries; m_bOwnHashEntryArray = true; rgpNewHashEntries = nullptr; lExit: SAFE_DELETE_ARRAY( rgpNewHashEntries ); return hr; } protected: void CleanArray() { if (m_bOwnHashEntryArray) { SAFE_DELETE_ARRAY(m_rgpHashEntries); m_bOwnHashEntryArray = false; } } public: void Cleanup() { for (size_t i = 0; i < m_NumHashSlots; ++ i) { SHashEntry *pCurrentEntry = m_rgpHashEntries[i]; SHashEntry *pTempEntry; while (nullptr != pCurrentEntry) { pTempEntry = pCurrentEntry->pNext; SAFE_DELETE(pCurrentEntry); pCurrentEntry = pTempEntry; -- m_NumEntries; } } CleanArray(); m_NumHashSlots = 0; assert(m_NumEntries == 0); } ~CEffectHashTable() { Cleanup(); } static uint32_t GetNextHashTableSize(_In_ uint32_t DesiredSize) { // figure out the next logical size to use for (size_t i = 0; i < _countof(c_PrimeSizes); ++i ) { if (c_PrimeSizes[i] >= DesiredSize) { return c_PrimeSizes[i]; } } return DesiredSize; } // O(n) function // Grows to the next suitable size (based off of the prime number table) // DesiredSize is merely a suggestion HRESULT Grow(_In_ uint32_t DesiredSize, _In_ uint32_t ProvidedArraySize = 0, _In_reads_opt_(ProvidedArraySize) void** ProvidedArray = nullptr, _In_ bool OwnProvidedArray = false) { HRESULT hr = S_OK; SHashEntry **rgpNewHashEntries = nullptr; uint32_t valuesMigrated = 0; uint32_t actualSize; VB( DesiredSize > m_NumHashSlots ); actualSize = GetNextHashTableSize(DesiredSize); if (ProvidedArray && ProvidedArraySize >= actualSize) { rgpNewHashEntries = reinterpret_cast(ProvidedArray); } else { OwnProvidedArray = true; VN( rgpNewHashEntries = new SHashEntry*[actualSize] ); } ZeroMemory(rgpNewHashEntries, sizeof(SHashEntry*) * actualSize); // Expensive operation: rebuild the hash table CIterator iter, nextIter; GetFirstEntry(&iter); while (!PastEnd(&iter)) { uint32_t index = iter.GetHash() % actualSize; // we need to advance to the next element // before we seize control of this element and move // it to the new table nextIter = iter; GetNextEntry(&nextIter); // seize this hash entry, migrate it to the new table iter.pHashEntry->pNext = rgpNewHashEntries[index]; rgpNewHashEntries[index] = iter.pHashEntry; iter = nextIter; ++ valuesMigrated; } assert(valuesMigrated == m_NumEntries); CleanArray(); m_rgpHashEntries = rgpNewHashEntries; m_NumHashSlots = actualSize; m_bOwnHashEntryArray = OwnProvidedArray; lExit: return hr; } HRESULT AutoGrow() { // arbitrary heuristic -- grow if 1:1 if (m_NumEntries >= m_NumHashSlots) { // grows this hash table so that it is roughly 50% full return Grow(m_NumEntries * 2 + 1); } return S_OK; } #if _DEBUG void PrintHashTableStats() { if (m_NumHashSlots == 0) { DPF(0, "Uninitialized hash table!"); return; } float variance = 0.0f; float mean = (float)m_NumEntries / (float)m_NumHashSlots; uint32_t unusedSlots = 0; DPF(0, "Hash table slots: %d, Entries in table: %d", m_NumHashSlots, m_NumEntries); for (size_t i = 0; i < m_NumHashSlots; ++ i) { uint32_t entries = 0; SHashEntry *pCurrentEntry = m_rgpHashEntries[i]; while (nullptr != pCurrentEntry) { SHashEntry *pCurrentEntry2 = m_rgpHashEntries[i]; // check other hash entries in this slot for hash collisions or duplications while (pCurrentEntry2 != pCurrentEntry) { if (pCurrentEntry->Hash == pCurrentEntry2->Hash) { if (pfnIsEqual(pCurrentEntry->Data, pCurrentEntry2->Data)) { assert(0); DPF(0, "Duplicate entry (identical hash, identical data) found!"); } else { DPF(0, "Hash collision (hash: %d)", pCurrentEntry->Hash); } } pCurrentEntry2 = pCurrentEntry2->pNext; } pCurrentEntry = pCurrentEntry->pNext; ++ entries; } if (0 == entries) { ++ unusedSlots; } // mean must be greater than 0 at this point variance += (float)entries * (float)entries / mean; } variance /= std::max(1.0f, (m_NumHashSlots - 1)); variance -= (mean * mean); DPF(0, "Mean number of entries per slot: %f, Standard deviation: %f, Unused slots; %d", mean, variance, unusedSlots); } #endif // _DEBUG // S_OK if element is found, E_FAIL otherwise HRESULT FindValueWithHash(_In_ T Data, _In_ uint32_t Hash, _Out_ CIterator *pIterator) { assert(m_NumHashSlots > 0); uint32_t index = Hash % m_NumHashSlots; SHashEntry *pEntry = m_rgpHashEntries[index]; while (nullptr != pEntry) { if (Hash == pEntry->Hash && pfnIsEqual(pEntry->Data, Data)) { pIterator->ppHashSlot = m_rgpHashEntries + index; pIterator->pHashEntry = pEntry; return S_OK; } pEntry = pEntry->pNext; } return E_FAIL; } // S_OK if element is found, E_FAIL otherwise HRESULT FindFirstMatchingValue(_In_ uint32_t Hash, _Out_ CIterator *pIterator) { assert(m_NumHashSlots > 0); uint32_t index = Hash % m_NumHashSlots; SHashEntry *pEntry = m_rgpHashEntries[index]; while (nullptr != pEntry) { if (Hash == pEntry->Hash) { pIterator->ppHashSlot = m_rgpHashEntries + index; pIterator->pHashEntry = pEntry; return S_OK; } pEntry = pEntry->pNext; } return E_FAIL; } // Adds data at the specified hash slot without checking for existence HRESULT AddValueWithHash(_In_ T Data, _In_ uint32_t Hash) { HRESULT hr = S_OK; assert(m_NumHashSlots > 0); SHashEntry *pHashEntry; uint32_t index = Hash % m_NumHashSlots; VN( pHashEntry = new SHashEntry ); pHashEntry->pNext = m_rgpHashEntries[index]; pHashEntry->Data = Data; pHashEntry->Hash = Hash; m_rgpHashEntries[index] = pHashEntry; ++ m_NumEntries; lExit: return hr; } // Iterator code: // // CMyHashTable::CIterator myIt; // for (myTable.GetFirstEntry(&myIt); !myTable.PastEnd(&myIt); myTable.GetNextEntry(&myIt) // { myTable.GetData(&myIt); } void GetFirstEntry(_Out_ CIterator *pIterator) { SHashEntry **ppEnd = m_rgpHashEntries + m_NumHashSlots; pIterator->ppHashSlot = m_rgpHashEntries; while (pIterator->ppHashSlot < ppEnd) { if (nullptr != *(pIterator->ppHashSlot)) { pIterator->pHashEntry = *(pIterator->ppHashSlot); return; } ++ pIterator->ppHashSlot; } } bool PastEnd(_Inout_ CIterator *pIterator) { SHashEntry **ppEnd = m_rgpHashEntries + m_NumHashSlots; assert(pIterator->ppHashSlot >= m_rgpHashEntries && pIterator->ppHashSlot <= ppEnd); return (pIterator->ppHashSlot == ppEnd); } void GetNextEntry(_Inout_ CIterator *pIterator) { SHashEntry **ppEnd = m_rgpHashEntries + m_NumHashSlots; assert(pIterator->ppHashSlot >= m_rgpHashEntries && pIterator->ppHashSlot <= ppEnd); assert(pIterator->pHashEntry != 0); _Analysis_assume_(pIterator->pHashEntry != 0); pIterator->pHashEntry = pIterator->pHashEntry->pNext; if (nullptr != pIterator->pHashEntry) { return; } ++ pIterator->ppHashSlot; while (pIterator->ppHashSlot < ppEnd) { pIterator->pHashEntry = *(pIterator->ppHashSlot); if (nullptr != pIterator->pHashEntry) { return; } ++ pIterator->ppHashSlot; } // hit the end of the list, ppHashSlot == ppEnd } void RemoveEntry(_Inout_ CIterator *pIterator) { SHashEntry *pTemp; SHashEntry **ppPrev; SHashEntry **ppEnd = m_rgpHashEntries + m_NumHashSlots; assert(pIterator && !PastEnd(pIterator)); ppPrev = pIterator->ppHashSlot; pTemp = *ppPrev; while (pTemp) { if (pTemp == pIterator->pHashEntry) { *ppPrev = pTemp->pNext; pIterator->ppHashSlot = ppEnd; delete pTemp; return; } ppPrev = &pTemp->pNext; pTemp = pTemp->pNext; } // Should never get here assert(0); } }; // Allocates the hash slots on the regular heap (since the // hash table can grow), but all hash entries are allocated on // a private heap template class CEffectHashTableWithPrivateHeap : public CEffectHashTable { protected: CDataBlockStore *m_pPrivateHeap; public: CEffectHashTableWithPrivateHeap() { m_pPrivateHeap = nullptr; } void Cleanup() { CleanArray(); m_NumHashSlots = 0; m_NumEntries = 0; } ~CEffectHashTableWithPrivateHeap() { Cleanup(); } // Call this only once void SetPrivateHeap(_In_ CDataBlockStore *pPrivateHeap) { assert(nullptr == m_pPrivateHeap); m_pPrivateHeap = pPrivateHeap; } // Adds data at the specified hash slot without checking for existence HRESULT AddValueWithHash(_In_ T Data, _In_ uint32_t Hash) { HRESULT hr = S_OK; assert(m_pPrivateHeap); _Analysis_assume_(m_pPrivateHeap); assert(m_NumHashSlots > 0); SHashEntry *pHashEntry; uint32_t index = Hash % m_NumHashSlots; VN( pHashEntry = new(*m_pPrivateHeap) SHashEntry ); pHashEntry->pNext = m_rgpHashEntries[index]; pHashEntry->Data = Data; pHashEntry->Hash = Hash; m_rgpHashEntries[index] = pHashEntry; ++ m_NumEntries; lExit: return hr; } };