mirror of https://github.com/nothings/stb-imv
fix stb_sync -- finish writing start mutex
This commit is contained in:
parent
43a913cc33
commit
c393e03d16
81
imv.c
81
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 <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
@ -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,17 +2151,28 @@ 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) {
|
||||
{
|
||||
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 *why = stbi_failure_reason();
|
||||
char buffer[512];
|
||||
sprintf(buffer, "'%s': %s", filename, why);
|
||||
error(buffer);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// fix the filename & path for consistency with readdir()
|
||||
stb_fixpath(filename);
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
270
stb.h
270
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);
|
||||
}
|
||||
|
@ -10825,16 +11025,17 @@ 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->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)
|
||||
|
|
78
stb_image.c
78
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 <stdio.h>
|
||||
#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,32 +299,39 @@ 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 {
|
||||
}
|
||||
#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,13 +374,15 @@ 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 {
|
||||
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)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue