diff --git a/Engine/CMakeLists.txt b/Engine/CMakeLists.txt index dc10984..7991388 100644 --- a/Engine/CMakeLists.txt +++ b/Engine/CMakeLists.txt @@ -18,17 +18,16 @@ add_library(ProjectDelta SHARED "include/delta/definitions.h" "src/delta/platform/os_internal.h" "src/delta/platform/os_win32.cpp" - "src/delta/platform/os_linux.cpp" "include/delta/platform/os.h" "include/delta/pch.h" "src/delta/pch.h" "src/delta/pch.cpp" - "src/delta/core/MemoryManager.h" - "src/delta/core/MemoryManager.cpp" - "src/delta/core/FrameAllocator.h" - "src/delta/core/FrameAllocator.cpp" - "src/delta/internal_definitions.h" - "src/delta/core/FreeListAllocator.h" "src/delta/core/FreeListAllocator.cpp") + "src/delta/core/EngineTypes.h" + "src/delta/core/ThreadContext.h" + "src/delta/core/ThreadContext.cpp" + "src/delta/core/MemoryConfig.h" + "src/delta/core/MemoryConfig.cpp" +) target_compile_definitions(ProjectDelta PRIVATE DLT_EXPORT_SYMBOLS diff --git a/Engine/include/delta/platform/os.h b/Engine/include/delta/platform/os.h index 5d0b22e..1eae07d 100644 --- a/Engine/include/delta/platform/os.h +++ b/Engine/include/delta/platform/os.h @@ -20,19 +20,22 @@ namespace delta::platform { struct OSInfo { - // cpu - char cpuManufacturerId[13]; - char cpuBrandString[sizeof(int) * 12 + 1]; const char* cpuArchitecture; - uint32_t cpuCoreCount; + + uint32_t cpuPhysicalCoreCount; + uint32_t cpuLogicalProcessorCount; + uint32_t maxEngineWorkerCount; + uint32_t osPageSize; + + char cpuBrandString[sizeof(int) * 12 + 1]; + char cpuManufacturerId[13]; + + bool cpuHasSMT; bool cpuHasAVX2; bool cpuHasAVX512f; bool cpuHasAVX512cd; bool cpuHasAVX512er; bool cpuHasAVX512pf; - - // general OS info - uint32_t osPageSize; }; struct MemoryStatus diff --git a/Engine/src/delta/core/EngineTypes.h b/Engine/src/delta/core/EngineTypes.h new file mode 100644 index 0000000..426dda2 --- /dev/null +++ b/Engine/src/delta/core/EngineTypes.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace delta::core +{ + struct ThreadPageCoordinator + { + uint8_t* virtualAddressBase; + size_t commitedOffset; + size_t reservedCapacity; + size_t pageSize; + }; + + struct EngineArena + { + uint8_t* backingMemory; + size_t capacity; + size_t offset; + }; + + struct alignas(64) ThreadExecutionContext + { + uint32_t threadIx; + uint32_t threadId; + + ThreadPageCoordinator pageCoordinator; + EngineArena transientArena; + delta::platform::Timer perThreadTimer; + }; + + extern thread_local ThreadExecutionContext* tl_CurrentThreadContext; +} diff --git a/Engine/src/delta/core/FrameAllocator.cpp b/Engine/src/delta/core/FrameAllocator.cpp deleted file mode 100644 index c45d22c..0000000 --- a/Engine/src/delta/core/FrameAllocator.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "FrameAllocator.h" -#include - -#define ALIGN_OFFSET(offset, alignment) ((offset + (alignment - 1)) & ~(alignment - 1)) - -using FrameAllocator = delta::core::FrameAllocator; -using Node = FrameAllocator::Node; - -static DLT_FORCE_INLINE void* allocate(FrameAllocator* allocator, uint64_t size, uint64_t alignedOffset) -{ - void* result = reinterpret_cast(allocator->current) + alignedOffset; - allocator->offset = size + alignedOffset; - return result; -} - -void delta::core::FrameAllocator_Init(FrameAllocator* allocator, MemoryManager::MemoryState* memState) -{ - allocator->memoryState = memState; - allocator->pageSize = memState->pageSize; - - void* rawPage = MemoryManager::AllocatePageLockFree(memState); - assert(rawPage != nullptr); // failed to allocate a page - - allocator->head = static_cast(rawPage); - allocator->head->next = nullptr; - - allocator->current = allocator->head; - allocator->offset = sizeof(Node); -} - -void* delta::core::FrameAllocator_Allocate(FrameAllocator* allocator, uint64_t size, uint64_t alignment) -{ - uint64_t alignedOffset = ALIGN_OFFSET(allocator->offset, alignment); - - if (alignedOffset + size <= allocator->pageSize) - return allocate(allocator, size, alignedOffset); - - if (allocator->current->next != nullptr) - { - allocator->current = allocator->current->next; - } - else - { - void* newPage = MemoryManager::AllocatePageLockFree(allocator->memoryState); - assert(newPage != nullptr); // failed to allocate a page - - Node* node = static_cast(newPage); - node->next = nullptr; - - allocator->current->next = node; - allocator->current = node; - } - - allocator->offset = sizeof(Node); - alignedOffset = ALIGN_OFFSET(allocator->offset, alignment); - - return allocate(allocator, size, alignedOffset); -} - -void delta::core::FrameAllocator_Flush(FrameAllocator* allocator) -{ - allocator->current = allocator->head; - allocator->offset = sizeof(Node); -} - -void delta::core::FrameAllocator_Shutdown(FrameAllocator* allocator) -{ - Node* current = allocator->head; - while (current != nullptr) - { - Node* n = current->next; - MemoryManager::freePage(allocator->memoryState, current); - current = n; - } -} diff --git a/Engine/src/delta/core/FrameAllocator.h b/Engine/src/delta/core/FrameAllocator.h deleted file mode 100644 index f5b423c..0000000 --- a/Engine/src/delta/core/FrameAllocator.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -namespace delta::core -{ - namespace MemoryManager { struct MemoryState; } - - struct FrameAllocator - { - struct Node { Node* next; }; - - MemoryManager::MemoryState* memoryState; - - Node* head; - Node* current; - - uint64_t pageSize; - uint64_t offset; - }; - - void FrameAllocator_Init(FrameAllocator* allocator, MemoryManager::MemoryState* memState); - void* FrameAllocator_Allocate(FrameAllocator* allocator, uint64_t size, uint64_t alignment = 8); - void FrameAllocator_Flush(FrameAllocator* allocator); - void FrameAllocator_Shutdown(FrameAllocator* allocator); -} diff --git a/Engine/src/delta/core/FreeListAllocator.cpp b/Engine/src/delta/core/FreeListAllocator.cpp deleted file mode 100644 index 3a07bc0..0000000 --- a/Engine/src/delta/core/FreeListAllocator.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "FreeListAllocator.h" -#include "MemoryManager.h" - -#define ALIGN(size, alignment) ((size + (alignment - 1)) & ~(size - 1)) - -using namespace delta::core; - -using MemoryState = MemoryManager::MemoryState; -using Node = FreeListAllocator::Node; - -DLT_FORCE_INLINE void* AllocatePageForBucket(FreeListAllocator* allocator, uint32_t bucketIx, uint64_t size) -{ - void* rawPage = MemoryManager::AllocatePageLockFree(allocator->memState); - - uint64_t numChunks = allocator->pageSize / size; - uint8_t* walker = static_cast(rawPage); - - Node* head = reinterpret_cast(walker); - Node* current = head; - - for (uint32_t i = 0; i < numChunks; i++) - { - walker += size; - Node* next = reinterpret_cast(walker); - current->next = next; - current = next; - } - - current->next = nullptr; - return head; -} - -void delta::core::FreeList_Init(FreeListAllocator* allocator, MemoryState* memState) -{ - allocator->memState = memState; - allocator->pageSize = memState->pageSize; -} - -void* delta::core::FreeList_Allocate(FreeListAllocator* allocator, uint64_t size, uint64_t alignment) -{ - uint64_t aligned = ALIGN(size, alignment); - - if (aligned < FreeListAllocator::MIN_ALLOC_SIZE) - aligned = FreeListAllocator::MIN_ALLOC_SIZE; - - if (aligned <= FreeListAllocator::MAX_BUCKET_SIZE) - { - uint32_t log = std::bit_width(aligned - 1); - uint32_t bucketIx = log - FreeListAllocator::INDEX_OFFSET; - - Node* bucket = allocator->buckets[bucketIx]; - if (bucket) - { - allocator->buckets[bucketIx] = bucket->next; - return bucket; - } - - return AllocatePageForBucket(allocator, bucketIx, aligned); - } - - return MemoryManager::AllocatePageLockFree(allocator->memState); -} diff --git a/Engine/src/delta/core/FreeListAllocator.h b/Engine/src/delta/core/FreeListAllocator.h deleted file mode 100644 index 8c115b3..0000000 --- a/Engine/src/delta/core/FreeListAllocator.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -namespace delta::core -{ - namespace MemoryManager { struct MemoryState; } - - struct FreeListAllocator - { - static constexpr uint64_t INDEX_OFFSET = 4ull; // 2^4 = 16 - static constexpr uint64_t MIN_ALLOC_SIZE = 16ull; - static constexpr uint64_t MAX_BUCKET_SIZE = 2048ull; - static constexpr uint32_t BUCKET_COUNT = 7ull; // log2(2048) - log2(16) = 7, calculated manually for simplicity - - struct Node { Node* next; }; - - MemoryManager::MemoryState* memState; - - Node* buckets[BUCKET_COUNT]; - - uint64_t pageSize; - }; - - void FreeList_Init(FreeListAllocator* allocator, MemoryManager::MemoryState* memState); - void* FreeList_Allocate(FreeListAllocator* allocator, uint64_t size, uint64_t alignment); -} diff --git a/Engine/src/delta/core/MemoryConfig.cpp b/Engine/src/delta/core/MemoryConfig.cpp new file mode 100644 index 0000000..fde284a --- /dev/null +++ b/Engine/src/delta/core/MemoryConfig.cpp @@ -0,0 +1,24 @@ +#include "MemoryConfig.h" + +#include + +namespace delta::core +{ + EngineMemoryConfig g_MemoryConfig{}; + std::atomic g_TotalLockedBytes{ 0 }; + + void MemoryConfig_Initialize(size_t physicalRamInstalled, uint32_t maxEngineWorkers) + { + g_MemoryConfig.totalPhysicalRam = physicalRamInstalled; + g_MemoryConfig.globalLockCeiling = (g_MemoryConfig.totalPhysicalRam / 10) * 7; + g_MemoryConfig.threadSoftBaseline = g_MemoryConfig.globalLockCeiling / maxEngineWorkers; + + g_TotalLockedBytes.store(0, std::memory_order_relaxed); + } + + void MemoryConfig_Shutdown() + { + g_MemoryConfig = {}; + g_TotalLockedBytes.store(0, std::memory_order_relaxed); + } +} diff --git a/Engine/src/delta/core/MemoryConfig.h b/Engine/src/delta/core/MemoryConfig.h new file mode 100644 index 0000000..1d34f5d --- /dev/null +++ b/Engine/src/delta/core/MemoryConfig.h @@ -0,0 +1,20 @@ +#pragma once + +namespace delta::core +{ + // TODO: This is actually crap. In this file, there should be a tight, flat memory map for each thread + // possibly modificable via config macros or maybe slightly dynamic. + + struct EngineMemoryConfig + { + size_t totalPhysicalRam; + size_t globalLockCeiling; + size_t threadSoftBaseline; + }; + + extern EngineMemoryConfig g_MemoryConfig; + extern std::atomic g_TotalLockedBytes; // TODO: Remove, redesign. completely unnecessary + + void MemoryConfig_Initialize(size_t physicalRamInstalled, uint32_t maxEngineWorkers); + void MemoryConfig_Shutdown(); +} diff --git a/Engine/src/delta/core/MemoryManager.cpp b/Engine/src/delta/core/MemoryManager.cpp deleted file mode 100644 index 2f40a55..0000000 --- a/Engine/src/delta/core/MemoryManager.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "MemoryManager.h" - -#include -#include - -#include - -#define SET_BIT(mask, bit) mask |= (1 << bit) - -using namespace delta::core; -using MemoryState = MemoryManager::MemoryState; -namespace os = delta::platform; - -// ct constants -static constexpr uint64_t VIRTUAL_MEMORY_RESERVATION = (1ull << 35ull); // 32gb - -static MemoryState g_memoryState{}; - -void MemoryManager::InitEngineMemory() -{ - { - const auto* osInfo = os::getOSInfo(); - const auto memStatus = os::getMemoryStatus(); - g_memoryState.pageSize = osInfo->osPageSize; - g_memoryState.maxCommitBudgetBytes = (memStatus.physicalInstalled / 100) * 7; // ~70% of total capacity - g_memoryState.totalVirtualSize = VIRTUAL_MEMORY_RESERVATION; - } - - uint64_t totalPages = VIRTUAL_MEMORY_RESERVATION / g_memoryState.pageSize; - g_memoryState.bitmaskCount = (totalPages + 63) / 64; - - uint64_t bitmaskBytes = g_memoryState.bitmaskCount * sizeof(atomic_u64_t); - - void* rawBitmaskPtr = os::ReserveMemory(bitmaskBytes); - rawBitmaskPtr = os::CommitMemory(rawBitmaskPtr, bitmaskBytes); - assert(rawBitmaskPtr != nullptr); // failed to allocate metadata - - g_memoryState.commitedBitmask = static_cast(rawBitmaskPtr); - for (uint64_t i = 0; i < g_memoryState.bitmaskCount; i++) - { - new (&g_memoryState.commitedBitmask[i]) atomic_u64_t(~0ull); - } - - g_memoryState.reservedBase = os::ReserveMemory(g_memoryState.totalVirtualSize); - assert(g_memoryState.reservedBase != nullptr); // failed to reserve addresses for allocations -} - -void* MemoryManager::AllocatePageLockFree(MemoryState* state) -{ - uint64_t currentRAM = state->currentlyCommitedBytes.load(std::memory_order_relaxed); - while (true) - { - if (currentRAM + state->pageSize > state->maxCommitBudgetBytes) - return nullptr; - - if (state->currentlyCommitedBytes.compare_exchange_weak( - currentRAM, currentRAM + state->pageSize, - std::memory_order_acquire, std::memory_order_relaxed) - ) - break; - } - - for (uint64_t i = 0; i < state->bitmaskCount; i++) - { - uint64_t expected = state->commitedBitmask[i].load(std::memory_order_relaxed); - while (expected != 0) - { - int bitIx = std::countr_zero(expected); - uint64_t desired = expected & ~(1ull << bitIx); - - if (state->commitedBitmask[i].compare_exchange_weak( - expected, desired, - std::memory_order_acquire, std::memory_order_relaxed)) - { - size_t absolutePageIndex = (i * 64) + bitIx; - uintptr_t targetPtr = reinterpret_cast(state->reservedBase) + (absolutePageIndex * state->pageSize); - void* commitedMemory = os::CommitMemory(reinterpret_cast(targetPtr), state->pageSize); - assert(commitedMemory != nullptr); // os failed to commit page - return commitedMemory; - } - } - } -} - -void MemoryManager::freePage(MemoryState* state, void* ptr) -{ - if (!ptr) - return; - - uintptr_t offset = reinterpret_cast(ptr) - reinterpret_cast(state->reservedBase); - size_t absolutePageIx = offset / state->pageSize; - - size_t arrIx = absolutePageIx / 64; - size_t bitIx = absolutePageIx % 64; - - os::DecommitMemory(ptr, state->pageSize); - state->commitedBitmask[arrIx].fetch_or((1ull << bitIx), std::memory_order_release); - state->currentlyCommitedBytes.fetch_sub(state->pageSize, std::memory_order_release); -} - -void MemoryManager::ShutdownEngineMemory() -{ - os::ReleaseMemory(g_memoryState.reservedBase); - os::ReleaseMemory(g_memoryState.commitedBitmask); -} - -MemoryState* MemoryManager::GetMemoryState() noexcept { return &g_memoryState; } diff --git a/Engine/src/delta/core/MemoryManager.h b/Engine/src/delta/core/MemoryManager.h deleted file mode 100644 index fec285b..0000000 --- a/Engine/src/delta/core/MemoryManager.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -namespace delta::core::MemoryManager -{ - using atomic_u64_t = std::atomic; - - struct MemoryState - { - void* reservedBase; - uint64_t totalVirtualSize; - uint64_t pageSize; - - uint64_t bitmaskCount; - atomic_u64_t* commitedBitmask; - - uint64_t maxCommitBudgetBytes; - atomic_u64_t currentlyCommitedBytes; - }; - - void InitEngineMemory(); - void ShutdownEngineMemory(); - void* AllocatePageLockFree(MemoryState* state); - void freePage(MemoryState* state, void* ptr); - MemoryState* GetMemoryState() noexcept; -} diff --git a/Engine/src/delta/core/ThreadContext.cpp b/Engine/src/delta/core/ThreadContext.cpp new file mode 100644 index 0000000..c289431 --- /dev/null +++ b/Engine/src/delta/core/ThreadContext.cpp @@ -0,0 +1,108 @@ +#include +#include "ThreadContext.h" +#include "MemoryConfig.h" + +#define ALIGN(size, alignment) ((size + (alignment - 1)) & ~(alignment - 1)) + +namespace delta::core +{ + static ThreadExecutionContext* g_ThreadContexts = nullptr; + thread_local ThreadExecutionContext* tl_CurrentThreadContext = nullptr; + + void ThreadContext_Initialize(uint32_t workerCount, size_t pageSize) + { + uint32_t totalThreads = workerCount + 1; // include main thread + + // assign 32Gb of address space per thread + // master pool size is rounded up to page size + constexpr size_t ADDR_SLICE_PER_THREAD = (1ull << 35); + size_t contextArraySize = sizeof(ThreadExecutionContext) * totalThreads; + size_t alignedContextArraySize = ALIGN(contextArraySize, pageSize); + size_t masterPoolSize = (ADDR_SLICE_PER_THREAD * totalThreads) + alignedContextArraySize; + + uint8_t* masterPoolBase = reinterpret_cast( + delta::platform::Memory_Reserve(masterPoolSize) + ); + assert(masterPoolBase != nullptr && "Failed to reserve master pool"); + + + g_ThreadContexts = reinterpret_cast( + delta::platform::Memory_Commit(masterPoolBase, alignedContextArraySize) + ); + assert(g_ThreadContexts != nullptr && "Failed to commit thread context array"); + + if (!delta::platform::Memory_Lock(masterPoolBase, alignedContextArraySize)) + std::cout << "[DeltaEngine-Warning] Failed to lock memory resource: Master Thread Context Pool\n"; + + uint8_t* virtualRunwayCursor = masterPoolBase + alignedContextArraySize; + for (uint32_t i = 0; i < totalThreads; i++) + { + ThreadExecutionContext& ctx = g_ThreadContexts[i]; + ctx.threadIx = i; + ctx.threadId = 0; // to be set later + + ctx.pageCoordinator.pageSize = pageSize; + ctx.pageCoordinator.virtualAddressBase = virtualRunwayCursor; + ctx.pageCoordinator.commitedOffset = 0; + ctx.pageCoordinator.reservedCapacity = ADDR_SLICE_PER_THREAD; + + virtualRunwayCursor += ADDR_SLICE_PER_THREAD; + + uint8_t* initialPageTarget = ctx.pageCoordinator.virtualAddressBase + ctx.pageCoordinator.commitedOffset; + void* initialPageMemory = delta::platform::Memory_Commit(initialPageTarget, pageSize); + assert(initialPageMemory != nullptr && "Failed to commit initial arena page!"); + + if (!delta::platform::Memory_Lock(initialPageTarget, pageSize)) + std::cout << "[DeltaEngine-Warning] Failed to lock memory resource: Thread-Local Arena " << i << "\n"; + + ctx.pageCoordinator.commitedOffset += pageSize; + ctx.transientArena.backingMemory = reinterpret_cast(initialPageTarget); + ctx.transientArena.capacity = pageSize; + ctx.transientArena.offset = 0; + + delta::platform::Timer_Initialize(&ctx.perThreadTimer); + } + + tl_CurrentThreadContext = &g_ThreadContexts[0]; + g_TotalLockedBytes.fetch_add(totalThreads * pageSize + alignedContextArraySize, std::memory_order_relaxed); + } + + void ThreadContext_Shutdown() + { + delta::platform::Memory_Release(g_ThreadContexts); + } + + void* ThreadArena_Allocate(EngineArena* arena, size_t size, size_t alignment) + { + uintptr_t currentAddress = reinterpret_cast(arena->backingMemory) + arena->offset; + uintptr_t alignedAddress = ALIGN(currentAddress, alignment); + size_t padding = alignedAddress - currentAddress; + size_t totalSpace = padding + size; + + if (totalSpace > arena->capacity) + { + // the slow path: assign more pages + // assigning twice 2 up front to avoid further slower-path-taking + size_t bytesNeeded = totalSpace - arena->capacity; + size_t spaceInPages = ALIGN(bytesNeeded, tl_CurrentThreadContext->pageCoordinator.pageSize) * 2; + uint8_t* targetAddress = arena->backingMemory + arena->capacity; + void* p = delta::platform::Memory_Commit(targetAddress, spaceInPages); + if (p && delta::platform::Memory_Lock(p, spaceInPages)) + { + return nullptr; + } + + arena->capacity += spaceInPages; + } + + uint8_t* ptr = reinterpret_cast(alignedAddress); + arena->offset += totalSpace; + + return ptr; + } + + void ThreadArena_Reset(EngineArena* arena) + { + arena->offset = 0; + } +} diff --git a/Engine/src/delta/core/ThreadContext.h b/Engine/src/delta/core/ThreadContext.h new file mode 100644 index 0000000..d637b05 --- /dev/null +++ b/Engine/src/delta/core/ThreadContext.h @@ -0,0 +1,14 @@ +#pragma once + +#include "EngineTypes.h" + +namespace delta::core +{ + // Lifecycle + void ThreadContext_Initialize(uint32_t workerCount, size_t pageSize); + void ThreadContext_Shutdown(); + + // Engine Arena API + void* ThreadArena_Allocate(EngineArena* arena, size_t size, size_t alignment = 8); + void ThreadArena_Reset(EngineArena* arena); +} diff --git a/Engine/src/delta/core/engine.cpp b/Engine/src/delta/core/engine.cpp index df9c1ff..93ca542 100644 --- a/Engine/src/delta/core/engine.cpp +++ b/Engine/src/delta/core/engine.cpp @@ -14,20 +14,32 @@ * limitations under the License. */ -#include - #include +#include #include -#include +#include +#include +#include void delta::Engine::Initialize(Context& context) { context.isRunning = true; delta::platform::Initialize(); - delta::core::MemoryManager::InitEngineMemory(); + const auto* osInfo = delta::platform::getOSInfo(); + const auto memStatus = delta::platform::getMemoryStatus(); + + delta::core::MemoryConfig_Initialize(memStatus.physicalInstalled, osInfo->maxEngineWorkerCount); + if (!delta::platform::Memory_ElevateLockLimit(delta::core::g_MemoryConfig.globalLockCeiling)) + { + context.isRunning = false; + return; + } + + delta::core::ThreadContext_Initialize(osInfo->maxEngineWorkerCount, osInfo->osPageSize); } void delta::Engine::Shutdown(Context& context) { - delta::core::MemoryManager::ShutdownEngineMemory(); + delta::core::ThreadContext_Shutdown(); + delta::core::MemoryConfig_Shutdown(); } diff --git a/Engine/src/delta/platform/os_internal.h b/Engine/src/delta/platform/os_internal.h index 09c5ced..f39bb45 100644 --- a/Engine/src/delta/platform/os_internal.h +++ b/Engine/src/delta/platform/os_internal.h @@ -18,18 +18,27 @@ namespace delta::platform { - struct BrandStringCall - { - int c1[4]; - int c2[4]; - int c3[4]; + struct BrandStringCall; + struct Timer_Internal; - static constexpr char UNSPECIFIED_VALUE[] = "(unspecified)"; + struct Timer + { + alignas(8) uint8_t opaqueData[32]; }; void Initialize(); - void* ReserveMemory(size_t reservationSize); - void* CommitMemory(void* mem, size_t commitSize); - void DecommitMemory(void* mem, size_t decommitSize); - void ReleaseMemory(void* mem); + + // Memory API + void* Memory_Reserve(size_t reservationSize); + void* Memory_Commit(void* mem, size_t commitSize); + void Memory_Decommit(void* mem, size_t decommitSize); + void Memory_Release(void* mem); + bool Memory_Lock(void* mem, size_t bytes); + bool Memory_ElevateLockLimit(size_t maxBytesToLock); + + // Timer API + void Timer_Initialize(Timer* timer); + int64_t Timer_GetTimestamp(); + double Timer_TicksToMilliseconds(const Timer* timer, int64_t startTicks, int64_t endTicks); + double Timer_TicksToMicroseconds(const Timer* timer, int64_t startTicks, int64_t endTicks); } diff --git a/Engine/src/delta/platform/os_linux.cpp b/Engine/src/delta/platform/os_linux.cpp deleted file mode 100644 index a124755..0000000 --- a/Engine/src/delta/platform/os_linux.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2026 Jakub Bączyk - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifdef __linux__ -#error "Linux API is currently under maintanance and is not available for a short while" - -#include - -#include -#include -#include -#include - -namespace delta::platform::os -{ - static Context g_context; - - inline static void fetchCpuidValues() - { - memset(g_context.cpu.manufacturerId, 0, 13); - - uint32_t eax, ebx, ecx, edx; - if (__get_cpuid(0, &eax, &ebx, &ecx, &edx)) - { - memcpy(g_context.cpu.manufacturerId, &ebx, sizeof(uint32_t)); - memcpy(g_context.cpu.manufacturerId + 4, &edx, sizeof(uint32_t)); - memcpy(g_context.cpu.manufacturerId + 8, &ecx, sizeof(uint32_t)); - } - - if (__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx)) - g_context.cpu.hasAVX2 = (ebx & (1 << 5)) != 0; - } - - void Initialize() - { - fetchCpuidValues(); - - struct sysinfo info; - if (sysinfo(&info) == 0) - { - g_context.memory.totalRam = (info.totalram * info.mem_unit) / (1 << 20); - g_context.memory.freeRam = (info.freeram * info.mem_unit) / (1 << 20); - } - - g_context.cpu.processorCount = sysconf(_SC_NPROCESSORS_ONLN); - g_context.config.osPageSize = getpagesize(); - } - - const Context* getContext() noexcept { return &g_context; } -} - -#endif diff --git a/Engine/src/delta/platform/os_win32.cpp b/Engine/src/delta/platform/os_win32.cpp index f8cc81b..007e195 100644 --- a/Engine/src/delta/platform/os_win32.cpp +++ b/Engine/src/delta/platform/os_win32.cpp @@ -27,6 +27,21 @@ namespace delta::platform { static OSInfo g_osInfo; + struct BrandStringCall + { + int c1[4]; + int c2[4]; + int c3[4]; + + static constexpr char UNSPECIFIED_VALUE[] = "(unspecified)"; + }; + + struct Timer_Internal + { + int64_t freq; + int64_t baseStartTime; + }; + enum CpuArchitecture : WORD { INTEL = 0, @@ -95,32 +110,94 @@ namespace delta::platform g_osInfo.cpuArchitecture = getArchitectureString(static_cast(info.wProcessorArchitecture)); // safe, points to static string literal g_osInfo.osPageSize = info.dwPageSize; - g_osInfo.cpuCoreCount = info.dwNumberOfProcessors; + g_osInfo.cpuLogicalProcessorCount = info.dwNumberOfProcessors; + + DWORD bufferSize = 0; + GetLogicalProcessorInformationEx(RelationProcessorCore, nullptr, &bufferSize); + + void* tempBuffer = malloc(bufferSize); + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX coreInfo = reinterpret_cast(tempBuffer); + + uint32_t physicalCores = 0; + uint32_t logicalProcessors = 0; + + if (tempBuffer && GetLogicalProcessorInformationEx(RelationProcessorCore, coreInfo, &bufferSize)) + { + uint8_t* ptr = reinterpret_cast(tempBuffer); + while (ptr < reinterpret_cast(tempBuffer) + bufferSize) + { + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX current = reinterpret_cast(ptr); + + if (current->Relationship == RelationProcessorCore) + { + physicalCores++; + + for (DWORD g = 0; g < current->Processor.GroupCount; g++) + { + logicalProcessors += __popcnt64(current->Processor.GroupMask[g].Mask); + } + } + + ptr += current->Size; + } + } + + free(tempBuffer); + + g_osInfo.cpuPhysicalCoreCount = physicalCores; + g_osInfo.cpuLogicalProcessorCount = logicalProcessors; + g_osInfo.cpuHasSMT = (logicalProcessors > physicalCores); + + if (physicalCores > 1) + g_osInfo.maxEngineWorkerCount = physicalCores - 1; + else + g_osInfo.maxEngineWorkerCount = 1; } - void* ReserveMemory(size_t reservationSize) + void* Memory_Reserve(size_t reservationSize) { // we don't allow accessing uncommited memory void* ptr = VirtualAlloc(nullptr, reservationSize, MEM_RESERVE, PAGE_NOACCESS); return ptr; } - void* CommitMemory(void* mem, size_t commitSize) + void* Memory_Commit(void* mem, size_t commitSize) { void* ptr = VirtualAlloc(mem, commitSize, MEM_COMMIT, PAGE_READWRITE); return ptr; } - void DecommitMemory(void* mem, size_t decommitSize) + void Memory_Decommit(void* mem, size_t decommitSize) { VirtualFree(mem, decommitSize, MEM_DECOMMIT); } - void ReleaseMemory(void* ptr) + void Memory_Release(void* ptr) { VirtualFree(ptr, 0, MEM_RELEASE); } + bool Memory_Lock(void* mem, size_t bytes) + { + return VirtualLock(mem, bytes); + } + + bool Memory_ElevateLockLimit(size_t maxBytesToLock) + { + HANDLE hProcess = GetCurrentProcess(); + size_t minWorkingSet = 0; + size_t maxWorkingSet = 0; + + if (GetProcessWorkingSetSize(hProcess, &minWorkingSet, &maxWorkingSet)) + { + static constexpr size_t SAFETY_BUFFER_SIZE = (1ull << 26); + size_t newMax = maxWorkingSet + maxBytesToLock + SAFETY_BUFFER_SIZE; + return SetProcessWorkingSetSize(hProcess, minWorkingSet, newMax); + } + + return false; + } + const OSInfo* getOSInfo() noexcept { return &g_osInfo; } MemoryStatus getMemoryStatus() @@ -136,6 +213,46 @@ namespace delta::platform return status; } + + // Timer + static_assert(sizeof(Timer_Internal) <= sizeof(Timer), "Timer opaque storage is too small!"); + + void Timer_Initialize(Timer* timer) + { + Timer_Internal* internal = reinterpret_cast(timer->opaqueData); + + LARGE_INTEGER freq; + BOOL freqResult = QueryPerformanceFrequency(&freq); + assert(freqResult && "Hardware high-performance counter is not supported by this system"); + internal->freq = freq.QuadPart; + + LARGE_INTEGER start; + QueryPerformanceCounter(&start); + internal->baseStartTime = start.QuadPart; + } + + int64_t Timer_GetTimestamp() + { + LARGE_INTEGER current; + QueryPerformanceCounter(¤t); + return current.QuadPart; + } + + double Timer_TicksToMilliseconds(const Timer* timer, int64_t startTicks, int64_t endTicks) + { + const Timer_Internal* internal = reinterpret_cast(timer); + int64_t elapsedTicks = endTicks - startTicks; + + return (static_cast(elapsedTicks) * 1000.0) / static_cast(internal->freq); + } + + double Timer_TicksToMicroseconds(const Timer* timer, int64_t startTicks, int64_t endTicks) + { + const Timer_Internal* internal = reinterpret_cast(timer); + int64_t elapsedTicks = endTicks - startTicks; + + return (static_cast(elapsedTicks) * 1000000.0) / static_cast(internal->freq); + } } #endif diff --git a/Examples/HelloWorldGame/game.cpp b/Examples/HelloWorldGame/game.cpp index 358c99e..a59d077 100644 --- a/Examples/HelloWorldGame/game.cpp +++ b/Examples/HelloWorldGame/game.cpp @@ -46,6 +46,9 @@ extern "C" "\tCPU Architecture: " << info->cpuArchitecture << "\n" << "\tCPU Manufacturer: " << info->cpuManufacturerId << "\n" << "\tCPU Model: " << info->cpuBrandString << "\n" << + "\tCPU Physical Cores: " << info->cpuPhysicalCoreCount << "\n" << + "\tCPU Logical Cores: " << info->cpuLogicalProcessorCount << "\n" << + "\tCPU Has SMT: " << STDOUT_BOOL_FORMAT(info->cpuHasSMT) << "\n" << "\tAVX2 Available: " << STDOUT_BOOL_FORMAT(info->cpuHasAVX2) << "\n" << "\tAVX512 Foundation Available: " << STDOUT_BOOL_FORMAT(info->cpuHasAVX512f) << "\n" << "\tAVX512 Conflict Detection Available: " << STDOUT_BOOL_FORMAT(info->cpuHasAVX512cd) << "\n" <<