diff --git a/imv.c b/imv.c index ea9a755..352b1cc 100644 --- a/imv.c +++ b/imv.c @@ -19,7 +19,7 @@ // Set section alignment to minimize alignment overhead #pragma comment(linker, "/FILEALIGN:0x200") -#define _WIN32_WINNT 0x0400 +#define _WIN32_WINNT 0x0500 #include #include #include @@ -29,7 +29,8 @@ #define STB_DEFINE #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 "resource.h" @@ -207,6 +208,7 @@ typedef struct stb_mutex cache_mutex, decode_mutex; stb_semaphore decode_queue; stb_semaphore disk_command_queue; +stb_sync resize_merge; // a request communicated from the main thread to the disk-loader task typedef struct @@ -1079,7 +1081,9 @@ struct // stb_sdict is a string dictionary (strings as keys, void * as values) // 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; // 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 -#define MAX_RESIZE 4 int resize_threads; // 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); // 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 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); // load initial image - image_data = stbi_load(filename, &image_x, &image_y, &image_n, BPP); - if (image_data == NULL) { - // 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 *why = stbi_failure_reason(); - char buffer[512]; - sprintf(buffer, "'%s': %s", filename, why); - error(buffer); - exit(0); + { + char *why=NULL; + int len; + uint8 *data = stb_file(filename, &len); + if (!data) + why = "Couldn't open file"; + else { + image_data = stbi_load_from_memory(data, len, &image_x, &image_y, &image_n, BPP); + if (image_data == NULL) + 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() @@ -2169,6 +2184,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine decode_mutex = stb_mutex_new(); decode_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 stb_create_thread(diskload_task, NULL); @@ -2336,7 +2352,6 @@ typedef struct SplitPoint *p; int j0,j1; float dy; - int done; } ImageProcess; #define CACHE_REBLOCK 64 @@ -2423,7 +2438,6 @@ void *image_resize_work(ImageProcess *q) y += q->dy; } } - q->done = TRUE; return NULL; } @@ -2462,25 +2476,17 @@ void image_resize_bilinear(Image *dest, Image *src) q[i].j1 = j1; q[i].dy = dy; q[i].p = p; - q[i].done = FALSE; j1 = j0; } if (resize_threads == 1) { image_resize_work(q); } else { - barrier(); + stb_sync_set_target(resize_merge, resize_threads); 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); - - for(;;) { - for (i=1; i < resize_threads; ++i) - if (!q[i].done) - break; - if (i == resize_threads) break; - Sleep(10); - } + stb_sync_reach_and_wait(resize_merge); } stb_tempfree(point_buffer, p); @@ -2718,7 +2724,6 @@ void * cubic_interp_1d_x_work(int n) x += dx; } } - barrier(); return NULL; } @@ -2734,21 +2739,11 @@ Image *cubic_interp_1d_x(Image *src, int out_w) if (resize_threads == 1) { cubic_interp_1d_x_work(0); } else { - volatile void *which[MAX_RESIZE]; - for (i=0; i < resize_threads; ++i) - which[i] = (void *) 1; - barrier(); + stb_sync_set_target(resize_merge, resize_threads); 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); - - for(;;) { - for (i=1; i < resize_threads; ++i) - if (which[i]) - break; - if (i == resize_threads) break; - Sleep(10); - } + stb_sync_reach_and_wait(resize_merge); } 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.delta = ((src->y-1)*65536-1) / (out_h-1); cubic_work.out_len = out_h; - barrier(); if (resize_threads == 1) { cubic_interp_1d_y_work(0); } else { - volatile void *which[MAX_RESIZE]; - for (i=0; i < resize_threads; ++i) - which[i] = (void *) 1; barrier(); + stb_sync_set_target(resize_merge, resize_threads); 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); - - for(;;) { - for (i=1; i < resize_threads; ++i) - if (which[i]) - break; - if (i == resize_threads) break; - Sleep(10); - } + stb_sync_reach_and_wait(resize_merge); } return cubic_work.out; } diff --git a/notes.txt b/notes.txt index 4e1817d..f2818e6 100644 --- a/notes.txt +++ b/notes.txt @@ -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) * feature: allow changing the label font size, toggle label in preferences * internal: various refactorings to clean up the code diff --git a/stb.h b/stb.h index e564b2c..2394f71 100644 --- a/stb.h +++ b/stb.h @@ -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 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 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. @@ -118,6 +118,8 @@ Bug reports, feature requests, etc. can be mailed to 'sean' at the above site. 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 with EnterCriticalRegion eventually) 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 -values) always stu__accept NULL for that parameter unless noted otherwise. +values) always accept NULL for that parameter unless noted otherwise. LEGEND / KEY @@ -3806,7 +3808,7 @@ int stb_perfect_create(stb_perfect *p, unsigned int *v, int n) break; // fails } } - // if succeeded, stu__accept + // if succeeded, accept if (k == bcount[i].count) { bcount[i].map = j; 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(;;) { int s=i; // first whitespace char; last nonwhite+1 int w; // word start - // stu__accept whitespace + // accept whitespace while (isspace(str[i])) { if (str[i] == '\n' || str[i] == '\r') { 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_semaphore; typedef void *stb_mutex; +typedef struct stb__sync *stb_sync; + #define STB_SEMAPHORE_NULL NULL #define STB_THREAD_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). 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); // 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); -// as above, but stb_sem_release 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); +// as above, but stb_sync_reach is called on 'rel' after work is complete +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 @@ -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_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_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 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_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 typedef struct @@ -10727,6 +10738,80 @@ int stb_processor_count(void) 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 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 +#ifndef STB_MUTEX_NATIVE 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_begin(stb_mutex m) { stb_sem_waitfor(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; @@ -10753,7 +10950,7 @@ typedef struct stb_thread_func f; void *d; volatile void **retval; - stb_semaphore sem; + stb_sync sync; } stb__workinfo; static volatile stb__workinfo *stb__work; @@ -10762,8 +10959,11 @@ struct stb__workqueue { int maxitems, numthreads; int oldest, newest; - stb_semaphore add_mutex, remove_mutex, available; + stb_semaphore available; + stb_mutex add, remove; stb__workinfo *work; + int added; + int done; }; static void *stb__thread_workloop(void *p) @@ -10773,16 +10973,16 @@ static void *stb__thread_workloop(void *p) void *z; stb__workinfo w; 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 if (++q->oldest == q->maxitems) 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 return NULL; z = w.f(w.d); 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; z = w.f(w.d); 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) { free(q->work); - stb_sem_delete(q->add_mutex); - stb_sem_delete(q->remove_mutex); + stb_mutex_delete(q->add); + stb_mutex_delete(q->remove); stb_sem_delete(q->available); 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)); if (q == NULL) return NULL; - q->available = stb_sem_new(stb__work_maxitems,1); - q->add_mutex = no_add_mutex ? STB_SEMAPHORE_NULL : stb_sem_new(1,0); - q->remove_mutex = no_remove_mutex ? STB_SEMAPHORE_NULL : stb_sem_new(1,0); + q->available = stb_sem_new(stb__work_maxitems,1); + q->add = no_add_mutex ? STB_MUTEX_NULL : stb_mutex_new(); + q->remove = no_remove_mutex ? STB_MUTEX_NULL : stb_mutex_new(); q->maxitems = max_units < 1 ? 1 : max_units; ++q->maxitems; // since head cannot equal tail, we need one extra q->work = (stb__workinfo *) malloc(q->maxitems * sizeof(*q->work)); q->newest = q->oldest = 0; q->numthreads = 0; + q->added = q->done = 0; if (q->work == NULL || q->available == STB_SEMAPHORE_NULL || - (q->add_mutex == STB_SEMAPHORE_NULL && !no_add_mutex) || - (q->remove_mutex == STB_SEMAPHORE_NULL && !no_remove_mutex)) { + (q->add == STB_MUTEX_NULL && !no_add_mutex) || + (q->remove == STB_MUTEX_NULL && !no_remove_mutex)) { stb__workq_delete_raw(q); return NULL; } @@ -10856,12 +11057,13 @@ static void stb_work_init(int num_threads) void stb_workq_delete(stb_workqueue *q) { for(;;) { - stb_sem_waitfor(q->add_mutex); + stb_mutex_begin(q->add); if (q->oldest == q->newest) { - stb_sem_release(q->add_mutex); + stb_mutex_end(q->add); stb__workq_delete_raw(q); return; } + stb_mutex_end(q->add); stb__thread_sleep(1); } } @@ -10875,7 +11077,7 @@ int stb_work_maxunits(int n) 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; 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.d = d; w.retval = return_code; - w.sem = rel; - stb_sem_waitfor(q->add_mutex); + w.sync = rel; + stb_mutex_begin(q->add); n = q->newest+1; if (n == q->maxitems) n=0; if (n == q->oldest) { // 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 q->newest = n; } - stb_sem_release(q->add_mutex); + stb_mutex_end(q->add); if (res) stb_sem_release(q->available); 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 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; 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; 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) { 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; 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); 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); else 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); } -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) diff --git a/stb_image.c b/stb_image.c index 395b660..a821c7c 100644 --- a/stb_image.c +++ b/stb_image.c @@ -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 - + + 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: stbi_info_* + PSD loader history: + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same 0.93 handle jpegtran output; verbose errors 0.92 read 4,8,16,24,32-bit BMP files of several formats 0.91 output 24-bit Windows 3.0 BMP files @@ -32,8 +44,7 @@ // - 8-bit samples only (jpeg, png) // - not threadsafe // - channel subsampling of at most 2 in each dimension (jpeg) -// - no delayed line count (jpeg) -- image height must be in header -// - unsophisticated error handling +// - no delayed line count (jpeg) -- IJG doesn't support either // // Basic usage: // int x,y,n; @@ -71,12 +82,15 @@ // 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() // can be queried for an extremely brief, end-user unfriendly explanation -// of why the load failed. Define STB_IMAGE_NO_FAILURE_REASON to avoid -// compiling these strings at all. +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// 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 +#endif 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 // 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_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); // for stbi_load_from_file, file pointer is left pointing immediately after image @@ -211,9 +227,9 @@ static int e(char *str) return 0; } -#ifdef STB_IMAGE_NO_FAILURE_STRINGS +#ifdef STBI_NO_FAILURE_STRINGS #define e(x,y) 0 -#elif defined(STB_IMAGE_FAILURE_USERMSG) +#elif defined(STBI_FAILURE_USERMSG) #define e(x,y) e(y) #else #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); } +#ifndef STBI_NO_STDIO unsigned char *stbi_load(char *filename, int *x, int *y, int *comp, int req_comp) { 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 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) { @@ -281,31 +299,38 @@ enum // An API for reading either from memory or file. // It fits on a single screen. No abstract base classes needed. +#ifndef STBI_NO_STDIO static FILE *img_file; +#endif static uint8 *img_buffer, *img_buffer_end; +#ifndef STBI_NO_STDIO static void start_file(FILE *f) { img_file = f; } +#endif static void start_mem(uint8 *buffer, int len) { +#ifndef STBI_NO_STDIO img_file = NULL; +#endif img_buffer = buffer; img_buffer_end = buffer+len; } static int get8(void) { +#ifndef STBI_NO_STDIO if (img_file) { int c = fgetc(img_file); 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) @@ -315,9 +340,11 @@ static uint8 get8u(void) static void skip(int n) { +#ifndef STBI_NO_STDIO if (img_file) fseek(img_file, n, SEEK_CUR); else +#endif img_buffer += n; } @@ -347,12 +374,14 @@ static uint32 get32le(void) static void getn(stbi_uc *buffer, int n) { - if (img_file) +#ifndef STBI_NO_STDIO + if (img_file) { fread(buffer, 1, n, img_file); - else { - memcpy(buffer, img_buffer, n); - img_buffer += n; + return; } +#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) { start_file(f); @@ -1301,6 +1331,7 @@ unsigned char *stbi_jpeg_load(char *filename, int *x, int *y, int *comp, int req fclose(f); return data; } +#endif 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); } +#ifndef STBI_NO_STDIO int stbi_jpeg_test_file(FILE *f) { int n,r; @@ -1317,6 +1349,7 @@ int stbi_jpeg_test_file(FILE *f) fseek(f,n,SEEK_SET); return r; } +#endif 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"); 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"); - } else { + } + else + #endif + { memcpy(idata+ioff, img_buffer, c.length); img_buffer += c.length; } @@ -2021,7 +2059,7 @@ static int parse_png_file(int scan, int req_comp) default: // if critical, fail 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"; invalid_chunk[0] = (uint8) (c.type >> 24); 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; } +#ifndef STBI_NO_STDIO unsigned char *stbi_png_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) { start_file(f); @@ -2075,6 +2114,7 @@ unsigned char *stbi_png_load(char *filename, int *x, int *y, int *comp, int req_ fclose(f); return data; } +#endif 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); } +#ifndef STBI_NO_STDIO int stbi_png_test_file(FILE *f) { int n,r; @@ -2091,6 +2132,7 @@ int stbi_png_test_file(FILE *f) fseek(f,n,SEEK_SET); return r; } +#endif int stbi_png_test_memory(unsigned char *buffer, int len) { @@ -2121,6 +2163,7 @@ static int bmp_test(void) return 0; } +#ifndef STBI_NO_STDIO int stbi_bmp_test_file (FILE *f) { int r,n = ftell(f); @@ -2129,6 +2172,7 @@ int stbi_bmp_test_file (FILE *f) fseek(f,n,SEEK_SET); return r; } +#endif 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; } +#ifndef STBI_NO_STDIO stbi_uc *stbi_bmp_load (char *filename, int *x, int *y, int *comp, int req_comp) { stbi_uc *data; @@ -2367,6 +2412,7 @@ stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, in start_file(f); 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) {