利用者:Phonybone/Archive/Modular particle system/Buffer Management

提供: wiki
< 利用者:Phonybone‎ | Archive‎ | Modular particle system
2018年6月29日 (金) 04:37時点におけるYamyam (トーク | 投稿記録)による版 (1版 をインポートしました)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
移動先: 案内検索

Buffer Management

Motivation

The current particle implementation allocates the particle buffer as a single contiguous memory block. This has serious drawbacks:

  1. The total amount of particles has to be known. This works for the current fixed-pipeline approach, but not when particles are emitted dynamically.
    Example: Emission of new particles from existing particles at a certain speed -> Depends on the simulation result
  2. A lot of memory can remain unused when particle lifetimes are short compared to overall simulation time. Disk caching can somewhat alleviate, but the general problem remains.
Image 2: Current buffer and cache allocation


In a perfect world, a particle would be allocated when it is born and freed when it dies. Frequent allocation (and freeing) of heap data is costly however. As a compromise paging system has been devised.

Paging System

  • Particle buffers are split into Pages with a fixed amount of particles.
  • Optimal page size can be determined from particle size, average lifetime, available memory, cache size, etc.
  • Each time a particle is emitted, it gets added to the last added page until the page is filled and a new page is allocated.
  • When all particles in a page are dead, it can be removed from active memory (cached frames remain).
Image 3: Page-based allocation. Compare the amount of unused allocated memory!

Particle Access HowTo

Old: Pointer Arithmetic

In previous versions of the particle system, one could simply use the system's main particle buffer pointer and add the index of the desired particle to get a pointer to the desired particle data. Here is an example of what this looked like:

Accessing a single particle:

int p = ...  /* Index of the particle from somewhere else. */
ParticleData *pa = psys->particles + p; /* Get the particle pointer 
                                           by adding to the main buffer pointer */

Iterating over all particles could be done in a simple loop:

for (int p = 0; p < psys->totpart; ++p) {
    ParticleData *pa = psys->particles + p;
    ...
}

New: Access by Functions and Iterators

With the new paged buffer system, the pointer arithmetic does not work any more. Instead there are now two ways of accessing particles, one for accessing a single particle and one for iterating over particles (or a subset, see below).

Indexed access

Access by index is done straightforward with a "get" function. Only living particles are guaranteed to be kept in memory, so you should always check the returned pointer:

int p = ...
ParticleData *pa = psys_get_particle(psys, p);
if (pa && pa->alive==PARS_ALIVE) { /* In case the particle is dead, it should not be used! */
    ...
}

Iterating

A common case is that you need to iterate over all particles or a specific subset of particles (e.g. all visible particles). For this there is the convenient method of using an iterator. Iterators are a small struct and should be created as local stack data. Do NOT allocate them as heap data and and store them permanently!

Good:

void my_func()
{
    ParticleIterator pit;
    ...
    for (psys_iterator_init(&pit, psys); pit.pa; psys_iterator_next(&pit)) {
        do_stuff_with_particle(pit.pa);
        do_stuff_with_particle_index(pit.index);
    }
}

BAD:

ParticleIterator* my_evil_func()
{
    return MEM_callocN(sizeof(ParticleIterator), "ParticleIterator");
}

Iterator custom skipping

The default iterator automatically skips dead and unborn particles. If you need to iterate over a specific subset of the particles, you can do so by writing a small skip function:

static int my_skip_func(ParticleIterator *pit)
{
    // Return 1 if the particle should be skipped
    return (pit.pa || pit.pa->alive!=PARS_ALIVE || pit.pa->loveliness == 0);
}

...

{
    ParticleIterator pit;
    for (psys_iterator_init_skip(&pit, psys, my_skip_func); 
         pit.pa; 
         psys_iterator_next(&pit)) {
        ...
    }
}