DirectX11/Eternal Return 모작

[DirectX 11 Eternal Return 모작] 7. ResourceManager

Vfly 2025. 9. 3. 00:16

전체 코드 : https://github.com/HyangRim/DirectX11-Engine-Client

 

GitHub - HyangRim/DirectX11-Engine-Client

Contribute to HyangRim/DirectX11-Engine-Client development by creating an account on GitHub.

github.com


 

Resource Manager와 동적 리소스 로딩 시스템

핵심 기능

  • 템플릿 기반 리소스 관리
  • 동적 로딩 : 필요 시점에 리소스 로드
  • 메모리 풀링 : 리소스 재사용으로 성능 최적화
  • 픽셀 레벨 접근 : 텍스쳐 데이터 실시간 분석

 

ResourceManager 핵심 구조

1. 기본 아키텍쳐

class ResourceManager
{
    DECLARE_SINGLE(ResourceManager);

public:
    void Init();

    template<typename T>
    shared_ptr<T> Load(const wstring& _key, const wstring& _path);

    template<typename T>
    bool Add(const wstring& _key, shared_ptr<T> _object);

    template<typename T>
    shared_ptr<T> Get(const wstring& _key);

    shared_ptr<Texture> GetOrAddTexture(const wstring& _key, const wstring& _path);
    shared_ptr<Model> GetOrAddModel(const wstring& _key, const wstring& _path);

private:
    using KeyObjMap = map<wstring, shared_ptr<ResourceBase>>;
    array<KeyObjMap, RESOURCE_TYPE_COUNT> m_resources;
};

 

2. 템플릿 기반 리소스 관리

template<typename T>
shared_ptr<T> ResourceManager::Load(const wstring& _key, const wstring& _path)
{
    ResourceType resourceType = GetResourceType<T>();
    KeyObjMap& keyObjMap = m_resources[static_cast<uint8>(resourceType)];

    auto findIt = keyObjMap.find(_key);
    if (findIt != keyObjMap.end())
    {
        return static_pointer_cast<T>(findIt->second);
    }

    shared_ptr<T> object = make_shared<T>();
    object->Load(_path);
    object->SetName(_key);
    keyObjMap[_key] = object;

    return object;
}

template<typename T>
ResourceType ResourceManager::GetResourceType()
{
    if (std::is_same_v<T, Mesh>)
        return ResourceType::Mesh;
    else if (std::is_same_v<T, Shader>)
        return ResourceType::Shader;
    else if (std::is_same_v<T, Texture>)
        return ResourceType::Texture;
    else if (std::is_same_v<T, Material>)
        return ResourceType::Material;
    else if (std::is_same_v<T, Model>)
        return ResourceType::Model;
    
    return ResourceType::None;
}

 

핵심 기능

  • 타입 안정성 : 컴파일 타입에 리소스 타입 검증
  • 중복 로딩 방지 : 키 기반 캐싱으로 메모리 절약
  • 자동 타입 추론 : is_same_v로 리소스 타입 자동 분류

 

모델 및 애니메이션 리소스 관리

1. 모델 리소스 구조

class Model : public ResourceBase, public enable_shared_from_this<Model>
{
public:
    void ReadMaterial(const wstring& _filename);
    void ReadModel(const wstring& _filename);
    void ReadAnimation(const wstring& _tag, const wstring& _filename);

    // 태그 기반 애니메이션 관리
    shared_ptr<ModelAnimation> GetAnimationByTag(const wstring& _tag);
    unordered_map<wstring, shared_ptr<ModelAnimation>>& GetAnimations() { return m_animations; }

private:
    // 리소스 캐싱
    void BindCacheInfo();

    // 리소스 컨테이너
    vector<shared_ptr<Material>> m_materials;
    vector<shared_ptr<ModelBone>> m_bones;
    vector<shared_ptr<ModelMesh>> m_meshes;
    unordered_map<wstring, shared_ptr<ModelAnimation>> m_animations;
};

 

2. 동적 애니메이션 로딩

void Model::ReadAnimation(const wstring& _tag, const wstring& _filename)
{
    wstring fullPath = _modelPath + _filename + L".clip";

    shared_ptr<FileUtils> file = make_shared<FileUtils>();
    file->Open(fullPath, FileMode::Read);
    shared_ptr<ModelAnimation> animation = make_shared<ModelAnimation>();

    // 애니메이션 메타데이터 로드
    animation->m_name = Utils::ToWString(file->Read<string>());
    animation->m_duration = file->Read<float>();
    animation->m_frameRate = file->Read<float>();
    animation->m_frameCount = file->Read<uint32>();

    uint32 keyframesCount = file->Read<uint32>();

    // 키프레임 데이터 로드
    for (uint32 i = 0; i < keyframesCount; i++)
    {
        shared_ptr<ModelKeyframe> keyframe = make_shared<ModelKeyframe>();
        keyframe->m_boneName = Utils::ToWString(file->Read<string>());

        uint32 size = file->Read<uint32>();

        if (size > 0)
        {
            keyframe->m_transforms.resize(size);
            void* ptr = &keyframe->m_transforms[0];
            file->Read(&ptr, sizeof(ModelKeyframeData) * size);
        }

        animation->keyframes[keyframe->m_boneName] = keyframe;
    }

    // 태그 기반 저장
    m_animations[_tag] = animation;
}

 

 

셰이더 관리

1. 동적 셰이더 상수 버퍼 관리

class Shader
{
public:
    // 다양한 타입의 상수 버퍼 관리
    void PushGlobalData(const Matrix& _view, const Matrix& _projection);
    void PushTransformData(const TransformDesc& _desc);
    void PushLightData(const LightDesc& _desc);
    void PushMaterialData(const MaterialDesc& _desc);
    void PushBoneData(const BoneDesc& _desc);
    void PushHealthBarData(float _healthRatio, float _manaRatio, int _type = 0);

private:
    // 상수 버퍼 컨테이너
    GlobalDesc m_globalDesc;
    shared_ptr<ConstantBuffer<GlobalDesc>> m_globalBuffer;
    ComPtr<ID3DX11EffectConstantBuffer> m_globalEffectBuffer;

    TransformDesc m_transformDesc;
    shared_ptr<ConstantBuffer<TransformDesc>> m_transformBuffer;
    ComPtr<ID3DX11EffectConstantBuffer> m_transformEffectBuffer;

    // ... 기타 상수 버퍼들
};

 

 

Character Select Scene에서의 동적 리소스 로딩 사례

1. 런타임 리소스 로딩

void CharacterSelectScene::LoadCharacterSelectSceneImages()
{
    LoadCharacterListSlotImages();
    LoadCharacterImages();
    LoadCharacterSkinListSlotImages();
    LoadBackGround();
    LoadCharacterFullAndHalfImages();
}

void CharacterSelectScene::LoadCharacterFullAndHalfImages()
{
    shared_ptr<Shader> shader = make_shared<Shader>(L"ImageShader.fx");

    auto SetupUIMaterial = [&](shared_ptr<Material> material) {
        material->SetShader(shader);
        material->SetRenderQueue(RenderQueue::Transparent);
        material->SetTransparent(true);
        material->SetRenderingMode(RenderingMode::Forward);
    };

    wstring prefixTagFull = L"Full";
    wstring prefixTagHalf = L"Half";
    wstring prefixPath = L"..\\Resources\\Textures\\UI\\CharacterSelectScene\\CharacterImages\\";

    for (int i = 0; i < characterNames.size(); i++)
    {
        for (int j = 0; j < skinCount[i]; j++)
        {
            // Full Image 동적 로딩
            shared_ptr<Material> charFullImage = make_shared<Material>();
            SetupUIMaterial(charFullImage);

            wstring tag = L"Char" + prefixTagFull + L"_" + characterNames[i] + L"_S00" + to_wstring(j);
            wstring path = prefixPath + characterNames[i] + L"\\" + tag + L".png";
            
            auto charFullTexture = RESOURCES->Load<Texture>(tag, path);
            charFullImage->SetDiffuseMap(charFullTexture);
            
            RESOURCES->Add(tag, charFullImage);

            // Half Image 동적 로딩
            shared_ptr<Material> charHalfImage = make_shared<Material>();
            SetupUIMaterial(charHalfImage);

            tag = L"Char" + prefixTagHalf + L"_" + characterNames[i] + L"_S00" + to_wstring(j);
            path = prefixPath + characterNames[i] + L"\\" + tag + L".png";
            
            auto charHalfTexture = RESOURCES->Load<Texture>(tag, path);
            charHalfImage->SetDiffuseMap(charHalfTexture);
            
            RESOURCES->Add(tag, charHalfImage);
        }
    }
}

 

2. 멀티스레드 리소스 로딩

class LumiaIsland : public Scene
{
private:
    // 멀티스레드 로딩용 함수
    static DWORD WINAPI BackgroundLoadingThread(LPVOID _param);
    void ProcessMainThreadTasks();
    
private:
    CRITICAL_SECTION m_loadingCS;
    HANDLE m_loadingThread;
    atomic<bool> m_loadingComplete{false};
    
    // 메인 스레드 작업 큐
    queue<function<void()>> m_mainThreadTasks;
    CRITICAL_SECTION m_mainThreadTasksCS;
    atomic<bool> m_objectsCreated{false};
};

DWORD WINAPI LumiaIsland::BackgroundLoadingThread(LPVOID _param)
{
    LumiaIsland* scene = static_cast<LumiaIsland*>(_param);
    
    // 백그라운드에서 리소스 로딩
    EnterCriticalSection(&scene->m_loadingCS);
    {
        // 대용량 리소스들을 백그라운드에서 로딩
        scene->LoadHeavyResources();
    }
    LeaveCriticalSection(&scene->m_loadingCS);
    
    scene->m_loadingComplete = true;
    return 0;
}

void LumiaIsland::ProcessMainThreadTasks()
{
    EnterCriticalSection(&m_mainThreadTasksCS);
    {
        while (!m_mainThreadTasks.empty())
        {
            auto task = m_mainThreadTasks.front();
            m_mainThreadTasks.pop();
            task();
        }
    }
    LeaveCriticalSection(&m_mainThreadTasksCS);
}