fix stb_sync -- finish writing start mutex

This commit is contained in:
nothings.org 2007-07-03 20:10:39 +00:00
parent 43a913cc33
commit c393e03d16
4 changed files with 350 additions and 109 deletions

95
imv.c
View File

@ -19,7 +19,7 @@
// Set section alignment to minimize alignment overhead // Set section alignment to minimize alignment overhead
#pragma comment(linker, "/FILEALIGN:0x200") #pragma comment(linker, "/FILEALIGN:0x200")
#define _WIN32_WINNT 0x0400 #define _WIN32_WINNT 0x0500
#include <windows.h> #include <windows.h>
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
@ -29,7 +29,8 @@
#define STB_DEFINE #define STB_DEFINE
#include "stb.h" /* http://nothings.org/stb.h */ #include "stb.h" /* http://nothings.org/stb.h */
#define STB_IMAGE_FAILURE_USERMSG #define STBI_FAILURE_USERMSG
#define STBI_NO_STDIO
#include "stb_image.c" /* http://nothings.org/stb_image.c */ #include "stb_image.c" /* http://nothings.org/stb_image.c */
#include "resource.h" #include "resource.h"
@ -207,6 +208,7 @@ typedef struct
stb_mutex cache_mutex, decode_mutex; stb_mutex cache_mutex, decode_mutex;
stb_semaphore decode_queue; stb_semaphore decode_queue;
stb_semaphore disk_command_queue; stb_semaphore disk_command_queue;
stb_sync resize_merge;
// a request communicated from the main thread to the disk-loader task // a request communicated from the main thread to the disk-loader task
typedef struct typedef struct
@ -1079,7 +1081,9 @@ struct
// stb_sdict is a string dictionary (strings as keys, void * as values) // stb_sdict is a string dictionary (strings as keys, void * as values)
// dictionary mapping filenames (key) to cached images (ImageFile *) // dictionary mapping filenames (key) to cached images (ImageFile *)
// @TODO: do we need this, or can it be in fileinfo? // We can't just replace this with an ImageFile* in the fileinfo (and
// the backpointer) because we keep ImageFile entries around for images
// not in the fileinfo list.
stb_sdict *file_cache; stb_sdict *file_cache;
// when switching/refreshing directories, free this data // when switching/refreshing directories, free this data
@ -2054,7 +2058,6 @@ int WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
} }
// number of threads to use in resizer // number of threads to use in resizer
#define MAX_RESIZE 4
int resize_threads; int resize_threads;
// whether 'cur' (the resized image currently displayed) actually comes from 'source' // whether 'cur' (the resized image currently displayed) actually comes from 'source'
@ -2087,7 +2090,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
strcat(helptext_center, VERSION); strcat(helptext_center, VERSION);
// determine the number of threads to use in the resizer // determine the number of threads to use in the resizer
resize_threads = stb_min(stb_processor_count(), MAX_RESIZE); resize_threads = stb_min(stb_processor_count(), 16);
resize_threads = 8;
// compute the amount of physical memory to set a guess for the cache size // compute the amount of physical memory to set a guess for the cache size
GlobalMemoryStatus(&mem); GlobalMemoryStatus(&mem);
@ -2147,16 +2151,27 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
resize_workers = stb_workq_new(resize_threads, resize_threads * 4); resize_workers = stb_workq_new(resize_threads, resize_threads * 4);
// load initial image // load initial image
image_data = stbi_load(filename, &image_x, &image_y, &image_n, BPP); {
if (image_data == NULL) { char *why=NULL;
// we treat errors on initial image differently: message box and exit... int len;
// now that we handle errors nicely, this is kind of dumb... but what uint8 *data = stb_file(filename, &len);
// size should the initial window be? if (!data)
char *why = stbi_failure_reason(); why = "Couldn't open file";
char buffer[512]; else {
sprintf(buffer, "'%s': %s", filename, why); image_data = stbi_load_from_memory(data, len, &image_x, &image_y, &image_n, BPP);
error(buffer); if (image_data == NULL)
exit(0); why = stbi_failure_reason();
}
if (why) {
// we treat errors on initial image differently: message box and exit...
// now that we handle errors nicely, this is kind of dumb... but what
// size should the initial window be?
char buffer[512];
sprintf(buffer, "'%s': %s", filename, why);
error(buffer);
exit(0);
}
} }
// fix the filename & path for consistency with readdir() // fix the filename & path for consistency with readdir()
@ -2169,6 +2184,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
decode_mutex = stb_mutex_new(); decode_mutex = stb_mutex_new();
decode_queue = stb_sem_new(1,1); decode_queue = stb_sem_new(1,1);
disk_command_queue = stb_sem_new(1,1); disk_command_queue = stb_sem_new(1,1);
resize_merge = stb_sync_new();
// go ahead and start the other tasks // go ahead and start the other tasks
stb_create_thread(diskload_task, NULL); stb_create_thread(diskload_task, NULL);
@ -2336,7 +2352,6 @@ typedef struct
SplitPoint *p; SplitPoint *p;
int j0,j1; int j0,j1;
float dy; float dy;
int done;
} ImageProcess; } ImageProcess;
#define CACHE_REBLOCK 64 #define CACHE_REBLOCK 64
@ -2423,7 +2438,6 @@ void *image_resize_work(ImageProcess *q)
y += q->dy; y += q->dy;
} }
} }
q->done = TRUE;
return NULL; return NULL;
} }
@ -2462,25 +2476,17 @@ void image_resize_bilinear(Image *dest, Image *src)
q[i].j1 = j1; q[i].j1 = j1;
q[i].dy = dy; q[i].dy = dy;
q[i].p = p; q[i].p = p;
q[i].done = FALSE;
j1 = j0; j1 = j0;
} }
if (resize_threads == 1) { if (resize_threads == 1) {
image_resize_work(q); image_resize_work(q);
} else { } else {
barrier(); stb_sync_set_target(resize_merge, resize_threads);
for (i=1; i < resize_threads; ++i) for (i=1; i < resize_threads; ++i)
stb_workq(resize_workers, image_resize_work, q+i, NULL); stb_workq_reach(resize_workers, image_resize_work, q+i, NULL, resize_merge);
image_resize_work(q); image_resize_work(q);
stb_sync_reach_and_wait(resize_merge);
for(;;) {
for (i=1; i < resize_threads; ++i)
if (!q[i].done)
break;
if (i == resize_threads) break;
Sleep(10);
}
} }
stb_tempfree(point_buffer, p); stb_tempfree(point_buffer, p);
@ -2718,7 +2724,6 @@ void * cubic_interp_1d_x_work(int n)
x += dx; x += dx;
} }
} }
barrier();
return NULL; return NULL;
} }
@ -2734,21 +2739,11 @@ Image *cubic_interp_1d_x(Image *src, int out_w)
if (resize_threads == 1) { if (resize_threads == 1) {
cubic_interp_1d_x_work(0); cubic_interp_1d_x_work(0);
} else { } else {
volatile void *which[MAX_RESIZE]; stb_sync_set_target(resize_merge, resize_threads);
for (i=0; i < resize_threads; ++i)
which[i] = (void *) 1;
barrier();
for (i=1; i < resize_threads; ++i) for (i=1; i < resize_threads; ++i)
stb_workq(resize_workers, (stb_thread_func) cubic_interp_1d_x_work, (void *) i, which+i); stb_workq_reach(resize_workers, (stb_thread_func) cubic_interp_1d_x_work, (void *) i, NULL, resize_merge);
cubic_interp_1d_x_work(0); cubic_interp_1d_x_work(0);
stb_sync_reach_and_wait(resize_merge);
for(;;) {
for (i=1; i < resize_threads; ++i)
if (which[i])
break;
if (i == resize_threads) break;
Sleep(10);
}
} }
return cubic_work.out; return cubic_work.out;
} }
@ -2784,26 +2779,16 @@ Image *cubic_interp_1d_y(Image *src, int out_h)
cubic_work.out = bmp_alloc(src->x, out_h); cubic_work.out = bmp_alloc(src->x, out_h);
cubic_work.delta = ((src->y-1)*65536-1) / (out_h-1); cubic_work.delta = ((src->y-1)*65536-1) / (out_h-1);
cubic_work.out_len = out_h; cubic_work.out_len = out_h;
barrier();
if (resize_threads == 1) { if (resize_threads == 1) {
cubic_interp_1d_y_work(0); cubic_interp_1d_y_work(0);
} else { } else {
volatile void *which[MAX_RESIZE];
for (i=0; i < resize_threads; ++i)
which[i] = (void *) 1;
barrier(); barrier();
stb_sync_set_target(resize_merge, resize_threads);
for (i=1; i < resize_threads; ++i) for (i=1; i < resize_threads; ++i)
stb_workq(resize_workers, (stb_thread_func) cubic_interp_1d_y_work, (void *) i, which+i); stb_workq_reach(resize_workers, (stb_thread_func) cubic_interp_1d_y_work, (void *) i, NULL, resize_merge);
cubic_interp_1d_y_work(0); cubic_interp_1d_y_work(0);
stb_sync_reach_and_wait(resize_merge);
for(;;) {
for (i=1; i < resize_threads; ++i)
if (which[i])
break;
if (i == resize_threads) break;
Sleep(10);
}
} }
return cubic_work.out; return cubic_work.out;
} }

View File

@ -1,3 +1,9 @@
Version 0.92: Beta 3
* internal: replace Sleep()-based thread-joining code with synchronization primitive
* internal: change work queue internals to use stb_mutex
* internal: change stb_mutex from using win32 semaphore to using CRITICAL_SECTION
* internal: stbi_load_from_memory() only; remove stdio from stb_image (500 bytes)
Version 0.91: Beta 2 (2007-07-01) Version 0.91: Beta 2 (2007-07-01)
* feature: allow changing the label font size, toggle label in preferences * feature: allow changing the label font size, toggle label in preferences
* internal: various refactorings to clean up the code * internal: various refactorings to clean up the code

272
stb.h
View File

@ -1,4 +1,4 @@
/* stb-1.89 -- Sean's Tool Box -- public domain -- http://nothings.org/stb.h /* stb-1.90 -- Sean's Tool Box -- public domain -- http://nothings.org/stb.h
no warranty is offered or implied; use this code at your own risk no warranty is offered or implied; use this code at your own risk
This is a single header file with a bunch of useful utilities This is a single header file with a bunch of useful utilities
@ -99,7 +99,7 @@ Bug reports, feature requests, etc. can be mailed to 'sean' at the above site.
I haven't documented which is which; my point is that if you're I haven't documented which is which; my point is that if you're
doing the latter, some care is required. For example, routines doing the latter, some care is required. For example, routines
which stu__accept an output buffer but no length for that buffer which accept an output buffer but no length for that buffer
are obviously not robust. are obviously not robust.
@ -118,6 +118,8 @@ Bug reports, feature requests, etc. can be mailed to 'sean' at the above site.
3. Version History 3. Version History
1.90 stb_mutex uses CRITICAL_REGION; new stb_sync primitive for thread joining;
workqueue supports stb_sync instead of stb_semaphore
1.89 support ';' in constant-string wildcards; stb_mutex wrapper (can implement 1.89 support ';' in constant-string wildcards; stb_mutex wrapper (can implement
with EnterCriticalRegion eventually) with EnterCriticalRegion eventually)
1.88 portable threading API (only for win32 so far); worker thread queueing 1.88 portable threading API (only for win32 so far); worker thread queueing
@ -228,7 +230,7 @@ Parenthesized items have since been removed.
Functions which take an output pointer parameter (e.g. for multiple return Functions which take an output pointer parameter (e.g. for multiple return
values) always stu__accept NULL for that parameter unless noted otherwise. values) always accept NULL for that parameter unless noted otherwise.
LEGEND / KEY LEGEND / KEY
@ -3806,7 +3808,7 @@ int stb_perfect_create(stb_perfect *p, unsigned int *v, int n)
break; // fails break; // fails
} }
} }
// if succeeded, stu__accept // if succeeded, accept
if (k == bcount[i].count) { if (k == bcount[i].count) {
bcount[i].map = j; bcount[i].map = j;
for (k=0; k < bcount[i].count; ++k) { for (k=0; k < bcount[i].count; ++k) {
@ -7209,7 +7211,7 @@ int stb_wordwrap(int *pairs, int pair_max, int count, char *str)
for(;;) { for(;;) {
int s=i; // first whitespace char; last nonwhite+1 int s=i; // first whitespace char; last nonwhite+1
int w; // word start int w; // word start
// stu__accept whitespace // accept whitespace
while (isspace(str[i])) { while (isspace(str[i])) {
if (str[i] == '\n' || str[i] == '\r') { if (str[i] == '\n' || str[i] == '\r') {
if (str[i] + str[i+1] == '\n' + '\r') ++i; if (str[i] + str[i+1] == '\n' + '\r') ++i;
@ -10589,9 +10591,12 @@ typedef void * (*stb_thread_func)(void *);
typedef void *stb_thread; typedef void *stb_thread;
typedef void *stb_semaphore; typedef void *stb_semaphore;
typedef void *stb_mutex; typedef void *stb_mutex;
typedef struct stb__sync *stb_sync;
#define STB_SEMAPHORE_NULL NULL #define STB_SEMAPHORE_NULL NULL
#define STB_THREAD_NULL NULL #define STB_THREAD_NULL NULL
#define STB_MUTEX_NULL NULL #define STB_MUTEX_NULL NULL
#define STB_SYNC_NULL NULL
// get the number of processors (limited to those in the affinity mask for this process). // get the number of processors (limited to those in the affinity mask for this process).
STB_EXTERN int stb_processor_count(void); STB_EXTERN int stb_processor_count(void);
@ -10607,8 +10612,8 @@ STB_EXTERN int stb_work_maxunits(int n);
// enqueue some work to be done (can do this from any thread, or even from a piece of work); // enqueue some work to be done (can do this from any thread, or even from a piece of work);
// return value of f is stored in *return_code if non-NULL // return value of f is stored in *return_code if non-NULL
STB_EXTERN int stb_work(stb_thread_func f, void *d, volatile void **return_code); STB_EXTERN int stb_work(stb_thread_func f, void *d, volatile void **return_code);
// as above, but stb_sem_release is called on 'rel' after work is complete // as above, but stb_sync_reach is called on 'rel' after work is complete
STB_EXTERN int stb_work_sem(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel); STB_EXTERN int stb_work_reach(stb_thread_func f, void *d, volatile void **return_code, stb_sync rel);
// support for independent queues with their own threads // support for independent queues with their own threads
@ -10620,7 +10625,7 @@ STB_EXTERN stb_workqueue*stb_workq_new_flags(int numthreads, int max_units, int
STB_EXTERN void stb_workq_delete(stb_workqueue *q); STB_EXTERN void stb_workq_delete(stb_workqueue *q);
STB_EXTERN void stb_workq_numthreads(stb_workqueue *q, int n); STB_EXTERN void stb_workq_numthreads(stb_workqueue *q, int n);
STB_EXTERN int stb_workq(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code); STB_EXTERN int stb_workq(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code);
STB_EXTERN int stb_workq_sem(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel); STB_EXTERN int stb_workq_reach(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_sync rel);
STB_EXTERN int stb_workq_length(stb_workqueue *q); STB_EXTERN int stb_workq_length(stb_workqueue *q);
STB_EXTERN stb_thread stb_create_thread (stb_thread_func f, void *d); STB_EXTERN stb_thread stb_create_thread (stb_thread_func f, void *d);
@ -10637,6 +10642,12 @@ STB_EXTERN void stb_mutex_delete(stb_mutex m);
STB_EXTERN void stb_mutex_begin(stb_mutex m); STB_EXTERN void stb_mutex_begin(stb_mutex m);
STB_EXTERN void stb_mutex_end(stb_mutex m); STB_EXTERN void stb_mutex_end(stb_mutex m);
STB_EXTERN stb_sync stb_sync_new(void);
STB_EXTERN void stb_sync_delete(stb_sync s);
STB_EXTERN int stb_sync_set_target(stb_sync s, int count);
STB_EXTERN void stb_sync_reach_and_wait(stb_sync s); // wait for 'target' reachers
STB_EXTERN int stb_sync_reach(stb_sync s);
#ifdef STB_DEFINE #ifdef STB_DEFINE
typedef struct typedef struct
@ -10727,6 +10738,80 @@ int stb_processor_count(void)
return stb_bitcount(proc); return stb_bitcount(proc);
} }
#ifdef _WINDOWS_
#define STB_MUTEX_NATIVE
DWORD foob;
void *stb_mutex_new(void)
{
CRITICAL_SECTION *p = (CRITICAL_SECTION *) malloc(sizeof(*p));
if (p)
InitializeCriticalSectionAndSpinCount(p, 500);
return p;
}
void stb_mutex_delete(void *p)
{
if (p) {
DeleteCriticalSection((CRITICAL_SECTION *) p);
free(p);
}
}
void stb_mutex_begin(void *p)
{
EnterCriticalSection((CRITICAL_SECTION *) p);
}
void stb_mutex_end(void *p)
{
LeaveCriticalSection((CRITICAL_SECTION *) p);
}
#endif // _WINDOWS_
#if 0
// for future reference,
// InterlockedCompareExchange for x86:
int cas64_mp(void * dest, void * xcmp, void * xxchg) {
__asm
{
mov esi, [xxchg] ; exchange
mov ebx, [esi + 0]
mov ecx, [esi + 4]
mov esi, [xcmp] ; comparand
mov eax, [esi + 0]
mov edx, [esi + 4]
mov edi, [dest] ; destination
lock cmpxchg8b [edi]
jz yyyy;
mov [esi + 0], eax;
mov [esi + 4], edx;
yyyy:
xor eax, eax;
setz al;
};
inline unsigned __int64 _InterlockedCompareExchange64(volatile unsigned __int64 *dest
,unsigned __int64 exchange
,unsigned __int64 comperand)
{
//value returned in eax::edx
__asm {
lea esi,comperand;
lea edi,exchange;
mov eax,[esi];
mov edx,4[esi];
mov ebx,[edi];
mov ecx,4[edi];
mov esi,dest;
lock CMPXCHG8B [esi];
}
#endif // #if 0
#endif // _WIN32 #endif // _WIN32
stb_thread stb_create_thread2(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel) stb_thread stb_create_thread2(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel)
@ -10740,11 +10825,123 @@ stb_thread stb_create_thread(stb_thread_func f, void *d)
} }
// mutex implemented by wrapping semaphore // mutex implemented by wrapping semaphore
#ifndef STB_MUTEX_NATIVE
stb_mutex stb_mutex_new(void) { return stb_sem_new(1,0); } stb_mutex stb_mutex_new(void) { return stb_sem_new(1,0); }
void stb_mutex_delete(stb_mutex m) { stb_sem_delete (m); } void stb_mutex_delete(stb_mutex m) { stb_sem_delete (m); }
void stb_mutex_begin(stb_mutex m) { stb_sem_waitfor(m); } void stb_mutex_begin(stb_mutex m) { stb_sem_waitfor(m); }
void stb_mutex_end(stb_mutex m) { stb_sem_release(m); } void stb_mutex_end(stb_mutex m) { stb_sem_release(m); }
#endif
// thread merge operation
struct stb__sync
{
int target; // target number of threads to hit it
int sofar; // total threads that hit it
int waiting; // total threads waiting
stb_mutex start; // mutex to prevent starting again before finishing previous
stb_mutex mutex; // mutex while tweaking state
stb_semaphore release; // semaphore wake up waiting threads
// we have to wake them up one at a time, rather than using a single release
// call, because win32 semaphores don't let you dynamically change the max count!
};
stb_sync stb_sync_new(void)
{
stb_sync s = malloc(sizeof(*s));
if (!s) return s;
s->target = s->sofar = s->waiting = 0;
s->mutex = stb_mutex_new();
s->start = stb_mutex_new();
s->release = stb_sem_new(1,0);
if (!s->mutex || !s->release || !s->start) {
if (s->mutex ) stb_mutex_delete(s->mutex);
if (s->start ) stb_mutex_delete(s->mutex);
if (s->release) stb_sem_delete(s->release);
free(s);
return NULL;
}
stb_sem_waitfor(s->release); // put merge into blocking state
return s;
}
void stb_sync_delete(stb_sync s)
{
if (s->waiting) {
// it's bad to delete while there are threads waiting!
// shall we let them continue, or just bail? just bail
assert(0);
}
stb_mutex_delete(s->mutex);
stb_mutex_delete(s->release);
free(s);
}
int stb_sync_set_target(stb_sync s, int count)
{
// don't allow setting a target until the last one is fully released;
// note that this can lead to inefficient pipelining, and maybe we'd
// be better off ping-ponging between two internal syncs?
// I tried seeing how often this happened using TryEnterCriticalSection
// and could _never_ get it to happen in imv(stb), even with more threads
// than processors.
stb_mutex_begin(s->start);
stb_mutex_begin(s->mutex);
if (s->target != 0) {
assert(0);
stb_mutex_end(s->mutex);
return FALSE;
}
s->target = count;
s->sofar = 0;
s->waiting = 0;
stb_mutex_end(s->mutex);
return TRUE;
}
void stb__sync_release(stb_sync s)
{
if (s->waiting)
stb_sem_release(s->release);
else {
s->target = 0;
stb_mutex_end(s->start);
}
}
int stb_sync_reach(stb_sync s)
{
int n;
stb_mutex_begin(s->mutex);
assert(s->sofar < s->target);
n = ++s->sofar; // record this value to avoid possible race if we did 'return s->sofar';
if (s->sofar == s->target)
stb__sync_release(s);
stb_mutex_end(s->mutex);
return n;
}
void stb_sync_reach_and_wait(stb_sync s)
{
stb_mutex_begin(s->mutex);
assert(s->sofar < s->target);
++s->sofar;
if (s->sofar == s->target) {
stb__sync_release(s);
stb_mutex_end(s->mutex);
} else {
++s->waiting; // we're waiting, so one more waiter
stb_mutex_end(s->mutex); // release the mutex to other threads
stb_sem_waitfor(s->release); // wait for merge completion
stb_mutex_begin(s->mutex); // on merge completion, grab the mutex
--s->waiting; // we're done waiting
stb__sync_release(s);
stb_mutex_end(s->mutex); // and now we're done
// this ends the same as the first case, but it's a lot
// clearer to understand without sharing the code
}
}
static int stb__work_maxitems = 64; static int stb__work_maxitems = 64;
@ -10753,7 +10950,7 @@ typedef struct
stb_thread_func f; stb_thread_func f;
void *d; void *d;
volatile void **retval; volatile void **retval;
stb_semaphore sem; stb_sync sync;
} stb__workinfo; } stb__workinfo;
static volatile stb__workinfo *stb__work; static volatile stb__workinfo *stb__work;
@ -10762,8 +10959,11 @@ struct stb__workqueue
{ {
int maxitems, numthreads; int maxitems, numthreads;
int oldest, newest; int oldest, newest;
stb_semaphore add_mutex, remove_mutex, available; stb_semaphore available;
stb_mutex add, remove;
stb__workinfo *work; stb__workinfo *work;
int added;
int done;
}; };
static void *stb__thread_workloop(void *p) static void *stb__thread_workloop(void *p)
@ -10773,16 +10973,16 @@ static void *stb__thread_workloop(void *p)
void *z; void *z;
stb__workinfo w; stb__workinfo w;
stb_sem_waitfor(q->available); stb_sem_waitfor(q->available);
stb_sem_waitfor(q->remove_mutex); stb_mutex_begin(q->remove);
memcpy(&w, (void *) &q->work[q->oldest], sizeof(w)); // C++ won't copy memcpy(&w, (void *) &q->work[q->oldest], sizeof(w)); // C++ won't copy
if (++q->oldest == q->maxitems) if (++q->oldest == q->maxitems)
q->oldest = 0; q->oldest = 0;
stb_sem_release(q->remove_mutex); stb_mutex_end(q->remove);
if (w.f == NULL) // null work is a signal to end the thread if (w.f == NULL) // null work is a signal to end the thread
return NULL; return NULL;
z = w.f(w.d); z = w.f(w.d);
if (w.retval) *w.retval = z; if (w.retval) *w.retval = z;
if (w.sem != STB_SEMAPHORE_NULL) stb_sem_release(w.sem); if (w.sync != STB_SYNC_NULL) stb_sync_reach(w.sync);
} }
} }
@ -10800,7 +11000,7 @@ static void *stb__thread_workloop_nomutex(void *p)
return NULL; return NULL;
z = w.f(w.d); z = w.f(w.d);
if (w.retval) *w.retval = z; if (w.retval) *w.retval = z;
if (w.sem != STB_SEMAPHORE_NULL) stb_sem_release(w.sem); if (w.sync != STB_SYNC_NULL) stb_sync_reach(w.sync);
} }
} }
@ -10814,8 +11014,8 @@ stb_workqueue *stb_workq_new(int num_threads, int max_units)
void stb__workq_delete_raw(stb_workqueue *q) void stb__workq_delete_raw(stb_workqueue *q)
{ {
free(q->work); free(q->work);
stb_sem_delete(q->add_mutex); stb_mutex_delete(q->add);
stb_sem_delete(q->remove_mutex); stb_mutex_delete(q->remove);
stb_sem_delete(q->available); stb_sem_delete(q->available);
free(q); free(q);
} }
@ -10824,17 +11024,18 @@ stb_workqueue *stb_workq_new_flags(int numthreads, int max_units, int no_add_mut
{ {
stb_workqueue *q = (stb_workqueue *) malloc(sizeof(*q)); stb_workqueue *q = (stb_workqueue *) malloc(sizeof(*q));
if (q == NULL) return NULL; if (q == NULL) return NULL;
q->available = stb_sem_new(stb__work_maxitems,1); q->available = stb_sem_new(stb__work_maxitems,1);
q->add_mutex = no_add_mutex ? STB_SEMAPHORE_NULL : stb_sem_new(1,0); q->add = no_add_mutex ? STB_MUTEX_NULL : stb_mutex_new();
q->remove_mutex = no_remove_mutex ? STB_SEMAPHORE_NULL : stb_sem_new(1,0); q->remove = no_remove_mutex ? STB_MUTEX_NULL : stb_mutex_new();
q->maxitems = max_units < 1 ? 1 : max_units; q->maxitems = max_units < 1 ? 1 : max_units;
++q->maxitems; // since head cannot equal tail, we need one extra ++q->maxitems; // since head cannot equal tail, we need one extra
q->work = (stb__workinfo *) malloc(q->maxitems * sizeof(*q->work)); q->work = (stb__workinfo *) malloc(q->maxitems * sizeof(*q->work));
q->newest = q->oldest = 0; q->newest = q->oldest = 0;
q->numthreads = 0; q->numthreads = 0;
q->added = q->done = 0;
if (q->work == NULL || q->available == STB_SEMAPHORE_NULL || if (q->work == NULL || q->available == STB_SEMAPHORE_NULL ||
(q->add_mutex == STB_SEMAPHORE_NULL && !no_add_mutex) || (q->add == STB_MUTEX_NULL && !no_add_mutex) ||
(q->remove_mutex == STB_SEMAPHORE_NULL && !no_remove_mutex)) { (q->remove == STB_MUTEX_NULL && !no_remove_mutex)) {
stb__workq_delete_raw(q); stb__workq_delete_raw(q);
return NULL; return NULL;
} }
@ -10856,12 +11057,13 @@ static void stb_work_init(int num_threads)
void stb_workq_delete(stb_workqueue *q) void stb_workq_delete(stb_workqueue *q)
{ {
for(;;) { for(;;) {
stb_sem_waitfor(q->add_mutex); stb_mutex_begin(q->add);
if (q->oldest == q->newest) { if (q->oldest == q->newest) {
stb_sem_release(q->add_mutex); stb_mutex_end(q->add);
stb__workq_delete_raw(q); stb__workq_delete_raw(q);
return; return;
} }
stb_mutex_end(q->add);
stb__thread_sleep(1); stb__thread_sleep(1);
} }
} }
@ -10875,7 +11077,7 @@ int stb_work_maxunits(int n)
return stb__work_maxitems; return stb__work_maxitems;
} }
static int stb__work_raw(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel) static int stb__work_raw(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_sync rel)
{ {
int n, res; int n, res;
stb__workinfo w; stb__workinfo w;
@ -10886,8 +11088,8 @@ static int stb__work_raw(stb_workqueue *q, stb_thread_func f, void *d, volatile
w.f = f; w.f = f;
w.d = d; w.d = d;
w.retval = return_code; w.retval = return_code;
w.sem = rel; w.sync = rel;
stb_sem_waitfor(q->add_mutex); stb_mutex_begin(q->add);
n = q->newest+1; if (n == q->maxitems) n=0; n = q->newest+1; if (n == q->maxitems) n=0;
if (n == q->oldest) { if (n == q->oldest) {
// wraparound, bail! // wraparound, bail!
@ -10897,7 +11099,7 @@ static int stb__work_raw(stb_workqueue *q, stb_thread_func f, void *d, volatile
memcpy((void *) &q->work[q->newest], &w, sizeof(w)); // C++ won't copy memcpy((void *) &q->work[q->newest], &w, sizeof(w)); // C++ won't copy
q->newest = n; q->newest = n;
} }
stb_sem_release(q->add_mutex); stb_mutex_end(q->add);
if (res) if (res)
stb_sem_release(q->available); stb_sem_release(q->available);
return res; return res;
@ -10906,10 +11108,12 @@ static int stb__work_raw(stb_workqueue *q, stb_thread_func f, void *d, volatile
int stb_workq_length(stb_workqueue *q) int stb_workq_length(stb_workqueue *q)
{ {
int o,n; int o,n;
if (q->remove_mutex) stb_sem_waitfor(q->remove_mutex); if (q->remove) stb_sem_waitfor(q->remove);
if (q->add ) stb_sem_waitfor(q->add);
o = q->oldest; o = q->oldest;
n = q->newest; n = q->newest;
if (q->remove_mutex) stb_sem_release(q->remove_mutex); if (q->add ) stb_sem_release(q->add);
if (q->remove) stb_sem_release(q->remove);
if (n > o) o += q->maxitems; if (n > o) o += q->maxitems;
return o-n; return o-n;
} }
@ -10917,10 +11121,10 @@ int stb_workq_length(stb_workqueue *q)
int stb_workq(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code) int stb_workq(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code)
{ {
if (f == NULL) return 0; if (f == NULL) return 0;
return stb_workq_sem(q, f, d, return_code, NULL); return stb_workq_reach(q, f, d, return_code, NULL);
} }
int stb_workq_sem(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel) int stb_workq_reach(stb_workqueue *q, stb_thread_func f, void *d, volatile void **return_code, stb_sync rel)
{ {
if (f == NULL) return 0; if (f == NULL) return 0;
return stb__work_raw(q, f, d, return_code, rel); return stb__work_raw(q, f, d, return_code, rel);
@ -10930,7 +11134,7 @@ void stb_workq_numthreads(stb_workqueue *q, int n)
{ {
stb_sem_waitfor(stb__threadsem); stb_sem_waitfor(stb__threadsem);
while (q->numthreads < n) { while (q->numthreads < n) {
if (q->remove_mutex == STB_SEMAPHORE_NULL) if (q->remove == STB_SEMAPHORE_NULL)
stb_create_thread(stb__thread_workloop_nomutex, q); stb_create_thread(stb__thread_workloop_nomutex, q);
else else
stb_create_thread(stb__thread_workloop, q); stb_create_thread(stb__thread_workloop, q);
@ -10948,9 +11152,9 @@ int stb_work(stb_thread_func f, void *d, volatile void **return_code)
return stb_workq(stb__work_global, f,d,return_code); return stb_workq(stb__work_global, f,d,return_code);
} }
int stb_work_sem(stb_thread_func f, void *d, volatile void **return_code, stb_semaphore rel) int stb_work_reach(stb_thread_func f, void *d, volatile void **return_code, stb_sync rel)
{ {
return stb_workq_sem(stb__work_global, f,d,return_code,rel); return stb_workq_reach(stb__work_global, f,d,return_code,rel);
} }
void stb_work_numthreads(int n) void stb_work_numthreads(int n)

View File

@ -1,10 +1,22 @@
/* stbi-0.93 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c /* stbi-0.94 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c
when you control the images you're loading when you control the images you're loading
QUICK NOTES:
Primarily of interest to game developers and other people who can
avoid problematic images and only need the trivial interface
JPEG baseline (no JPEG progressive, no oddball channel decimations)
PNG non-interlaced
BMP non-1bpp, non-RLE
writes BMP,TGA (define STBI_NO_WRITE to remove code)
decoded from memory or through stdio FILE (define STBI_NO_STDIO to remove code)
TODO: TODO:
stbi_info_* stbi_info_*
PSD loader
history: history:
0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same
0.93 handle jpegtran output; verbose errors 0.93 handle jpegtran output; verbose errors
0.92 read 4,8,16,24,32-bit BMP files of several formats 0.92 read 4,8,16,24,32-bit BMP files of several formats
0.91 output 24-bit Windows 3.0 BMP files 0.91 output 24-bit Windows 3.0 BMP files
@ -32,8 +44,7 @@
// - 8-bit samples only (jpeg, png) // - 8-bit samples only (jpeg, png)
// - not threadsafe // - not threadsafe
// - channel subsampling of at most 2 in each dimension (jpeg) // - channel subsampling of at most 2 in each dimension (jpeg)
// - no delayed line count (jpeg) -- image height must be in header // - no delayed line count (jpeg) -- IJG doesn't support either
// - unsophisticated error handling
// //
// Basic usage: // Basic usage:
// int x,y,n; // int x,y,n;
@ -71,12 +82,15 @@
// If image loading fails for any reason, the return value will be NULL, // If image loading fails for any reason, the return value will be NULL,
// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() // and *x, *y, *comp will be unchanged. The function stbi_failure_reason()
// can be queried for an extremely brief, end-user unfriendly explanation // can be queried for an extremely brief, end-user unfriendly explanation
// of why the load failed. Define STB_IMAGE_NO_FAILURE_REASON to avoid // of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid
// compiling these strings at all. // compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly
// more user-friendly ones.
// //
// Paletted PNG images are automatically depalettized. // Paletted PNG and BMP images are automatically depalettized.
#ifndef STBI_NO_STDIO
#include <stdio.h> #include <stdio.h>
#endif
enum enum
{ {
@ -107,8 +121,10 @@ extern int stbi_write_tga (char *filename, int x, int y, in
// PRIMARY API - works on images of any type // PRIMARY API - works on images of any type
// load image by filename, open file, or memory buffer // load image by filename, open file, or memory buffer
#ifndef STBI_NO_STDIO
extern stbi_uc *stbi_load (char *filename, int *x, int *y, int *comp, int req_comp); extern stbi_uc *stbi_load (char *filename, int *x, int *y, int *comp, int req_comp);
extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp);
#endif
extern stbi_uc *stbi_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp); extern stbi_uc *stbi_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp);
// for stbi_load_from_file, file pointer is left pointing immediately after image // for stbi_load_from_file, file pointer is left pointing immediately after image
@ -211,9 +227,9 @@ static int e(char *str)
return 0; return 0;
} }
#ifdef STB_IMAGE_NO_FAILURE_STRINGS #ifdef STBI_NO_FAILURE_STRINGS
#define e(x,y) 0 #define e(x,y) 0
#elif defined(STB_IMAGE_FAILURE_USERMSG) #elif defined(STBI_FAILURE_USERMSG)
#define e(x,y) e(y) #define e(x,y) e(y)
#else #else
#define e(x,y) e(x) #define e(x,y) e(x)
@ -226,6 +242,7 @@ void stbi_image_free(unsigned char *retval_from_stbi_load)
free(retval_from_stbi_load); free(retval_from_stbi_load);
} }
#ifndef STBI_NO_STDIO
unsigned char *stbi_load(char *filename, int *x, int *y, int *comp, int req_comp) unsigned char *stbi_load(char *filename, int *x, int *y, int *comp, int req_comp)
{ {
FILE *f = fopen(filename, "rb"); FILE *f = fopen(filename, "rb");
@ -246,6 +263,7 @@ unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_c
return stbi_bmp_load_from_file(f,x,y,comp,req_comp); return stbi_bmp_load_from_file(f,x,y,comp,req_comp);
return ep("unknown image type", "Image not of any known type, or corrupt"); return ep("unknown image type", "Image not of any known type, or corrupt");
} }
#endif
unsigned char *stbi_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) unsigned char *stbi_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp)
{ {
@ -281,31 +299,38 @@ enum
// An API for reading either from memory or file. // An API for reading either from memory or file.
// It fits on a single screen. No abstract base classes needed. // It fits on a single screen. No abstract base classes needed.
#ifndef STBI_NO_STDIO
static FILE *img_file; static FILE *img_file;
#endif
static uint8 *img_buffer, *img_buffer_end; static uint8 *img_buffer, *img_buffer_end;
#ifndef STBI_NO_STDIO
static void start_file(FILE *f) static void start_file(FILE *f)
{ {
img_file = f; img_file = f;
} }
#endif
static void start_mem(uint8 *buffer, int len) static void start_mem(uint8 *buffer, int len)
{ {
#ifndef STBI_NO_STDIO
img_file = NULL; img_file = NULL;
#endif
img_buffer = buffer; img_buffer = buffer;
img_buffer_end = buffer+len; img_buffer_end = buffer+len;
} }
static int get8(void) static int get8(void)
{ {
#ifndef STBI_NO_STDIO
if (img_file) { if (img_file) {
int c = fgetc(img_file); int c = fgetc(img_file);
return c == EOF ? 0 : c; return c == EOF ? 0 : c;
} else {
if (img_buffer < img_buffer_end)
return *img_buffer++;
return 0;
} }
#endif
if (img_buffer < img_buffer_end)
return *img_buffer++;
return 0;
} }
static uint8 get8u(void) static uint8 get8u(void)
@ -315,9 +340,11 @@ static uint8 get8u(void)
static void skip(int n) static void skip(int n)
{ {
#ifndef STBI_NO_STDIO
if (img_file) if (img_file)
fseek(img_file, n, SEEK_CUR); fseek(img_file, n, SEEK_CUR);
else else
#endif
img_buffer += n; img_buffer += n;
} }
@ -347,12 +374,14 @@ static uint32 get32le(void)
static void getn(stbi_uc *buffer, int n) static void getn(stbi_uc *buffer, int n)
{ {
if (img_file) #ifndef STBI_NO_STDIO
if (img_file) {
fread(buffer, 1, n, img_file); fread(buffer, 1, n, img_file);
else { return;
memcpy(buffer, img_buffer, n);
img_buffer += n;
} }
#endif
memcpy(buffer, img_buffer, n);
img_buffer += n;
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@ -1286,6 +1315,7 @@ static uint8 *load_jpeg_image(int *out_x, int *out_y, int *comp, int req_comp)
} }
} }
#ifndef STBI_NO_STDIO
unsigned char *stbi_jpeg_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) unsigned char *stbi_jpeg_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
{ {
start_file(f); start_file(f);
@ -1301,6 +1331,7 @@ unsigned char *stbi_jpeg_load(char *filename, int *x, int *y, int *comp, int req
fclose(f); fclose(f);
return data; return data;
} }
#endif
unsigned char *stbi_jpeg_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) unsigned char *stbi_jpeg_load_from_memory(stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp)
{ {
@ -1308,6 +1339,7 @@ unsigned char *stbi_jpeg_load_from_memory(stbi_uc *buffer, int len, int *x, int
return load_jpeg_image(x,y,comp,req_comp); return load_jpeg_image(x,y,comp,req_comp);
} }
#ifndef STBI_NO_STDIO
int stbi_jpeg_test_file(FILE *f) int stbi_jpeg_test_file(FILE *f)
{ {
int n,r; int n,r;
@ -1317,6 +1349,7 @@ int stbi_jpeg_test_file(FILE *f)
fseek(f,n,SEEK_SET); fseek(f,n,SEEK_SET);
return r; return r;
} }
#endif
int stbi_jpeg_test_memory(unsigned char *buffer, int len) int stbi_jpeg_test_memory(unsigned char *buffer, int len)
{ {
@ -1982,9 +2015,14 @@ static int parse_png_file(int scan, int req_comp)
p = (uint8 *) realloc(idata, idata_limit); if (p == NULL) return e("outofmem", "Out of memory"); p = (uint8 *) realloc(idata, idata_limit); if (p == NULL) return e("outofmem", "Out of memory");
idata = p; idata = p;
} }
if (img_file) { #ifndef STBI_NO_STDIO
if (img_file)
{
if (fread(idata+ioff,1,c.length,img_file) != c.length) return e("outofdata","Corrupt PNG"); if (fread(idata+ioff,1,c.length,img_file) != c.length) return e("outofdata","Corrupt PNG");
} else { }
else
#endif
{
memcpy(idata+ioff, img_buffer, c.length); memcpy(idata+ioff, img_buffer, c.length);
img_buffer += c.length; img_buffer += c.length;
} }
@ -2021,7 +2059,7 @@ static int parse_png_file(int scan, int req_comp)
default: default:
// if critical, fail // if critical, fail
if ((c.type & (1 << 29)) == 0) { if ((c.type & (1 << 29)) == 0) {
#ifndef STB_IMAGE_NO_FAILURE_STRINGS #ifndef STBI_NO_FAILURE_STRINGS
static char invalid_chunk[] = "XXXX chunk not known"; static char invalid_chunk[] = "XXXX chunk not known";
invalid_chunk[0] = (uint8) (c.type >> 24); invalid_chunk[0] = (uint8) (c.type >> 24);
invalid_chunk[1] = (uint8) (c.type >> 16); invalid_chunk[1] = (uint8) (c.type >> 16);
@ -2060,6 +2098,7 @@ static unsigned char *do_png(int *x, int *y, int *n, int req_comp)
return result; return result;
} }
#ifndef STBI_NO_STDIO
unsigned char *stbi_png_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) unsigned char *stbi_png_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
{ {
start_file(f); start_file(f);
@ -2075,6 +2114,7 @@ unsigned char *stbi_png_load(char *filename, int *x, int *y, int *comp, int req_
fclose(f); fclose(f);
return data; return data;
} }
#endif
unsigned char *stbi_png_load_from_memory(unsigned char *buffer, int len, int *x, int *y, int *comp, int req_comp) unsigned char *stbi_png_load_from_memory(unsigned char *buffer, int len, int *x, int *y, int *comp, int req_comp)
{ {
@ -2082,6 +2122,7 @@ unsigned char *stbi_png_load_from_memory(unsigned char *buffer, int len, int *x,
return do_png(x,y,comp,req_comp); return do_png(x,y,comp,req_comp);
} }
#ifndef STBI_NO_STDIO
int stbi_png_test_file(FILE *f) int stbi_png_test_file(FILE *f)
{ {
int n,r; int n,r;
@ -2091,6 +2132,7 @@ int stbi_png_test_file(FILE *f)
fseek(f,n,SEEK_SET); fseek(f,n,SEEK_SET);
return r; return r;
} }
#endif
int stbi_png_test_memory(unsigned char *buffer, int len) int stbi_png_test_memory(unsigned char *buffer, int len)
{ {
@ -2121,6 +2163,7 @@ static int bmp_test(void)
return 0; return 0;
} }
#ifndef STBI_NO_STDIO
int stbi_bmp_test_file (FILE *f) int stbi_bmp_test_file (FILE *f)
{ {
int r,n = ftell(f); int r,n = ftell(f);
@ -2129,6 +2172,7 @@ int stbi_bmp_test_file (FILE *f)
fseek(f,n,SEEK_SET); fseek(f,n,SEEK_SET);
return r; return r;
} }
#endif
int stbi_bmp_test_memory (stbi_uc *buffer, int len) int stbi_bmp_test_memory (stbi_uc *buffer, int len)
{ {
@ -2352,6 +2396,7 @@ static stbi_uc *bmp_load(int *x, int *y, int *comp, int req_comp)
return out; return out;
} }
#ifndef STBI_NO_STDIO
stbi_uc *stbi_bmp_load (char *filename, int *x, int *y, int *comp, int req_comp) stbi_uc *stbi_bmp_load (char *filename, int *x, int *y, int *comp, int req_comp)
{ {
stbi_uc *data; stbi_uc *data;
@ -2367,6 +2412,7 @@ stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, in
start_file(f); start_file(f);
return bmp_load(x,y,comp,req_comp); return bmp_load(x,y,comp,req_comp);
} }
#endif
stbi_uc *stbi_bmp_load_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp) stbi_uc *stbi_bmp_load_from_memory (stbi_uc *buffer, int len, int *x, int *y, int *comp, int req_comp)
{ {