Use the generic task scheduler for threaded particle tasks, i.e.

distribution and path caching for child particles.

This gives a significant improvement of viewport playback performance
with higher child particle counts. Particles previously used their own
threads and had a rather high limit for threading. Also threading
apparently was disabled because only 1 thread was being used ...
This commit is contained in:
Lukas Tönne 2014-10-06 18:58:41 +02:00
parent c1f4542f0f
commit fe8fad54b1
Notes: blender-bot 2023-02-14 09:19:02 +01:00
Referenced by issue #44353, Particle distribution overlaps if number is greater than 256
Referenced by issue #44177, particles appear 100s of time at the same place in random
3 changed files with 206 additions and 196 deletions

View File

@ -160,11 +160,11 @@ typedef struct ParticleThreadContext {
float *vg_effector;
} ParticleThreadContext;
typedef struct ParticleThread {
typedef struct ParticleTask {
ParticleThreadContext *ctx;
struct RNG *rng, *rng_path;
int num, tot;
} ParticleThread;
int begin, end;
} ParticleTask;
typedef struct ParticleBillboardData {
struct Object *ob;
@ -347,8 +347,9 @@ void psys_get_dupli_texture(struct ParticleSystem *psys, struct ParticleSettings
void psys_get_dupli_path_transform(struct ParticleSimulationData *sim, struct ParticleData *pa, struct ChildParticle *cpa,
struct ParticleCacheKey *cache, float mat[4][4], float *scale);
ParticleThread *psys_threads_create(struct ParticleSimulationData *sim);
void psys_threads_free(ParticleThread *threads);
void psys_thread_context_init(struct ParticleThreadContext *ctx, struct ParticleSimulationData *sim);
void psys_tasks_create(struct ParticleThreadContext *ctx, int totpart, struct ParticleTask **r_tasks, int *r_numtasks);
void psys_tasks_free(struct ParticleTask *tasks, int numtasks);
void psys_make_billboard(ParticleBillboardData *bb, float xvec[3], float yvec[3], float zvec[3], float center[3]);
void psys_apply_hair_lattice(struct Scene *scene, struct Object *ob, struct ParticleSystem *psys);

View File

@ -53,6 +53,7 @@
#include "BLI_utildefines.h"
#include "BLI_kdtree.h"
#include "BLI_rand.h"
#include "BLI_task.h"
#include "BLI_threads.h"
#include "BLI_linklist.h"
@ -2441,17 +2442,15 @@ static void get_strand_normal(Material *ma, const float surfnor[3], float surfdi
copy_v3_v3(nor, vnor);
}
static int psys_threads_init_path(ParticleThread *threads, Scene *scene, float cfra, int editupdate)
static bool psys_thread_context_init_path(ParticleThreadContext *ctx, ParticleSimulationData *sim, Scene *scene, float cfra, int editupdate)
{
ParticleThreadContext *ctx = threads[0].ctx;
/* Object *ob = ctx->sim.ob; */
ParticleSystem *psys = ctx->sim.psys;
ParticleSystem *psys = sim->psys;
ParticleSettings *part = psys->part;
/* ParticleEditSettings *pset = &scene->toolsettings->particle; */
int totparent = 0, between = 0;
int steps = (int)pow(2.0, (double)part->draw_step);
int steps = 1 << part->draw_step;
int totchild = psys->totchild;
int i, seed, totthread = threads[0].tot;
psys_thread_context_init(ctx, sim);
/*---start figuring out what is actually wanted---*/
if (psys_in_edit_mode(scene, psys)) {
@ -2460,7 +2459,7 @@ static int psys_threads_init_path(ParticleThread *threads, Scene *scene, float c
if (psys->renderdata == 0 && (psys->edit == NULL || pset->flag & PE_DRAW_PART) == 0)
totchild = 0;
steps = (int)pow(2.0, (double)pset->draw_step);
steps = 1 << pset->draw_step;
}
if (totchild && part->childtype == PART_CHILD_FACES) {
@ -2480,18 +2479,8 @@ static int psys_threads_init_path(ParticleThread *threads, Scene *scene, float c
totparent = MIN2(totparent, totchild);
}
if (totchild == 0) return 0;
/* init random number generator */
seed = 31415926 + ctx->sim.psys->seed;
if (ctx->editupdate || totchild < 10000)
totthread = 1;
for (i = 0; i < totthread; i++) {
threads[i].rng_path = BLI_rng_new(seed);
threads[i].tot = totthread;
}
if (totchild == 0)
return false;
/* fill context values */
ctx->between = between;
@ -2514,21 +2503,21 @@ static int psys_threads_init_path(ParticleThread *threads, Scene *scene, float c
if (psys->part->flag & PART_CHILD_EFFECT)
ctx->vg_effector = psys_cache_vgroup(ctx->dm, psys, PSYS_VG_EFFECTOR);
/* set correct ipo timing */
#if 0 // XXX old animation system
if (part->flag & PART_ABS_TIME && part->ipo) {
calc_ipo(part->ipo, cfra);
execute_ipo((ID *)part, part->ipo);
}
#endif // XXX old animation system
return true;
}
return 1;
static void psys_task_init_path(ParticleTask *task, ParticleSimulationData *sim)
{
/* init random number generator */
int seed = 31415926 + sim->psys->seed;
task->rng_path = BLI_rng_new(seed);
}
/* note: this function must be thread safe, except for branching! */
static void psys_thread_create_path(ParticleThread *thread, struct ChildParticle *cpa, ParticleCacheKey *child_keys, int i)
static void psys_thread_create_path(ParticleTask *task, struct ChildParticle *cpa, ParticleCacheKey *child_keys, int i)
{
ParticleThreadContext *ctx = thread->ctx;
ParticleThreadContext *ctx = task->ctx;
Object *ob = ctx->sim.ob;
ParticleSystem *psys = ctx->sim.psys;
ParticleSettings *part = psys->part;
@ -2794,89 +2783,80 @@ static void psys_thread_create_path(ParticleThread *thread, struct ChildParticle
child_keys->steps = -1;
}
static void *exec_child_path_cache(void *data)
static void exec_child_path_cache(TaskPool *UNUSED(pool), void *taskdata, int UNUSED(threadid))
{
ParticleThread *thread = (ParticleThread *)data;
ParticleThreadContext *ctx = thread->ctx;
ParticleTask *task = taskdata;
ParticleThreadContext *ctx = task->ctx;
ParticleSystem *psys = ctx->sim.psys;
ParticleCacheKey **cache = psys->childcache;
ChildParticle *cpa;
int i, totchild = ctx->totchild, first = 0;
int i;
if (thread->tot > 1) {
first = ctx->parent_pass ? 0 : ctx->totparent;
totchild = ctx->parent_pass ? ctx->totparent : ctx->totchild;
cpa = psys->child + task->begin;
for (i = task->begin; i < task->end; ++i, ++cpa) {
psys_thread_create_path(task, cpa, cache[i], i);
}
cpa = psys->child + first + thread->num;
for (i = first + thread->num; i < totchild; i += thread->tot, cpa += thread->tot)
psys_thread_create_path(thread, cpa, cache[i], i);
return 0;
}
void psys_cache_child_paths(ParticleSimulationData *sim, float cfra, int editupdate)
{
ParticleThread *pthreads;
ParticleThreadContext *ctx;
ListBase threads;
int i, totchild, totparent, totthread;
TaskScheduler *task_scheduler;
TaskPool *task_pool;
ParticleThreadContext ctx;
ParticleTask *tasks_parent, *tasks_child;
int numtasks_parent, numtasks_child;
int i, totchild, totparent;
if (sim->psys->flag & PSYS_GLOBAL_HAIR)
return;
pthreads = psys_threads_create(sim);
if (!psys_threads_init_path(pthreads, sim->scene, cfra, editupdate)) {
psys_threads_free(pthreads);
/* create a task pool for child path tasks */
if (!psys_thread_context_init_path(&ctx, sim, sim->scene, cfra, editupdate))
return;
}
ctx = pthreads[0].ctx;
totchild = ctx->totchild;
totparent = ctx->totparent;
task_scheduler = BLI_task_scheduler_get();
task_pool = BLI_task_pool_create(task_scheduler, &ctx);
totchild = ctx.totchild;
totparent = ctx.totparent;
if (editupdate && sim->psys->childcache && totchild == sim->psys->totchildcache) {
; /* just overwrite the existing cache */
}
else {
/* clear out old and create new empty path cache */
free_child_path_cache(sim->psys);
sim->psys->childcache = psys_alloc_path_cache_buffers(&sim->psys->childcachebufs, totchild, ctx->steps + 1);
sim->psys->childcache = psys_alloc_path_cache_buffers(&sim->psys->childcachebufs, totchild, ctx.steps + 1);
sim->psys->totchildcache = totchild;
}
totthread = pthreads[0].tot;
if (totthread > 1) {
/* make virtual child parents thread safe by calculating them first */
if (totparent) {
BLI_init_threads(&threads, exec_child_path_cache, totthread);
for (i = 0; i < totthread; i++) {
pthreads[i].ctx->parent_pass = 1;
BLI_insert_thread(&threads, &pthreads[i]);
}
BLI_end_threads(&threads);
for (i = 0; i < totthread; i++)
pthreads[i].ctx->parent_pass = 0;
}
BLI_init_threads(&threads, exec_child_path_cache, totthread);
for (i = 0; i < totthread; i++)
BLI_insert_thread(&threads, &pthreads[i]);
BLI_end_threads(&threads);
/* cache parent paths */
ctx.parent_pass = 1;
psys_tasks_create(&ctx, totparent, &tasks_parent, &numtasks_parent);
for (i = 0; i < numtasks_parent; ++i) {
ParticleTask *task = &tasks_parent[i];
psys_task_init_path(task, sim);
BLI_task_pool_push(task_pool, exec_child_path_cache, task, false, TASK_PRIORITY_LOW);
}
else
exec_child_path_cache(&pthreads[0]);
BLI_task_pool_work_and_wait(task_pool);
/* cache child paths */
ctx.parent_pass = 0;
psys_tasks_create(&ctx, totchild, &tasks_child, &numtasks_child);
for (i = 0; i < numtasks_child; ++i) {
ParticleTask *task = &tasks_child[i];
psys_task_init_path(task, sim);
BLI_task_pool_push(task_pool, exec_child_path_cache, task, false, TASK_PRIORITY_LOW);
}
BLI_task_pool_work_and_wait(task_pool);
psys_threads_free(pthreads);
BLI_task_pool_free(task_pool);
psys_tasks_free(tasks_parent, numtasks_parent);
psys_tasks_free(tasks_child, numtasks_child);
}
/* figure out incremental rotations along path starting from unit quat */
static void cache_key_incremental_rotation(ParticleCacheKey *key0, ParticleCacheKey *key1, ParticleCacheKey *key2, float *prev_tangent, int i)
{

View File

@ -68,6 +68,7 @@
#include "BLI_kdtree.h"
#include "BLI_kdopbvh.h"
#include "BLI_sort.h"
#include "BLI_task.h"
#include "BLI_threads.h"
#include "BLI_linklist.h"
@ -789,7 +790,7 @@ static int distribute_binary_search(float *sum, int n, float value)
/* note: this function must be thread safe, for from == PART_FROM_CHILD */
#define ONLY_WORKING_WITH_PA_VERTS 0
static void distribute_threads_exec(ParticleThread *thread, ParticleData *pa, ChildParticle *cpa, int p)
static void distribute_threads_exec(ParticleTask *thread, ParticleData *pa, ChildParticle *cpa, int p)
{
ParticleThreadContext *ctx= thread->ctx;
Object *ob= ctx->sim.ob;
@ -979,36 +980,40 @@ static void distribute_threads_exec(ParticleThread *thread, ParticleData *pa, Ch
BLI_rng_skip(thread->rng, rng_skip_tot);
}
static void *distribute_threads_exec_cb(void *data)
static void exec_distribute_parent(TaskPool *UNUSED(pool), void *taskdata, int UNUSED(threadid))
{
ParticleThread *thread= (ParticleThread*)data;
ParticleSystem *psys= thread->ctx->sim.psys;
ParticleTask *task = taskdata;
ParticleSystem *psys= task->ctx->sim.psys;
ParticleData *pa;
int p;
pa= psys->particles + task->begin;
for (p = task->begin; p < task->end; ++p, ++pa)
distribute_threads_exec(task, pa, NULL, p);
}
static void exec_distribute_child(TaskPool *UNUSED(pool), void *taskdata, int UNUSED(threadid))
{
ParticleTask *task = taskdata;
ParticleSystem *psys = task->ctx->sim.psys;
ChildParticle *cpa;
int p, totpart;
if (thread->ctx->from == PART_FROM_CHILD) {
totpart= psys->totchild;
cpa= psys->child;
for (p=0; p<totpart; p++, cpa++) {
if (thread->ctx->skip) /* simplification skip */
BLI_rng_skip(thread->rng, PSYS_RND_DIST_SKIP * thread->ctx->skip[p]);
if ((p+thread->num) % thread->tot == 0)
distribute_threads_exec(thread, NULL, cpa, p);
else /* thread skip */
BLI_rng_skip(thread->rng, PSYS_RND_DIST_SKIP);
}
int p;
/* RNG skipping at the beginning */
cpa = psys->child;
for (p = 0; p < task->begin; ++p, ++cpa) {
if (task->ctx->skip) /* simplification skip */
BLI_rng_skip(task->rng, PSYS_RND_DIST_SKIP * task->ctx->skip[p]);
BLI_rng_skip(task->rng, PSYS_RND_DIST_SKIP);
}
else {
totpart= psys->totpart;
pa= psys->particles + thread->num;
for (p=thread->num; p<totpart; p+=thread->tot, pa+=thread->tot)
distribute_threads_exec(thread, pa, NULL, p);
for (; p < task->end; ++p, ++cpa) {
if (task->ctx->skip) /* simplification skip */
BLI_rng_skip(task->rng, PSYS_RND_DIST_SKIP * task->ctx->skip[p]);
distribute_threads_exec(task, NULL, cpa, p);
}
return 0;
}
static int distribute_compare_orig_index(const void *p1, const void *p2, void *user_data)
@ -1061,18 +1066,19 @@ static void distribute_invalid(Scene *scene, ParticleSystem *psys, int from)
/* Creates a distribution of coordinates on a DerivedMesh */
/* This is to denote functionality that does not yet work with mesh - only derived mesh */
static int distribute_threads_init_data(ParticleThread *threads, Scene *scene, DerivedMesh *finaldm, int from)
static int psys_thread_context_init_distribute(ParticleThreadContext *ctx, ParticleSimulationData *sim, int from)
{
ParticleThreadContext *ctx= threads[0].ctx;
Object *ob= ctx->sim.ob;
ParticleSystem *psys= ctx->sim.psys;
Scene *scene = sim->scene;
DerivedMesh *finaldm = sim->psmd->dm;
Object *ob = sim->ob;
ParticleSystem *psys= sim->psys;
ParticleData *pa=0, *tpars= 0;
ParticleSettings *part;
ParticleSeam *seams= 0;
KDTree *tree=0;
DerivedMesh *dm= NULL;
float *jit= NULL;
int i, seed, p=0, totthread= threads[0].tot;
int i, p=0;
int cfrom=0;
int totelem=0, totpart, *particle_element=0, children=0, totseam=0;
int jitlevel= 1, distr;
@ -1081,18 +1087,20 @@ static int distribute_threads_init_data(ParticleThread *threads, Scene *scene, D
if (ELEM(NULL, ob, psys, psys->part))
return 0;
part=psys->part;
totpart=psys->totpart;
if (totpart==0)
return 0;
if (!finaldm->deformedOnly && !finaldm->getTessFaceDataArray(finaldm, CD_ORIGINDEX)) {
printf("Can't create particles with the current modifier stack, disable destructive modifiers\n");
// XXX error("Can't paint with the current modifier stack, disable destructive modifiers");
return 0;
}
psys_thread_context_init(ctx, sim);
/* First handle special cases */
if (from == PART_FROM_CHILD) {
/* Simple children */
@ -1392,52 +1400,54 @@ static int distribute_threads_init_data(ParticleThread *threads, Scene *scene, D
alloc_child_particles(psys, totpart);
}
if (!children || psys->totchild < 10000)
totthread= 1;
seed= 31415926 + ctx->sim.psys->seed;
for (i=0; i<totthread; i++) {
threads[i].rng= BLI_rng_new(seed);
threads[i].tot= totthread;
}
return 1;
}
static void psys_task_init_distribute(ParticleTask *task, ParticleSimulationData *sim)
{
/* init random number generator */
int seed = 31415926 + sim->psys->seed;
task->rng = BLI_rng_new(seed);
}
static void distribute_particles_on_dm(ParticleSimulationData *sim, int from)
{
TaskScheduler *task_scheduler;
TaskPool *task_pool;
ParticleThreadContext ctx;
ParticleTask *tasks;
DerivedMesh *finaldm = sim->psmd->dm;
ListBase threads;
ParticleThread *pthreads;
ParticleThreadContext *ctx;
int i, totthread;
pthreads= psys_threads_create(sim);
if (!distribute_threads_init_data(pthreads, sim->scene, finaldm, from)) {
psys_threads_free(pthreads);
int i, totpart, numtasks;
/* create a task pool for distribution tasks */
if (!psys_thread_context_init_distribute(&ctx, sim, from))
return;
task_scheduler = BLI_task_scheduler_get();
task_pool = BLI_task_pool_create(task_scheduler, &ctx);
totpart = (from == PART_FROM_CHILD ? sim->psys->totchild : sim->psys->totpart);
psys_tasks_create(&ctx, totpart, &tasks, &numtasks);
for (i = 0; i < numtasks; ++i) {
ParticleTask *task = &tasks[i];
psys_task_init_distribute(task, sim);
if (from == PART_FROM_CHILD)
BLI_task_pool_push(task_pool, exec_distribute_child, task, false, TASK_PRIORITY_LOW);
else
BLI_task_pool_push(task_pool, exec_distribute_parent, task, false, TASK_PRIORITY_LOW);
}
totthread= pthreads[0].tot;
if (totthread > 1) {
BLI_init_threads(&threads, distribute_threads_exec_cb, totthread);
for (i=0; i<totthread; i++)
BLI_insert_thread(&threads, &pthreads[i]);
BLI_end_threads(&threads);
}
else
distribute_threads_exec_cb(&pthreads[0]);
BLI_task_pool_work_and_wait(task_pool);
BLI_task_pool_free(task_pool);
psys_calc_dmcache(sim->ob, finaldm, sim->psys);
ctx= pthreads[0].ctx;
if (ctx->dm != finaldm)
ctx->dm->release(ctx->dm);
psys_threads_free(pthreads);
if (ctx.dm != finaldm)
ctx.dm->release(ctx.dm);
psys_tasks_free(tasks, numtasks);
}
/* ready for future use, to emit particles without geometry */
@ -1470,35 +1480,55 @@ static void distribute_particles(ParticleSimulationData *sim, int from)
}
/* threaded child particle distribution and path caching */
ParticleThread *psys_threads_create(ParticleSimulationData *sim)
void psys_thread_context_init(ParticleThreadContext *ctx, ParticleSimulationData *sim)
{
ParticleThread *threads;
ParticleThreadContext *ctx;
int i, totthread = BKE_scene_num_threads(sim->scene);
threads= MEM_callocN(sizeof(ParticleThread)*totthread, "ParticleThread");
ctx= MEM_callocN(sizeof(ParticleThreadContext), "ParticleThreadContext");
memset(ctx, 0, sizeof(ParticleThreadContext));
ctx->sim = *sim;
ctx->dm= ctx->sim.psmd->dm;
ctx->ma= give_current_material(sim->ob, sim->psys->part->omat);
memset(threads, 0, sizeof(ParticleThread)*totthread);
for (i=0; i<totthread; i++) {
threads[i].ctx= ctx;
threads[i].num= i;
threads[i].tot= totthread;
}
return threads;
ctx->dm = ctx->sim.psmd->dm;
ctx->ma = give_current_material(sim->ob, sim->psys->part->omat);
}
void psys_threads_free(ParticleThread *threads)
{
ParticleThreadContext *ctx= threads[0].ctx;
int i, totthread= threads[0].tot;
#define MAX_PARTICLES_PER_TASK 256 /* XXX arbitrary - maybe use at least number of points instead for better balancing? */
BLI_INLINE int ceil_ii(int a, int b)
{
return (a + b - 1) / b;
}
void psys_tasks_create(ParticleThreadContext *ctx, int totpart, ParticleTask **r_tasks, int *r_numtasks)
{
ParticleTask *tasks;
int numtasks = ceil_ii(totpart, MAX_PARTICLES_PER_TASK);
float particles_per_task = (float)totpart / (float)numtasks, p, pnext;
int i;
tasks = MEM_callocN(sizeof(ParticleTask) * numtasks, "ParticleThread");
*r_numtasks = numtasks;
*r_tasks = tasks;
printf("made %d tasks:\n", numtasks);
p = 0.0f;
printf(" %d", (int)p);
for (i = 0; i < numtasks; i++, p = pnext) {
pnext = p + particles_per_task;
tasks[i].ctx = ctx;
tasks[i].begin = (int)p;
tasks[i].end = min_ii((int)pnext, totpart);
printf("..%d", tasks[i].end);
}
printf("\n");
}
void psys_tasks_free(ParticleTask *tasks, int numtasks)
{
ParticleThreadContext *ctx;
int i;
if (numtasks == 0)
return;
ctx = tasks[0].ctx;
/* path caching */
if (ctx->vg_length)
MEM_freeN(ctx->vg_length);
@ -1529,15 +1559,14 @@ void psys_threads_free(ParticleThread *threads)
BLI_kdtree_free(ctx->tree);
/* threads */
for (i=0; i<totthread; i++) {
if (threads[i].rng)
BLI_rng_free(threads[i].rng);
if (threads[i].rng_path)
BLI_rng_free(threads[i].rng_path);
for (i = 0; i < numtasks; ++i) {
if (tasks[i].rng)
BLI_rng_free(tasks[i].rng);
if (tasks[i].rng_path)
BLI_rng_free(tasks[i].rng_path);
}
MEM_freeN(ctx);
MEM_freeN(threads);
MEM_freeN(tasks);
}
static void initialize_particle_texture(ParticleSimulationData *sim, ParticleData *pa, int p)