Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorLukas Tönne <lukas.toenne@gmail.com>2014-10-06 20:58:41 +0400
committerLukas Tönne <lukas.toenne@gmail.com>2015-01-20 11:30:03 +0300
commitfe8fad54b16b01b52811fab7b002ff7ea72cb800 (patch)
treea9215830e5caa55cfd49dcc09ac4406ef984b1c1 /source
parentc1f4542f0f5d00ed47b679bc41ce1d5d33d462ab (diff)
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 ...
Diffstat (limited to 'source')
-rw-r--r--source/blender/blenkernel/BKE_particle.h11
-rw-r--r--source/blender/blenkernel/intern/particle.c158
-rw-r--r--source/blender/blenkernel/intern/particle_system.c227
3 files changed, 203 insertions, 193 deletions
diff --git a/source/blender/blenkernel/BKE_particle.h b/source/blender/blenkernel/BKE_particle.h
index 39cf5505382..15d3c2f5ee8 100644
--- a/source/blender/blenkernel/BKE_particle.h
+++ b/source/blender/blenkernel/BKE_particle.h
@@ -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);
diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c
index ed2d5d2cf02..6a5dc5fa66a 100644
--- a/source/blender/blenkernel/intern/particle.c
+++ b/source/blender/blenkernel/intern/particle.c
@@ -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)
{
diff --git a/source/blender/blenkernel/intern/particle_system.c b/source/blender/blenkernel/intern/particle_system.c
index a54488f15db..cd50d95eb89 100644
--- a/source/blender/blenkernel/intern/particle_system.c
+++ b/source/blender/blenkernel/intern/particle_system.c
@@ -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;
- 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]);
+ int p;
+
+ pa= psys->particles + task->begin;
+ for (p = task->begin; p < task->end; ++p, ++pa)
+ distribute_threads_exec(task, pa, NULL, 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);
- }
+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;
+
+ /* 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);
+ ctx->dm = ctx->sim.psmd->dm;
+ ctx->ma = give_current_material(sim->ob, sim->psys->part->omat);
+}
- for (i=0; i<totthread; i++) {
- threads[i].ctx= ctx;
- threads[i].num= i;
- threads[i].tot= totthread;
- }
+#define MAX_PARTICLES_PER_TASK 256 /* XXX arbitrary - maybe use at least number of points instead for better balancing? */
- return threads;
+BLI_INLINE int ceil_ii(int a, int b)
+{
+ return (a + b - 1) / b;
}
-void psys_threads_free(ParticleThread *threads)
+void psys_tasks_create(ParticleThreadContext *ctx, int totpart, ParticleTask **r_tasks, int *r_numtasks)
{
- ParticleThreadContext *ctx= threads[0].ctx;
- int i, totthread= threads[0].tot;
+ 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)