Skip to content

Commit 905010a

Browse files
committed
lib+driver+common: Support platforms with no threading
I was experimenting with getting c-ray to run on Tilck[1], and ran into issues when c-ray ignored errors coming from pthread_create(3). Now both the thread pool and other users fall back to performing work on the main thread synchronously, should starting a thread fail. A new macro exists in thread.h to simulate this case on a system with threading support: CR_SIMULATE_NOTHREADS [1] https://github.com/vvaltchev/tilck
1 parent 1cf5f91 commit 905010a

File tree

7 files changed

+77
-29
lines changed

7 files changed

+77
-29
lines changed

src/common/platform/thread.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ void thread_wait(struct cr_thread *t) {
4343
// allocation here to extend the lifetime.
4444

4545
int thread_create_detach(struct cr_thread *t) {
46+
#ifdef CR_SIMULATE_NOTHREADS
47+
return -1;
48+
#endif
4649
if (!t) return -1;
4750
struct cr_thread *temp = calloc(1, sizeof(*temp));
4851
*temp = *t;
@@ -51,13 +54,18 @@ int thread_create_detach(struct cr_thread *t) {
5154
CloseHandle(t->thread_handle);
5255
return 0;
5356
#else
54-
pthread_create(&t->thread_id, NULL, thread_stub, temp);
57+
int rc = pthread_create(&t->thread_id, NULL, thread_stub, temp);
58+
if (rc)
59+
return rc;
5560
pthread_detach(t->thread_id);
5661
return 0;
5762
#endif
5863
}
5964

6065
int thread_start(struct cr_thread *t) {
66+
#ifdef CR_SIMULATE_NOTHREADS
67+
return -1;
68+
#endif
6169
if (!t) return -1;
6270
struct cr_thread *temp = calloc(1, sizeof(*temp));
6371
*temp = *t;
@@ -69,9 +77,9 @@ int thread_start(struct cr_thread *t) {
6977
pthread_attr_t attribs;
7078
pthread_attr_init(&attribs);
7179
pthread_attr_setdetachstate(&attribs, PTHREAD_CREATE_JOINABLE);
72-
int ret = pthread_create(&t->thread_id, &attribs, thread_stub, temp);
80+
int rc = pthread_create(&t->thread_id, &attribs, thread_stub, temp);
7381
pthread_attr_destroy(&attribs);
74-
return ret;
82+
return rc;
7583
#endif
7684
}
7785

src/common/platform/thread.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
// small thread/sync abstraction for POSIX and Windows
2222

23+
// #define CR_SIMULATE_NOTHREADS
24+
2325
/**
2426
Thread information struct to communicate with main thread
2527
*/

src/common/platform/thread_pool.c

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "thread_pool.h"
1010
#include "mutex.h"
1111
#include "thread.h"
12+
#include "signal.h"
1213
#include "../logging.h"
1314

1415
// Mostly based on John Schember's excellent blog post:
@@ -63,6 +64,7 @@ static struct cr_task *thread_pool_get_task(struct cr_thread_pool *pool) {
6364
}
6465

6566
static void *cr_worker(void *arg) {
67+
block_signals();
6668
struct cr_thread_pool *pool = arg;
6769
while (true) {
6870
mutex_lock(pool->mutex);
@@ -91,7 +93,8 @@ static void *cr_worker(void *arg) {
9193
}
9294

9395
struct cr_thread_pool *thread_pool_create(size_t threads) {
94-
if (!threads) threads = 2;
96+
if (!threads)
97+
threads = 2;
9598
struct cr_thread_pool *pool = calloc(1, sizeof(*pool));
9699
logr(debug, "Spawning thread pool (%zut, %p)\n", threads, (void *)pool);
97100
pool->alive_threads = threads;
@@ -106,13 +109,22 @@ struct cr_thread_pool *thread_pool_create(size_t threads) {
106109
.thread_fn = cr_worker,
107110
.user_data = pool
108111
};
109-
thread_create_detach(&pool->threads[i]);
112+
if (thread_create_detach(&pool->threads[i]))
113+
goto fail;
110114
}
111115
return pool;
116+
fail:
117+
free(pool->threads);
118+
mutex_destroy(pool->mutex);
119+
thread_cond_destroy(&pool->work_available);
120+
thread_cond_destroy(&pool->work_ongoing);
121+
free(pool);
122+
return NULL;
112123
}
113124

114125
void thread_pool_destroy(struct cr_thread_pool *pool) {
115-
if (!pool) return;
126+
if (!pool)
127+
return;
116128
logr(debug, "Closing thread pool (%zut, %p)\n", pool->alive_threads, (void *)pool);
117129
mutex_lock(pool->mutex);
118130
// Clear work queue
@@ -139,7 +151,11 @@ void thread_pool_destroy(struct cr_thread_pool *pool) {
139151
}
140152

141153
bool thread_pool_enqueue(struct cr_thread_pool *pool, void (*fn)(void *arg), void *arg) {
142-
if (!pool) return false;
154+
if (!pool) { // Fall back to running synchronously
155+
if (fn)
156+
fn(arg);
157+
return false;
158+
}
143159
struct cr_task *task = task_create(fn, arg);
144160
if (!task) return false;
145161
mutex_lock(pool->mutex);
@@ -156,7 +172,8 @@ bool thread_pool_enqueue(struct cr_thread_pool *pool, void (*fn)(void *arg), voi
156172
}
157173

158174
void thread_pool_wait(struct cr_thread_pool *pool) {
159-
if (!pool) return;
175+
if (!pool)
176+
return;
160177
mutex_lock(pool->mutex);
161178
while (true) {
162179
if (pool->first || (!pool->stop_flag && pool->active_threads != 0) || (pool->stop_flag && pool->alive_threads != 0)) {

src/driver/main.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,13 @@ static void on_start(struct cr_renderer_cb_info *cb_info, void *user_data) {
7474
.cb_info = cb_info,
7575
.driver = d,
7676
};
77-
thread_start(&(struct cr_thread){
77+
int rc = thread_start(&(struct cr_thread){
7878
.thread_fn = win_init_task,
7979
.user_data = ctx,
8080
});
81+
if (rc) { // Try synchronously, then.
82+
win_init_task(ctx);
83+
}
8184
#endif
8285
}
8386

src/lib/api/c-ray.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,6 @@ struct bvh_build_task_arg {
247247
};
248248

249249
void bvh_build_task(void *arg) {
250-
block_signals();
251250
// Mesh array may get realloc'd at any time, so we use a copy of mesh while working
252251
struct bvh_build_task_arg *bt = (struct bvh_build_task_arg *)arg;
253252
struct timeval timer = { 0 };

src/lib/nodes/colornode.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
#include <datatypes/scene.h>
1717
#include "bsdfnode.h"
1818
#include "../../common/platform/thread_pool.h"
19-
#include "../../common/platform/signal.h"
2019
#include "../../common/timer.h"
2120

2221
#include "colornode.h"
@@ -32,7 +31,6 @@ struct decode_task_arg {
3231
};
3332

3433
void tex_decode_task(void *arg) {
35-
block_signals();
3634
struct decode_task_arg *dt = (struct decode_task_arg *)arg;
3735
struct timeval timer = { 0 };
3836
timer_start(&timer);

src/lib/renderer/renderer.c

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -275,35 +275,56 @@ void renderer_render(struct renderer *r) {
275275
}
276276
});
277277
}
278+
bool threads_not_supported = false;
278279
for (size_t w = 0; w < r->state.workers.count; ++w) {
279280
r->state.workers.items[w].thread.user_data = &r->state.workers.items[w];
280281
r->state.workers.items[w].tiles = &set;
281-
if (thread_start(&r->state.workers.items[w].thread))
282-
logr(error, "Failed to start worker %zu\n", w);
282+
if (thread_start(&r->state.workers.items[w].thread)) {
283+
threads_not_supported = true;
284+
break;
285+
}
283286
}
284287

285-
//Start main thread loop to handle renderer feedback and state management
286-
while (r->state.s == r_rendering) {
287-
size_t inactive = 0;
288-
for (size_t w = 0; w < r->state.workers.count; ++w) {
289-
if (r->state.workers.items[w].thread_complete) inactive++;
290-
}
291-
if (g_aborted || inactive == r->state.workers.count) break;
292-
293-
struct callback status = r->state.callbacks[cr_cb_status_update];
294-
if (status.fn) {
295-
update_cb_info(r, &set, &cb_info);
296-
status.fn(&cb_info, status.user_data);
288+
if (threads_not_supported) {
289+
struct worker *w = &r->state.workers.items[0];
290+
w->thread.thread_fn = render_single_iteration;
291+
while (r->state.s == r_rendering) {
292+
w->thread.thread_fn(w->thread.user_data);
293+
if (g_aborted || w->thread_complete)
294+
break;
295+
struct callback status = r->state.callbacks[cr_cb_status_update];
296+
if (status.fn) {
297+
update_cb_info(r, &set, &cb_info);
298+
status.fn(&cb_info, status.user_data);
299+
}
297300
}
298301

299-
timer_sleep_ms(r->state.workers.items[0].paused ? paused_msec : active_msec);
302+
} else {
303+
//Start main thread loop to handle renderer feedback and state management
304+
while (r->state.s == r_rendering) {
305+
size_t inactive = 0;
306+
for (size_t w = 0; w < r->state.workers.count; ++w) {
307+
if (r->state.workers.items[w].thread_complete) inactive++;
308+
}
309+
if (g_aborted || inactive == r->state.workers.count)
310+
break;
311+
312+
struct callback status = r->state.callbacks[cr_cb_status_update];
313+
if (status.fn) {
314+
update_cb_info(r, &set, &cb_info);
315+
status.fn(&cb_info, status.user_data);
316+
}
317+
318+
timer_sleep_ms(r->state.workers.items[0].paused ? paused_msec : active_msec);
319+
}
300320
}
301321

322+
302323
r->state.s = r_exiting;
303324
r->state.current_set = NULL;
304-
325+
305326
//Make sure render threads are terminated before continuing (This blocks)
306-
for (size_t w = 0; w < r->state.workers.count; ++w)
327+
for (size_t w = 0; !threads_not_supported && w < r->state.workers.count; ++w)
307328
thread_wait(&r->state.workers.items[w].thread);
308329

309330
struct callback stop = r->state.callbacks[cr_cb_on_stop];

0 commit comments

Comments
 (0)