diff --git a/files.cmake b/files.cmake index f91756a9d5..fb678f5bcb 100644 --- a/files.cmake +++ b/files.cmake @@ -1453,6 +1453,7 @@ set(DUSK_FILES src/dusk/imgui/ImGuiProcessOverlay.cpp src/dusk/imgui/ImGuiCameraOverlay.cpp src/dusk/imgui/ImGuiHeapOverlay.cpp + src/dusk/imgui/ImGuiActorSpawner.cpp src/dusk/imgui/ImGuiDebugPad.cpp src/dusk/imgui/ImGuiControllerOverlay.cpp src/dusk/imgui/ImGuiStubLog.cpp @@ -1466,6 +1467,7 @@ set(DUSK_FILES src/dusk/iso_validate.cpp src/dusk/livesplit.cpp src/dusk/offset_ptr.cpp + src/dusk/vmem.cpp src/dusk/OSContext.cpp src/dusk/OSThread.cpp src/dusk/OSMutex.cpp diff --git a/include/dusk/memory.h b/include/dusk/memory.h deleted file mode 100644 index dd55181170..0000000000 --- a/include/dusk/memory.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef DUSK_MEMORY_H -#define DUSK_MEMORY_H - -#if TARGET_PC -#define HEAP_SIZE(original, dusk) (dusk) -#else -#define HEAP_SIZE(original, dusk) (original) -#endif - -#endif diff --git a/include/dusk/vmem.h b/include/dusk/vmem.h new file mode 100644 index 0000000000..1bf3f2d6ab --- /dev/null +++ b/include/dusk/vmem.h @@ -0,0 +1,54 @@ +#pragma once +#include +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +namespace dusk { +#endif + +// Reserve a contiguous virtual address range without committing physical pages +void* vmem_reserve(size_t size); + +// Commit physical backing for pages in a previously reserved range, ptr and size should be page-aligned +bool vmem_commit(void* ptr, size_t size); + +// Decommit physical pages in a reserved range, releasing RAM without releasing address space +void vmem_decommit(void* ptr, size_t size); + +// Release an entire virtual reservation obtained from vmem_reserve +void vmem_release(void* ptr, size_t size); + +// Returns the OS page size +size_t vmem_page_size(); + +// Shared vmem arena +// All JKR heap vmem reservations are sub-allocated from a single large reservation, +// keeping the total entry count at 1 regardless of how many heaps exist + +// Must be called once before any JKR heap is created +void vmem_arena_init(); + +// Allocate a slot of size bytes (page-aligned) from the arena +void* vmem_arena_alloc(size_t size); + +// Return a slot to the arena and decommit its physical pages +void vmem_arena_free(void* ptr, size_t size); + +#ifdef __cplusplus +} // namespace dusk + +// Total virtual address space reserved for the shared JKR heap arena +inline constexpr size_t JKR_VMEM_ARENA_SIZE = 128ULL * 1024 * 1024 * 1024; // 128 GB + +// Virtual address space reserved per JKR heap (one slot in the shared arena) +inline constexpr size_t JKR_HEAP_VIRTUAL_RESERVE = 64ULL * 1024 * 1024; // 64 MB + +// Minimum growth increment when a JKR heap expands into reserved but uncommitted pages +inline constexpr size_t JKR_HEAP_GROW_CHUNK = 16ULL * 1024 * 1024; // 16 MB + +// Maximum number of free slots the arena can track (= total slots in the arena) +inline constexpr size_t JKR_VMEM_MAX_FREE_SLOTS = JKR_VMEM_ARENA_SIZE / JKR_HEAP_VIRTUAL_RESERVE; + +#endif diff --git a/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h b/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h index 09687e688b..78366eb1d3 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRExpHeap.h @@ -127,6 +127,13 @@ public: [[nodiscard]] const CMemBlock* getFreeHead() const { return mHeadFreeList; } [[nodiscard]] CMemBlock* getUsedHead() { return mHeadUsedList; } [[nodiscard]] const CMemBlock* getUsedHead() const { return mHeadUsedList; } + + void* mVmemBase; // base of VM reservation + size_t mVmemCapacity; // total reserved bytes + size_t mVmemCommitted; // page-aligned committed bytes so far + + // Commit more pages and splice them into the free list + bool growHeap(u32 needed); #endif }; diff --git a/libs/JSystem/include/JSystem/JKernel/JKRSolidHeap.h b/libs/JSystem/include/JSystem/JKernel/JKRSolidHeap.h index a14fd915fe..2a9a5f52cd 100644 --- a/libs/JSystem/include/JSystem/JKernel/JKRSolidHeap.h +++ b/libs/JSystem/include/JSystem/JKernel/JKRSolidHeap.h @@ -61,6 +61,15 @@ public: static JKRSolidHeap* create(u32, JKRHeap*, bool); static void* getState_(TState* state) { return getState_buf_(state); } + +#if TARGET_PC + void* mVmemBase; // base of VM reservation + size_t mVmemCapacity; // total reserved bytes + size_t mVmemCommitted; // page-aligned committed bytes so far + + // Commit more pages and extend the free region + bool growHeap(u32 needed); +#endif }; inline JKRSolidHeap* JKRCreateSolidHeap(u32 param_0, JKRHeap* heap, bool param_2) { diff --git a/libs/JSystem/src/JKernel/JKRExpHeap.cpp b/libs/JSystem/src/JKernel/JKRExpHeap.cpp index 4a6712cb65..6a50080bd9 100644 --- a/libs/JSystem/src/JKernel/JKRExpHeap.cpp +++ b/libs/JSystem/src/JKernel/JKRExpHeap.cpp @@ -10,6 +10,11 @@ #include "JSystem/JUtility/JUTConsole.h" #include "JSystem/JUtility/JUTException.h" #include +#if TARGET_PC +#include "dusk/vmem.h" +#include +#include "dusk/logging.h" +#endif JKRExpHeap* JKRExpHeap::createRoot(int maxHeaps, bool errorFlag) { JKRExpHeap* heap = NULL; @@ -71,21 +76,49 @@ JKRExpHeap* JKRExpHeap::create(u32 size, JKRHeap* parent, bool errorFlag) { u32 alignedSize = ALIGN_PREV(size, 0x10); - if (alignedSize < expHeapSize + blockSize) + if (alignedSize < expHeapSize + blockSize) { return NULL; + } - u8* memory = (u8*)JKRAllocFromHeap(parent, alignedSize, 0x10); - u8* dataPtr = (memory + expHeapSize); +#if TARGET_PC + u8* vmemBase = (u8*)dusk::vmem_arena_alloc(JKR_HEAP_VIRTUAL_RESERVE); + if (!vmemBase) { + return NULL; + } + + const size_t pageSize = dusk::vmem_page_size(); + size_t commitSize = ALIGN_NEXT((size_t)alignedSize, pageSize); + if (!dusk::vmem_commit(vmemBase, commitSize)) { + dusk::vmem_arena_free(vmemBase, JKR_HEAP_VIRTUAL_RESERVE); + return NULL; + } + + u8* memory = vmemBase; + u8* dataPtr = memory + expHeapSize; + + newHeap = JKR_NEW_ARGS(memory) JKRExpHeap(dataPtr, alignedSize - expHeapSize, parent, errorFlag); + if (newHeap == NULL) { + dusk::vmem_arena_free(vmemBase, JKR_HEAP_VIRTUAL_RESERVE); + return NULL; + } + + newHeap->mVmemBase = vmemBase; + newHeap->mVmemCapacity = JKR_HEAP_VIRTUAL_RESERVE; + newHeap->mVmemCommitted = commitSize; +#else + u8* memory = (u8*)JKRAllocFromHeap(parent, alignedSize, 0x10); + u8* dataPtr = memory + expHeapSize; if (!memory) { return NULL; } - newHeap = JKR_NEW_ARGS (memory) JKRExpHeap(dataPtr, alignedSize - expHeapSize, parent, errorFlag); - + newHeap = JKR_NEW_ARGS(memory) JKRExpHeap(dataPtr, alignedSize - expHeapSize, parent, errorFlag); if (newHeap == NULL) { JKRFree(memory); return NULL; } +#endif + #if DEBUG if (newHeap) { u8* local_30 = dataPtr + sizeof(CMemBlock); @@ -102,9 +135,16 @@ JKRExpHeap* JKRExpHeap::create(u32 size, JKRHeap* parent, bool errorFlag) { JKRExpHeap* JKRExpHeap::create(void* ptr, u32 size, JKRHeap* parent, bool errorFlag) { JKRHeap* parent2; if (parent == NULL) { +#if TARGET_PC + // VM-backed heaps live outside the root heap's address range, so find() fails + // findAllHeap() searches the full tree + parent2 = getRootHeap()->findAllHeap(ptr); +#else parent2 = getRootHeap()->find(ptr); - if (!parent2) +#endif + if (!parent2) { return NULL; + } } else { parent2 = parent; } @@ -136,6 +176,15 @@ JKRExpHeap* JKRExpHeap::create(void* ptr, u32 size, JKRHeap* parent, bool errorF } void JKRExpHeap::do_destroy() { +#if TARGET_PC + if (mVmemBase) { + void* vmemBase = mVmemBase; + size_t vmemCapacity = mVmemCapacity; + this->~JKRExpHeap(); + dusk::vmem_arena_free(vmemBase, vmemCapacity); + return; + } +#endif if (!field_0x6e) { JKRHeap* heap = getParent(); if (heap) { @@ -163,6 +212,11 @@ JKRExpHeap::JKRExpHeap(void* data, u32 size, JKRHeap* parent, bool errorFlag) mHeadFreeList->initiate(NULL, NULL, size - sizeof(CMemBlock), 0, 0); mHeadUsedList = NULL; mTailUsedList = NULL; +#if TARGET_PC + mVmemBase = nullptr; + mVmemCapacity = 0; + mVmemCommitted = 0; +#endif } JKRExpHeap::~JKRExpHeap() { @@ -214,6 +268,24 @@ void* JKRExpHeap::do_alloc(u32 size, int alignment) { #endif #if TARGET_PC + if (!ptr && mVmemBase) { + // Heap is full, commit the next chunk of reserved VM and retry + if (growHeap(size)) { + if (alignment >= 0) { + if (alignment <= 4) { + ptr = allocFromHead(size); + } else { + ptr = allocFromHead(size, alignment); + } + } else { + if (-alignment <= 4) { + ptr = allocFromTail(size); + } else { + ptr = allocFromTail(size, -alignment); + } + } + } + } if (!ptr) { // Allocation failed. OSReport_Error( @@ -491,6 +563,49 @@ static void dummy() { OS_REPORT("newSize > 0"); } +#if TARGET_PC +bool JKRExpHeap::growHeap(u32 needed) { + // Determine how much to commit + // Always grow by at least JKR_HEAP_GROW_CHUNK + const size_t pageSize = dusk::vmem_page_size(); + size_t wantBytes = (size_t)needed + sizeof(CMemBlock); + size_t growAmount = std::max(wantBytes, JKR_HEAP_GROW_CHUNK); + growAmount = ALIGN_NEXT(growAmount, pageSize); + + size_t remaining = mVmemCapacity - mVmemCommitted; + if (growAmount > remaining) { + // Clamp to whatever reservation is left + growAmount = ALIGN_PREV(remaining, pageSize); + if (growAmount < wantBytes) { + return false; + } + } + + void* commitBase = (u8*)mVmemBase + mVmemCommitted; + if (!dusk::vmem_commit(commitBase, growAmount)) { + return false; + } + + // Splice the new committed region into the free list as a single block at mEnd + CMemBlock* newBlock = (CMemBlock*)mEnd; + newBlock->size = (u32)(growAmount - sizeof(CMemBlock)); + newBlock->mFlags = 0; + + mEnd = (u8*)mEnd + growAmount; + mSize += (u32)growAmount; + mVmemCommitted += growAmount; + + recycleFreeBlock(newBlock); + + DuskLog.debug("[JKRExpHeap] '{}' grew by {} MB (committed: {} MB / reserved: {} MB)\n", + getName(), + growAmount / (1024 * 1024), + mVmemCommitted / (1024 * 1024), + mVmemCapacity / (1024 * 1024)); + return true; +} +#endif + void JKRExpHeap::do_freeAll() { lock(); JKRHeap::callAllDisposer(); diff --git a/libs/JSystem/src/JKernel/JKRSolidHeap.cpp b/libs/JSystem/src/JKernel/JKRSolidHeap.cpp index ab2c301db0..3dccabac11 100644 --- a/libs/JSystem/src/JKernel/JKRSolidHeap.cpp +++ b/libs/JSystem/src/JKernel/JKRSolidHeap.cpp @@ -1,4 +1,4 @@ -#include "JSystem/JSystem.h" // IWYU pragma: keep +#include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JKernel/JKRSolidHeap.h" #include "JSystem/JGadget/binary.h" @@ -7,6 +7,11 @@ #include "global.h" #include #include +#if TARGET_PC +#include "dusk/vmem.h" +#include +#include "dusk/logging.h" +#endif JKRSolidHeap* JKRSolidHeap::create(u32 size, JKRHeap* heap, bool useErrorHandler) { if (!heap) { @@ -19,18 +24,56 @@ JKRSolidHeap* JKRSolidHeap::create(u32 size, JKRHeap* heap, bool useErrorHandler } u32 alignedSize = ALIGN_PREV(size, 0x10); - if (alignedSize < solidHeapSize) + if (alignedSize < solidHeapSize) { return NULL; + } +#if TARGET_PC + u8* vmemBase = (u8*)dusk::vmem_arena_alloc(JKR_HEAP_VIRTUAL_RESERVE); + if (!vmemBase) { + return NULL; + } + const size_t pageSize = dusk::vmem_page_size(); + size_t commitSize = ALIGN_NEXT((size_t)alignedSize, pageSize); + if (!dusk::vmem_commit(vmemBase, commitSize)) { + dusk::vmem_arena_free(vmemBase, JKR_HEAP_VIRTUAL_RESERVE); + return NULL; + } + + u8* mem = vmemBase; + void* dataPtr = mem + solidHeapSize; + + JKRSolidHeap* newHeap = JKR_NEW_ARGS(mem) JKRSolidHeap(dataPtr, alignedSize - solidHeapSize, heap, useErrorHandler); + if (newHeap == NULL) { + dusk::vmem_arena_free(vmemBase, JKR_HEAP_VIRTUAL_RESERVE); + return NULL; + } + + newHeap->mVmemBase = vmemBase; + newHeap->mVmemCapacity = JKR_HEAP_VIRTUAL_RESERVE; + newHeap->mVmemCommitted = commitSize; + return newHeap; +#else u8* mem = (u8*)JKRAllocFromHeap(heap, alignedSize, 0x10); void* dataPtr = mem + solidHeapSize; - if (!mem) + if (!mem) { return NULL; + } return JKR_NEW_ARGS (mem) JKRSolidHeap(dataPtr, alignedSize - solidHeapSize, heap, useErrorHandler); +#endif } void JKRSolidHeap::do_destroy(void) { +#if TARGET_PC + if (mVmemBase) { + void* vmemBase = mVmemBase; + size_t vmemCapacity = mVmemCapacity; + this->~JKRSolidHeap(); + dusk::vmem_arena_free(vmemBase, vmemCapacity); + return; + } +#endif JKRHeap* parent = getParent(); if (parent) { this->~JKRSolidHeap(); @@ -44,6 +87,11 @@ JKRSolidHeap::JKRSolidHeap(void* start, u32 size, JKRHeap* parent, bool useError mSolidHead = (u8*)mStart; mSolidTail = (u8*)mEnd; field_0x78 = NULL; +#if TARGET_PC + mVmemBase = nullptr; + mVmemCapacity = 0; + mVmemCommitted = 0; +#endif #if DEBUG if (mDebugFill) { JKRFillMemory(mStart, mSize, JKRValue_DEBUGFILL_NOTUSE); @@ -59,6 +107,15 @@ s32 JKRSolidHeap::adjustSize(void) { int r25 = 0; JKRHeap* parent = getParent(); if (parent) { +#if TARGET_PC + if (mVmemBase) { + // VM-backed heap, can't resize in parent, but this is not a failure + // Return what the trimmed size would have been so the caller doesn't log an error + u32 thisSize = (uintptr_t)mStart - (uintptr_t)this; + u32 newSize = ALIGN_NEXT(mSolidHead - mStart, 0x20); + return (s32)(thisSize + newSize); + } +#endif lock(); u32 thisSize = (uintptr_t)mStart - (uintptr_t)this; u32 newSize = ALIGN_NEXT(mSolidHead - mStart, 0x20); @@ -110,6 +167,11 @@ void* JKRSolidHeap::allocFromHead(u32 size, int alignment) { void* ptr = NULL; uintptr_t alignedStart = (alignment - 1 + (uintptr_t)mSolidHead) & ~(alignment - 1); u32 totalSize = size + (alignedStart - (uintptr_t)mSolidHead); +#if TARGET_PC + if (totalSize > mFreeSize && mVmemBase) { + growHeap(totalSize); + } +#endif if (totalSize <= mFreeSize) { #if DEBUG if (mCheckMemoryFilled) { @@ -137,6 +199,15 @@ void* JKRSolidHeap::allocFromTail(u32 size, int alignment) { void* ptr = NULL; uintptr_t alignedStart = ALIGN_PREV((uintptr_t)mSolidTail - size, alignment); u32 totalSize = (uintptr_t)mSolidTail - (uintptr_t)alignedStart; +#if TARGET_PC + if (totalSize > mFreeSize && mVmemBase) { + if (growHeap(totalSize)) { + // mSolidTail moved to new mEnd; recompute from the new tail position + alignedStart = ALIGN_PREV((uintptr_t)mSolidTail - size, alignment); + totalSize = (uintptr_t)mSolidTail - (uintptr_t)alignedStart; + } + } +#endif if (totalSize <= mFreeSize) { ptr = (void*)alignedStart; mSolidTail -= totalSize; @@ -158,6 +229,47 @@ void* JKRSolidHeap::allocFromTail(u32 size, int alignment) { return ptr; } +#if TARGET_PC +bool JKRSolidHeap::growHeap(u32 needed) { + // Growth is only safe when no tail allocations exist yet + if (mSolidTail != mEnd) { + return false; + } + + const size_t pageSize = dusk::vmem_page_size(); + size_t wantBytes = (size_t)needed; + size_t growAmount = std::max(wantBytes, JKR_HEAP_GROW_CHUNK); + growAmount = ALIGN_NEXT(growAmount, pageSize); + + size_t remaining = mVmemCapacity - mVmemCommitted; + if (growAmount > remaining) { + growAmount = ALIGN_PREV(remaining, pageSize); + if (growAmount < wantBytes) { + return false; + } + } + + void* commitBase = (u8*)mVmemBase + mVmemCommitted; + if (!dusk::vmem_commit(commitBase, growAmount)) { + return false; + } + + // Extend the heap end and the tail pointer + mEnd = (u8*)mEnd + growAmount; + mSolidTail = mEnd; + mFreeSize += (u32)growAmount; + mSize += (u32)growAmount; + mVmemCommitted += growAmount; + + DuskLog.debug("[JKRSolidHeap] '{}' grew by {} MB (committed: {} MB / reserved: {} MB)\n", + getName(), + growAmount / (1024 * 1024), + mVmemCommitted / (1024 * 1024), + mVmemCapacity / (1024 * 1024)); + return true; +} +#endif + void JKRSolidHeap::do_free(void* ptr) { JUTWarningConsole_f("free: cannot free memory block (%08x)\n", ptr); } diff --git a/src/d/actor/d_a_coach_2D.cpp b/src/d/actor/d_a_coach_2D.cpp index eab333cb8b..7c53aa1f10 100644 --- a/src/d/actor/d_a_coach_2D.cpp +++ b/src/d/actor/d_a_coach_2D.cpp @@ -14,8 +14,6 @@ #include "JSystem/J2DGraph/J2DAnmLoader.h" #include -#include "dusk/memory.h" - class daCoach2D_HIO_c : public mDoHIO_entry_c { public: struct Param { @@ -155,7 +153,7 @@ int daCoach2D_c::createHeap() { int daCoach2D_c::create() { int phase_state = dComIfG_resLoad(this, l_arcName); if (phase_state == cPhs_COMPLEATE_e) { - if (!fopAcM_entrySolidHeap(this, daCoach2D_createHeap, HEAP_SIZE(0x5050, 0x6000))) { + if (!fopAcM_entrySolidHeap(this, daCoach2D_createHeap, 0x5050)) { return cPhs_ERROR_e; } diff --git a/src/d/d_eye_hl.cpp b/src/d/d_eye_hl.cpp index 16161b5ee7..6e4bc694d2 100644 --- a/src/d/d_eye_hl.cpp +++ b/src/d/d_eye_hl.cpp @@ -78,7 +78,14 @@ void dEyeHL_mng_c::remove(dEyeHL_c* i_obj) { next = m_obj; } +#if TARGET_PC + // Skip the write if the heap owning m_timg was already destroyed + if (JKRHeap::findFromRoot(i_obj->m_timg) != nullptr) { + i_obj->m_timg->LODBias = i_obj->m_lodBias; + } +#else i_obj->m_timg->LODBias = i_obj->m_lodBias; +#endif i_obj->m_timg = NULL; i_obj->m_pre = NULL; i_obj->m_next = NULL; diff --git a/src/d/d_k_wmark.cpp b/src/d/d_k_wmark.cpp index b93ab14211..379b55f532 100644 --- a/src/d/d_k_wmark.cpp +++ b/src/d/d_k_wmark.cpp @@ -6,7 +6,6 @@ #include "d/dolzel.h" // IWYU pragma: keep #include "d/d_k_wmark.h" -#include "dusk/memory.h" #include "JSystem/J3DGraphBase/J3DMaterial.h" #include "SSystem/SComponent/c_math.h" #include "d/actor/d_a_player.h" @@ -34,7 +33,7 @@ int dkWmark_c::create() { mColorType = this->parameters; } - mpHeap = mDoExt_createSolidHeapFromGameToCurrent(HEAP_SIZE(0x880, 0x1100), 0x20); + mpHeap = mDoExt_createSolidHeapFromGameToCurrent(0x880, 0x20); if (mpHeap != NULL) { JKRHEAP_NAME(mpHeap, "dkWmark_c::mpHeap"); J3DModelData* modelData = (J3DModelData*)dComIfG_getObjectRes("Alink", 0x23); diff --git a/src/d/d_kankyo.cpp b/src/d/d_kankyo.cpp index 6b84fbae43..aad6e8f7e7 100644 --- a/src/d/d_kankyo.cpp +++ b/src/d/d_kankyo.cpp @@ -1,7 +1,6 @@ #include "d/dolzel.h" // IWYU pragma: keep #include "d/d_kankyo.h" -#include "dusk/memory.h" #ifdef __REVOLUTION_SDK__ #include #else @@ -1186,7 +1185,7 @@ static void undwater_init() { J3DModelData* modelData2 = (J3DModelData*)dComIfG_getObjectRes("Always", 0x1D); JUT_ASSERT(1867, modelData2 != NULL); - g_env_light.undwater_ef_heap = mDoExt_createSolidHeapFromGameToCurrent(HEAP_SIZE(0x600, 0xC00), 0x20); + g_env_light.undwater_ef_heap = mDoExt_createSolidHeapFromGameToCurrent(0x600, 0x20); JKRHEAP_NAME(g_env_light.undwater_ef_heap, "g_env_light.undwater_ef_heap"); if (g_env_light.undwater_ef_heap != NULL) { diff --git a/src/d/d_menu_fmap.cpp b/src/d/d_menu_fmap.cpp index 149e03349d..862dac2ea8 100644 --- a/src/d/d_menu_fmap.cpp +++ b/src/d/d_menu_fmap.cpp @@ -21,7 +21,6 @@ #include "d/d_msg_object.h" #include "d/d_msg_scrn_explain.h" #include "d/d_stage.h" -#include "dusk/memory.h" #include "f_op/f_op_msg_mng.h" static dMf_HIO_c g_fmHIO; @@ -190,7 +189,7 @@ dMenu_Fmap_c::dMenu_Fmap_c(JKRExpHeap* i_heap, STControl* i_stick, CSTControl* i field_0x148[i] = 0.0f; } - mpTalkHeap = JKRCreateExpHeap(HEAP_SIZE(0x32000, 0x40000), mpHeap, false); + mpTalkHeap = JKRCreateExpHeap(0x32000, mpHeap, false); JUT_ASSERT(359, mpTalkHeap != NULL); JKRHEAP_NAME(mpTalkHeap, "dMenu_Fmap_c::mpTalkHeap"); field_0x200 = 0; diff --git a/src/d/d_meter2.cpp b/src/d/d_meter2.cpp index 5bd321e7a8..499e167bba 100644 --- a/src/d/d_meter2.cpp +++ b/src/d/d_meter2.cpp @@ -24,16 +24,12 @@ #include "d/actor/d_a_horse.h" #include -#include "dusk/memory.h" - -#include "dusk/memory.h" - int dMeter2_c::_create() { stage_stag_info_class* stag_info = dComIfGp_getStageStagInfo(); if (dStage_stagInfo_GetUpButton(stag_info) == 1) { - mpHeap = fopMsgM_createExpHeap(HEAP_SIZE(0x5A400, 0xA0000), NULL); + mpHeap = fopMsgM_createExpHeap(0x5A400, NULL); } else { - mpHeap = fopMsgM_createExpHeap(HEAP_SIZE(0x60800, 0xC1000), NULL); + mpHeap = fopMsgM_createExpHeap(0x60800, NULL); } JKRHEAP_NAME(mpHeap, "dMeter2_c"); @@ -236,7 +232,7 @@ int dMeter2_c::_create() { dMeter2Info_setMeterMapClass(mpMap); mpHeap->getTotalFreeSize(); - mpSubHeap = fopMsgM_createExpHeap(HEAP_SIZE(0x5000, 0x6500), mpHeap); + mpSubHeap = fopMsgM_createExpHeap(0x5000, mpHeap); JKRHEAP_NAME(mpSubHeap, "dMeter2_c mpSubHeap"); field_0x108 = NULL; mpSubContents = NULL; diff --git a/src/d/d_resorce.cpp b/src/d/d_resorce.cpp index bb982c4afb..8dce18c75a 100644 --- a/src/d/d_resorce.cpp +++ b/src/d/d_resorce.cpp @@ -36,15 +36,27 @@ dRes_info_c::dRes_info_c() { dRes_info_c::~dRes_info_c() { if (mDMCommand != NULL) { - mDMCommand->destroy(); +#if TARGET_PC + if (JKRHeap::findFromRoot(mDMCommand) != nullptr) { +#endif + mDMCommand->destroy(); +#if TARGET_PC + } +#endif mDMCommand = NULL; } else if (mArchive != NULL) { - deleteArchiveRes(); - if (mDataHeap != NULL) { - mDoExt_destroySolidHeap(mDataHeap); - mDataHeap = NULL; - mArchive->unmount(); +#if TARGET_PC + if (JKRHeap::findFromRoot(mArchive) != nullptr) { +#endif + deleteArchiveRes(); + if (mDataHeap != NULL) { + mDoExt_destroySolidHeap(mDataHeap); + mDataHeap = NULL; + mArchive->unmount(); + } +#if TARGET_PC } +#endif mRes = NULL; mArchive = NULL; } diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index 6baa07da30..73d8af5623 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -10,7 +10,6 @@ #include "d/d_meter2_info.h" #include "d/d_s_name.h" #include "dusk/imgui/ImGuiConsole.hpp" -#include "dusk/memory.h" #include "dusk/settings.h" #include "f_op/f_op_overlap_mng.h" #include "f_op/f_op_scene_mng.h" @@ -77,7 +76,7 @@ static s32 resLoad(request_of_phase_process_class* i_phase, char* i_resName) { s32 dScnName_c::create() { int phase_state = resLoad(&phase, "fileSel"); if (phase_state == cPhs_COMPLEATE_e) { - mHeap = JKRCreateExpHeap(HEAP_SIZE(0x180000, 0x1C0000), mDoExt_getGameHeap(), false); + mHeap = JKRCreateExpHeap(0x180000, mDoExt_getGameHeap(), false); JUT_ASSERT(289, mHeap != NULL); JKRHEAP_NAME(mHeap, "File select"); diff --git a/src/d/d_s_play.cpp b/src/d/d_s_play.cpp index 38308d0668..5ae1c76273 100644 --- a/src/d/d_s_play.cpp +++ b/src/d/d_s_play.cpp @@ -39,10 +39,6 @@ #include "JSystem/JKernel/JKRAram.h" #include "JSystem/JKernel/JKRAramArchive.h" -#if TARGET_PC -#include "dusk/memory.h" -#endif - #if DEBUG #include "d/d_s_menu.h" #include "d/d_debug_pad.h" @@ -1424,7 +1420,7 @@ static int phase_4(dScnPly_c* i_this) { dComIfGd_setViewport(NULL); dComIfGd_setView(NULL); - JKRExpHeap* heap = fopMsgM_createExpHeap(HEAP_SIZE(0xBB800, 0x177000), NULL); + JKRExpHeap* heap = fopMsgM_createExpHeap(0xBB800, NULL); #if TARGET_PC heap->setName("Scene2DHeap"); #endif @@ -1432,7 +1428,7 @@ static int phase_4(dScnPly_c* i_this) { JUT_ASSERT(2704, heap != NULL); dComIfGp_setExpHeap2D(heap); - JKRExpHeap* heap2 = fopMsgM_createExpHeap(HEAP_SIZE(0xA800, 0x15000), NULL); + JKRExpHeap* heap2 = fopMsgM_createExpHeap(0xA800, NULL); #if TARGET_PC heap2->setName("SceneMsgHeap"); #endif diff --git a/src/dusk/imgui/ImGuiActorSpawner.cpp b/src/dusk/imgui/ImGuiActorSpawner.cpp new file mode 100644 index 0000000000..f3aefd4c41 --- /dev/null +++ b/src/dusk/imgui/ImGuiActorSpawner.cpp @@ -0,0 +1,142 @@ +#include "imgui.h" + +#include "ImGuiMenuTools.hpp" +#include "d/actor/d_a_alink.h" +#include "d/d_com_inf_game.h" +#include "f_op/f_op_actor_mng.h" +#include "SSystem/SComponent/c_sxyz.h" +#include "SSystem/SComponent/c_xyz.h" + +namespace dusk { +namespace { + +struct ActorSpawnerState { + int actorId = 0; + int params = -1; + int argument = -1; + int angleX = 0; + int angleY = 0; + int angleZ = 0; + float scaleX = 1.0f; + float scaleY = 1.0f; + float scaleZ = 1.0f; + bool usePlayerRoom = true; + int manualRoom = 0; + int spawnCount = 1; + bool hasResult = false; + unsigned int lastResult = 0; + int lastAttempted = 0; +}; + +ActorSpawnerState s_state; + +} // namespace + +void ImGuiMenuTools::ShowActorSpawner() { + if (!m_showActorSpawner) { + return; + } + + if (!ImGui::Begin("Actor Spawner", &m_showActorSpawner)) { + ImGui::End(); + return; + } + + daAlink_c* player = (daAlink_c*)dComIfGp_getPlayer(0); + + ImGui::SeparatorText("Actor"); + ImGui::InputInt("Actor ID", &s_state.actorId); + ImGui::InputInt("Params (hex)", &s_state.params, 0, 0, ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputInt("Argument", &s_state.argument); + s_state.argument = (s_state.argument < -128) ? -128 : (s_state.argument > 127) ? 127 : s_state.argument; + + ImGui::SeparatorText("Angle"); + ImGui::InputInt("Angle X", &s_state.angleX); + ImGui::InputInt("Angle Y", &s_state.angleY); + ImGui::InputInt("Angle Z", &s_state.angleZ); + + ImGui::SeparatorText("Scale"); + ImGui::InputFloat("Scale X", &s_state.scaleX, 0.1f, 1.0f); + ImGui::InputFloat("Scale Y", &s_state.scaleY, 0.1f, 1.0f); + ImGui::InputFloat("Scale Z", &s_state.scaleZ, 0.1f, 1.0f); + + ImGui::SeparatorText("Spawn"); + ImGui::InputInt("Count", &s_state.spawnCount); + if (s_state.spawnCount < 1) { + s_state.spawnCount = 1; + } + + ImGui::SeparatorText("Position"); + ImGui::Checkbox("Use player room", &s_state.usePlayerRoom); + if (!s_state.usePlayerRoom) { + ImGui::InputInt("Room No", &s_state.manualRoom); + } + + if (player != nullptr) { + ImGui::Text("Spawn pos: %.2f, %.2f, %.2f", + player->current.pos.x, player->current.pos.y, player->current.pos.z); + } else { + ImGui::TextDisabled("Player not available"); + } + + ImGui::Separator(); + + bool canSpawn = player != nullptr; + if (!canSpawn) { + ImGui::BeginDisabled(); + } + + if (ImGui::Button("Spawn", ImVec2(-1, 0))) { + cXyz pos = player->current.pos; + csXyz angle; + angle.set((s16)s_state.angleX, (s16)s_state.angleY, (s16)s_state.angleZ); + cXyz scale(s_state.scaleX, s_state.scaleY, s_state.scaleZ); + int roomNo = s_state.usePlayerRoom ? player->current.roomNo : s_state.manualRoom; + + layer_class* savedLayer = fpcLy_CurrentLayer(); + base_process_class* playScene = fpcM_SearchByName(fpcNm_PLAY_SCENE_e); + if (playScene != nullptr) { + fpcLy_SetCurrentLayer(&((process_node_class*)playScene)->layer); + } + + s_state.lastResult = 0; + s_state.lastAttempted = s_state.spawnCount; + for (int i = 0; i < s_state.spawnCount; ++i) { + unsigned int result = fopAcM_create( + (s16)s_state.actorId, + (u32)s_state.params, + &pos, + roomNo, + &angle, + &scale, + (s8)s_state.argument + ); + if (result != 0) { + s_state.lastResult = result; + } + } + s_state.hasResult = true; + + fpcLy_SetCurrentLayer(savedLayer); + } + + if (!canSpawn) { + ImGui::EndDisabled(); + } + + if (s_state.hasResult) { + if (s_state.lastResult != 0) { + if (s_state.lastAttempted == 1) { + ImGui::Text("Spawned: proc ID %u", s_state.lastResult); + } else { + ImGui::Text("Spawned %d (last proc ID %u)", s_state.lastAttempted, s_state.lastResult); + } + } else { + ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), "Spawn failed (returned 0)"); + } + } + + ImGui::End(); +} + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index a94c7d1f5b..9bbd366104 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -430,6 +430,7 @@ namespace dusk { m_menuTools.ShowAudioDebug(); m_menuTools.ShowSaveEditor(); m_menuTools.ShowStateShare(); + m_menuTools.ShowActorSpawner(); } m_menuTools.ShowAchievements(); DuskDebugPad(); // temporary, remove later diff --git a/src/dusk/imgui/ImGuiHeapOverlay.cpp b/src/dusk/imgui/ImGuiHeapOverlay.cpp index 577db12fc1..f0b4478bed 100644 --- a/src/dusk/imgui/ImGuiHeapOverlay.cpp +++ b/src/dusk/imgui/ImGuiHeapOverlay.cpp @@ -6,6 +6,7 @@ #include "JSystem/JFramework/JFWSystem.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "JSystem/JKernel/JKRHeap.h" +#include "JSystem/JKernel/JKRSolidHeap.h" #include "absl/container/flat_hash_map.h" #include "imgui.h" @@ -178,11 +179,11 @@ namespace dusk { } void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) { + const char* heapName = data.Safe ? heap->getName() : "INVALID"; char title[128]; - const char* name = data.Safe ? heap->getName() : "INVALID"; - snprintf(title, sizeof(title), "Heap %s##%p", heap->getName(), static_cast(heap)); + snprintf(title, sizeof(title), "Heap %s##%p", heapName, static_cast(heap)); - if (!ImGui::Begin(name, &open)) { + if (!ImGui::Begin(title, &open)) { ImGui::End(); return; } @@ -200,6 +201,30 @@ namespace dusk { const auto freeSize = BytesToString(heap->getFreeSize()); ImGui::Text("Size: %08X (%s), free: %08X (%s)", heap->getSize(), size.c_str(), heap->getFreeSize(), freeSize.c_str()); + { + void* vmemBase = nullptr; + size_t vmemCapacity = 0; + size_t vmemCommitted = 0; + if (heap->getHeapType() == 'EXPH') { + auto* h = static_cast(heap); + vmemBase = h->mVmemBase; vmemCapacity = h->mVmemCapacity; vmemCommitted = h->mVmemCommitted; + } else if (heap->getHeapType() == 'SLID') { + auto* h = static_cast(heap); + vmemBase = h->mVmemBase; vmemCapacity = h->mVmemCapacity; vmemCommitted = h->mVmemCommitted; + } + if (vmemBase) { + ImGui::SeparatorText("Virtual Memory"); + ImGui::Text("Base: %p", vmemBase); + const auto committedStr = BytesToString(vmemCommitted); + const auto capacityStr = BytesToString(vmemCapacity); + ImGui::Text("Committed: %s / %s reserved", committedStr.c_str(), capacityStr.c_str()); + float pct = vmemCapacity > 0 ? (float)vmemCommitted / (float)vmemCapacity : 0.0f; + char overlayBuf[32]; + snprintf(overlayBuf, sizeof(overlayBuf), "%.1f%%", pct * 100.0f); + ImGui::ProgressBar(pct, ImVec2(-1.0f, 0.0f), overlayBuf); + } + } + if (ImGui::Button("Check")) { data.HeapCheckFailed = !heap->check(); data.HeapCheckRan = true; diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index 2e3738aab9..7271a36015 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -119,6 +119,7 @@ namespace dusk { ImGui::MenuItem("Audio Debug", hotkeys::SHOW_AUDIO_DEBUG, &m_showAudioDebug); ImGui::MenuItem("Bloom", nullptr, &m_showBloomWindow); ImGui::MenuItem("Stub Log", nullptr, &m_showStubLog); + ImGui::MenuItem("Actor Spawner", nullptr, &m_showActorSpawner); if (!dusk::IsGameLaunched) { ImGui::EndDisabled(); diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index de94ad2d8f..257fed9719 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -29,6 +29,7 @@ namespace dusk { void ShowStateShare(); void ShowAchievements(); void notifyAchievement(std::string name); + void ShowActorSpawner(); private: bool m_showDebugOverlay = false; @@ -71,6 +72,8 @@ namespace dusk { bool m_showAchievements = false; ImGuiAchievements m_achievementsWindow; + + bool m_showActorSpawner = false; }; } diff --git a/src/dusk/vmem.cpp b/src/dusk/vmem.cpp new file mode 100644 index 0000000000..ef7a0a12ad --- /dev/null +++ b/src/dusk/vmem.cpp @@ -0,0 +1,122 @@ +#include "dusk/vmem.h" + +#if _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#ifndef MAP_ANONYMOUS +#define MAP_ANONYMOUS MAP_ANON +#endif +#endif + +#include +#include +#include + +namespace dusk { + +size_t vmem_page_size() { +#if _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +#else + return (size_t)sysconf(_SC_PAGESIZE); +#endif +} + +void* vmem_reserve(size_t size) { +#if _WIN32 + return VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_NOACCESS); +#else + void* p = mmap(nullptr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + return (p == MAP_FAILED) ? nullptr : p; +#endif +} + +bool vmem_commit(void* ptr, size_t size) { +#if _WIN32 + return VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != nullptr; +#else + return mprotect(ptr, size, PROT_READ | PROT_WRITE) == 0; +#endif +} + +void vmem_decommit(void* ptr, size_t size) { +#if _WIN32 + VirtualFree(ptr, size, MEM_DECOMMIT); +#else + mprotect(ptr, size, PROT_NONE); + madvise(ptr, size, MADV_DONTNEED); +#endif +} + +void vmem_release(void* ptr, size_t size) { +#if _WIN32 + (void)size; + VirtualFree(ptr, 0, MEM_RELEASE); +#else + munmap(ptr, size); +#endif +} + + +namespace { + +static void* s_arenaBase = nullptr; +static size_t s_arenaTotal = 0; +static std::atomic s_arenaBump{0}; + +struct FreeSlot { void* ptr; size_t size; }; +static FreeSlot s_free[JKR_VMEM_MAX_FREE_SLOTS]; +static size_t s_freeCount = 0; +static std::mutex s_freeMutex; + +} // namespace + +void vmem_arena_init() { + s_arenaBase = vmem_reserve(JKR_VMEM_ARENA_SIZE); + s_arenaTotal = JKR_VMEM_ARENA_SIZE; +} + +void* vmem_arena_alloc(size_t size) { + const size_t pageSize = vmem_page_size(); + size = (size + pageSize - 1) & ~(pageSize - 1); + + { + std::lock_guard lock(s_freeMutex); + for (size_t i = 0; i < s_freeCount; ++i) { + if (s_free[i].size >= size) { + void* ptr = s_free[i].ptr; + s_free[i] = s_free[--s_freeCount]; + return ptr; + } + } + } + + size_t offset = s_arenaBump.fetch_add(size); + if (offset + size > s_arenaTotal) { + s_arenaBump.fetch_sub(size); + return nullptr; + } + return static_cast(s_arenaBase) + offset; +} + +void vmem_arena_free(void* ptr, size_t size) { + if (!ptr) { + return; + } + const size_t pageSize = vmem_page_size(); + size = (size + pageSize - 1) & ~(pageSize - 1); + + vmem_decommit(ptr, size); + + std::lock_guard lock(s_freeMutex); + if (s_freeCount < JKR_VMEM_MAX_FREE_SLOTS) { + s_free[s_freeCount++] = {ptr, size}; + } +} + +} // namespace dusk diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index d25debb38e..36db835477 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -71,6 +71,7 @@ #include "dusk/config.hpp" #include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/settings.h" +#include "dusk/vmem.h" #include "dusk/version.hpp" #include "dusk/discord_presence.hpp" #include "tracy/Tracy.hpp" @@ -551,12 +552,19 @@ int game_main(int argc, char* argv[]) { config.desiredBackend = ResolveDesiredBackend(parsed_arg_options); config.logCallback = &aurora_log_callback; config.logLevel = startupLogLevel; - config.mem1Size = 256 * 1024 * 1024; + // Child heaps use independent vmem reservations on PC + config.mem1Size = DUSK_IF_ELSE(16, 256) * 1024 * 1024; config.mem2Size = 24 * 1024 * 1024; config.allowJoystickBackgroundEvents = true; config.imGuiInitCallback = &aurora_imgui_init_callback; config.allowTextureReplacements = true; config.allowTextureDumps = false; + + #if TARGET_PC + dusk::vmem_arena_init(); + #endif + + auroraInfo = aurora_initialize(argc, argv, &config); }