erebus
changeset 31:53a98c148bf8
- introduced SurfaceGeometry to carry all the geometric information input to
BRDF sampling and evaluation functions.
- made Reflectance keep an (optional) pointer to its material
- simplified PhongRefl::sample_dir, with the help of SurfaceGeometry
- worked around microsoft's broken std::thread implementation's deadlock on join
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Sat, 07 Jun 2014 09:14:17 +0300 |
parents | e78f68d03ae9 |
children | b1fc96c71bcc |
files | liberebus/src/brdf.cc liberebus/src/brdf.h liberebus/src/erebus.cc liberebus/src/erebus_impl.h liberebus/src/rt.cc liberebus/src/threadpool.cc |
diffstat | 6 files changed, 204 insertions(+), 75 deletions(-) [+] |
line diff
1.1 --- a/liberebus/src/brdf.cc Sat Jun 07 06:10:21 2014 +0300 1.2 +++ b/liberebus/src/brdf.cc Sat Jun 07 09:14:17 2014 +0300 1.3 @@ -1,16 +1,85 @@ 1.4 +#include <math.h> 1.5 #include <algorithm> 1.6 #include "brdf.h" 1.7 +#include "material.h" 1.8 #include "erebus_impl.h" 1.9 1.10 +// --- class SurfaceGeometry --- 1.11 +SurfaceGeometry::SurfaceGeometry(const Vector3 &norm, VecLength st) 1.12 + : SurfaceGeometry(norm, Vector2(0, 0), st) 1.13 +{ 1.14 +} 1.15 + 1.16 +SurfaceGeometry::SurfaceGeometry(const Vector3 &norm, const Vector2 &uvarg, VecLength st) 1.17 + : normal(norm), uv(uvarg) 1.18 +{ 1.19 + if(st == unknown) { 1.20 + normal.normalize(); 1.21 + } 1.22 + 1.23 + tangent = Vector3(1.0f, 0.0f, 0.0f); 1.24 + if(fabs(dot_product(normal, tangent)) - 1.0f < 1e-4f) { 1.25 + tangent = Vector3(0.0f, 0.0f, 1.0f); 1.26 + } 1.27 + Vector3 bitan = cross_product(normal, tangent); 1.28 + tangent = cross_product(bitan, normal); 1.29 +} 1.30 + 1.31 +SurfaceGeometry::SurfaceGeometry(const Vector3 &norm, const Vector3 &tang, VecLength st) 1.32 + : SurfaceGeometry(norm, tang, Vector2(0, 0), st) 1.33 +{ 1.34 +} 1.35 + 1.36 +SurfaceGeometry::SurfaceGeometry(const Vector3 &norm, const Vector3 &tang, const Vector2 &uvarg, VecLength st) 1.37 + : normal(norm), tangent(tang), uv(uvarg) 1.38 +{ 1.39 + if(st == unknown) { 1.40 + normal.normalize(); 1.41 + tangent.normalize(); 1.42 + } 1.43 +} 1.44 + 1.45 +Vector3 SurfaceGeometry::spherical_to_world(float theta, float phi) const 1.46 +{ 1.47 + float x = cos(theta) * sin(phi); 1.48 + float y = sin(theta) * sin(phi); 1.49 + float z = cos(phi); 1.50 + return sample_to_world(Vector3(x, y, z)); 1.51 +} 1.52 + 1.53 +Vector3 SurfaceGeometry::sample_to_world(const Vector3 &v) const 1.54 +{ 1.55 + Matrix3x3 xform; 1.56 + xform.set_column_vector(tangent, 0); 1.57 + xform.set_column_vector(cross_product(normal, tangent), 1); 1.58 + xform.set_column_vector(normal, 2); 1.59 + return v.transformed(xform); 1.60 +} 1.61 + 1.62 +Vector3 SurfaceGeometry::world_to_sample(const Vector3 &v) const 1.63 +{ 1.64 + Matrix3x3 xform; 1.65 + xform.set_row_vector(tangent, 0); 1.66 + xform.set_row_vector(cross_product(normal, tangent), 1); 1.67 + xform.set_row_vector(normal, 2); 1.68 + return v.transformed(xform); 1.69 +} 1.70 + 1.71 // ---- class Reflectance ---- 1.72 Reflectance::Reflectance() 1.73 { 1.74 + mtl = 0; 1.75 } 1.76 1.77 -float Reflectance::sample(const Vector3 &norm, const Vector3 &outdir, Vector3 *indir) const 1.78 +Reflectance::Reflectance(const Material *mtl) 1.79 { 1.80 - *indir = sample_dir(norm, outdir); 1.81 - return eval(norm, outdir, *indir); 1.82 + this->mtl = mtl; 1.83 +} 1.84 + 1.85 +float Reflectance::sample(const SurfaceGeometry &geom, const Vector3 &outdir, Vector3 *indir) const 1.86 +{ 1.87 + *indir = sample_dir(geom, outdir); 1.88 + return eval(geom, outdir, *indir); 1.89 } 1.90 1.91 // --- class CompositeRefl ---- 1.92 @@ -19,7 +88,13 @@ 1.93 valid_checked = false; 1.94 } 1.95 1.96 -int CompositeRefl::pick_brdf(const Vector3 &norm, const Vector3 &outdir) const 1.97 +CompositeRefl::CompositeRefl(const Material *mtl) 1.98 + : Reflectance(mtl) 1.99 +{ 1.100 + valid_checked = false; 1.101 +} 1.102 + 1.103 +int CompositeRefl::pick_brdf(const SurfaceGeometry &geom, const Vector3 &outdir) const 1.104 { 1.105 if(sub_brdf.empty()) { 1.106 return -1; 1.107 @@ -34,7 +109,7 @@ 1.108 float *cdf = (float*)alloca(brdf_count * sizeof *cdf); 1.109 for(int i=0; i<brdf_count; i++) { 1.110 const SubRefl &sub = sub_brdf[i]; 1.111 - float w = sub.weight_func ? sub.weight_func(norm, outdir) : sub.weight; 1.112 + float w = sub.weight_func ? sub.weight_func(geom, outdir) : sub.weight; 1.113 cdf[i] = i > 0 ? cdf[i - 1] + w : w; 1.114 } 1.115 1.116 @@ -83,93 +158,74 @@ 1.117 "warning: don't call CompositeRefl's sample_dir and eval separately\n" 1.118 " it'll pick different brdfs each time! use sample instead\n"; 1.119 1.120 -Vector3 CompositeRefl::sample_dir(const Vector3 &norm, const Vector3 &outdir) const 1.121 +Vector3 CompositeRefl::sample_dir(const SurfaceGeometry &geom, const Vector3 &outdir) const 1.122 { 1.123 if(!warned_composite_usage) { 1.124 fputs(composite_usage_warnstr, stderr); 1.125 warned_composite_usage = true; 1.126 } 1.127 - int bidx = pick_brdf(norm, outdir); 1.128 - return sub_brdf[bidx].brdf->sample_dir(norm, outdir); 1.129 + int bidx = pick_brdf(geom, outdir); 1.130 + return sub_brdf[bidx].brdf->sample_dir(geom, outdir); 1.131 } 1.132 1.133 -float CompositeRefl::sample(const Vector3 &norm, const Vector3 &outdir, Vector3 *indir) const 1.134 +float CompositeRefl::sample(const SurfaceGeometry &geom, const Vector3 &outdir, Vector3 *indir) const 1.135 { 1.136 - int bidx = pick_brdf(norm, outdir); 1.137 - return sub_brdf[bidx].brdf->sample(norm, outdir, indir); 1.138 + int bidx = pick_brdf(geom, outdir); 1.139 + return sub_brdf[bidx].brdf->sample(geom, outdir, indir); 1.140 } 1.141 1.142 -float CompositeRefl::eval(const Vector3 &norm, const Vector3 &outdir, const Vector3 &indir) const 1.143 +float CompositeRefl::eval(const SurfaceGeometry &geom, const Vector3 &outdir, const Vector3 &indir) const 1.144 { 1.145 if(!warned_composite_usage) { 1.146 fputs(composite_usage_warnstr, stderr); 1.147 warned_composite_usage = true; 1.148 } 1.149 - int bidx = pick_brdf(norm, outdir); 1.150 - return sub_brdf[bidx].brdf->eval(norm, outdir, indir); 1.151 + int bidx = pick_brdf(geom, outdir); 1.152 + return sub_brdf[bidx].brdf->eval(geom, outdir, indir); 1.153 } 1.154 1.155 // --- class LambertRefl --- 1.156 -Vector3 LambertRefl::sample_dir(const Vector3 &norm, const Vector3 &outdir) const 1.157 +Vector3 LambertRefl::sample_dir(const SurfaceGeometry &geom, const Vector3 &outdir) const 1.158 { 1.159 Vector3 dir = Vector3{randf(-1, 1), randf(-1, 1), randf(-1, 1)}.normalized(); 1.160 - return dot_product(dir, norm) < 0.0 ? -dir : dir; 1.161 + return dot_product(dir, geom.normal) < 0.0 ? -dir : dir; 1.162 } 1.163 1.164 -float LambertRefl::eval(const Vector3 &norm, const Vector3 &outdir, const Vector3 &indir) const 1.165 +float LambertRefl::eval(const SurfaceGeometry &geom, const Vector3 &outdir, const Vector3 &indir) const 1.166 { 1.167 - return dot_product(norm, outdir); 1.168 + return dot_product(geom.normal, outdir); 1.169 } 1.170 1.171 // --- class MirrorRefl --- 1.172 -Vector3 MirrorRefl::sample_dir(const Vector3 &norm, const Vector3 &outdir) const 1.173 +Vector3 MirrorRefl::sample_dir(const SurfaceGeometry &geom, const Vector3 &outdir) const 1.174 { 1.175 - return outdir.reflection(norm); 1.176 + return outdir.reflection(geom.normal); 1.177 } 1.178 1.179 -float MirrorRefl::eval(const Vector3 &norm, const Vector3 &outdir, const Vector3 &indir) const 1.180 +float MirrorRefl::eval(const SurfaceGeometry &geom, const Vector3 &outdir, const Vector3 &indir) const 1.181 { 1.182 return 1.0f; 1.183 } 1.184 1.185 // --- class PhongRefl --- 1.186 -PhongRefl::PhongRefl() 1.187 +Vector3 PhongRefl::sample_dir(const SurfaceGeometry &geom, const Vector3 &outdir) const 1.188 { 1.189 - shininess = 42.0; 1.190 -} 1.191 + Vector3 refl = outdir.reflection(geom.normal).normalized(); 1.192 + SurfaceGeometry refl_geom(refl, SurfaceGeometry::unit); 1.193 1.194 -Vector3 PhongRefl::sample_dir(const Vector3 &norm, const Vector3 &outdir) const 1.195 -{ 1.196 - // construct orthonormal basis with k being the reflection dir 1.197 - Vector3 refl = outdir.reflection(norm).normalized(); 1.198 - Vector3 up = Vector3(0, 0, 1); 1.199 - if(fabs(dot_product(refl, up)) - 1.0 < 0.001) { 1.200 - up = Vector3(0, 1, 0); 1.201 - } 1.202 - Vector3 right = cross_product(up, refl); 1.203 - up = cross_product(refl, right); 1.204 - 1.205 - // construct the matrix transposed to move the sample from reflection 1.206 - // space to world space 1.207 - Matrix3x3 xform; 1.208 - xform.set_column_vector(right, 0); 1.209 - xform.set_column_vector(up, 1); 1.210 - xform.set_column_vector(refl, 2); 1.211 + float shininess = mtl ? mtl->get_attrib_value("shininess", geom.uv.x, geom.uv.y) : 42.0; 1.212 1.213 float phi = acos(pow(randf(), 1.0 / (shininess + 1))); 1.214 float theta = 2.0 * M_PI * randf(); 1.215 1.216 - Vector3 v; 1.217 - v.x = cos(theta) * sin(phi); 1.218 - v.y = sin(theta) * sin(phi); 1.219 - v.z = cos(phi); 1.220 - v.transform(xform); 1.221 - return v; 1.222 + return refl_geom.spherical_to_world(theta, phi); 1.223 } 1.224 1.225 -float PhongRefl::eval(const Vector3 &norm, const Vector3 &outdir, const Vector3 &indir) const 1.226 +float PhongRefl::eval(const SurfaceGeometry &geom, const Vector3 &outdir, const Vector3 &indir) const 1.227 { 1.228 - Vector3 refl = outdir.reflection(norm); 1.229 + float shininess = mtl ? mtl->get_attrib_value("shininess", geom.uv.x, geom.uv.y) : 42.0; 1.230 + 1.231 + Vector3 refl = outdir.reflection(geom.normal); 1.232 float dot = std::max<float>(dot_product(indir, refl), 0.0f); 1.233 return pow(dot, shininess); 1.234 }
2.1 --- a/liberebus/src/brdf.h Sat Jun 07 06:10:21 2014 +0300 2.2 +++ b/liberebus/src/brdf.h Sat Jun 07 09:14:17 2014 +0300 2.3 @@ -4,18 +4,56 @@ 2.4 #include <vector> 2.5 #include "texture.h" 2.6 2.7 +class Material; 2.8 + 2.9 +class SurfaceGeometry { 2.10 +public: 2.11 + Vector3 normal, tangent; 2.12 + Vector2 uv; 2.13 + 2.14 + enum VecLength { unit, unknown }; 2.15 + 2.16 + explicit SurfaceGeometry(const Vector3 &norm, VecLength st = unknown); 2.17 + SurfaceGeometry(const Vector3 &norm, const Vector2 &uv, VecLength st = unknown); 2.18 + SurfaceGeometry(const Vector3 &norm, const Vector3 &tang, VecLength st = unknown); 2.19 + SurfaceGeometry(const Vector3 &norm, const Vector3 &tang, const Vector2 &uv, VecLength st = unknown); 2.20 + 2.21 + /** create a cartesian direction vector in sample space (zenith = +Z) and 2.22 + * transform it to world space. 2.23 + * \param theta the horizontal angle (azimuth, in radians) around the Z axis [0, 2pi] 2.24 + * \param phi the vertical angle (elevation, in radians) away from the Z axis [0, pi] 2.25 + */ 2.26 + Vector3 spherical_to_world(float theta, float phi) const; 2.27 + /// transforms a direction vector from sample space (centered around Z) to world space 2.28 + Vector3 sample_to_world(const Vector3 &v) const; 2.29 + /// transforms a direction vector from world space to sample space (centered around Z) 2.30 + Vector3 world_to_sample(const Vector3 &v) const; 2.31 +}; 2.32 + 2.33 +/// abstract bidirection reflectance distribution function base class 2.34 class Reflectance { 2.35 +protected: 2.36 + const Material *mtl; // pointer to the material we belong to 2.37 + 2.38 public: 2.39 Reflectance(); 2.40 + explicit Reflectance(const Material *mtl); 2.41 virtual ~Reflectance() = default; 2.42 2.43 - virtual Vector3 sample_dir(const Vector3 &norm, const Vector3 &outdir) const = 0; 2.44 - virtual float sample(const Vector3 &norm, const Vector3 &outdir, Vector3 *indir) const; 2.45 - virtual float eval(const Vector3 &norm, const Vector3 &outdir, const Vector3 &indir) const = 0; 2.46 + void set_material(const Material *mtl); 2.47 + const Material *get_material() const; 2.48 + 2.49 + /// given an outgoing light direction generate an incidence direction to sample the BRDF 2.50 + virtual Vector3 sample_dir(const SurfaceGeometry &geom, const Vector3 &outdir) const = 0; 2.51 + /// given an outgoing direction, generate an incidence direction, and evaluate its probability 2.52 + virtual float sample(const SurfaceGeometry &geom, const Vector3 &outdir, Vector3 *indir) const; 2.53 + /// given an outgoing direction and an incidence direction, evaluate the probability of this path 2.54 + virtual float eval(const SurfaceGeometry &geom, const Vector3 &outdir, const Vector3 &indir) const = 0; 2.55 }; 2.56 2.57 -typedef float (*CompReflWeightFunc)(const Vector3 &norm, const Vector3 &outdir); 2.58 +typedef float (*CompReflWeightFunc)(const SurfaceGeometry &geom, const Vector3 &outdir); 2.59 2.60 +/// composite BRDF, with multiple weighted sub-reflectances. 2.61 class CompositeRefl : public Reflectance { 2.62 private: 2.63 struct SubRefl { 2.64 @@ -26,41 +64,41 @@ 2.65 std::vector<SubRefl> sub_brdf; 2.66 mutable bool valid_checked; 2.67 2.68 - int pick_brdf(const Vector3 &norm, const Vector3 &outdir) const; 2.69 + int pick_brdf(const SurfaceGeometry &geom, const Vector3 &outdir) const; 2.70 2.71 public: 2.72 CompositeRefl(); 2.73 + explicit CompositeRefl(const Material *mtl); 2.74 2.75 virtual void add_brdf(Reflectance *brdf, float weight); 2.76 virtual void add_brdf(Reflectance *brdf, CompReflWeightFunc weight_func); 2.77 2.78 bool check_valid() const; 2.79 2.80 - Vector3 sample_dir(const Vector3 &norm, const Vector3 &outdir) const override; 2.81 - float sample(const Vector3 &norm, const Vector3 &outdir, Vector3 *indir) const override; 2.82 - float eval(const Vector3 &norm, const Vector3 &outdir, const Vector3 &indir) const override; 2.83 + Vector3 sample_dir(const SurfaceGeometry &geom, const Vector3 &outdir) const override; 2.84 + float sample(const SurfaceGeometry &geom, const Vector3 &outdir, Vector3 *indir) const override; 2.85 + float eval(const SurfaceGeometry &geom, const Vector3 &outdir, const Vector3 &indir) const override; 2.86 }; 2.87 2.88 +/// lambertian perfect diffuse reflectance 2.89 class LambertRefl : public Reflectance { 2.90 public: 2.91 - Vector3 sample_dir(const Vector3 &norm, const Vector3 &outdir) const override; 2.92 - float eval(const Vector3 &norm, const Vector3 &outdir, const Vector3 &indir) const override; 2.93 + Vector3 sample_dir(const SurfaceGeometry &geom, const Vector3 &outdir) const override; 2.94 + float eval(const SurfaceGeometry &geom, const Vector3 &outdir, const Vector3 &indir) const override; 2.95 }; 2.96 2.97 +/// perfect specular reflectance 2.98 class MirrorRefl : public Reflectance { 2.99 public: 2.100 - Vector3 sample_dir(const Vector3 &norm, const Vector3 &outdir) const override; 2.101 - float eval(const Vector3 &norm, const Vector3 &outdir, const Vector3 &indir) const override; 2.102 + Vector3 sample_dir(const SurfaceGeometry &geom, const Vector3 &outdir) const override; 2.103 + float eval(const SurfaceGeometry &geom, const Vector3 &outdir, const Vector3 &indir) const override; 2.104 }; 2.105 2.106 +/// glossy phong reflectance with lafortune sampling 2.107 class PhongRefl : public Reflectance { 2.108 public: 2.109 - float shininess; 2.110 - 2.111 - PhongRefl(); 2.112 - 2.113 - Vector3 sample_dir(const Vector3 &norm, const Vector3 &outdir) const override; 2.114 - float eval(const Vector3 &norm, const Vector3 &outdir, const Vector3 &indir) const override; 2.115 + Vector3 sample_dir(const SurfaceGeometry &geom, const Vector3 &outdir) const override; 2.116 + float eval(const SurfaceGeometry &geom, const Vector3 &outdir, const Vector3 &indir) const override; 2.117 }; 2.118 2.119 #endif // BRDF_H_
3.1 --- a/liberebus/src/erebus.cc Sat Jun 07 06:10:21 2014 +0300 3.2 +++ b/liberebus/src/erebus.cc Sat Jun 07 09:14:17 2014 +0300 3.3 @@ -22,11 +22,13 @@ 3.4 3.5 struct erebus *erb_init(void) 3.6 { 3.7 - struct erebus *ctx; 3.8 + struct erebus *ctx = 0; 3.9 try { 3.10 ctx = new struct erebus; 3.11 + ctx->tpool = new ThreadPool; 3.12 } 3.13 catch(...) { 3.14 + delete ctx; 3.15 return 0; 3.16 } 3.17 3.18 @@ -47,7 +49,11 @@ 3.19 3.20 void erb_destroy(struct erebus *ctx) 3.21 { 3.22 - delete ctx; 3.23 + if(ctx) { 3.24 + // make sure the threadpool stops BEFORE destroying the framebuffers etc in ctx 3.25 + delete ctx->tpool; 3.26 + delete ctx; 3.27 + } 3.28 } 3.29 3.30 void erb_setopti(struct erebus *ctx, enum erb_option opt, int val) 3.31 @@ -154,9 +160,9 @@ 3.32 3.33 int erb_render_rect(struct erebus *ctx, int x, int y, int width, int height, long timeout) 3.34 { 3.35 - while(ctx->tpool.pending()) { 3.36 + while(ctx->tpool->pending()) { 3.37 if(timeout > 0) { 3.38 - long wait_interval = ctx->tpool.wait(timeout); 3.39 + long wait_interval = ctx->tpool->wait(timeout); 3.40 timeout -= wait_interval; 3.41 } else { 3.42 return 1; 3.43 @@ -180,7 +186,7 @@ 3.44 blk.sample = ctx->cur_sample; 3.45 blk.frame = ctx->cur_frame; 3.46 3.47 - ctx->tpool.add_work(std::bind(render_block, ctx, blk)); 3.48 + ctx->tpool->add_work(std::bind(render_block, ctx, blk)); 3.49 3.50 x += BLKSZ; 3.51 } 3.52 @@ -188,7 +194,7 @@ 3.53 } 3.54 3.55 ++ctx->cur_sample; 3.56 - ctx->tpool.wait(timeout); // wait for completion 3.57 + ctx->tpool->wait(timeout); // wait for completion 3.58 return ctx->cur_sample > erb_getopti(ctx, ERB_OPT_MAX_SAMPLES) ? 0 : 1; 3.59 } 3.60
4.1 --- a/liberebus/src/erebus_impl.h Sat Jun 07 06:10:21 2014 +0300 4.2 +++ b/liberebus/src/erebus_impl.h Sat Jun 07 09:14:17 2014 +0300 4.3 @@ -28,7 +28,7 @@ 4.4 Image<float> accum; // sample accumulator per pixel 4.5 Option options[ERB_NUM_OPTIONS]; 4.6 4.7 - ThreadPool tpool; 4.8 + ThreadPool *tpool; 4.9 4.10 // render state 4.11 float inv_gamma;
5.1 --- a/liberebus/src/rt.cc Sat Jun 07 06:10:21 2014 +0300 5.2 +++ b/liberebus/src/rt.cc Sat Jun 07 09:14:17 2014 +0300 5.3 @@ -46,7 +46,7 @@ 5.4 float shininess = mtl->get_attrib_value("shininess"); 5.5 5.6 Vector3 sample_dir; 5.7 - float prob = brdf->sample(norm, -hit.world_ray.dir, &sample_dir); 5.8 + float prob = brdf->sample(SurfaceGeometry(norm), -hit.world_ray.dir, &sample_dir); 5.9 if(iter < max_iter && randf() <= prob && ray.energy * prob > 0.001) { 5.10 Ray sample_ray; 5.11 sample_ray.origin = ray.origin + ray.dir * hit.dist;
6.1 --- a/liberebus/src/threadpool.cc Sat Jun 07 06:10:21 2014 +0300 6.2 +++ b/liberebus/src/threadpool.cc Sat Jun 07 09:14:17 2014 +0300 6.3 @@ -19,23 +19,52 @@ 6.4 thread = new std::thread[num_threads]; 6.5 for(int i=0; i<num_threads; i++) { 6.6 thread[i] = std::thread(&ThreadPool::thread_func, this); 6.7 + 6.8 +#ifdef _MSC_VER 6.9 + /* detach the thread to avoid having to join them in the destructor, which 6.10 + * causes a deadlock in msvc implementation when called after main returns 6.11 + */ 6.12 + thread[i].detach(); 6.13 +#endif 6.14 } 6.15 this->num_threads = num_threads; 6.16 } 6.17 6.18 ThreadPool::~ThreadPool() 6.19 { 6.20 +#ifdef _MSC_VER 6.21 + workq_mutex.lock(); 6.22 + workq.clear(); 6.23 + qsize = 0; 6.24 + workq_mutex.unlock(); 6.25 +#endif 6.26 + 6.27 quit = true; 6.28 workq_condvar.notify_all(); 6.29 6.30 printf("ThreadPool: waiting for %d worker threads to stop ", num_threads); 6.31 fflush(stdout); 6.32 +#ifndef _MSC_VER 6.33 for(int i=0; i<num_threads; i++) { 6.34 thread[i].join(); 6.35 putchar('.'); 6.36 fflush(stdout); 6.37 } 6.38 + 6.39 +#else 6.40 + // spin until all threads are done... 6.41 + std::unique_lock<std::mutex> lock(workq_mutex); 6.42 + while(nactive > 0) { 6.43 + lock.unlock(); 6.44 + std::this_thread::sleep_for(std::chrono::milliseconds(128)); 6.45 + putchar('.'); 6.46 + fflush(stdout); 6.47 + lock.lock(); 6.48 + } 6.49 +#endif // _MSC_VER 6.50 + 6.51 putchar('\n'); 6.52 + delete [] thread; 6.53 } 6.54 6.55 void ThreadPool::add_work(std::function<void ()> func)