游戏资产的动态流式传输、加载、卸载和共享

Dynamic Streaming, Loading, Unloading and Sharing of Game Assets

我目前正在设计一个在游戏引擎中处理游戏资产的系统,我只是在寻找一些 input/discussion 的最佳方法。我目前的系统是非常手动和传统的,我正在寻找更自动化的东西来代替它。我的目标是实现一个系统:

  1. 根据需要动态加载和卸载数据,例如在大型开放世界中导航时。
  2. 在重用相同数据的资产之间共享数据,确保数据仅在不再有任何用户时从内存中卸载。
  3. 管理资产的质量 changes/partial 或预览或 LOD 版本。

到目前为止,我的新系统看起来像这样:

我目前的方法是抽象资产加载和卸载的概念,并且简单地让代码引用 'using' 一个资产,或者 'unusing' 它,并且在资产内部我保留一个整数跟踪资产有多少用户。当资产达到 0 个用户时,它将从相关词典中删除。

我使用特定的定制管理器 class 作为每种类型资源的中央控制,例如 TextureManager 或 MeshManager。每个 AssetManager 内部都有一个线程 运行ning,用于处理来自游戏主线程之外的流式资产。单独的 AssetStreamerThreads 运行 一直在后台运行,当没有工作要做时阻塞,等待主线程队列中的作业完成。当有工作要做时,他们抓住它,完成它,然后 return 它到一个完成的队列,供主线程在下一次更新中接收。

这些线程中的每一个都处理从硬盘上的文件解码资产(或者理论上任何地方真的,甚至下载?)并将其放入内存,准备好通过几次 OpenGL 调用简单地上传到 GPU .数据存储在来回传递的作业中。

管理器保留每个已加载的资产的字典,文件路径等同于对内存中已有资产对象的引用。

伪代码:

// A simple structure to store job data, Data would be tailored to each type of Asset
class AssetLoadJob {
    boolean complete = false;
    String filepath;
    Asset targetAsset;
    Data data;
}

// The Asset Manager
class AssetManager {
    Dictionary<String,Asset> assets;
    Queue<AssetLoadJob> jobsList;
    Queue<AssetLoadJob> jobsComplete;

    // Startup and run the streamer thread.
    public AssetManager {
        AssetStreamerThread streamer = new AssetStreamerThread(this);
        streamer.run();
    }

    // To fetch an asset to use. Note, the Asset returned may not be loaded, but will be eventually.
    public Asset use(String filepath) {
        if(assets.exists(filepath)) {
            assets.addUser();
            return assets.get(filepath);
        }
        else {
            Asset newAsset = new Asset();
            jobsList.add(new AssetLoadJob(newAsset, filepath));
        }
    }

    // Don't need it anymore? Let the manager know.
    public void unuse(String filepath) {
        assets.get(filepath).removeUser();
        if(assets.get(filepath).getUsers() < 1) {
            assets.get(filepath).unload();
            assets.remove(filepath);
        }
    }

    // Called before each update() loop in the game engine.
    public void processJobs() {
        foreach(jobsComplete as finishedJob) {
            finishedJob.targetAsset.receiveData(finishedJob.data);
            jobsComplete.remove(finishedJob);
        }
    }

    // Accessed by worker thread
    public AssetLoadJob getJob() {
        return jobsList.remove();
    }

    // Accessed by worker thread
    public void returnJob(AssetLoadJob job) {
        jobsComplete.add(job);
    }
}

// The worker thread which handles loading content.
class AssetStreamerThread {

    AssetManager mgr;

    public AssetStreamerThread(AssetManager mgr) {
        this.mgr = mgr;
    }

    // The out of main thread loop which runs forever.
    public void run() {
        while(forever) {
            AssetLoadJob job = mgr.getJob(); // Blocking until returns valid job.
            // Load job data.. 
            mgr.returnJob(job);
        }
    }
}

// An abstract example of an Asset. In practice, this might be instead a Texture, Mesh, Sound object, etc.
class Asset {

    private int users = 0;
    private boolean loaded = false;

    // Since we can't access users integer directly, these next two methods control increments/decrements.
    public void addUser() {
        users++;
    }

    public void removeUser() {
        users--;
    }

    public int getUsers() {
        return users;
    }

    // The method to Bind an Asset for rendering, such as Binding a texture before drawing an object with it.
    public void bind() {
        if(loaded) {
            // Use loaded data on GPU
        } else {
            // Use placeholder for missing data or just use 'empty' data. Eg: a checkerboard texture for missing textures or solid black 1px x 1px texture, or whatever. A question mark shape for meshes, or simply nothing at all.
        }
    }

    // This method is called by the Manager to give the Asset it's data when it's loaded.
    public void receiveData(data) {
        // Upload data to GPU
        loaded = true;
    }

    // Called by Manager, informs the Asset it can release resources.
    public void unload() {
        // Unload data from GPU
        loaded = false;
    }
}

优点:

缺点:

结论:

这是迄今为止我能想到的最好的,但我脑子里还有很多未知数。我至少在正确的轨道上吗?我知道我已经问了很多问题,但我并不是在寻找每个问题的答案。任何解决我的部分或大部分查询的答案都将被接受。在我开始实施它之前,我主要只是在寻找新的方向来考虑完成这个概念。或者来自经验丰富的大师的警告,他们在我之前走过这些路,知道龙在哪里。

虽然这些都是很好的问题,但您确实在这里问了太多问题。

我可以回答的一个方面是您的引用计数 - 不要那样做。利用垃圾收集器,所有工作 Java 已经投入其中。

只需让您的中央存储对加载的数据保持 SoftReferenceWeakReference。如果该引用已变为 null,那么您将需要在下次被要求时重新加载它。 Java 将在需要内存时自动对不需要的内容进行垃圾回收。您不需要引用计数或任何其他内容。