parent
							
								
									71ead9adea
								
							
						
					
					
						commit
						368a956b96
					
				
				 41 changed files with 3876 additions and 0 deletions
			
			
		| @ -0,0 +1,36 @@ | ||||
| Import('env', 'arch', 'SHARED') | ||||
| 
 | ||||
| if SHARED: | ||||
|   fxn = env.SharedLibrary | ||||
| else: | ||||
|   fxn = env.Library | ||||
| 
 | ||||
| _common = fxn('common', ['params.cc', 'swaglog.c', 'util.c', 'cqueue.c'], LIBS="json") | ||||
| _visionipc = fxn('visionipc', ['visionipc.c', 'ipc.c']) | ||||
| 
 | ||||
| files = [ | ||||
|   'buffering.c', | ||||
|   'clutil.c', | ||||
|   'efd.c', | ||||
|   'glutil.c', | ||||
|   'visionimg.cc', | ||||
| ] | ||||
| 
 | ||||
| if arch == "aarch64": | ||||
|   defines = {} | ||||
|   files += [ | ||||
|     'framebuffer.cc', | ||||
|     'touch.c', | ||||
|     'visionbuf_ion.c', | ||||
|   ] | ||||
|   _gpu_libs = ['gui', 'adreno_utils'] | ||||
| else: | ||||
|   defines = {"CLU_NO_CACHE": None} | ||||
|   files += [ | ||||
|     'visionbuf_cl.c', | ||||
|   ] | ||||
|   _gpu_libs = ["GL"] | ||||
| 
 | ||||
| _gpucommon = fxn('gpucommon', files, CPPDEFINES=defines, LIBS=_gpu_libs) | ||||
| Export('_common', '_visionipc', '_gpucommon', '_gpu_libs') | ||||
| 
 | ||||
| @ -0,0 +1,438 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdbool.h> | ||||
| #include <assert.h> | ||||
| #include <string.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| #include "common/efd.h" | ||||
| 
 | ||||
| #include "buffering.h" | ||||
| 
 | ||||
| void tbuffer_init(TBuffer *tb, int num_bufs, const char* name) { | ||||
|   assert(num_bufs >= 3); | ||||
| 
 | ||||
|   memset(tb, 0, sizeof(TBuffer)); | ||||
|   tb->reading = (bool*)calloc(num_bufs, sizeof(bool)); | ||||
|   assert(tb->reading); | ||||
|   tb->pending_idx = -1; | ||||
|   tb->num_bufs = num_bufs; | ||||
|   tb->name = name; | ||||
| 
 | ||||
|   pthread_mutex_init(&tb->lock, NULL); | ||||
|   pthread_cond_init(&tb->cv, NULL); | ||||
|   tb->efd = efd_init(); | ||||
|   assert(tb->efd >= 0); | ||||
| } | ||||
| 
 | ||||
| void tbuffer_init2(TBuffer *tb, int num_bufs, const char* name, | ||||
|                    void (*release_cb)(void* c, int idx), | ||||
|                    void* cb_cookie) { | ||||
| 
 | ||||
|   tbuffer_init(tb, num_bufs, name); | ||||
| 
 | ||||
|   tb->release_cb = release_cb; | ||||
|   tb->cb_cookie = cb_cookie; | ||||
| } | ||||
| 
 | ||||
| int tbuffer_efd(TBuffer *tb) { | ||||
|   return tb->efd; | ||||
| } | ||||
| 
 | ||||
| int tbuffer_select(TBuffer *tb) { | ||||
|   pthread_mutex_lock(&tb->lock); | ||||
| 
 | ||||
|   int i; | ||||
|   for (i=0; i<tb->num_bufs; i++) { | ||||
|     if (!tb->reading[i] && i != tb->pending_idx) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   assert(i < tb->num_bufs); | ||||
| 
 | ||||
|   pthread_mutex_unlock(&tb->lock); | ||||
|   return i; | ||||
| } | ||||
| 
 | ||||
| void tbuffer_dispatch(TBuffer *tb, int idx) { | ||||
|   pthread_mutex_lock(&tb->lock); | ||||
| 
 | ||||
|   if (tb->pending_idx != -1) { | ||||
|     //printf("tbuffer (%s) dropped!\n", tb->name ? tb->name : "?");
 | ||||
|     if (tb->release_cb) { | ||||
|       tb->release_cb(tb->cb_cookie, tb->pending_idx); | ||||
|     } | ||||
|     tb->pending_idx = -1; | ||||
|   } | ||||
| 
 | ||||
|   tb->pending_idx = idx; | ||||
| 
 | ||||
|   efd_write(tb->efd); | ||||
|   pthread_cond_signal(&tb->cv); | ||||
| 
 | ||||
|   pthread_mutex_unlock(&tb->lock);  
 | ||||
| } | ||||
| 
 | ||||
| int tbuffer_acquire(TBuffer *tb) { | ||||
|   pthread_mutex_lock(&tb->lock); | ||||
| 
 | ||||
|   if (tb->stopped) { | ||||
|     pthread_mutex_unlock(&tb->lock); | ||||
|     return -1; | ||||
|   } | ||||
| 
 | ||||
|   while (tb->pending_idx == -1) { | ||||
|     pthread_cond_wait(&tb->cv, &tb->lock); | ||||
| 
 | ||||
|     if (tb->stopped) { | ||||
|       pthread_mutex_unlock(&tb->lock); | ||||
|       return -1; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   efd_clear(tb->efd); | ||||
| 
 | ||||
|   int ret = tb->pending_idx; | ||||
|   assert(ret < tb->num_bufs); | ||||
| 
 | ||||
|   tb->reading[ret] = true; | ||||
|   tb->pending_idx = -1; | ||||
| 
 | ||||
|   pthread_mutex_unlock(&tb->lock); | ||||
| 
 | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| static void tbuffer_release_locked(TBuffer *tb, int idx) { | ||||
|   assert(idx < tb->num_bufs); | ||||
|   if (!tb->reading[idx]) { | ||||
|     printf("!! releasing tbuffer we aren't reading %d\n", idx); | ||||
|   } | ||||
| 
 | ||||
|   if (tb->release_cb) { | ||||
|     tb->release_cb(tb->cb_cookie, idx); | ||||
|   } | ||||
| 
 | ||||
|   tb->reading[idx] = false; | ||||
| } | ||||
| 
 | ||||
| void tbuffer_release(TBuffer *tb, int idx) { | ||||
|   pthread_mutex_lock(&tb->lock); | ||||
|   tbuffer_release_locked(tb, idx); | ||||
|   pthread_mutex_unlock(&tb->lock); | ||||
| } | ||||
| 
 | ||||
| void tbuffer_release_all(TBuffer *tb) { | ||||
|   pthread_mutex_lock(&tb->lock); | ||||
|   for (int i=0; i<tb->num_bufs; i++) { | ||||
|     if (tb->reading[i]) { | ||||
|       tbuffer_release_locked(tb, i); | ||||
|     } | ||||
|   } | ||||
|   pthread_mutex_unlock(&tb->lock); | ||||
| } | ||||
| 
 | ||||
| void tbuffer_stop(TBuffer *tb) { | ||||
|   pthread_mutex_lock(&tb->lock); | ||||
|   tb->stopped = true; | ||||
|   efd_write(tb->efd); | ||||
|   pthread_cond_signal(&tb->cv); | ||||
|   pthread_mutex_unlock(&tb->lock); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| void pool_init(Pool *s, int num_bufs) { | ||||
|   assert(num_bufs > 3); | ||||
| 
 | ||||
|   memset(s, 0, sizeof(*s)); | ||||
|   s->num_bufs = num_bufs; | ||||
| 
 | ||||
|   s->refcnt = (int*)calloc(num_bufs, sizeof(int)); | ||||
|   s->ts = (int*)calloc(num_bufs, sizeof(int)); | ||||
| 
 | ||||
|   s->counter = 1; | ||||
| 
 | ||||
|   pthread_mutex_init(&s->lock, NULL); | ||||
| } | ||||
| 
 | ||||
| void pool_init2(Pool *s, int num_bufs, | ||||
|   void (*release_cb)(void* c, int idx), void* cb_cookie) { | ||||
| 
 | ||||
|   pool_init(s, num_bufs); | ||||
|   s->cb_cookie = cb_cookie; | ||||
|   s->release_cb = release_cb; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void pool_acquire(Pool *s, int idx) { | ||||
|   pthread_mutex_lock(&s->lock); | ||||
| 
 | ||||
|   assert(idx >= 0 && idx < s->num_bufs); | ||||
| 
 | ||||
|   s->refcnt[idx]++; | ||||
| 
 | ||||
|   pthread_mutex_unlock(&s->lock); | ||||
| } | ||||
| 
 | ||||
| static void pool_release_locked(Pool *s, int idx) { | ||||
|   // printf("release %d refcnt %d\n", idx, s->refcnt[idx]);
 | ||||
| 
 | ||||
|   assert(idx >= 0 && idx < s->num_bufs); | ||||
| 
 | ||||
|   assert(s->refcnt[idx] > 0); | ||||
|   s->refcnt[idx]--; | ||||
| 
 | ||||
|   // printf("release %d -> %d, %p\n", idx, s->refcnt[idx], s->release_cb);
 | ||||
|   if (s->refcnt[idx] == 0 && s->release_cb) { | ||||
|     // printf("call %p\b", s->release_cb);
 | ||||
|     s->release_cb(s->cb_cookie, idx); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void pool_release(Pool *s, int idx) { | ||||
|   pthread_mutex_lock(&s->lock); | ||||
|   pool_release_locked(s, idx); | ||||
|   pthread_mutex_unlock(&s->lock); | ||||
| } | ||||
| 
 | ||||
| TBuffer* pool_get_tbuffer(Pool *s) { | ||||
|   pthread_mutex_lock(&s->lock); | ||||
| 
 | ||||
|   assert(s->num_tbufs < POOL_MAX_TBUFS); | ||||
|   TBuffer* tbuf = &s->tbufs[s->num_tbufs]; | ||||
|   s->num_tbufs++; | ||||
|   tbuffer_init2(tbuf, s->num_bufs, | ||||
|                 "pool", (void (*)(void *, int))pool_release, s); | ||||
| 
 | ||||
|   bool stopped = s->stopped; | ||||
|   pthread_mutex_unlock(&s->lock); | ||||
| 
 | ||||
|   // Stop the tbuffer so we can return a valid object.
 | ||||
|   // We must stop here because the pool_stop may have already been called,
 | ||||
|   // in which case tbuffer_stop may never be called again.
 | ||||
|   if (stopped) { | ||||
|     tbuffer_stop(tbuf); | ||||
|   } | ||||
|   return tbuf; | ||||
| } | ||||
| 
 | ||||
| PoolQueue* pool_get_queue(Pool *s) { | ||||
|   pthread_mutex_lock(&s->lock); | ||||
| 
 | ||||
|   int i; | ||||
|   for (i = 0; i < POOL_MAX_QUEUES; i++) { | ||||
|     if (!s->queues[i].inited) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   assert(i < POOL_MAX_QUEUES); | ||||
| 
 | ||||
|   PoolQueue *c = &s->queues[i]; | ||||
|   memset(c, 0, sizeof(*c)); | ||||
| 
 | ||||
|   c->pool = s; | ||||
|   c->inited = true; | ||||
| 
 | ||||
|   c->efd = efd_init(); | ||||
|   assert(c->efd >= 0); | ||||
| 
 | ||||
|   c->num_bufs = s->num_bufs; | ||||
|   c->num = c->num_bufs+1; | ||||
|   c->idx = (int*)malloc(sizeof(int)*c->num); | ||||
|   memset(c->idx, -1, sizeof(int)*c->num); | ||||
| 
 | ||||
|   pthread_mutex_init(&c->lock, NULL); | ||||
|   pthread_cond_init(&c->cv, NULL); | ||||
| 
 | ||||
|   pthread_mutex_unlock(&s->lock); | ||||
|   return c; | ||||
| } | ||||
| 
 | ||||
| void pool_release_queue(PoolQueue *c) { | ||||
|   Pool *s = c->pool; | ||||
| 
 | ||||
|   pthread_mutex_lock(&s->lock); | ||||
|   pthread_mutex_lock(&c->lock); | ||||
| 
 | ||||
|   for (int i=0; i<c->num; i++) { | ||||
|     if (c->idx[i] != -1) { | ||||
|       pool_release_locked(s, c->idx[i]); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   close(c->efd); | ||||
|   free(c->idx); | ||||
| 
 | ||||
|   c->inited = false; | ||||
| 
 | ||||
|   pthread_mutex_unlock(&c->lock); | ||||
| 
 | ||||
|   pthread_mutex_destroy(&c->lock); | ||||
|   pthread_cond_destroy(&c->cv); | ||||
| 
 | ||||
|   pthread_mutex_unlock(&s->lock); | ||||
| } | ||||
| 
 | ||||
| int pool_select(Pool *s) { | ||||
|   pthread_mutex_lock(&s->lock); | ||||
| 
 | ||||
|   int i; | ||||
|   for (i=0; i<s->num_bufs; i++) { | ||||
|     if (s->refcnt[i] == 0) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (i >= s->num_bufs) { | ||||
|     // overwrite the oldest
 | ||||
|     // still being using in a queue or tbuffer :/
 | ||||
| 
 | ||||
|     int min_k = 0; | ||||
|     int min_ts = s->ts[0]; | ||||
|     for (int k=1; k<s->num_bufs; k++) { | ||||
|       if (s->ts[k] < min_ts) { | ||||
|         min_ts = s->ts[k]; | ||||
|         min_k = k; | ||||
|       } | ||||
|     } | ||||
|     i = min_k; | ||||
|     printf("pool is full! evicted %d\n", min_k); | ||||
| 
 | ||||
|     // might be really bad if the user is doing pointery stuff
 | ||||
|     if (s->release_cb) { | ||||
|       s->release_cb(s->cb_cookie, min_k); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   s->refcnt[i]++; | ||||
| 
 | ||||
|   s->ts[i] = s->counter; | ||||
|   s->counter++; | ||||
| 
 | ||||
|   pthread_mutex_unlock(&s->lock); | ||||
| 
 | ||||
|   return i; | ||||
| } | ||||
| 
 | ||||
| void pool_push(Pool *s, int idx) { | ||||
|   pthread_mutex_lock(&s->lock); | ||||
| 
 | ||||
|   // printf("push %d head %d tail %d\n", idx, s->head, s->tail);
 | ||||
| 
 | ||||
|   assert(idx >= 0 && idx < s->num_bufs); | ||||
| 
 | ||||
|   s->ts[idx] = s->counter; | ||||
|   s->counter++; | ||||
| 
 | ||||
|   assert(s->refcnt[idx] > 0); | ||||
|   s->refcnt[idx]--; //push is a implcit release
 | ||||
| 
 | ||||
|   int num_tbufs = s->num_tbufs; | ||||
|   s->refcnt[idx] += num_tbufs; | ||||
| 
 | ||||
|   // dispatch pool queues
 | ||||
|   for (int i=0; i<POOL_MAX_QUEUES; i++) { | ||||
|     PoolQueue *c = &s->queues[i]; | ||||
|     if (!c->inited) continue; | ||||
|     
 | ||||
|     pthread_mutex_lock(&c->lock); | ||||
|     if (((c->head+1) % c->num) == c->tail) { | ||||
|       // queue is full. skip for now
 | ||||
|       pthread_mutex_unlock(&c->lock); | ||||
|       continue; | ||||
|     } | ||||
| 
 | ||||
|     s->refcnt[idx]++; | ||||
| 
 | ||||
|     c->idx[c->head] = idx; | ||||
|     c->head = (c->head+1) % c->num; | ||||
|     assert(c->head != c->tail); | ||||
|     pthread_mutex_unlock(&c->lock); | ||||
| 
 | ||||
|     efd_write(c->efd); | ||||
|     pthread_cond_signal(&c->cv); | ||||
|   } | ||||
| 
 | ||||
|   pthread_mutex_unlock(&s->lock); | ||||
| 
 | ||||
|   for (int i=0; i<num_tbufs; i++) { | ||||
|     tbuffer_dispatch(&s->tbufs[i], idx); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| int poolq_pop(PoolQueue *c) { | ||||
|   pthread_mutex_lock(&c->lock); | ||||
| 
 | ||||
|   if (c->stopped) { | ||||
|     pthread_mutex_unlock(&c->lock); | ||||
|     return -1; | ||||
|   } | ||||
| 
 | ||||
|   while (c->head == c->tail) { | ||||
|     pthread_cond_wait(&c->cv, &c->lock); | ||||
| 
 | ||||
|     if (c->stopped) { | ||||
|       pthread_mutex_unlock(&c->lock); | ||||
|       return -1; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // printf("pop head %d tail %d\n", s->head, s->tail);
 | ||||
| 
 | ||||
|   assert(c->head != c->tail); | ||||
| 
 | ||||
|   int r = c->idx[c->tail]; | ||||
|   c->idx[c->tail] = -1; | ||||
|   c->tail = (c->tail+1) % c->num; | ||||
| 
 | ||||
|   // queue event is level triggered
 | ||||
|   if (c->head == c->tail) { | ||||
|     efd_clear(c->efd); | ||||
|   } | ||||
| 
 | ||||
|   // printf("pop %d head %d tail %d\n", r, s->head, s->tail);
 | ||||
| 
 | ||||
|   assert(r >= 0 && r < c->num_bufs); | ||||
| 
 | ||||
|   pthread_mutex_unlock(&c->lock); | ||||
| 
 | ||||
|   return r; | ||||
| } | ||||
| 
 | ||||
| int poolq_efd(PoolQueue *c) { | ||||
|   return c->efd; | ||||
| } | ||||
| 
 | ||||
| void poolq_release(PoolQueue *c, int idx) { | ||||
|   pool_release(c->pool, idx); | ||||
| } | ||||
| 
 | ||||
| void pool_stop(Pool *s) { | ||||
|   for (int i=0; i<s->num_tbufs; i++) { | ||||
|     tbuffer_stop(&s->tbufs[i]); | ||||
|   } | ||||
| 
 | ||||
|   pthread_mutex_lock(&s->lock); | ||||
|   s->stopped = true; | ||||
|   for (int i=0; i<POOL_MAX_QUEUES; i++) { | ||||
|     PoolQueue *c = &s->queues[i]; | ||||
|     if (!c->inited) continue; | ||||
| 
 | ||||
|     pthread_mutex_lock(&c->lock); | ||||
|     c->stopped = true; | ||||
|     pthread_mutex_unlock(&c->lock); | ||||
|     efd_write(c->efd); | ||||
|     pthread_cond_signal(&c->cv); | ||||
|   } | ||||
|   pthread_mutex_unlock(&s->lock); | ||||
| } | ||||
| @ -0,0 +1,123 @@ | ||||
| #ifndef BUFFERING_H | ||||
| #define BUFFERING_H | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <pthread.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| // Tripple buffering helper
 | ||||
| 
 | ||||
| typedef struct TBuffer { | ||||
|     pthread_mutex_t lock; | ||||
|     pthread_cond_t cv; | ||||
|     int efd; | ||||
| 
 | ||||
|     bool* reading; | ||||
|     int pending_idx; | ||||
| 
 | ||||
|     int num_bufs; | ||||
|     const char* name; | ||||
| 
 | ||||
|     void (*release_cb)(void* c, int idx); | ||||
|     void *cb_cookie; | ||||
| 
 | ||||
|     bool stopped; | ||||
| } TBuffer; | ||||
| 
 | ||||
| // num_bufs must be at least the number of buffers that can be acquired simultaniously plus two
 | ||||
| void tbuffer_init(TBuffer *tb, int num_bufs, const char* name); | ||||
| 
 | ||||
| void tbuffer_init2(TBuffer *tb, int num_bufs, const char* name, | ||||
|   void (*release_cb)(void* c, int idx), | ||||
|   void* cb_cookie); | ||||
| 
 | ||||
| // returns an eventfd that signals if a buffer is ready and tbuffer_acquire shouldn't to block.
 | ||||
| // useful to polling on multiple tbuffers.
 | ||||
| int tbuffer_efd(TBuffer *tb); | ||||
| 
 | ||||
| // Chooses a buffer that's not reading or pending
 | ||||
| int tbuffer_select(TBuffer *tb); | ||||
| 
 | ||||
| // Called when the writer is done with their buffer
 | ||||
| //  - Wakes up the reader if it's waiting
 | ||||
| //  - releases the pending buffer if the reader's too slow
 | ||||
| void tbuffer_dispatch(TBuffer *tb, int idx); | ||||
| 
 | ||||
| // Called when the reader wants a new buffer, will return -1 when stopped
 | ||||
| int tbuffer_acquire(TBuffer *tb); | ||||
| 
 | ||||
| // Called when the reader is done with their buffer
 | ||||
| void tbuffer_release(TBuffer *tb, int idx); | ||||
| 
 | ||||
| void tbuffer_release_all(TBuffer *tb); | ||||
| 
 | ||||
| void tbuffer_stop(TBuffer *tb); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| // pool: buffer pool + queue thing...
 | ||||
| 
 | ||||
| #define POOL_MAX_TBUFS 8 | ||||
| #define POOL_MAX_QUEUES 8 | ||||
| 
 | ||||
| typedef struct Pool Pool; | ||||
| 
 | ||||
| typedef struct PoolQueue { | ||||
|   pthread_mutex_t lock; | ||||
|   pthread_cond_t cv; | ||||
|   Pool* pool; | ||||
|   bool inited; | ||||
|   bool stopped; | ||||
|   int efd; | ||||
|   int num_bufs; | ||||
|   int num; | ||||
|   int head, tail; | ||||
|   int* idx; | ||||
| } PoolQueue; | ||||
| 
 | ||||
| int poolq_pop(PoolQueue *s); | ||||
| int poolq_efd(PoolQueue *s); | ||||
| void poolq_release(PoolQueue *c, int idx); | ||||
| 
 | ||||
| typedef struct Pool { | ||||
|   pthread_mutex_t lock; | ||||
|   bool stopped; | ||||
|   int num_bufs; | ||||
|   int counter; | ||||
| 
 | ||||
|   int* ts; | ||||
|   int* refcnt; | ||||
| 
 | ||||
|   void (*release_cb)(void* c, int idx); | ||||
|   void *cb_cookie; | ||||
| 
 | ||||
|   int num_tbufs; | ||||
|   TBuffer tbufs[POOL_MAX_TBUFS]; | ||||
|   PoolQueue queues[POOL_MAX_QUEUES]; | ||||
| } Pool; | ||||
| 
 | ||||
| void pool_init(Pool *s, int num_bufs); | ||||
| void pool_init2(Pool *s, int num_bufs, | ||||
|   void (*release_cb)(void* c, int idx), void* cb_cookie); | ||||
| 
 | ||||
| TBuffer* pool_get_tbuffer(Pool *s); | ||||
| 
 | ||||
| PoolQueue* pool_get_queue(Pool *s); | ||||
| void pool_release_queue(PoolQueue *q); | ||||
| 
 | ||||
| int pool_select(Pool *s); | ||||
| void pool_push(Pool *s, int idx); | ||||
| void pool_acquire(Pool *s, int idx); | ||||
| void pool_release(Pool *s, int idx); | ||||
| void pool_stop(Pool *s); | ||||
| 
 | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| }  // extern "C"
 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,418 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdbool.h> | ||||
| #include <assert.h> | ||||
| #include <string.h> | ||||
| #include <unistd.h> | ||||
| #include <inttypes.h> | ||||
| #include <sys/stat.h> | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
| #include <OpenCL/cl.h> | ||||
| #else | ||||
| #include <CL/cl.h> | ||||
| #endif | ||||
| 
 | ||||
| #include "common/util.h" | ||||
| 
 | ||||
| #include "clutil.h" | ||||
| 
 | ||||
| typedef struct CLUProgramIndex { | ||||
|   uint64_t index_hash; | ||||
|   const uint8_t* bin_data; | ||||
|   const uint8_t* bin_end; | ||||
| } CLUProgramIndex; | ||||
| 
 | ||||
| #ifdef CLU_NO_SRC | ||||
| #include "clcache_bins.h" | ||||
| #else | ||||
| static const CLUProgramIndex clu_index[] = {}; | ||||
| #endif | ||||
| 
 | ||||
| void clu_init(void) { | ||||
| #ifndef CLU_NO_SRC | ||||
|   mkdir("/tmp/clcache", 0777); | ||||
|   unlink("/tmp/clcache/index.cli"); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| cl_program cl_create_program_from_file(cl_context ctx, const char* path) { | ||||
|   char* src_buf = read_file(path, NULL); | ||||
|   assert(src_buf); | ||||
| 
 | ||||
|   int err = 0; | ||||
|   cl_program ret = clCreateProgramWithSource(ctx, 1, (const char**)&src_buf, NULL, &err); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   free(src_buf); | ||||
| 
 | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| static char* get_version_string(cl_platform_id platform) { | ||||
|   size_t size = 0; | ||||
|   int err; | ||||
|   err = clGetPlatformInfo(platform, CL_PLATFORM_VERSION, 0, NULL, &size); | ||||
|   assert(err == 0); | ||||
|   char *str = malloc(size); | ||||
|   assert(str); | ||||
|   err = clGetPlatformInfo(platform, CL_PLATFORM_VERSION, size, str, NULL); | ||||
|   assert(err == 0); | ||||
|   return str; | ||||
| } | ||||
| 
 | ||||
| void cl_print_info(cl_platform_id platform, cl_device_id device) { | ||||
|   char str[4096]; | ||||
| 
 | ||||
|   clGetPlatformInfo(platform, CL_PLATFORM_VENDOR, sizeof(str), str, NULL); | ||||
|   printf("vendor: '%s'\n", str); | ||||
| 
 | ||||
|   char* version = get_version_string(platform); | ||||
|   printf("platform version: '%s'\n", version); | ||||
|   free(version); | ||||
| 
 | ||||
|   clGetPlatformInfo(platform, CL_PLATFORM_PROFILE, sizeof(str), str, NULL); | ||||
|   printf("profile: '%s'\n", str); | ||||
| 
 | ||||
|   clGetPlatformInfo(platform, CL_PLATFORM_EXTENSIONS, sizeof(str), str, NULL); | ||||
|   printf("extensions: '%s'\n", str); | ||||
| 
 | ||||
|   clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(str), str, NULL); | ||||
|   printf("name: '%s'\n", str); | ||||
| 
 | ||||
|   clGetDeviceInfo(device, CL_DEVICE_VERSION, sizeof(str), str, NULL); | ||||
|   printf("device version: '%s'\n", str); | ||||
| 
 | ||||
|   size_t sz; | ||||
|   clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(sz), &sz, NULL); | ||||
|   printf("max work group size: %u\n", sz); | ||||
| 
 | ||||
|   cl_device_type type; | ||||
|   clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(type), &type, NULL); | ||||
|   printf("type = 0x%04x = ", (unsigned int)type); | ||||
|   switch(type) { | ||||
|   case CL_DEVICE_TYPE_CPU: | ||||
|     printf("CL_DEVICE_TYPE_CPU\n"); | ||||
|     break; | ||||
|   case CL_DEVICE_TYPE_GPU: | ||||
|     printf("CL_DEVICE_TYPE_GPU\n"); | ||||
|     break; | ||||
|   case CL_DEVICE_TYPE_ACCELERATOR: | ||||
|     printf("CL_DEVICE_TYPE_ACCELERATOR\n"); | ||||
|     break; | ||||
|   default: | ||||
|     printf("Other...\n" ); | ||||
|     break; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void cl_print_build_errors(cl_program program, cl_device_id device) { | ||||
|   cl_build_status status; | ||||
|   clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, | ||||
|           sizeof(cl_build_status), &status, NULL); | ||||
| 
 | ||||
|   size_t log_size; | ||||
|   clGetProgramBuildInfo(program, device, | ||||
|           CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); | ||||
| 
 | ||||
|   char* log = calloc(log_size+1, 1); | ||||
|   assert(log); | ||||
| 
 | ||||
|   clGetProgramBuildInfo(program, device, | ||||
|           CL_PROGRAM_BUILD_LOG, log_size+1, log, NULL); | ||||
| 
 | ||||
|   printf("build failed; status=%d, log:\n%s\n", | ||||
|           status, log); | ||||
| 
 | ||||
|   free(log); | ||||
| } | ||||
| 
 | ||||
| uint64_t clu_index_hash(const char* s) { | ||||
|   size_t sl = strlen(s); | ||||
|   assert(sl < 128); | ||||
|   uint64_t x = 0; | ||||
|   for (int i=127; i>=0; i--) { | ||||
|     x *= 65599ULL; | ||||
|     x += (uint8_t)s[i<sl ? sl-1-i : sl]; | ||||
|   } | ||||
|   return x ^ (x >> 32); | ||||
| } | ||||
| 
 | ||||
| uint64_t clu_fnv_hash(const uint8_t *data, size_t len) { | ||||
|   /* 64 bit Fowler/Noll/Vo FNV-1a hash code */ | ||||
|   uint64_t hval = 0xcbf29ce484222325ULL; | ||||
|   const uint8_t *dp = data; | ||||
|   const uint8_t *de = data + len; | ||||
|   while (dp < de) { | ||||
|     hval ^= (uint64_t) *dp++; | ||||
|     hval += (hval << 1) + (hval << 4) + (hval << 5) + | ||||
|         (hval << 7) + (hval << 8) + (hval << 40); | ||||
|   } | ||||
| 
 | ||||
|   return hval; | ||||
| } | ||||
| 
 | ||||
| cl_program cl_cached_program_from_hash(cl_context ctx, cl_device_id device_id, uint64_t hash) { | ||||
|   int err; | ||||
| 
 | ||||
|   char cache_path[1024]; | ||||
|   snprintf(cache_path, sizeof(cache_path), "/tmp/clcache/%016" PRIx64 ".clb", hash); | ||||
| 
 | ||||
|   size_t bin_size; | ||||
|   uint8_t *bin = read_file(cache_path, &bin_size); | ||||
|   if (!bin) { | ||||
|     return NULL; | ||||
|   } | ||||
| 
 | ||||
|   cl_program prg = clCreateProgramWithBinary(ctx, 1, &device_id, &bin_size, (const uint8_t**)&bin, NULL, &err); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   free(bin); | ||||
| 
 | ||||
|   err = clBuildProgram(prg, 1, &device_id, NULL, NULL, NULL); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   return prg; | ||||
| } | ||||
| 
 | ||||
| static uint8_t* get_program_binary(cl_program prg, size_t *out_size) { | ||||
|   int err; | ||||
| 
 | ||||
|   cl_uint num_devices; | ||||
|   err = clGetProgramInfo(prg, CL_PROGRAM_NUM_DEVICES, sizeof(num_devices), &num_devices, NULL); | ||||
|   assert(err == 0); | ||||
|   assert(num_devices == 1); | ||||
| 
 | ||||
|   size_t binary_size = 0; | ||||
|   err = clGetProgramInfo(prg, CL_PROGRAM_BINARY_SIZES, sizeof(binary_size), &binary_size, NULL); | ||||
|   assert(err == 0); | ||||
|   assert(binary_size > 0); | ||||
| 
 | ||||
|   uint8_t *binary_buf = malloc(binary_size); | ||||
|   assert(binary_buf); | ||||
| 
 | ||||
|   uint8_t* bufs[1] = { binary_buf, }; | ||||
| 
 | ||||
|   err = clGetProgramInfo(prg, CL_PROGRAM_BINARIES, sizeof(bufs), &bufs, NULL); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   *out_size = binary_size; | ||||
|   return binary_buf; | ||||
| } | ||||
| 
 | ||||
| cl_program cl_cached_program_from_string(cl_context ctx, cl_device_id device_id, | ||||
|                                          const char* src, const char* args, | ||||
|                                          uint64_t *out_hash) { | ||||
|   int err; | ||||
| 
 | ||||
|   cl_platform_id platform; | ||||
|   err = clGetDeviceInfo(device_id, CL_DEVICE_PLATFORM, sizeof(platform), &platform, NULL); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   const char* platform_version = get_version_string(platform); | ||||
| 
 | ||||
|   const size_t hash_len = strlen(platform_version)+1+strlen(src)+1+strlen(args)+1; | ||||
|   char* hash_buf = malloc(hash_len); | ||||
|   assert(hash_buf); | ||||
|   memset(hash_buf, 0, hash_len); | ||||
|   snprintf(hash_buf, hash_len, "%s%c%s%c%s", platform_version, 1, src, 1, args); | ||||
|   free((void*)platform_version); | ||||
| 
 | ||||
|   uint64_t hash = clu_fnv_hash((uint8_t*)hash_buf, hash_len); | ||||
|   free(hash_buf); | ||||
| 
 | ||||
|   cl_program prg = NULL; | ||||
| #ifndef CLU_NO_CACHE | ||||
|   prg = cl_cached_program_from_hash(ctx, device_id, hash); | ||||
| #endif | ||||
|   if (prg == NULL) { | ||||
|     prg = clCreateProgramWithSource(ctx, 1, (const char**)&src, NULL, &err); | ||||
|     assert(err == 0); | ||||
| 
 | ||||
|     err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); | ||||
|     if (err != 0) { | ||||
|       cl_print_build_errors(prg, device_id); | ||||
|     } | ||||
|     assert(err == 0); | ||||
| 
 | ||||
| #ifndef CLU_NO_CACHE | ||||
|     // write program binary to cache
 | ||||
| 
 | ||||
|     size_t binary_size; | ||||
|     uint8_t *binary_buf = get_program_binary(prg, &binary_size); | ||||
| 
 | ||||
|     char cache_path[1024]; | ||||
|     snprintf(cache_path, sizeof(cache_path), "/tmp/clcache/%016" PRIx64 ".clb", hash); | ||||
|     FILE* of = fopen(cache_path, "wb"); | ||||
|     assert(of); | ||||
|     fwrite(binary_buf, 1, binary_size, of); | ||||
|     fclose(of); | ||||
| 
 | ||||
|     free(binary_buf); | ||||
| #endif | ||||
|   } | ||||
| 
 | ||||
|   if (out_hash) *out_hash = hash; | ||||
|   return prg; | ||||
| } | ||||
| 
 | ||||
| cl_program cl_cached_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args, | ||||
|                                        uint64_t *out_hash) { | ||||
|   char* src_buf = read_file(path, NULL); | ||||
|   assert(src_buf); | ||||
|   cl_program ret = cl_cached_program_from_string(ctx, device_id, src_buf, args, out_hash); | ||||
|   free(src_buf); | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| static void add_index(uint64_t index_hash, uint64_t src_hash) { | ||||
|   FILE *f = fopen("/tmp/clcache/index.cli", "a"); | ||||
|   assert(f); | ||||
|   fprintf(f, "%016" PRIx64 " %016" PRIx64 "\n", index_hash, src_hash); | ||||
|   fclose(f); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| cl_program cl_program_from_index(cl_context ctx, cl_device_id device_id, uint64_t index_hash) { | ||||
|   int err; | ||||
| 
 | ||||
|   int i; | ||||
|   for (i=0; i<ARRAYSIZE(clu_index); i++) { | ||||
|     if (clu_index[i].index_hash == index_hash) { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   if (i >= ARRAYSIZE(clu_index)) { | ||||
|     assert(false); | ||||
|   } | ||||
| 
 | ||||
|   size_t bin_size = clu_index[i].bin_end - clu_index[i].bin_data; | ||||
|   const uint8_t *bin_data = clu_index[i].bin_data; | ||||
| 
 | ||||
|   cl_program prg = clCreateProgramWithBinary(ctx, 1, &device_id, &bin_size, (const uint8_t**)&bin_data, NULL, &err); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   err = clBuildProgram(prg, 1, &device_id, NULL, NULL, NULL); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   return prg; | ||||
| } | ||||
| 
 | ||||
| cl_program cl_index_program_from_string(cl_context ctx, cl_device_id device_id, | ||||
|                                         const char* src, const char* args, | ||||
|                                         uint64_t index_hash) { | ||||
|   uint64_t src_hash = 0; | ||||
|   cl_program ret = cl_cached_program_from_string(ctx, device_id, src, args, &src_hash); | ||||
| #ifndef CLU_NO_CACHE | ||||
|   add_index(index_hash, src_hash); | ||||
| #endif | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| cl_program cl_index_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args, | ||||
|                                       uint64_t index_hash) { | ||||
|   uint64_t src_hash = 0; | ||||
|   cl_program ret = cl_cached_program_from_file(ctx, device_id, path, args, &src_hash); | ||||
| #ifndef CLU_NO_CACHE | ||||
|   add_index(index_hash, src_hash); | ||||
| #endif | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * Given a cl code and return a string represenation | ||||
|  */ | ||||
| const char* cl_get_error_string(int err) { | ||||
|     switch (err) { | ||||
|         case 0: return "CL_SUCCESS"; | ||||
|         case -1: return "CL_DEVICE_NOT_FOUND"; | ||||
|         case -2: return "CL_DEVICE_NOT_AVAILABLE"; | ||||
|         case -3: return "CL_COMPILER_NOT_AVAILABLE"; | ||||
|         case -4: return "CL_MEM_OBJECT_ALLOCATION_FAILURE"; | ||||
|         case -5: return "CL_OUT_OF_RESOURCES"; | ||||
|         case -6: return "CL_OUT_OF_HOST_MEMORY"; | ||||
|         case -7: return "CL_PROFILING_INFO_NOT_AVAILABLE"; | ||||
|         case -8: return "CL_MEM_COPY_OVERLAP"; | ||||
|         case -9: return "CL_IMAGE_FORMAT_MISMATCH"; | ||||
|         case -10: return "CL_IMAGE_FORMAT_NOT_SUPPORTED"; | ||||
|         case -12: return "CL_MAP_FAILURE"; | ||||
|         case -13: return "CL_MISALIGNED_SUB_BUFFER_OFFSET"; | ||||
|         case -14: return "CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST"; | ||||
|         case -15: return "CL_COMPILE_PROGRAM_FAILURE"; | ||||
|         case -16: return "CL_LINKER_NOT_AVAILABLE"; | ||||
|         case -17: return "CL_LINK_PROGRAM_FAILURE"; | ||||
|         case -18: return "CL_DEVICE_PARTITION_FAILED"; | ||||
|         case -19: return "CL_KERNEL_ARG_INFO_NOT_AVAILABLE"; | ||||
|         case -30: return "CL_INVALID_VALUE"; | ||||
|         case -31: return "CL_INVALID_DEVICE_TYPE"; | ||||
|         case -32: return "CL_INVALID_PLATFORM"; | ||||
|         case -33: return "CL_INVALID_DEVICE"; | ||||
|         case -34: return "CL_INVALID_CONTEXT"; | ||||
|         case -35: return "CL_INVALID_QUEUE_PROPERTIES"; | ||||
|         case -36: return "CL_INVALID_COMMAND_QUEUE"; | ||||
|         case -37: return "CL_INVALID_HOST_PTR"; | ||||
|         case -38: return "CL_INVALID_MEM_OBJECT"; | ||||
|         case -39: return "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR"; | ||||
|         case -40: return "CL_INVALID_IMAGE_SIZE"; | ||||
|         case -41: return "CL_INVALID_SAMPLER"; | ||||
|         case -42: return "CL_INVALID_BINARY"; | ||||
|         case -43: return "CL_INVALID_BUILD_OPTIONS"; | ||||
|         case -44: return "CL_INVALID_PROGRAM"; | ||||
|         case -45: return "CL_INVALID_PROGRAM_EXECUTABLE"; | ||||
|         case -46: return "CL_INVALID_KERNEL_NAME"; | ||||
|         case -47: return "CL_INVALID_KERNEL_DEFINITION"; | ||||
|         case -48: return "CL_INVALID_KERNEL"; | ||||
|         case -49: return "CL_INVALID_ARG_INDEX"; | ||||
|         case -50: return "CL_INVALID_ARG_VALUE"; | ||||
|         case -51: return "CL_INVALID_ARG_SIZE"; | ||||
|         case -52: return "CL_INVALID_KERNEL_ARGS"; | ||||
|         case -53: return "CL_INVALID_WORK_DIMENSION"; | ||||
|         case -54: return "CL_INVALID_WORK_GROUP_SIZE"; | ||||
|         case -55: return "CL_INVALID_WORK_ITEM_SIZE"; | ||||
|         case -56: return "CL_INVALID_GLOBAL_OFFSET"; | ||||
|         case -57: return "CL_INVALID_EVENT_WAIT_LIST"; | ||||
|         case -58: return "CL_INVALID_EVENT"; | ||||
|         case -59: return "CL_INVALID_OPERATION"; | ||||
|         case -60: return "CL_INVALID_GL_OBJECT"; | ||||
|         case -61: return "CL_INVALID_BUFFER_SIZE"; | ||||
|         case -62: return "CL_INVALID_MIP_LEVEL"; | ||||
|         case -63: return "CL_INVALID_GLOBAL_WORK_SIZE"; | ||||
|         case -64: return "CL_INVALID_PROPERTY"; | ||||
|         case -65: return "CL_INVALID_IMAGE_DESCRIPTOR"; | ||||
|         case -66: return "CL_INVALID_COMPILER_OPTIONS"; | ||||
|         case -67: return "CL_INVALID_LINKER_OPTIONS"; | ||||
|         case -68: return "CL_INVALID_DEVICE_PARTITION_COUNT"; | ||||
|         case -69: return "CL_INVALID_PIPE_SIZE"; | ||||
|         case -70: return "CL_INVALID_DEVICE_QUEUE"; | ||||
|         case -71: return "CL_INVALID_SPEC_ID"; | ||||
|         case -72: return "CL_MAX_SIZE_RESTRICTION_EXCEEDED"; | ||||
|         case -1002: return "CL_INVALID_D3D10_DEVICE_KHR"; | ||||
|         case -1003: return "CL_INVALID_D3D10_RESOURCE_KHR"; | ||||
|         case -1004: return "CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR"; | ||||
|         case -1005: return "CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR"; | ||||
|         case -1006: return "CL_INVALID_D3D11_DEVICE_KHR"; | ||||
|         case -1007: return "CL_INVALID_D3D11_RESOURCE_KHR"; | ||||
|         case -1008: return "CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR"; | ||||
|         case -1009: return "CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR"; | ||||
|         case -1010: return "CL_INVALID_DX9_MEDIA_ADAPTER_KHR"; | ||||
|         case -1011: return "CL_INVALID_DX9_MEDIA_SURFACE_KHR"; | ||||
|         case -1012: return "CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR"; | ||||
|         case -1013: return "CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR"; | ||||
|         case -1093: return "CL_INVALID_EGL_OBJECT_KHR"; | ||||
|         case -1092: return "CL_EGL_RESOURCE_NOT_ACQUIRED_KHR"; | ||||
|         case -1001: return "CL_PLATFORM_NOT_FOUND_KHR"; | ||||
|         case -1057: return "CL_DEVICE_PARTITION_FAILED_EXT"; | ||||
|         case -1058: return "CL_INVALID_PARTITION_COUNT_EXT"; | ||||
|         case -1059: return "CL_INVALID_PARTITION_NAME_EXT"; | ||||
|         case -1094: return "CL_INVALID_ACCELERATOR_INTEL"; | ||||
|         case -1095: return "CL_INVALID_ACCELERATOR_TYPE_INTEL"; | ||||
|         case -1096: return "CL_INVALID_ACCELERATOR_DESCRIPTOR_INTEL"; | ||||
|         case -1097: return "CL_ACCELERATOR_TYPE_NOT_SUPPORTED_INTEL"; | ||||
|         case -1000: return "CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR"; | ||||
|         case -1098: return "CL_INVALID_VA_API_MEDIA_ADAPTER_INTEL"; | ||||
|         case -1099: return "CL_INVALID_VA_API_MEDIA_SURFACE_INTEL"; | ||||
|         case -1100: return "CL_VA_API_MEDIA_SURFACE_ALREADY_ACQUIRED_INTEL"; | ||||
|         case -1101: return "CL_VA_API_MEDIA_SURFACE_NOT_ACQUIRED_INTEL"; | ||||
|         default: return "CL_UNKNOWN_ERROR"; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,87 @@ | ||||
| #ifndef CLUTIL_H | ||||
| #define CLUTIL_H | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
| #include <OpenCL/cl.h> | ||||
| #else | ||||
| #include <CL/cl.h> | ||||
| #endif | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void clu_init(void); | ||||
| 
 | ||||
| cl_program cl_create_program_from_file(cl_context ctx, const char* path); | ||||
| void cl_print_info(cl_platform_id platform, cl_device_id device); | ||||
| void cl_print_build_errors(cl_program program, cl_device_id device); | ||||
| void cl_print_build_errors(cl_program program, cl_device_id device); | ||||
| 
 | ||||
| cl_program cl_cached_program_from_hash(cl_context ctx, cl_device_id device_id, uint64_t hash); | ||||
| cl_program cl_cached_program_from_string(cl_context ctx, cl_device_id device_id, | ||||
|                                          const char* src, const char* args, | ||||
|                                          uint64_t *out_hash); | ||||
| cl_program cl_cached_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args, | ||||
|                                        uint64_t *out_hash); | ||||
| 
 | ||||
| cl_program cl_program_from_index(cl_context ctx, cl_device_id device_id, uint64_t index_hash); | ||||
| 
 | ||||
| cl_program cl_index_program_from_string(cl_context ctx, cl_device_id device_id, | ||||
|                                         const char* src, const char* args, | ||||
|                                         uint64_t index_hash); | ||||
| cl_program cl_index_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args, | ||||
|                                       uint64_t index_hash); | ||||
| 
 | ||||
| uint64_t clu_index_hash(const char *s); | ||||
| uint64_t clu_fnv_hash(const uint8_t *data, size_t len); | ||||
| 
 | ||||
| const char* cl_get_error_string(int err); | ||||
| 
 | ||||
| static inline int cl_check_error(int err) { | ||||
|   if (err != 0) { | ||||
|     fprintf(stderr, "%s\n", cl_get_error_string(err)); | ||||
|     exit(1); | ||||
|   } | ||||
|   return err; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // // string hash macro. compiler, I'm so sorry.
 | ||||
| #define CLU_H1(s,i,x)   (x*65599ULL+(uint8_t)s[(i)<strlen(s)?strlen(s)-1-(i):strlen(s)]) | ||||
| #define CLU_H4(s,i,x)   CLU_H1(s,i,CLU_H1(s,i+1,CLU_H1(s,i+2,CLU_H1(s,i+3,x)))) | ||||
| #define CLU_H16(s,i,x)  CLU_H4(s,i,CLU_H4(s,i+4,CLU_H4(s,i+8,CLU_H4(s,i+12,x)))) | ||||
| #define CLU_H64(s,i,x)  CLU_H16(s,i,CLU_H16(s,i+16,CLU_H16(s,i+32,CLU_H16(s,i+48,x)))) | ||||
| // #define CLU_H256(s,i,x) CLU_H64(s,i,CLU_H64(s,i+64,CLU_H64(s,i+128,CLU_H64(s,i+192,x))))
 | ||||
| #define CLU_H128(s,i,x) CLU_H64(s,i,CLU_H64(s,i+64,x)) | ||||
| #define CLU_HASH(s)    ((uint64_t)(CLU_H128(s,0,0)^(CLU_H128(s,0,0)>>32))) | ||||
| 
 | ||||
| #define CLU_STRINGIFY(x) #x | ||||
| #define CLU_STRINGIFY2(x) CLU_STRINGIFY(x) | ||||
| #define CLU_LINESTR CLU_STRINGIFY2(__LINE__) | ||||
| 
 | ||||
| #ifdef CLU_NO_SRC | ||||
| 
 | ||||
|  #define CLU_LOAD_FROM_STRING(ctx, device_id, src, args) \ | ||||
|   cl_program_from_index(ctx, device_id, CLU_HASH("\1" __FILE__ "\1" CLU_LINESTR) ^ clu_fnv_hash((const uint8_t*)__func__, strlen(__func__)) ^ clu_fnv_hash((const uint8_t*)args, strlen(args))) | ||||
|  #define CLU_LOAD_FROM_FILE(ctx, device_id, path, args) \ | ||||
|   cl_program_from_index(ctx, device_id, CLU_HASH("\2" path) ^ clu_fnv_hash((const uint8_t*)args, strlen(args))) | ||||
| 
 | ||||
| #else | ||||
| 
 | ||||
|  #define CLU_LOAD_FROM_STRING(ctx, device_id, src, args) \ | ||||
|   cl_index_program_from_string(ctx, device_id, src, args, clu_index_hash("\1" __FILE__ "\1" CLU_LINESTR) ^ clu_fnv_hash((const uint8_t*)__func__, strlen(__func__)) ^ clu_fnv_hash((const uint8_t*)args, strlen(args))) | ||||
|  #define CLU_LOAD_FROM_FILE(ctx, device_id, path, args) \ | ||||
|   cl_index_program_from_file(ctx, device_id, path, args, clu_index_hash("\2" path) ^ clu_fnv_hash((const uint8_t*)args, strlen(args))) | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,52 @@ | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <assert.h> | ||||
| 
 | ||||
| #include "cqueue.h" | ||||
| 
 | ||||
| void queue_init(Queue *q) { | ||||
|   memset(q, 0, sizeof(*q)); | ||||
|   TAILQ_INIT(&q->q); | ||||
|   pthread_mutex_init(&q->lock, NULL); | ||||
|   pthread_cond_init(&q->cv, NULL); | ||||
| } | ||||
| 
 | ||||
| void* queue_pop(Queue *q) { | ||||
|   pthread_mutex_lock(&q->lock); | ||||
|   while (TAILQ_EMPTY(&q->q)) { | ||||
|     pthread_cond_wait(&q->cv, &q->lock); | ||||
|   } | ||||
|   QueueEntry *entry = TAILQ_FIRST(&q->q); | ||||
|   TAILQ_REMOVE(&q->q, entry, entries); | ||||
|   pthread_mutex_unlock(&q->lock); | ||||
| 
 | ||||
|   void* r = entry->data; | ||||
|   free(entry); | ||||
|   return r; | ||||
| } | ||||
| 
 | ||||
| void* queue_try_pop(Queue *q) { | ||||
|   pthread_mutex_lock(&q->lock); | ||||
| 
 | ||||
|   void* r = NULL; | ||||
|   if (!TAILQ_EMPTY(&q->q)) { | ||||
|     QueueEntry *entry = TAILQ_FIRST(&q->q); | ||||
|     TAILQ_REMOVE(&q->q, entry, entries); | ||||
|     r = entry->data; | ||||
|     free(entry); | ||||
|   } | ||||
| 
 | ||||
|   pthread_mutex_unlock(&q->lock); | ||||
|   return r; | ||||
| } | ||||
| 
 | ||||
| void queue_push(Queue *q, void *data) { | ||||
|   QueueEntry *entry = calloc(1, sizeof(QueueEntry)); | ||||
|   assert(entry); | ||||
|   entry->data = data; | ||||
| 
 | ||||
|   pthread_mutex_lock(&q->lock); | ||||
|   TAILQ_INSERT_TAIL(&q->q, entry, entries); | ||||
|   pthread_cond_signal(&q->cv); | ||||
|   pthread_mutex_unlock(&q->lock); | ||||
| } | ||||
| @ -0,0 +1,33 @@ | ||||
| #ifndef COMMON_CQUEUE_H | ||||
| #define COMMON_CQUEUE_H | ||||
| 
 | ||||
| #include <sys/queue.h> | ||||
| #include <pthread.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| // a blocking queue
 | ||||
| 
 | ||||
| typedef struct QueueEntry { | ||||
|   TAILQ_ENTRY(QueueEntry) entries; | ||||
|   void *data; | ||||
| } QueueEntry; | ||||
| 
 | ||||
| typedef struct Queue { | ||||
|   TAILQ_HEAD(queue, QueueEntry) q; | ||||
|   pthread_mutex_t lock; | ||||
|   pthread_cond_t cv; | ||||
| } Queue; | ||||
| 
 | ||||
| void queue_init(Queue *q); | ||||
| void* queue_pop(Queue *q); | ||||
| void* queue_try_pop(Queue *q); | ||||
| void queue_push(Queue *q, void *data); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| }  // extern "C"
 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,56 @@ | ||||
| #include <stdlib.h> | ||||
| #include <assert.h> | ||||
| 
 | ||||
| #ifdef __linux__ | ||||
| #include <sys/eventfd.h> | ||||
| #else | ||||
| #include <sys/time.h> | ||||
| #include <sys/event.h> | ||||
| #define EVENT_IDENT 42 | ||||
| #endif | ||||
| 
 | ||||
| #include "efd.h" | ||||
| 
 | ||||
| 
 | ||||
| int efd_init() { | ||||
| #ifdef __linux__ | ||||
|   return eventfd(0, EFD_CLOEXEC); | ||||
| #else | ||||
|   int fd = kqueue(); | ||||
|   assert(fd >= 0); | ||||
| 
 | ||||
|   struct kevent kev; | ||||
|   EV_SET(&kev, EVENT_IDENT, EVFILT_USER, EV_ADD | EV_CLEAR, 0, 0, NULL); | ||||
| 
 | ||||
|   struct timespec timeout = {0, 0}; | ||||
|   int err = kevent(fd, &kev, 1, NULL, 0, &timeout); | ||||
|   assert(err != -1); | ||||
| 
 | ||||
|   return fd; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void efd_write(int fd) { | ||||
| #ifdef __linux__ | ||||
|   eventfd_write(fd, 1); | ||||
| #else | ||||
|   struct kevent kev; | ||||
|   EV_SET(&kev, EVENT_IDENT, EVFILT_USER, 0, NOTE_TRIGGER, 0, NULL); | ||||
| 
 | ||||
|   struct timespec timeout = {0, 0}; | ||||
|   int err = kevent(fd, &kev, 1, NULL, 0, &timeout); | ||||
|   assert(err != -1); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void efd_clear(int fd) { | ||||
| #ifdef __linux__ | ||||
|   eventfd_t efd_cnt; | ||||
|   eventfd_read(fd, &efd_cnt); | ||||
| #else | ||||
|   struct kevent kev; | ||||
|   struct timespec timeout = {0, 0}; | ||||
|   int nfds = kevent(fd, NULL, 0, &kev, 1, &timeout); | ||||
|   assert(nfds != -1); | ||||
| #endif | ||||
| } | ||||
| @ -0,0 +1,17 @@ | ||||
| #ifndef EFD_H | ||||
| #define EFD_H | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| // event fd: a semaphore that can be poll()'d
 | ||||
| int efd_init(); | ||||
| void efd_write(int fd); | ||||
| void efd_clear(int fd); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,142 @@ | ||||
| #include <cstdio> | ||||
| #include <cstdlib> | ||||
| #include <cassert> | ||||
| 
 | ||||
| #include <ui/DisplayInfo.h> | ||||
| 
 | ||||
| #include <gui/ISurfaceComposer.h> | ||||
| #include <gui/Surface.h> | ||||
| #include <gui/SurfaceComposerClient.h> | ||||
| 
 | ||||
| 
 | ||||
| #include <GLES2/gl2.h> | ||||
| #include <EGL/eglext.h> | ||||
| 
 | ||||
| #define BACKLIGHT_CONTROL "/sys/class/leds/lcd-backlight/brightness" | ||||
| #define BACKLIGHT_LEVEL "205" | ||||
| 
 | ||||
| using namespace android; | ||||
| 
 | ||||
| struct FramebufferState { | ||||
|     sp<SurfaceComposerClient> session; | ||||
|     sp<IBinder> dtoken; | ||||
|     DisplayInfo dinfo; | ||||
|     sp<SurfaceControl> control; | ||||
| 
 | ||||
|     sp<Surface> s; | ||||
|     EGLDisplay display; | ||||
| 
 | ||||
|     EGLint egl_major, egl_minor; | ||||
|     EGLConfig config; | ||||
|     EGLSurface surface; | ||||
|     EGLContext context; | ||||
| }; | ||||
| 
 | ||||
| extern "C" void framebuffer_set_power(FramebufferState *s, int mode) { | ||||
|   SurfaceComposerClient::setDisplayPowerMode(s->dtoken, mode); | ||||
| } | ||||
| 
 | ||||
| extern "C" FramebufferState* framebuffer_init( | ||||
|     const char* name, int32_t layer, int alpha, | ||||
|     int *out_w, int *out_h) { | ||||
|   status_t status; | ||||
|   int success; | ||||
| 
 | ||||
|   FramebufferState *s = new FramebufferState; | ||||
| 
 | ||||
|   s->session = new SurfaceComposerClient(); | ||||
|   assert(s->session != NULL); | ||||
| 
 | ||||
|   s->dtoken = SurfaceComposerClient::getBuiltInDisplay( | ||||
|                 ISurfaceComposer::eDisplayIdMain); | ||||
|   assert(s->dtoken != NULL); | ||||
| 
 | ||||
|   status = SurfaceComposerClient::getDisplayInfo(s->dtoken, &s->dinfo); | ||||
|   assert(status == 0); | ||||
| 
 | ||||
|   //int orientation = 3; // rotate framebuffer 270 degrees
 | ||||
|   int orientation = 1; // rotate framebuffer 90 degrees
 | ||||
|   if(orientation == 1 || orientation == 3) { | ||||
|       int temp = s->dinfo.h; | ||||
|       s->dinfo.h = s->dinfo.w; | ||||
|       s->dinfo.w = temp; | ||||
|   } | ||||
| 
 | ||||
|   printf("dinfo %dx%d\n", s->dinfo.w, s->dinfo.h); | ||||
| 
 | ||||
|   Rect destRect(s->dinfo.w, s->dinfo.h); | ||||
|   s->session->setDisplayProjection(s->dtoken, orientation, destRect, destRect); | ||||
| 
 | ||||
|   s->control = s->session->createSurface(String8(name), | ||||
|                   s->dinfo.w, s->dinfo.h, PIXEL_FORMAT_RGBX_8888); | ||||
|   assert(s->control != NULL); | ||||
| 
 | ||||
|   SurfaceComposerClient::openGlobalTransaction(); | ||||
|   status = s->control->setLayer(layer); | ||||
|   SurfaceComposerClient::closeGlobalTransaction(); | ||||
|   assert(status == 0); | ||||
| 
 | ||||
|   s->s = s->control->getSurface(); | ||||
|   assert(s->s != NULL); | ||||
| 
 | ||||
|   // init opengl and egl
 | ||||
|   const EGLint attribs[] = { | ||||
|     EGL_RED_SIZE,     8, | ||||
|     EGL_GREEN_SIZE,   8, | ||||
|     EGL_BLUE_SIZE,    8, | ||||
|     EGL_ALPHA_SIZE,   alpha ? 8 : 0, | ||||
|     EGL_DEPTH_SIZE,   0, | ||||
|     EGL_STENCIL_SIZE, 8, | ||||
|     EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, | ||||
|     EGL_NONE, | ||||
|   }; | ||||
| 
 | ||||
|   s->display = eglGetDisplay(EGL_DEFAULT_DISPLAY); | ||||
|   assert(s->display != EGL_NO_DISPLAY); | ||||
| 
 | ||||
|   success = eglInitialize(s->display, &s->egl_major, &s->egl_minor); | ||||
|   assert(success); | ||||
| 
 | ||||
|   printf("egl version %d.%d\n", s->egl_major, s->egl_minor); | ||||
|   
 | ||||
|   EGLint num_configs; | ||||
|   success = eglChooseConfig(s->display, attribs, &s->config, 1, &num_configs); | ||||
|   assert(success); | ||||
| 
 | ||||
|   s->surface = eglCreateWindowSurface(s->display, s->config, s->s.get(), NULL); | ||||
|   assert(s->surface != EGL_NO_SURFACE); | ||||
| 
 | ||||
|   const EGLint context_attribs[] = { | ||||
|     EGL_CONTEXT_CLIENT_VERSION, 3, | ||||
|     EGL_NONE, | ||||
|   }; | ||||
|   s->context = eglCreateContext(s->display, s->config, NULL, context_attribs); | ||||
|   assert(s->context != EGL_NO_CONTEXT); | ||||
| 
 | ||||
|   EGLint w, h; | ||||
|   eglQuerySurface(s->display, s->surface, EGL_WIDTH, &w); | ||||
|   eglQuerySurface(s->display, s->surface, EGL_HEIGHT, &h); | ||||
|   printf("egl w %d h %d\n", w, h); | ||||
| 
 | ||||
|   success = eglMakeCurrent(s->display, s->surface, s->surface, s->context); | ||||
|   assert(success); | ||||
| 
 | ||||
|   printf("gl version %s\n", glGetString(GL_VERSION)); | ||||
| 
 | ||||
| 
 | ||||
|   // set brightness
 | ||||
|   int brightness_fd = open(BACKLIGHT_CONTROL, O_RDWR); | ||||
|   const char brightness_level[] = BACKLIGHT_LEVEL; | ||||
|   write(brightness_fd, brightness_level, strlen(brightness_level)); | ||||
| 
 | ||||
|   if (out_w) *out_w = w; | ||||
|   if (out_h) *out_h = h; | ||||
| 
 | ||||
|   return s; | ||||
| } | ||||
| 
 | ||||
| extern "C" void framebuffer_swap(FramebufferState *s) { | ||||
|   eglSwapBuffers(s->display, s->surface); | ||||
|   assert(glGetError() == GL_NO_ERROR); | ||||
| } | ||||
| 
 | ||||
| @ -0,0 +1,50 @@ | ||||
| #ifndef FRAMEBUFFER_H | ||||
| #define FRAMEBUFFER_H | ||||
| 
 | ||||
| #include <EGL/eglext.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct FramebufferState FramebufferState; | ||||
| 
 | ||||
| FramebufferState* framebuffer_init( | ||||
|     const char* name, int32_t layer, int alpha, | ||||
|     int *out_w, int *out_h); | ||||
| 
 | ||||
| void framebuffer_set_power(FramebufferState *s, int mode); | ||||
| void framebuffer_swap(FramebufferState *s); | ||||
| 
 | ||||
| /* Display power modes */ | ||||
| enum { | ||||
|     /* The display is turned off (blanked). */ | ||||
|     HWC_POWER_MODE_OFF      = 0, | ||||
|     /* The display is turned on and configured in a low power state
 | ||||
|      * that is suitable for presenting ambient information to the user, | ||||
|      * possibly with lower fidelity than normal but greater efficiency. */ | ||||
|     HWC_POWER_MODE_DOZE     = 1, | ||||
|     /* The display is turned on normally. */ | ||||
|     HWC_POWER_MODE_NORMAL   = 2, | ||||
|     /* The display is configured as in HWC_POWER_MODE_DOZE but may
 | ||||
|      * stop applying frame buffer updates from the graphics subsystem. | ||||
|      * This power mode is effectively a hint from the doze dream to | ||||
|      * tell the hardware that it is done drawing to the display for the | ||||
|      * time being and that the display should remain on in a low power | ||||
|      * state and continue showing its current contents indefinitely | ||||
|      * until the mode changes. | ||||
|      * | ||||
|      * This mode may also be used as a signal to enable hardware-based doze | ||||
|      * functionality.  In this case, the doze dream is effectively | ||||
|      * indicating that the hardware is free to take over the display | ||||
|      * and manage it autonomously to implement low power always-on display | ||||
|      * functionality. */ | ||||
|     HWC_POWER_MODE_DOZE_SUSPEND  = 3, | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,71 @@ | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| #include <GLES3/gl3.h> | ||||
| 
 | ||||
| #include "glutil.h" | ||||
| 
 | ||||
| GLuint load_shader(GLenum shaderType, const char *src) { | ||||
|   GLint status = 0, len = 0; | ||||
|   GLuint shader; | ||||
| 
 | ||||
|   if (!(shader = glCreateShader(shaderType))) | ||||
|     return 0; | ||||
| 
 | ||||
|   glShaderSource(shader, 1, &src, NULL); | ||||
|   glCompileShader(shader); | ||||
|   glGetShaderiv(shader, GL_COMPILE_STATUS, &status); | ||||
| 
 | ||||
|   if (status) | ||||
|     return shader; | ||||
| 
 | ||||
|   glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len); | ||||
|   if (len) { | ||||
|     char *msg = (char*)malloc(len); | ||||
|     if (msg) { | ||||
|       glGetShaderInfoLog(shader, len, NULL, msg); | ||||
|       msg[len-1] = 0; | ||||
|       fprintf(stderr, "error compiling shader:\n%s\n", msg); | ||||
|       free(msg); | ||||
|     } | ||||
|   } | ||||
|   glDeleteShader(shader); | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| GLuint load_program(const char *vert_src, const char *frag_src) { | ||||
|   GLuint vert, frag, prog; | ||||
|   GLint status = 0, len = 0; | ||||
| 
 | ||||
|   if (!(vert = load_shader(GL_VERTEX_SHADER, vert_src))) | ||||
|     return 0; | ||||
|   if (!(frag = load_shader(GL_FRAGMENT_SHADER, frag_src))) | ||||
|     goto fail_frag; | ||||
|   if (!(prog = glCreateProgram())) | ||||
|     goto fail_prog; | ||||
| 
 | ||||
|   glAttachShader(prog, vert); | ||||
|   glAttachShader(prog, frag); | ||||
|   glLinkProgram(prog); | ||||
| 
 | ||||
|   glGetProgramiv(prog, GL_LINK_STATUS, &status); | ||||
|   if (status) | ||||
|     return prog; | ||||
| 
 | ||||
|   glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len); | ||||
|   if (len) { | ||||
|     char *buf = (char*) malloc(len); | ||||
|     if (buf) { | ||||
|       glGetProgramInfoLog(prog, len, NULL, buf); | ||||
|       buf[len-1] = 0; | ||||
|       fprintf(stderr, "error linking program:\n%s\n", buf); | ||||
|       free(buf); | ||||
|     } | ||||
|   } | ||||
|   glDeleteProgram(prog); | ||||
| fail_prog: | ||||
|   glDeleteShader(frag); | ||||
| fail_frag: | ||||
|   glDeleteShader(vert); | ||||
|   return 0; | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| #ifndef COMMON_GLUTIL_H | ||||
| #define COMMON_GLUTIL_H | ||||
| 
 | ||||
| #include <GLES3/gl3.h> | ||||
| GLuint load_shader(GLenum shaderType, const char *src); | ||||
| GLuint load_program(const char *vert_src, const char *frag_src); | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,119 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| #include <unistd.h> | ||||
| #include <assert.h> | ||||
| #include <errno.h> | ||||
| 
 | ||||
| #include <sys/mman.h> | ||||
| #include <sys/socket.h> | ||||
| #include <sys/un.h> | ||||
| 
 | ||||
| #include "ipc.h" | ||||
| 
 | ||||
| int ipc_connect(const char* socket_path) { | ||||
|   int err; | ||||
| 
 | ||||
|   int sock = socket(AF_UNIX, SOCK_SEQPACKET, 0); | ||||
|   assert(sock >= 0); | ||||
|   struct sockaddr_un addr = { | ||||
|     .sun_family = AF_UNIX, | ||||
|   }; | ||||
|   snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path); | ||||
|   err = connect(sock, (struct sockaddr*)&addr, sizeof(addr)); | ||||
|   if (err != 0) { | ||||
|     close(sock); | ||||
|     return -1; | ||||
|   } | ||||
| 
 | ||||
|   return sock; | ||||
| } | ||||
| 
 | ||||
| int ipc_bind(const char* socket_path) { | ||||
|   int err; | ||||
| 
 | ||||
|   unlink(socket_path); | ||||
| 
 | ||||
|   int sock = socket(AF_UNIX, SOCK_SEQPACKET, 0); | ||||
|   struct sockaddr_un addr = { | ||||
|     .sun_family = AF_UNIX, | ||||
|   }; | ||||
|   snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", socket_path); | ||||
|   err = bind(sock, (struct sockaddr *)&addr, sizeof(addr)); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   err = listen(sock, 3); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   return sock; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| int ipc_sendrecv_with_fds(bool send, int fd, void *buf, size_t buf_size, int* fds, int num_fds, | ||||
|                           int *out_num_fds) { | ||||
|   int err; | ||||
| 
 | ||||
|   char control_buf[CMSG_SPACE(sizeof(int) * num_fds)]; | ||||
|   memset(control_buf, 0, CMSG_SPACE(sizeof(int) * num_fds)); | ||||
| 
 | ||||
|   struct iovec iov = { | ||||
|     .iov_base = buf, | ||||
|     .iov_len = buf_size, | ||||
|   }; | ||||
|   struct msghdr msg = { | ||||
|     .msg_iov = &iov, | ||||
|     .msg_iovlen = 1, | ||||
|   }; | ||||
| 
 | ||||
|   if (num_fds > 0) { | ||||
|     assert(fds); | ||||
| 
 | ||||
|     msg.msg_control = control_buf; | ||||
|     msg.msg_controllen = CMSG_SPACE(sizeof(int) * num_fds); | ||||
|   } | ||||
| 
 | ||||
|   if (send) { | ||||
|     if (num_fds) { | ||||
|       struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); | ||||
|       assert(cmsg); | ||||
|       cmsg->cmsg_level = SOL_SOCKET; | ||||
|       cmsg->cmsg_type = SCM_RIGHTS; | ||||
|       cmsg->cmsg_len = CMSG_LEN(sizeof(int) * num_fds); | ||||
|       memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * num_fds); | ||||
|       // printf("send clen %d -> %d\n", num_fds, cmsg->cmsg_len);
 | ||||
|     } | ||||
|     return sendmsg(fd, &msg, 0); | ||||
|   } else { | ||||
|     int r = recvmsg(fd, &msg, 0); | ||||
|     if (r < 0) return r; | ||||
| 
 | ||||
|     int recv_fds = 0; | ||||
|     if (msg.msg_controllen > 0) { | ||||
|       struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); | ||||
|       assert(cmsg); | ||||
|       assert(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS); | ||||
|       recv_fds = (cmsg->cmsg_len - CMSG_LEN(0)); | ||||
|       assert(recv_fds > 0 && (recv_fds % sizeof(int)) == 0); | ||||
|       recv_fds /= sizeof(int); | ||||
|       // printf("recv clen %d -> %d\n", cmsg->cmsg_len, recv_fds);
 | ||||
|       // assert(cmsg->cmsg_len == CMSG_LEN(sizeof(int) * num_fds));
 | ||||
| 
 | ||||
|       assert(fds && recv_fds <= num_fds); | ||||
|       memcpy(fds, CMSG_DATA(cmsg), sizeof(int) * recv_fds); | ||||
|     } | ||||
| 
 | ||||
|     if (msg.msg_flags) { | ||||
|       for (int i=0; i<recv_fds; i++) { | ||||
|         close(fds[i]); | ||||
|       } | ||||
|       return -1; | ||||
|     } | ||||
| 
 | ||||
|     if (fds) { | ||||
|       assert(out_num_fds); | ||||
|       *out_num_fds = recv_fds; | ||||
|     } | ||||
|     return r; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| #ifndef IPC_H | ||||
| #define IPC_H | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| int ipc_connect(const char* socket_path); | ||||
| int ipc_bind(const char* socket_path); | ||||
| int ipc_sendrecv_with_fds(bool send, int fd, void *buf, size_t buf_size, int* fds, int num_fds, | ||||
|                           int *out_num_fds); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| }  // extern "C"
 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,88 @@ | ||||
| #ifndef COMMON_MAT_H | ||||
| #define COMMON_MAT_H | ||||
| 
 | ||||
| typedef struct vec3 { | ||||
| 	float v[3]; | ||||
| } vec3; | ||||
| 
 | ||||
| typedef struct vec4 { | ||||
|   float v[4]; | ||||
| } vec4; | ||||
| 
 | ||||
| typedef struct mat3 { | ||||
| 	float v[3*3]; | ||||
| } mat3; | ||||
| 
 | ||||
| typedef struct mat4 { | ||||
|   float v[4*4]; | ||||
| } mat4; | ||||
| 
 | ||||
| static inline mat3 matmul3(const mat3 a, const mat3 b) { | ||||
|   mat3 ret = {{0.0}}; | ||||
|   for (int r=0; r<3; r++) { | ||||
|     for (int c=0; c<3; c++) { | ||||
|       float v = 0.0; | ||||
|       for (int k=0; k<3; k++) { | ||||
|         v += a.v[r*3+k] * b.v[k*3+c]; | ||||
|       } | ||||
|       ret.v[r*3+c] = v; | ||||
|     } | ||||
|   } | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| static inline vec3 matvecmul3(const mat3 a, const vec3 b) { | ||||
|   vec3 ret = {{0.0}}; | ||||
|   for (int r=0; r<3; r++) { | ||||
|     for (int c=0; c<3; c++) { | ||||
|       ret.v[r] += a.v[r*3+c] * b.v[c]; | ||||
|     } | ||||
|   } | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| static inline mat4 matmul(const mat4 a, const mat4 b) { | ||||
|   mat4 ret = {{0.0}}; | ||||
|   for (int r=0; r<4; r++) { | ||||
|     for (int c=0; c<4; c++) { | ||||
|       float v = 0.0; | ||||
|       for (int k=0; k<4; k++) { | ||||
|         v += a.v[r*4+k] * b.v[k*4+c]; | ||||
|       } | ||||
|       ret.v[r*4+c] = v; | ||||
|     } | ||||
|   } | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| static inline vec4 matvecmul(const mat4 a, const vec4 b) { | ||||
|   vec4 ret = {{0.0}}; | ||||
|   for (int r=0; r<4; r++) { | ||||
|     for (int c=0; c<4; c++) { | ||||
|       ret.v[r] += a.v[r*4+c] * b.v[c]; | ||||
|     } | ||||
|   } | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| // scales the input and output space of a transformation matrix
 | ||||
| // that assumes pixel-center origin.
 | ||||
| static inline mat3 transform_scale_buffer(const mat3 in, float s) { | ||||
|   // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s
 | ||||
| 
 | ||||
|   mat3 transform_out = {{ | ||||
|     1.0f/s, 0.0f, 0.5f, | ||||
|     0.0f, 1.0f/s, 0.5f, | ||||
|     0.0f, 0.0f, 1.0f, | ||||
|   }}; | ||||
| 
 | ||||
|   mat3 transform_in = {{ | ||||
|     s,  0.0f, -0.5f*s, | ||||
|     0.0f, s, -0.5f*s, | ||||
|     0.0f, 0.0f, 1.0f, | ||||
|   }}; | ||||
| 
 | ||||
|   return matmul3(transform_in, matmul3(in, transform_out)); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,15 @@ | ||||
| // the c version of cereal/messaging.py
 | ||||
| 
 | ||||
| #include <zmq.h> | ||||
| 
 | ||||
| // TODO: refactor to take in service instead of endpoint?
 | ||||
| void *sub_sock(void *ctx, const char *endpoint) { | ||||
|   void* sock = zmq_socket(ctx, ZMQ_SUB); | ||||
|   assert(sock); | ||||
|   zmq_setsockopt(sock, ZMQ_SUBSCRIBE, "", 0); | ||||
|   int reconnect_ivl = 500; | ||||
|   zmq_setsockopt(sock, ZMQ_RECONNECT_IVL_MAX, &reconnect_ivl, sizeof(reconnect_ivl)); | ||||
|   zmq_connect(sock, endpoint); | ||||
|   return sock; | ||||
| } | ||||
| 
 | ||||
| @ -0,0 +1,40 @@ | ||||
| #ifndef MODELDATA_H | ||||
| #define MODELDATA_H | ||||
| 
 | ||||
| #define MODEL_PATH_DISTANCE 192 | ||||
| #define POLYFIT_DEGREE 4 | ||||
| #define SPEED_PERCENTILES 10 | ||||
| #define DESIRE_PRED_SIZE 32 | ||||
| #define OTHER_META_SIZE 4 | ||||
| 
 | ||||
| typedef struct PathData { | ||||
|   float points[MODEL_PATH_DISTANCE]; | ||||
|   float prob; | ||||
|   float std; | ||||
|   float stds[MODEL_PATH_DISTANCE]; | ||||
|   float poly[POLYFIT_DEGREE]; | ||||
| } PathData; | ||||
| 
 | ||||
| typedef struct LeadData { | ||||
|   float dist; | ||||
|   float prob; | ||||
|   float std; | ||||
|   float rel_y; | ||||
|   float rel_y_std; | ||||
|   float rel_v; | ||||
|   float rel_v_std; | ||||
|   float rel_a; | ||||
|   float rel_a_std; | ||||
| } LeadData; | ||||
| 
 | ||||
| typedef struct ModelData { | ||||
|   PathData path; | ||||
|   PathData left_lane; | ||||
|   PathData right_lane; | ||||
|   LeadData lead; | ||||
|   LeadData lead_future; | ||||
|   float meta[OTHER_META_SIZE + DESIRE_PRED_SIZE]; | ||||
|   float speed[SPEED_PERCENTILES]; | ||||
| } ModelData; | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,13 @@ | ||||
| #ifndef COMMON_MUTEX_H | ||||
| #define COMMON_MUTEX_H | ||||
| 
 | ||||
| #include <pthread.h> | ||||
| 
 | ||||
| static inline void mutex_init_reentrant(pthread_mutex_t *mutex) { | ||||
|   pthread_mutexattr_t attr; | ||||
|   pthread_mutexattr_init(&attr); | ||||
|   pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); | ||||
|   pthread_mutex_init(mutex, &attr); | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,307 @@ | ||||
| #include "common/params.h" | ||||
| 
 | ||||
| #ifndef _GNU_SOURCE | ||||
| #define _GNU_SOURCE | ||||
| #endif  // _GNU_SOURCE
 | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <unistd.h> | ||||
| #include <dirent.h> | ||||
| #include <sys/file.h> | ||||
| #include <sys/stat.h> | ||||
| 
 | ||||
| #include <map> | ||||
| #include <string> | ||||
| 
 | ||||
| #include "common/util.h" | ||||
| #include "common/utilpp.h" | ||||
| 
 | ||||
| 
 | ||||
| namespace { | ||||
| 
 | ||||
| template <typename T> | ||||
| T* null_coalesce(T* a, T* b) { | ||||
|   return a != NULL ? a : b; | ||||
| } | ||||
| 
 | ||||
| static const char* default_params_path = null_coalesce( | ||||
|     const_cast<const char*>(getenv("PARAMS_PATH")), "/data/params"); | ||||
| 
 | ||||
| }  // namespace
 | ||||
| 
 | ||||
| static int fsync_dir(const char* path){ | ||||
|   int result = 0; | ||||
|   int fd = open(path, O_RDONLY); | ||||
| 
 | ||||
|   if (fd < 0){ | ||||
|     result = -1; | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   result = fsync(fd); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|  cleanup: | ||||
|   int result_close = 0; | ||||
|   if (fd >= 0){ | ||||
|     result_close = close(fd); | ||||
|   } | ||||
| 
 | ||||
|   if (result_close < 0) { | ||||
|     return result_close; | ||||
|   } else { | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| int write_db_value(const char* params_path, const char* key, const char* value, | ||||
|                    size_t value_size) { | ||||
|   // Information about safely and atomically writing a file: https://lwn.net/Articles/457667/
 | ||||
|   // 1) Create temp file
 | ||||
|   // 2) Write data to temp file
 | ||||
|   // 3) fsync() the temp file
 | ||||
|   // 4) rename the temp file to the real name
 | ||||
|   // 5) fsync() the containing directory
 | ||||
| 
 | ||||
|   int lock_fd = -1; | ||||
|   int tmp_fd = -1; | ||||
|   int result; | ||||
|   char tmp_path[1024]; | ||||
|   char path[1024]; | ||||
|   ssize_t bytes_written; | ||||
| 
 | ||||
|   if (params_path == NULL) { | ||||
|     params_path = default_params_path; | ||||
|   } | ||||
| 
 | ||||
|   // Write value to temp.
 | ||||
|   result = | ||||
|       snprintf(tmp_path, sizeof(tmp_path), "%s/.tmp_value_XXXXXX", params_path); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   tmp_fd = mkstemp(tmp_path); | ||||
|   bytes_written = write(tmp_fd, value, value_size); | ||||
|   if (bytes_written != value_size) { | ||||
|     result = -20; | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // Build lock path
 | ||||
|   result = snprintf(path, sizeof(path), "%s/.lock", params_path); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
|   lock_fd = open(path, 0); | ||||
| 
 | ||||
|   // Build key path
 | ||||
|   result = snprintf(path, sizeof(path), "%s/d/%s", params_path, key); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // Take lock.
 | ||||
|   result = flock(lock_fd, LOCK_EX); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // change permissions to 0666 for apks
 | ||||
|   result = fchmod(tmp_fd, 0666); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // fsync to force persist the changes.
 | ||||
|   result = fsync(tmp_fd); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // Move temp into place.
 | ||||
|   result = rename(tmp_path, path); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // fsync parent directory
 | ||||
|   result = snprintf(path, sizeof(path), "%s/d", params_path); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   result = fsync_dir(path); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
| cleanup: | ||||
|   // Release lock.
 | ||||
|   if (lock_fd >= 0) { | ||||
|     close(lock_fd); | ||||
|   } | ||||
|   if (tmp_fd >= 0) { | ||||
|     if (result < 0) { | ||||
|       remove(tmp_path); | ||||
|     } | ||||
|     close(tmp_fd); | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| int delete_db_value(const char* params_path, const char* key) { | ||||
|   int lock_fd = -1; | ||||
|   int result; | ||||
|   char path[1024]; | ||||
| 
 | ||||
|   if (params_path == NULL) { | ||||
|     params_path = default_params_path; | ||||
|   } | ||||
| 
 | ||||
|   // Build lock path, and open lockfile
 | ||||
|   result = snprintf(path, sizeof(path), "%s/.lock", params_path); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
|   lock_fd = open(path, 0); | ||||
| 
 | ||||
|   // Take lock.
 | ||||
|   result = flock(lock_fd, LOCK_EX); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // Build key path
 | ||||
|   result = snprintf(path, sizeof(path), "%s/d/%s", params_path, key); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // Delete value.
 | ||||
|   result = remove(path); | ||||
|   if (result != 0) { | ||||
|     result = ERR_NO_VALUE; | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // fsync parent directory
 | ||||
|   result = snprintf(path, sizeof(path), "%s/d", params_path); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   result = fsync_dir(path); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
| cleanup: | ||||
|   // Release lock.
 | ||||
|   if (lock_fd >= 0) { | ||||
|     close(lock_fd); | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| int read_db_value(const char* params_path, const char* key, char** value, | ||||
|                   size_t* value_sz) { | ||||
|   int lock_fd = -1; | ||||
|   int result; | ||||
|   char path[1024]; | ||||
| 
 | ||||
|   if (params_path == NULL) { | ||||
|     params_path = default_params_path; | ||||
|   } | ||||
| 
 | ||||
|   result = snprintf(path, sizeof(path), "%s/.lock", params_path); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
|   lock_fd = open(path, 0); | ||||
| 
 | ||||
|   result = snprintf(path, sizeof(path), "%s/d/%s", params_path, key); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // Take lock.
 | ||||
|   result = flock(lock_fd, LOCK_EX); | ||||
|   if (result < 0) { | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   // Read value.
 | ||||
|   // TODO(mgraczyk): If there is a lot of contention, we can release the lock
 | ||||
|   //                 after opening the file, before reading.
 | ||||
|   *value = static_cast<char*>(read_file(path, value_sz)); | ||||
|   if (*value == NULL) { | ||||
|     result = -22; | ||||
|     goto cleanup; | ||||
|   } | ||||
| 
 | ||||
|   result = 0; | ||||
| 
 | ||||
| cleanup: | ||||
|   // Release lock.
 | ||||
|   if (lock_fd >= 0) { | ||||
|     close(lock_fd); | ||||
|   } | ||||
|   return result; | ||||
| } | ||||
| 
 | ||||
| void read_db_value_blocking(const char* params_path, const char* key, | ||||
|                             char** value, size_t* value_sz) { | ||||
|   while (1) { | ||||
|     const int result = read_db_value(params_path, key, value, value_sz); | ||||
|     if (result == 0) { | ||||
|       return; | ||||
|     } else { | ||||
|       // Sleep for 0.1 seconds.
 | ||||
|       usleep(100000); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| int read_db_all(const char* params_path, std::map<std::string, std::string> *params) { | ||||
|   int err = 0; | ||||
| 
 | ||||
|   if (params_path == NULL) { | ||||
|     params_path = default_params_path; | ||||
|   } | ||||
| 
 | ||||
|   std::string lock_path = util::string_format("%s/.lock", params_path); | ||||
| 
 | ||||
|   int lock_fd = open(lock_path.c_str(), 0); | ||||
|   if (lock_fd < 0) return -1; | ||||
| 
 | ||||
|   err = flock(lock_fd, LOCK_EX); | ||||
|   if (err < 0) return err; | ||||
| 
 | ||||
|   std::string key_path = util::string_format("%s/d", params_path); | ||||
|   DIR *d = opendir(key_path.c_str()); | ||||
|   if (!d) { | ||||
|     close(lock_fd); | ||||
|     return -1; | ||||
|   } | ||||
| 
 | ||||
|   struct dirent *de = NULL; | ||||
|   while ((de = readdir(d))) { | ||||
|     if (!isalnum(de->d_name[0])) continue; | ||||
|     std::string key = std::string(de->d_name); | ||||
| 
 | ||||
|     if (key == "AccessToken") continue; | ||||
| 
 | ||||
|     std::string value = util::read_file(util::string_format("%s/%s", key_path.c_str(), key.c_str())); | ||||
| 
 | ||||
|     (*params)[key] = value; | ||||
|   } | ||||
| 
 | ||||
|   closedir(d); | ||||
| 
 | ||||
|   close(lock_fd); | ||||
|   return 0; | ||||
| } | ||||
| @ -0,0 +1,47 @@ | ||||
| #ifndef _SELFDRIVE_COMMON_PARAMS_H_ | ||||
| #define _SELFDRIVE_COMMON_PARAMS_H_ | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #define ERR_NO_VALUE -33 | ||||
| 
 | ||||
| int write_db_value(const char* params_path, const char* key, const char* value, | ||||
|                    size_t value_size); | ||||
| 
 | ||||
| // Reads a value from the params database.
 | ||||
| // Inputs:
 | ||||
| //  params_path: The path of the database, or NULL to use the default.
 | ||||
| //  key: The key to read.
 | ||||
| //  value: A pointer where a newly allocated string containing the db value will
 | ||||
| //         be written.
 | ||||
| //  value_sz: A pointer where the size of value will be written. Does not
 | ||||
| //            include the NULL terminator.
 | ||||
| //
 | ||||
| // Returns: Negative on failure, otherwise 0.
 | ||||
| int read_db_value(const char* params_path, const char* key, char** value, | ||||
|                   size_t* value_sz); | ||||
| 
 | ||||
| // Delete a value from the params database.
 | ||||
| // Inputs are the same as read_db_value, without value and value_sz.
 | ||||
| int delete_db_value(const char* params_path, const char* key); | ||||
| 
 | ||||
| // Reads a value from the params database, blocking until successful.
 | ||||
| // Inputs are the same as read_db_value.
 | ||||
| void read_db_value_blocking(const char* params_path, const char* key, | ||||
|                             char** value, size_t* value_sz); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| }  // extern "C"
 | ||||
| #endif | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| #include <map> | ||||
| #include <string> | ||||
| int read_db_all(const char* params_path, std::map<std::string, std::string> *params); | ||||
| #endif | ||||
| 
 | ||||
| #endif  // _SELFDRIVE_COMMON_PARAMS_H_
 | ||||
| @ -0,0 +1,182 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdbool.h> | ||||
| #include <math.h> | ||||
| #include <unistd.h> | ||||
| #include <assert.h> | ||||
| #include <ctype.h> | ||||
| 
 | ||||
| #include <GLES3/gl3.h> | ||||
| #include <EGL/egl.h> | ||||
| #include <EGL/eglext.h> | ||||
| 
 | ||||
| #include "nanovg.h" | ||||
| #define NANOVG_GLES3_IMPLEMENTATION | ||||
| #include "nanovg_gl.h" | ||||
| #include "nanovg_gl_utils.h" | ||||
| 
 | ||||
| #include "framebuffer.h" | ||||
| #include "spinner.h" | ||||
| 
 | ||||
| #define SPINTEXT_LENGTH 128 | ||||
| 
 | ||||
| // external resources linked in
 | ||||
| extern const unsigned char _binary_opensans_semibold_ttf_start[]; | ||||
| extern const unsigned char _binary_opensans_semibold_ttf_end[]; | ||||
| 
 | ||||
| extern const unsigned char _binary_img_spinner_track_png_start[]; | ||||
| extern const unsigned char _binary_img_spinner_track_png_end[]; | ||||
| 
 | ||||
| extern const unsigned char _binary_img_spinner_comma_png_start[]; | ||||
| extern const unsigned char _binary_img_spinner_comma_png_end[]; | ||||
| 
 | ||||
| bool stdin_input_available() { | ||||
|   struct timeval timeout; | ||||
|   timeout.tv_sec = 0; | ||||
|   timeout.tv_usec = 0; | ||||
| 
 | ||||
|   fd_set fds; | ||||
|   FD_ZERO(&fds); | ||||
|   FD_SET(STDIN_FILENO, &fds); | ||||
|   select(STDIN_FILENO+1, &fds, NULL, NULL, &timeout); | ||||
|   return (FD_ISSET(0, &fds)); | ||||
| } | ||||
| 
 | ||||
| int spin(int argc, char** argv) { | ||||
|   int err; | ||||
| 
 | ||||
|   bool draw_progress = false; | ||||
|   float progress_val = 0.0; | ||||
| 
 | ||||
|   char spintext[SPINTEXT_LENGTH]; | ||||
|   spintext[0] = 0; | ||||
| 
 | ||||
|   const char* spintext_arg = NULL; | ||||
|   if (argc >= 2) { | ||||
|     strncpy(spintext, argv[1], SPINTEXT_LENGTH); | ||||
|   } | ||||
| 
 | ||||
|   // spinner
 | ||||
|   int fb_w, fb_h; | ||||
|   FramebufferState *fb = framebuffer_init("spinner", 0x00001000, false, | ||||
|                                           &fb_w, &fb_h); | ||||
|   assert(fb); | ||||
| 
 | ||||
|   NVGcontext *vg = nvgCreateGLES3(NVG_ANTIALIAS | NVG_STENCIL_STROKES); | ||||
|   assert(vg); | ||||
| 
 | ||||
|   int font = nvgCreateFontMem(vg, "Bold", (unsigned char*)_binary_opensans_semibold_ttf_start, _binary_opensans_semibold_ttf_end-_binary_opensans_semibold_ttf_start, 0); | ||||
|   assert(font >= 0); | ||||
| 
 | ||||
|   int spinner_img = nvgCreateImageMem(vg, 0, (unsigned char*)_binary_img_spinner_track_png_start, _binary_img_spinner_track_png_end - _binary_img_spinner_track_png_start); | ||||
|   assert(spinner_img >= 0); | ||||
|   int spinner_img_s = 360; | ||||
|   int spinner_img_x = ((fb_w/2)-(spinner_img_s/2)); | ||||
|   int spinner_img_y = 260; | ||||
|   int spinner_img_xc = (fb_w/2); | ||||
|   int spinner_img_yc = (fb_h/2)-100; | ||||
|   int spinner_comma_img = nvgCreateImageMem(vg, 0, (unsigned char*)_binary_img_spinner_comma_png_start, _binary_img_spinner_comma_png_end - _binary_img_spinner_comma_png_start); | ||||
|   assert(spinner_comma_img >= 0); | ||||
| 
 | ||||
|   for (int cnt = 0; ; cnt++) { | ||||
|     // Check stdin for new text
 | ||||
|     if (stdin_input_available()){ | ||||
|       fgets(spintext, SPINTEXT_LENGTH, stdin); | ||||
|       spintext[strcspn(spintext, "\n")] = 0; | ||||
| 
 | ||||
|       // Check if number (update progress bar)
 | ||||
|       size_t len = strlen(spintext); | ||||
|       bool is_number = len > 0; | ||||
|       for (int i = 0; i < len; i++){ | ||||
|         if (!isdigit(spintext[i])){ | ||||
|           is_number = false; | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       if (is_number) { | ||||
|         progress_val = (float)(atoi(spintext)) / 100.0; | ||||
|         progress_val = fmin(1.0, progress_val); | ||||
|         progress_val = fmax(0.0, progress_val); | ||||
|       } | ||||
| 
 | ||||
|       draw_progress = is_number; | ||||
|     } | ||||
| 
 | ||||
|     glClearColor(0.1, 0.1, 0.1, 1.0); | ||||
|     glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); | ||||
|     glEnable(GL_BLEND); | ||||
|     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | ||||
|     nvgBeginFrame(vg, fb_w, fb_h, 1.0f); | ||||
| 
 | ||||
|     // background
 | ||||
|     nvgBeginPath(vg); | ||||
|     NVGpaint bg = nvgLinearGradient(vg, fb_w, 0, fb_w, fb_h, | ||||
|     nvgRGBA(0, 0, 0, 175), nvgRGBA(0, 0, 0, 255)); | ||||
|     nvgFillPaint(vg, bg); | ||||
|     nvgRect(vg, 0, 0, fb_w, fb_h); | ||||
|     nvgFill(vg); | ||||
| 
 | ||||
|     // spin track
 | ||||
|     nvgSave(vg); | ||||
|     nvgTranslate(vg, spinner_img_xc, spinner_img_yc); | ||||
|     nvgRotate(vg, (3.75*M_PI * cnt/120.0)); | ||||
|     nvgTranslate(vg, -spinner_img_xc, -spinner_img_yc); | ||||
|     NVGpaint spinner_imgPaint = nvgImagePattern(vg, spinner_img_x, spinner_img_y, | ||||
|       spinner_img_s, spinner_img_s, 0, spinner_img, 0.6f); | ||||
|     nvgBeginPath(vg); | ||||
|     nvgFillPaint(vg, spinner_imgPaint); | ||||
|     nvgRect(vg, spinner_img_x, spinner_img_y, spinner_img_s, spinner_img_s); | ||||
|     nvgFill(vg); | ||||
|     nvgRestore(vg); | ||||
| 
 | ||||
|     // comma
 | ||||
|     NVGpaint comma_imgPaint = nvgImagePattern(vg, spinner_img_x, spinner_img_y, | ||||
|       spinner_img_s, spinner_img_s, 0, spinner_comma_img, 1.0f); | ||||
|     nvgBeginPath(vg); | ||||
|     nvgFillPaint(vg, comma_imgPaint); | ||||
|     nvgRect(vg, spinner_img_x, spinner_img_y, spinner_img_s, spinner_img_s); | ||||
|     nvgFill(vg); | ||||
| 
 | ||||
|     if (draw_progress){ | ||||
|       // draw progress bar
 | ||||
|       int progress_width = 1000; | ||||
|       int progress_x = fb_w/2-progress_width/2; | ||||
|       int progress_y = 775; | ||||
|       int progress_height = 25; | ||||
| 
 | ||||
|       NVGpaint paint = nvgBoxGradient( | ||||
|           vg, progress_x + 1, progress_y + 1, | ||||
|           progress_width - 2, progress_height, 3, 4, nvgRGB(27, 27, 27), nvgRGB(27, 27, 27)); | ||||
|       nvgBeginPath(vg); | ||||
|       nvgRoundedRect(vg, progress_x, progress_y, progress_width, progress_height, 12); | ||||
|       nvgFillPaint(vg, paint); | ||||
|       nvgFill(vg); | ||||
| 
 | ||||
|       int bar_pos = ((progress_width - 2) * progress_val); | ||||
| 
 | ||||
|       paint = nvgBoxGradient( | ||||
|           vg, progress_x, progress_y, | ||||
|           bar_pos+1.5f, progress_height-1, 3, 4, | ||||
|           nvgRGB(245, 245, 245), nvgRGB(105, 105, 105)); | ||||
| 
 | ||||
|       nvgBeginPath(vg); | ||||
|       nvgRoundedRect( | ||||
|           vg, progress_x+1, progress_y+1, | ||||
|           bar_pos, progress_height-2, 12); | ||||
|       nvgFillPaint(vg, paint); | ||||
|       nvgFill(vg); | ||||
|     } else { | ||||
|       // message
 | ||||
|       nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_TOP); | ||||
|       nvgFontSize(vg, 96.0f); | ||||
|       nvgText(vg, fb_w/2, (fb_h*2/3)+24, spintext, NULL); | ||||
|     } | ||||
| 
 | ||||
|     nvgEndFrame(vg); | ||||
|     framebuffer_swap(fb); | ||||
|     assert(glGetError() == GL_NO_ERROR); | ||||
|   } | ||||
| 
 | ||||
|   return 0; | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| #ifndef COMMON_SPINNER_H | ||||
| #define COMMON_SPINNER_H | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| int spin(int argc, char** argv); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,119 @@ | ||||
| #define _GNU_SOURCE | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdarg.h> | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| #include <assert.h> | ||||
| 
 | ||||
| #include <pthread.h> | ||||
| #include <zmq.h> | ||||
| #include <json.h> | ||||
| 
 | ||||
| #include "common/timing.h" | ||||
| #include "common/version.h" | ||||
| 
 | ||||
| #include "swaglog.h" | ||||
| 
 | ||||
| typedef struct LogState { | ||||
|   pthread_mutex_t lock; | ||||
|   bool inited; | ||||
|   JsonNode *ctx_j; | ||||
|   void *zctx; | ||||
|   void *sock; | ||||
|   int print_level; | ||||
| } LogState; | ||||
| 
 | ||||
| static LogState s = { | ||||
|   .lock = PTHREAD_MUTEX_INITIALIZER, | ||||
| }; | ||||
| 
 | ||||
| static void cloudlog_bind_locked(const char* k, const char* v) { | ||||
|   json_append_member(s.ctx_j, k, json_mkstring(v)); | ||||
| } | ||||
| 
 | ||||
| static void cloudlog_init() { | ||||
|   if (s.inited) return; | ||||
|   s.ctx_j = json_mkobject(); | ||||
|   s.zctx = zmq_ctx_new(); | ||||
|   s.sock = zmq_socket(s.zctx, ZMQ_PUSH); | ||||
|   zmq_connect(s.sock, "ipc:///tmp/logmessage"); | ||||
| 
 | ||||
|   s.print_level = CLOUDLOG_WARNING; | ||||
|   const char* print_level = getenv("LOGPRINT"); | ||||
|   if (print_level) { | ||||
|     if (strcmp(print_level, "debug") == 0) { | ||||
|       s.print_level = CLOUDLOG_DEBUG; | ||||
|     } else if (strcmp(print_level, "info") == 0) { | ||||
|       s.print_level = CLOUDLOG_INFO; | ||||
|     } else if (strcmp(print_level, "warning") == 0) { | ||||
|       s.print_level = CLOUDLOG_WARNING; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // openpilot bindings
 | ||||
|   char* dongle_id = getenv("DONGLE_ID"); | ||||
|   if (dongle_id) { | ||||
|     cloudlog_bind_locked("dongle_id", dongle_id); | ||||
|   } | ||||
|   cloudlog_bind_locked("version", COMMA_VERSION); | ||||
|   bool dirty = !getenv("CLEAN"); | ||||
|   json_append_member(s.ctx_j, "dirty", json_mkbool(dirty)); | ||||
| 
 | ||||
|   s.inited = true; | ||||
| } | ||||
| 
 | ||||
| void cloudlog_e(int levelnum, const char* filename, int lineno, const char* func, | ||||
|                 const char* fmt, ...) { | ||||
|   pthread_mutex_lock(&s.lock); | ||||
|   cloudlog_init(); | ||||
| 
 | ||||
|   char* msg_buf = NULL; | ||||
|   va_list args; | ||||
|   va_start(args, fmt); | ||||
|   vasprintf(&msg_buf, fmt, args); | ||||
|   va_end(args); | ||||
| 
 | ||||
|   if (!msg_buf) { | ||||
|     pthread_mutex_unlock(&s.lock); | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   if (levelnum >= s.print_level) { | ||||
|     printf("%s: %s\n", filename, msg_buf); | ||||
|   } | ||||
| 
 | ||||
|   JsonNode *log_j = json_mkobject(); | ||||
|   assert(log_j); | ||||
| 
 | ||||
|   json_append_member(log_j, "msg", json_mkstring(msg_buf)); | ||||
|   json_append_member(log_j, "ctx", s.ctx_j); | ||||
|   json_append_member(log_j, "levelnum", json_mknumber(levelnum)); | ||||
|   json_append_member(log_j, "filename", json_mkstring(filename)); | ||||
|   json_append_member(log_j, "lineno", json_mknumber(lineno)); | ||||
|   json_append_member(log_j, "funcname", json_mkstring(func)); | ||||
|   json_append_member(log_j, "created", json_mknumber(seconds_since_epoch())); | ||||
| 
 | ||||
|   char* log_s = json_encode(log_j); | ||||
|   assert(log_s); | ||||
| 
 | ||||
|   json_remove_from_parent(s.ctx_j);  
 | ||||
| 
 | ||||
|   json_delete(log_j); | ||||
|   free(msg_buf); | ||||
| 
 | ||||
|   char levelnum_c = levelnum; | ||||
|   zmq_send(s.sock, &levelnum_c, 1, ZMQ_NOBLOCK | ZMQ_SNDMORE); | ||||
|   zmq_send(s.sock, log_s, strlen(log_s), ZMQ_NOBLOCK); | ||||
|   free(log_s); | ||||
| 
 | ||||
|   pthread_mutex_unlock(&s.lock); | ||||
| } | ||||
| 
 | ||||
| void cloudlog_bind(const char* k, const char* v) { | ||||
|   pthread_mutex_lock(&s.lock); | ||||
|   cloudlog_init(); | ||||
|   cloudlog_bind_locked(k, v); | ||||
|   pthread_mutex_unlock(&s.lock); | ||||
| } | ||||
| @ -0,0 +1,68 @@ | ||||
| #ifndef SWAGLOG_H | ||||
| #define SWAGLOG_H | ||||
| 
 | ||||
| #include "selfdrive/common/timing.h" | ||||
| 
 | ||||
| #define CLOUDLOG_DEBUG 10 | ||||
| #define CLOUDLOG_INFO 20 | ||||
| #define CLOUDLOG_WARNING 30 | ||||
| #define CLOUDLOG_ERROR 40 | ||||
| #define CLOUDLOG_CRITICAL 50 | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| void cloudlog_e(int levelnum, const char* filename, int lineno, const char* func, | ||||
|                 const char* fmt, ...) /*__attribute__ ((format (printf, 6, 7)))*/; | ||||
| 
 | ||||
| void cloudlog_bind(const char* k, const char* v); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #define cloudlog(lvl, fmt, ...) cloudlog_e(lvl, __FILE__, __LINE__, \ | ||||
|                                            __func__, \
 | ||||
|                                            fmt, ## __VA_ARGS__) | ||||
| 
 | ||||
| #define cloudlog_rl(burst, millis, lvl, fmt, ...)   \ | ||||
| {                                                   \
 | ||||
|   static uint64_t __begin = 0;                      \
 | ||||
|   static int __printed = 0;                         \
 | ||||
|   static int __missed = 0;                          \
 | ||||
|                                                     \
 | ||||
|   int __burst = (burst);                            \
 | ||||
|   int __millis = (millis);                          \
 | ||||
|   uint64_t __ts = nanos_since_boot();               \
 | ||||
|                                                     \
 | ||||
|   if (!__begin) __begin = __ts;                     \
 | ||||
|                                                     \
 | ||||
|   if (__begin + __millis*1000000ULL < __ts) {       \
 | ||||
|     if (__missed) {                                 \
 | ||||
|       cloudlog(CLOUDLOG_WARNING, "cloudlog: %d messages supressed", __missed); \
 | ||||
|     }                                               \
 | ||||
|     __begin = 0;                                    \
 | ||||
|     __printed = 0;                                  \
 | ||||
|     __missed = 0;                                   \
 | ||||
|   }                                                 \
 | ||||
|                                                     \
 | ||||
|   if (__printed < __burst) {                        \
 | ||||
|     cloudlog(lvl, fmt, ## __VA_ARGS__);             \
 | ||||
|     __printed++;                                    \
 | ||||
|   } else {                                          \
 | ||||
|     __missed++;                                     \
 | ||||
|   }                                                 \
 | ||||
| } | ||||
| 
 | ||||
| #define LOGD(fmt, ...) cloudlog(CLOUDLOG_DEBUG, fmt, ## __VA_ARGS__) | ||||
| #define LOG(fmt, ...) cloudlog(CLOUDLOG_INFO, fmt, ## __VA_ARGS__) | ||||
| #define LOGW(fmt, ...) cloudlog(CLOUDLOG_WARNING, fmt, ## __VA_ARGS__) | ||||
| #define LOGE(fmt, ...) cloudlog(CLOUDLOG_ERROR, fmt, ## __VA_ARGS__) | ||||
| 
 | ||||
| #define LOGD_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_DEBUG, fmt, ## __VA_ARGS__) | ||||
| #define LOG_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_INFO, fmt, ## __VA_ARGS__) | ||||
| #define LOGW_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_WARNING, fmt, ## __VA_ARGS__) | ||||
| #define LOGE_100(fmt, ...) cloudlog_rl(2, 100, CLOUDLOG_ERROR, fmt, ## __VA_ARGS__) | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,64 @@ | ||||
| #include "selfdrive/common/params.h" | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| static const char* const kUsage = "%s: read|write|read_block params_path key [value]\n"; | ||||
| 
 | ||||
| int main(int argc, const char* argv[]) { | ||||
|   if (argc < 4) { | ||||
|     printf(kUsage, argv[0]); | ||||
|     return 0; | ||||
|   } | ||||
| 
 | ||||
|   const char* params_path = argv[2]; | ||||
|   const char* key = argv[3]; | ||||
|   if (strcmp(argv[1], "read") == 0) { | ||||
|     char* value; | ||||
|     size_t value_size; | ||||
|     int result = read_db_value(params_path, key, &value, &value_size); | ||||
|     if (result >= 0) { | ||||
|       fprintf(stdout, "Read %zu bytes: ", value_size); | ||||
|       fwrite(value, 1, value_size, stdout); | ||||
|       fprintf(stdout, "\n"); | ||||
|       free(value); | ||||
|     } else { | ||||
|       fprintf(stderr, "Error reading: %d\n", result); | ||||
|       return -1; | ||||
|     } | ||||
|   } else if (strcmp(argv[1], "write") == 0) { | ||||
|     if (argc < 5) { | ||||
|       fprintf(stderr, "Error: write value required\n"); | ||||
|       return 1; | ||||
|     } | ||||
| 
 | ||||
|     const char* value = argv[4]; | ||||
|     const size_t value_size = strlen(value); | ||||
|     int result = write_db_value(params_path, key, value, value_size); | ||||
|     if (result >= 0) { | ||||
|       fprintf(stdout, "Wrote %s to %s\n", value, key); | ||||
|     } else { | ||||
|       fprintf(stderr, "Error writing: %d\n", result); | ||||
|       return -1; | ||||
|     } | ||||
|   } else if (strcmp(argv[1], "read_block") == 0) { | ||||
|     char* value; | ||||
|     size_t value_size; | ||||
|     read_db_value_blocking(params_path, key, &value, &value_size); | ||||
|     fprintf(stdout, "Read %zu bytes: ", value_size); | ||||
|     fwrite(value, 1, value_size, stdout); | ||||
|     fprintf(stdout, "\n"); | ||||
|     free(value); | ||||
|   } else { | ||||
|     printf(kUsage, argv[0]); | ||||
|     return 1; | ||||
|   } | ||||
| 
 | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| // BUILD:
 | ||||
| // $ gcc -I$HOME/one selfdrive/common/test_params.c selfdrive/common/params.c selfdrive/common/util.c -o ./test_params
 | ||||
| // $ seq 0 100000 | xargs -P20 -I{} ./test_params write /data/params DongleId {} && sleep 0.1 &
 | ||||
| // $ while ./test_params read /data/params DongleId; do sleep 0.05; done
 | ||||
| @ -0,0 +1,54 @@ | ||||
| #ifndef COMMON_TIMING_H | ||||
| #define COMMON_TIMING_H | ||||
| 
 | ||||
| #include <stdint.h> | ||||
| #include <time.h> | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
| #define CLOCK_BOOTTIME CLOCK_MONOTONIC | ||||
| #endif | ||||
| 
 | ||||
| static inline uint64_t nanos_since_boot() { | ||||
|   struct timespec t; | ||||
|   clock_gettime(CLOCK_BOOTTIME, &t); | ||||
|   return t.tv_sec * 1000000000ULL + t.tv_nsec; | ||||
| } | ||||
| 
 | ||||
| static inline double millis_since_boot() { | ||||
|   struct timespec t; | ||||
|   clock_gettime(CLOCK_BOOTTIME, &t); | ||||
|   return t.tv_sec * 1000.0 + t.tv_nsec * 1e-6; | ||||
| } | ||||
| 
 | ||||
| static inline double seconds_since_boot() { | ||||
|   struct timespec t; | ||||
|   clock_gettime(CLOCK_BOOTTIME, &t); | ||||
|   return (double)t.tv_sec + t.tv_nsec * 1e-9;; | ||||
| } | ||||
| 
 | ||||
| static inline uint64_t nanos_since_epoch() { | ||||
|   struct timespec t; | ||||
|   clock_gettime(CLOCK_REALTIME, &t); | ||||
|   return t.tv_sec * 1000000000ULL + t.tv_nsec; | ||||
| } | ||||
| 
 | ||||
| static inline double seconds_since_epoch() { | ||||
|   struct timespec t; | ||||
|   clock_gettime(CLOCK_REALTIME, &t); | ||||
|   return (double)t.tv_sec + t.tv_nsec * 1e-9; | ||||
| } | ||||
| 
 | ||||
| // you probably should use nanos_since_boot instead
 | ||||
| static inline uint64_t nanos_monotonic() { | ||||
|   struct timespec t; | ||||
|   clock_gettime(CLOCK_MONOTONIC, &t); | ||||
|   return t.tv_sec * 1000000000ULL + t.tv_nsec; | ||||
| } | ||||
| 
 | ||||
| static inline uint64_t nanos_monotonic_raw() { | ||||
|   struct timespec t; | ||||
|   clock_gettime(CLOCK_MONOTONIC_RAW, &t); | ||||
|   return t.tv_sec * 1000000000ULL + t.tv_nsec; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,120 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdbool.h> | ||||
| #include <assert.h> | ||||
| #include <unistd.h> | ||||
| #include <fcntl.h> | ||||
| #include <dirent.h> | ||||
| #include <sys/poll.h> | ||||
| #include <linux/input.h> | ||||
| 
 | ||||
| #include "touch.h" | ||||
| 
 | ||||
| static int find_dev() { | ||||
|   int err; | ||||
| 
 | ||||
|   int ret = -1; | ||||
| 
 | ||||
|   DIR *dir = opendir("/dev/input"); | ||||
|   assert(dir); | ||||
|   struct dirent* de = NULL; | ||||
|   while ((de = readdir(dir))) { | ||||
|     if (strncmp(de->d_name, "event", 5)) continue; | ||||
| 
 | ||||
|     int fd = openat(dirfd(dir), de->d_name, O_RDONLY); | ||||
|     assert(fd >= 0); | ||||
| 
 | ||||
|     unsigned char ev_bits[KEY_MAX / 8 + 1]; | ||||
|     err = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(ev_bits)), ev_bits); | ||||
|     assert(err >= 0); | ||||
| 
 | ||||
|     const int x_key = ABS_MT_POSITION_X / 8; | ||||
|     const int y_key = ABS_MT_POSITION_Y / 8; | ||||
|     if ((ev_bits[x_key] & (ABS_MT_POSITION_X - x_key)) && | ||||
|         (ev_bits[y_key] & (ABS_MT_POSITION_Y - y_key))) { | ||||
|       ret = fd; | ||||
|       break; | ||||
|     } | ||||
|     close(fd); | ||||
|   } | ||||
|   closedir(dir); | ||||
| 
 | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| void touch_init(TouchState *s) { | ||||
|   s->fd = find_dev(); | ||||
|   assert(s->fd >= 0); | ||||
| } | ||||
| 
 | ||||
| int touch_poll(TouchState *s, int* out_x, int* out_y, int timeout) { | ||||
|   assert(out_x && out_y); | ||||
|   bool up = false; | ||||
|   while (true) { | ||||
|     struct pollfd polls[] = {{ | ||||
|       .fd = s->fd, | ||||
|       .events = POLLIN, | ||||
|     }}; | ||||
|     int err = poll(polls, 1, timeout); | ||||
|     if (err < 0) { | ||||
|       return -1; | ||||
|     } | ||||
|     if (!(polls[0].revents & POLLIN)) { | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     struct input_event event; | ||||
|     err = read(polls[0].fd, &event, sizeof(event)); | ||||
|     if (err < sizeof(event)) { | ||||
|       return -1; | ||||
|     } | ||||
| 
 | ||||
|     switch (event.type) { | ||||
|     case EV_ABS: | ||||
|       if (event.code == ABS_MT_POSITION_X) { | ||||
|         s->last_x = event.value; | ||||
|       } else if (event.code == ABS_MT_POSITION_Y) { | ||||
|         s->last_y = event.value; | ||||
|       } else if (event.code == ABS_MT_TRACKING_ID && event.value != -1) { | ||||
|         up = true; | ||||
|       } | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
|   if (up) { | ||||
|     // adjust for flippening
 | ||||
|     *out_x = s->last_y; | ||||
|     *out_y = 1080 - s->last_x; | ||||
|   } | ||||
|   return up; | ||||
| } | ||||
| 
 | ||||
| int touch_read(TouchState *s, int* out_x, int* out_y) { | ||||
|   assert(out_x && out_y); | ||||
|   struct input_event event; | ||||
|   int err = read(s->fd, &event, sizeof(event)); | ||||
|   if (err < sizeof(event)) { | ||||
|     return -1; | ||||
|   } | ||||
|   bool up = false; | ||||
|   switch (event.type) { | ||||
|   case EV_ABS: | ||||
|     if (event.code == ABS_MT_POSITION_X) { | ||||
|       s->last_x = event.value; | ||||
|     } else if (event.code == ABS_MT_POSITION_Y) { | ||||
|       s->last_y = event.value; | ||||
|     } | ||||
|     up = true; | ||||
|     break; | ||||
|   default: | ||||
|     break; | ||||
|   } | ||||
|   if (up) { | ||||
|     // adjust for flippening
 | ||||
|     *out_x = s->last_y; | ||||
|     *out_y = 1080 - s->last_x; | ||||
|   } | ||||
|   return up; | ||||
| } | ||||
| @ -0,0 +1,21 @@ | ||||
| #ifndef TOUCH_H | ||||
| #define TOUCH_H | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct TouchState { | ||||
|   int fd; | ||||
|   int last_x, last_y; | ||||
| } TouchState; | ||||
| 
 | ||||
| void touch_init(TouchState *s); | ||||
| int touch_poll(TouchState *s, int *out_x, int *out_y, int timeout); | ||||
| int touch_read(TouchState *s, int* out_x, int* out_y); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,61 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <assert.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| #ifdef __linux__ | ||||
| #include <sys/prctl.h> | ||||
| #include <sys/syscall.h> | ||||
| #include <sched.h> | ||||
| #endif | ||||
| 
 | ||||
| void* read_file(const char* path, size_t* out_len) { | ||||
|   FILE* f = fopen(path, "r"); | ||||
|   if (!f) { | ||||
|     return NULL; | ||||
|   } | ||||
|   fseek(f, 0, SEEK_END); | ||||
|   long f_len = ftell(f); | ||||
|   rewind(f); | ||||
| 
 | ||||
|   char* buf = (char*)calloc(f_len, 1); | ||||
|   assert(buf); | ||||
| 
 | ||||
|   size_t num_read = fread(buf, f_len, 1, f); | ||||
|   fclose(f); | ||||
| 
 | ||||
|   if (num_read != 1) { | ||||
|     free(buf); | ||||
|     return NULL; | ||||
|   } | ||||
| 
 | ||||
|   if (out_len) { | ||||
|     *out_len = f_len; | ||||
|   } | ||||
| 
 | ||||
|   return buf; | ||||
| } | ||||
| 
 | ||||
| void set_thread_name(const char* name) { | ||||
| #ifdef __linux__ | ||||
|   // pthread_setname_np is dumb (fails instead of truncates)
 | ||||
|   prctl(PR_SET_NAME, (unsigned long)name, 0, 0, 0); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| int set_realtime_priority(int level) { | ||||
| #ifdef __linux__ | ||||
| 
 | ||||
|   long tid = syscall(SYS_gettid); | ||||
| 
 | ||||
|   // should match python using chrt
 | ||||
|   struct sched_param sa; | ||||
|   memset(&sa, 0, sizeof(sa)); | ||||
|   sa.sched_priority = level; | ||||
|   return sched_setscheduler(tid, SCHED_FIFO, &sa); | ||||
| #else | ||||
|   return -1; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| @ -0,0 +1,48 @@ | ||||
| #ifndef COMMON_UTIL_H | ||||
| #define COMMON_UTIL_H | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| #ifndef __cplusplus | ||||
| 
 | ||||
| #define min(a,b) \ | ||||
|  ({ __typeof__ (a) _a = (a); \
 | ||||
|      __typeof__ (b) _b = (b); \
 | ||||
|    _a < _b ? _a : _b; }) | ||||
| 
 | ||||
| #define max(a,b) \ | ||||
|  ({ __typeof__ (a) _a = (a); \
 | ||||
|      __typeof__ (b) _b = (b); \
 | ||||
|    _a > _b ? _a : _b; }) | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| #define clamp(a,b,c) \ | ||||
|  ({ __typeof__ (a) _a = (a); \
 | ||||
|      __typeof__ (b) _b = (b); \
 | ||||
|      __typeof__ (c) _c = (c); \
 | ||||
|    _a < _b ? _b : (_a > _c ? _c : _a); }) | ||||
| 
 | ||||
| #define ARRAYSIZE(x) (sizeof(x)/sizeof(x[0])) | ||||
| 
 | ||||
| #define ALIGN(x, align) (((x) + (align)-1) & ~((align)-1)) | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| // Reads a file into a newly allocated buffer.
 | ||||
| //
 | ||||
| // Returns NULL on failure, otherwise the NULL-terminated file contents.
 | ||||
| // The result must be freed by the caller.
 | ||||
| void* read_file(const char* path, size_t* out_len); | ||||
| 
 | ||||
| void set_thread_name(const char* name); | ||||
| 
 | ||||
| int set_realtime_priority(int level); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,66 @@ | ||||
| #ifndef UTILPP_H | ||||
| #define UTILPP_H | ||||
| 
 | ||||
| #include <cstdio> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| #include <string> | ||||
| #include <memory> | ||||
| #include <sstream> | ||||
| #include <fstream> | ||||
| 
 | ||||
| namespace util { | ||||
| 
 | ||||
| inline bool starts_with(std::string s, std::string prefix) { | ||||
|   return s.compare(0, prefix.size(), prefix) == 0; | ||||
| } | ||||
| 
 | ||||
| template<typename ... Args> | ||||
| inline std::string string_format( const std::string& format, Args ... args ) { | ||||
|     size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; | ||||
|     std::unique_ptr<char[]> buf( new char[ size ] ); | ||||
|     snprintf( buf.get(), size, format.c_str(), args ... ); | ||||
|     return std::string( buf.get(), buf.get() + size - 1 ); | ||||
| } | ||||
| 
 | ||||
| inline std::string read_file(std::string fn) { | ||||
|   std::ifstream t(fn); | ||||
|   std::stringstream buffer; | ||||
|   buffer << t.rdbuf(); | ||||
|   return buffer.str(); | ||||
| } | ||||
| 
 | ||||
| inline std::string tohex(const uint8_t* buf, size_t buf_size) { | ||||
|   std::unique_ptr<char[]> hexbuf(new char[buf_size*2+1]); | ||||
|   for (size_t i=0; i < buf_size; i++) { | ||||
|     sprintf(&hexbuf[i*2], "%02x", buf[i]); | ||||
|   } | ||||
|   hexbuf[buf_size*2] = 0; | ||||
|   return std::string(hexbuf.get(), hexbuf.get() + buf_size*2); | ||||
| } | ||||
| 
 | ||||
| inline std::string base_name(std::string const & path) { | ||||
|   size_t pos = path.find_last_of("/"); | ||||
|   if (pos == std::string::npos) return path; | ||||
|   return path.substr(pos + 1); | ||||
| } | ||||
| 
 | ||||
| inline std::string dir_name(std::string const & path) { | ||||
|   size_t pos = path.find_last_of("/"); | ||||
|   if (pos == std::string::npos) return ""; | ||||
|   return path.substr(0, pos); | ||||
| } | ||||
| 
 | ||||
| inline std::string readlink(std::string path) { | ||||
|   char buff[4096]; | ||||
|   ssize_t len = ::readlink(path.c_str(), buff, sizeof(buff)-1); | ||||
|   if (len != -1) { | ||||
|     buff[len] = '\0'; | ||||
|     return std::string(buff); | ||||
|   } | ||||
|   return ""; | ||||
| } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1 @@ | ||||
| #define COMMA_VERSION "0.7.2" | ||||
| @ -0,0 +1,39 @@ | ||||
| #ifndef IONBUF_H | ||||
| #define IONBUF_H | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
| #include <OpenCL/cl.h> | ||||
| #else | ||||
| #include <CL/cl.h> | ||||
| #endif | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef struct VisionBuf { | ||||
|   size_t len; | ||||
|   void* addr; | ||||
|   int handle; | ||||
|   int fd; | ||||
| 
 | ||||
|   cl_context ctx; | ||||
|   cl_device_id device_id; | ||||
|   cl_mem buf_cl; | ||||
|   cl_command_queue copy_q; | ||||
| } VisionBuf; | ||||
| 
 | ||||
| #define VISIONBUF_SYNC_FROM_DEVICE 0 | ||||
| #define VISIONBUF_SYNC_TO_DEVICE 1 | ||||
| 
 | ||||
| VisionBuf visionbuf_allocate(size_t len); | ||||
| VisionBuf visionbuf_allocate_cl(size_t len, cl_device_id device_id, cl_context ctx, cl_mem *out_mem); | ||||
| cl_mem visionbuf_to_cl(const VisionBuf* buf, cl_device_id device_id, cl_context ctx); | ||||
| void visionbuf_sync(const VisionBuf* buf, int dir); | ||||
| void visionbuf_free(const VisionBuf* buf); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,110 @@ | ||||
| #include "visionbuf.h" | ||||
| 
 | ||||
| #include <stdio.h> | ||||
| #include <fcntl.h> | ||||
| #include <assert.h> | ||||
| #include <stdlib.h> | ||||
| #include <unistd.h> | ||||
| #include <sys/mman.h> | ||||
| #include <sys/types.h> | ||||
| 
 | ||||
| #ifdef __APPLE__ | ||||
| #include <OpenCL/cl.h> | ||||
| #else | ||||
| #include <CL/cl.h> | ||||
| #endif | ||||
| 
 | ||||
| int offset = 0; | ||||
| void *malloc_with_fd(size_t len, int *fd) { | ||||
|   char full_path[0x100]; | ||||
|   snprintf(full_path, sizeof(full_path)-1, "/dev/shm/visionbuf_%d_%d", getpid(), offset++); | ||||
|   *fd = open(full_path, O_RDWR | O_CREAT, 0777); | ||||
|   unlink(full_path); | ||||
|   ftruncate(*fd, len); | ||||
|   void *addr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0); | ||||
|   return addr; | ||||
| } | ||||
| 
 | ||||
| VisionBuf visionbuf_allocate(size_t len) { | ||||
|   // const size_t alignment = 4096;
 | ||||
|   // void* addr = aligned_alloc(alignment, alignment * ((len - 1) / alignment + 1));
 | ||||
|   //void* addr = calloc(1, len);
 | ||||
| 
 | ||||
|   int fd; | ||||
|   void *addr = malloc_with_fd(len, &fd); | ||||
| 
 | ||||
|   return (VisionBuf){ | ||||
|       .len = len, .addr = addr, .handle = 1, .fd = fd, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| cl_mem visionbuf_to_cl(const VisionBuf* buf, cl_device_id device_id, cl_context ctx) { | ||||
|   // HACK because this platform is just for convenience
 | ||||
|   VisionBuf *w_buf = (VisionBuf*)buf; | ||||
|   cl_mem ret; | ||||
|   *w_buf = visionbuf_allocate_cl(buf->len, device_id, ctx, &ret); | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| VisionBuf visionbuf_allocate_cl(size_t len, cl_device_id device_id, cl_context ctx, cl_mem *out_mem) { | ||||
|   int err; | ||||
|   assert(out_mem); | ||||
| 
 | ||||
| #if __OPENCL_VERSION__ >= 200 | ||||
|   void* host_ptr = | ||||
|       clSVMAlloc(ctx, CL_MEM_READ_WRITE | CL_MEM_SVM_FINE_GRAIN_BUFFER, len, 0); | ||||
|   assert(host_ptr); | ||||
| #else | ||||
|   int fd; | ||||
|   void* host_ptr = malloc_with_fd(len, &fd); | ||||
| 
 | ||||
|   cl_command_queue q = clCreateCommandQueue(ctx, device_id, 0, &err); | ||||
|   assert(err == 0); | ||||
| #endif | ||||
| 
 | ||||
|   cl_mem mem = clCreateBuffer(ctx, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, len, host_ptr, &err); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   *out_mem = mem; | ||||
| 
 | ||||
|   return (VisionBuf){ | ||||
|       .len = len, .addr = host_ptr, .handle = 0, .fd = fd, | ||||
|       .device_id = device_id, .ctx = ctx, .buf_cl = mem, | ||||
| 
 | ||||
| #if __OPENCL_VERSION__ < 200 | ||||
|       .copy_q = q, | ||||
| #endif | ||||
| 
 | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| void visionbuf_sync(const VisionBuf* buf, int dir) { | ||||
|   int err = 0; | ||||
|   if (!buf->buf_cl) return; | ||||
| 
 | ||||
| #if __OPENCL_VERSION__ < 200 | ||||
|   if (dir == VISIONBUF_SYNC_FROM_DEVICE) { | ||||
|     err = clEnqueueReadBuffer(buf->copy_q, buf->buf_cl, CL_FALSE, 0, buf->len, buf->addr, 0, NULL, NULL); | ||||
|   } else { | ||||
|     err = clEnqueueWriteBuffer(buf->copy_q, buf->buf_cl, CL_FALSE, 0, buf->len, buf->addr, 0, NULL, NULL); | ||||
|   } | ||||
|   assert(err == 0); | ||||
|   clFinish(buf->copy_q); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void visionbuf_free(const VisionBuf* buf) { | ||||
|   if (buf->handle) { | ||||
|     munmap(buf->addr, buf->len); | ||||
|     close(buf->fd); | ||||
|   } else { | ||||
|     int err = clReleaseMemObject(buf->buf_cl); | ||||
|     assert(err == 0); | ||||
| #if __OPENCL_VERSION__ >= 200 | ||||
|     clSVMFree(buf->ctx, buf->addr); | ||||
| #else | ||||
|     munmap(buf->addr, buf->len); | ||||
|     close(buf->fd); | ||||
| #endif | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,141 @@ | ||||
| #include <stdlib.h> | ||||
| #include <stdio.h> | ||||
| #include <assert.h> | ||||
| #include <sys/mman.h> | ||||
| #include <sys/ioctl.h> | ||||
| 
 | ||||
| #include <linux/ion.h> | ||||
| #include <CL/cl_ext.h> | ||||
| 
 | ||||
| #include <msm_ion.h> | ||||
| 
 | ||||
| #include "visionbuf.h" | ||||
| 
 | ||||
| 
 | ||||
| // just hard-code these for convenience
 | ||||
| // size_t device_page_size = 0;
 | ||||
| // clGetDeviceInfo(device_id, CL_DEVICE_PAGE_SIZE_QCOM,
 | ||||
| //                 sizeof(device_page_size), &device_page_size,
 | ||||
| //                 NULL);
 | ||||
| 
 | ||||
| // size_t padding_cl = 0;
 | ||||
| // clGetDeviceInfo(device_id, CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM,
 | ||||
| //                 sizeof(padding_cl), &padding_cl,
 | ||||
| //                 NULL);
 | ||||
| #define DEVICE_PAGE_SIZE_CL 4096 | ||||
| #define PADDING_CL 0 | ||||
| 
 | ||||
| static int ion_fd = -1; | ||||
| static void ion_init() { | ||||
|   if (ion_fd == -1) { | ||||
|     ion_fd = open("/dev/ion", O_RDWR | O_NONBLOCK); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| VisionBuf visionbuf_allocate(size_t len) { | ||||
|   int err; | ||||
| 
 | ||||
|   ion_init(); | ||||
| 
 | ||||
|   struct ion_allocation_data ion_alloc = {0}; | ||||
|   ion_alloc.len = len + PADDING_CL; | ||||
|   ion_alloc.align = 4096; | ||||
|   ion_alloc.heap_id_mask = 1 << ION_IOMMU_HEAP_ID; | ||||
|   ion_alloc.flags = ION_FLAG_CACHED; | ||||
| 
 | ||||
|   err = ioctl(ion_fd, ION_IOC_ALLOC, &ion_alloc); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   struct ion_fd_data ion_fd_data = {0}; | ||||
|   ion_fd_data.handle = ion_alloc.handle; | ||||
|   err = ioctl(ion_fd, ION_IOC_SHARE, &ion_fd_data); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   void *addr = mmap(NULL, ion_alloc.len, | ||||
|                     PROT_READ | PROT_WRITE, | ||||
|                     MAP_SHARED, ion_fd_data.fd, 0); | ||||
|   assert(addr != MAP_FAILED); | ||||
| 
 | ||||
|   memset(addr, 0, ion_alloc.len); | ||||
| 
 | ||||
|   return (VisionBuf){ | ||||
|     .len = len, | ||||
|     .addr = addr, | ||||
|     .handle = ion_alloc.handle, | ||||
|     .fd = ion_fd_data.fd, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| VisionBuf visionbuf_allocate_cl(size_t len, cl_device_id device_id, cl_context ctx, cl_mem *out_mem) { | ||||
|   VisionBuf r = visionbuf_allocate(len); | ||||
|   *out_mem = visionbuf_to_cl(&r, device_id, ctx); | ||||
|   return r; | ||||
| } | ||||
| 
 | ||||
| cl_mem visionbuf_to_cl(const VisionBuf* buf, cl_device_id device_id, cl_context ctx) { | ||||
|   int err = 0; | ||||
| 
 | ||||
|   assert(((uintptr_t)buf->addr % DEVICE_PAGE_SIZE_CL) == 0); | ||||
| 
 | ||||
|   cl_mem_ion_host_ptr ion_cl = {0}; | ||||
|   ion_cl.ext_host_ptr.allocation_type = CL_MEM_ION_HOST_PTR_QCOM; | ||||
|   ion_cl.ext_host_ptr.host_cache_policy = CL_MEM_HOST_UNCACHED_QCOM; | ||||
|   ion_cl.ion_filedesc = buf->fd; | ||||
|   ion_cl.ion_hostptr = buf->addr; | ||||
| 
 | ||||
|   cl_mem mem = clCreateBuffer(ctx, | ||||
|                               CL_MEM_USE_HOST_PTR | CL_MEM_EXT_HOST_PTR_QCOM, | ||||
|                               buf->len, &ion_cl, &err); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   return mem; | ||||
| } | ||||
| 
 | ||||
| void visionbuf_sync(const VisionBuf* buf, int dir) { | ||||
|   int err; | ||||
| 
 | ||||
|   struct ion_fd_data fd_data = {0}; | ||||
|   fd_data.fd = buf->fd; | ||||
|   err = ioctl(ion_fd, ION_IOC_IMPORT, &fd_data); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   struct ion_flush_data flush_data = {0}; | ||||
|   flush_data.handle = fd_data.handle; | ||||
|   flush_data.vaddr = buf->addr; | ||||
|   flush_data.offset = 0; | ||||
|   flush_data.length = buf->len; | ||||
| 
 | ||||
|   // ION_IOC_INV_CACHES ~= DMA_FROM_DEVICE
 | ||||
|   // ION_IOC_CLEAN_CACHES ~= DMA_TO_DEVICE
 | ||||
|   // ION_IOC_CLEAN_INV_CACHES ~= DMA_BIDIRECTIONAL
 | ||||
| 
 | ||||
|   struct ion_custom_data custom_data = {0}; | ||||
| 
 | ||||
|   switch (dir) { | ||||
|   case VISIONBUF_SYNC_FROM_DEVICE: | ||||
|     custom_data.cmd = ION_IOC_INV_CACHES; | ||||
|     break; | ||||
|   case VISIONBUF_SYNC_TO_DEVICE: | ||||
|     custom_data.cmd = ION_IOC_CLEAN_CACHES; | ||||
|     break; | ||||
|   default: | ||||
|     assert(0); | ||||
|   } | ||||
| 
 | ||||
|   custom_data.arg = (unsigned long)&flush_data; | ||||
|   err = ioctl(ion_fd, ION_IOC_CUSTOM, &custom_data); | ||||
|   assert(err == 0); | ||||
| 
 | ||||
|   struct ion_handle_data handle_data = {0}; | ||||
|   handle_data.handle = fd_data.handle; | ||||
|   err = ioctl(ion_fd, ION_IOC_FREE, &handle_data); | ||||
|   assert(err == 0); | ||||
| } | ||||
| 
 | ||||
| void visionbuf_free(const VisionBuf* buf) { | ||||
|   struct ion_handle_data handle_data = { | ||||
|     .handle = buf->handle, | ||||
|   }; | ||||
|   int ret = ioctl(ion_fd, ION_IOC_FREE, &handle_data); | ||||
|   assert(ret == 0); | ||||
| } | ||||
| @ -0,0 +1,122 @@ | ||||
| #include <cassert> | ||||
| 
 | ||||
| #ifdef QCOM | ||||
| #include <system/graphics.h> | ||||
| #include <ui/GraphicBuffer.h> | ||||
| #include <ui/PixelFormat.h> | ||||
| #include <gralloc_priv.h> | ||||
| 
 | ||||
| #include <GLES3/gl3.h> | ||||
| #define GL_GLEXT_PROTOTYPES | ||||
| #include <GLES2/gl2ext.h> | ||||
| 
 | ||||
| #include <EGL/egl.h> | ||||
| #define EGL_EGLEXT_PROTOTYPES | ||||
| #include <EGL/eglext.h> | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| #include "common/util.h" | ||||
| #include "common/visionbuf.h" | ||||
| 
 | ||||
| #include "common/visionimg.h" | ||||
| 
 | ||||
| #ifdef QCOM | ||||
| 
 | ||||
| using namespace android; | ||||
| 
 | ||||
| // from libadreno_utils.so
 | ||||
| extern "C" void compute_aligned_width_and_height(int width, | ||||
|                                       int height, | ||||
|                                       int bpp, | ||||
|                                       int tile_mode, | ||||
|                                       int raster_mode, | ||||
|                                       int padding_threshold, | ||||
|                                       int *aligned_w, | ||||
|                                       int *aligned_h); | ||||
| #endif | ||||
| 
 | ||||
| void visionimg_compute_aligned_width_and_height(int width, int height, int *aligned_w, int *aligned_h) { | ||||
| #ifdef QCOM | ||||
|   compute_aligned_width_and_height(ALIGN(width, 32), ALIGN(height, 32), 3, 0, 0, 512, aligned_w, aligned_h); | ||||
| #else | ||||
|   *aligned_w = width; *aligned_h = height; | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| VisionImg visionimg_alloc_rgb24(int width, int height, VisionBuf *out_buf) { | ||||
|   int aligned_w = 0, aligned_h = 0; | ||||
|   visionimg_compute_aligned_width_and_height(width, height, &aligned_w, &aligned_h); | ||||
| 
 | ||||
|   int stride = aligned_w * 3; | ||||
|   size_t size = aligned_w * aligned_h * 3; | ||||
| 
 | ||||
|   VisionBuf buf = visionbuf_allocate(size); | ||||
| 
 | ||||
|   *out_buf = buf; | ||||
| 
 | ||||
|   return (VisionImg){ | ||||
|     .fd = buf.fd, | ||||
|     .format = VISIONIMG_FORMAT_RGB24, | ||||
|     .width = width, | ||||
|     .height = height, | ||||
|     .stride = stride, | ||||
|     .size = size, | ||||
|     .bpp = 3, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| #ifdef QCOM | ||||
| 
 | ||||
| EGLClientBuffer visionimg_to_egl(const VisionImg *img, void **pph) { | ||||
|   assert((img->size % img->stride) == 0); | ||||
|   assert((img->stride % img->bpp) == 0); | ||||
| 
 | ||||
|   int format = 0; | ||||
|   if (img->format == VISIONIMG_FORMAT_RGB24) { | ||||
|     format = HAL_PIXEL_FORMAT_RGB_888; | ||||
|   } else { | ||||
|     assert(false); | ||||
|   } | ||||
| 
 | ||||
|   private_handle_t* hnd = new private_handle_t(img->fd, img->size, | ||||
|                              private_handle_t::PRIV_FLAGS_USES_ION|private_handle_t::PRIV_FLAGS_FRAMEBUFFER, | ||||
|                              0, format, | ||||
|                              img->stride/img->bpp, img->size/img->stride, | ||||
|                              img->width, img->height); | ||||
| 
 | ||||
|   GraphicBuffer* gb = new GraphicBuffer(img->width, img->height, (PixelFormat)format, | ||||
|                                         GraphicBuffer::USAGE_HW_TEXTURE, img->stride/img->bpp, hnd, false); | ||||
|   // GraphicBuffer is ref counted by EGLClientBuffer(ANativeWindowBuffer), no need and not possible to release.
 | ||||
|   *pph = hnd; | ||||
|   return (EGLClientBuffer) gb->getNativeBuffer(); | ||||
| } | ||||
| 
 | ||||
| GLuint visionimg_to_gl(const VisionImg *img, EGLImageKHR *pkhr, void **pph) { | ||||
| 
 | ||||
|   EGLClientBuffer buf = visionimg_to_egl(img, pph); | ||||
| 
 | ||||
|   EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); | ||||
|   assert(display != EGL_NO_DISPLAY); | ||||
| 
 | ||||
|   EGLint img_attrs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE }; | ||||
|   EGLImageKHR image = eglCreateImageKHR(display, EGL_NO_CONTEXT, | ||||
|                                         EGL_NATIVE_BUFFER_ANDROID, buf, img_attrs); | ||||
|   assert(image != EGL_NO_IMAGE_KHR); | ||||
| 
 | ||||
|   GLuint tex = 0; | ||||
|   glGenTextures(1, &tex); | ||||
|   glBindTexture(GL_TEXTURE_2D, tex); | ||||
|   glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image); | ||||
|   *pkhr = image; | ||||
|   return tex; | ||||
| } | ||||
| 
 | ||||
| void visionimg_destroy_gl(EGLImageKHR khr, void *ph) { | ||||
|   EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); | ||||
|   assert(display != EGL_NO_DISPLAY); | ||||
|   eglDestroyImageKHR(display, khr); | ||||
|   delete (private_handle_t*)ph; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,36 @@ | ||||
| #ifndef VISIONIMG_H | ||||
| #define VISIONIMG_H | ||||
| 
 | ||||
| #include "common/visionbuf.h" | ||||
| 
 | ||||
| #include <GLES3/gl3.h> | ||||
| #include <EGL/egl.h> | ||||
| #include <EGL/eglext.h> | ||||
| #undef Status | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| #define VISIONIMG_FORMAT_RGB24 1 | ||||
| 
 | ||||
| typedef struct VisionImg { | ||||
|   int fd; | ||||
|   int format; | ||||
|   int width, height, stride; | ||||
|   int bpp; | ||||
|   size_t size; | ||||
| } VisionImg; | ||||
| 
 | ||||
| void visionimg_compute_aligned_width_and_height(int width, int height, int *aligned_w, int *aligned_h); | ||||
| VisionImg visionimg_alloc_rgb24(int width, int height, VisionBuf *out_buf); | ||||
| 
 | ||||
| EGLClientBuffer visionimg_to_egl(const VisionImg *img, void **pph); | ||||
| GLuint visionimg_to_gl(const VisionImg *img, EGLImageKHR *pkhr, void **pph); | ||||
| void visionimg_destroy_gl(EGLImageKHR khr, void *ph); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| }  // extern "C"
 | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,194 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| #include <unistd.h> | ||||
| #include <assert.h> | ||||
| #include <errno.h> | ||||
| 
 | ||||
| #include <sys/mman.h> | ||||
| #include <sys/socket.h> | ||||
| #include <sys/un.h> | ||||
| 
 | ||||
| #include "ipc.h" | ||||
| 
 | ||||
| #include "visionipc.h" | ||||
| 
 | ||||
| typedef struct VisionPacketWire { | ||||
|   int type; | ||||
|   VisionPacketData d; | ||||
| } VisionPacketWire; | ||||
| 
 | ||||
| int vipc_connect() { | ||||
|   return ipc_connect(VIPC_SOCKET_PATH); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| int vipc_recv(int fd, VisionPacket *out_p) { | ||||
|   VisionPacketWire p = {0}; | ||||
|   VisionPacket p2 = {0}; | ||||
|   int ret = ipc_sendrecv_with_fds(false, fd, &p, sizeof(p), (int*)p2.fds, VIPC_MAX_FDS, &p2.num_fds); | ||||
|   if (ret < 0) { | ||||
|     printf("vipc_recv err: %s\n", strerror(errno)); | ||||
|   } else { | ||||
|     p2.type = p.type; | ||||
|     p2.d = p.d; | ||||
|     *out_p = p2; | ||||
|   } | ||||
|   //printf("%d = vipc_recv(%d, %d): %d %d %d %u\n", ret, fd, p2.num_fds, out_p->d.stream_bufs.type, out_p->d.stream_bufs.width, out_p->d.stream_bufs.height, out_p->d.stream_bufs.buf_len);
 | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| int vipc_send(int fd, const VisionPacket *p2) { | ||||
|   assert(p2->num_fds <= VIPC_MAX_FDS); | ||||
| 
 | ||||
|   VisionPacketWire p = { | ||||
|     .type = p2->type, | ||||
|     .d = p2->d, | ||||
|   }; | ||||
|   int ret = ipc_sendrecv_with_fds(true, fd, (void*)&p, sizeof(p), (int*)p2->fds, p2->num_fds, NULL); | ||||
|   //printf("%d = vipc_send(%d, %d): %d %d %d %u\n", ret, fd, p2->num_fds, p2->d.stream_bufs.type, p2->d.stream_bufs.width, p2->d.stream_bufs.height, p2->d.stream_bufs.buf_len);
 | ||||
|   return ret; | ||||
| } | ||||
| 
 | ||||
| void vipc_bufs_load(VIPCBuf *bufs, const VisionStreamBufs *stream_bufs, | ||||
|                      int num_fds, const int* fds) { | ||||
|   for (int i=0; i<num_fds; i++) { | ||||
|     if (bufs[i].addr) { | ||||
|       munmap(bufs[i].addr, bufs[i].len); | ||||
|       bufs[i].addr = NULL; | ||||
|       close(bufs[i].fd); | ||||
|     } | ||||
|     bufs[i].fd = fds[i]; | ||||
|     bufs[i].len = stream_bufs->buf_len; | ||||
|     bufs[i].addr = mmap(NULL, bufs[i].len, | ||||
|                         PROT_READ | PROT_WRITE, | ||||
|                         MAP_SHARED, bufs[i].fd, 0); | ||||
|     // printf("b %d %zu -> %p\n", bufs[i].fd, bufs[i].len, bufs[i].addr);
 | ||||
|     assert(bufs[i].addr != MAP_FAILED); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| int visionstream_init(VisionStream *s, VisionStreamType type, bool tbuffer, VisionStreamBufs *out_bufs_info) { | ||||
|   int err; | ||||
| 
 | ||||
|   memset(s, 0, sizeof(*s)); | ||||
| 
 | ||||
|   s->last_idx = -1; | ||||
| 
 | ||||
|   s->ipc_fd = vipc_connect(); | ||||
|   if (s->ipc_fd < 0) return -1; | ||||
| 
 | ||||
|   VisionPacket p = { | ||||
|     .type = VIPC_STREAM_SUBSCRIBE, | ||||
|     .d = { .stream_sub = { | ||||
|       .type = type, | ||||
|       .tbuffer = tbuffer, | ||||
|     }, }, | ||||
|   }; | ||||
|   err = vipc_send(s->ipc_fd, &p); | ||||
|   if (err < 0) { | ||||
|     close(s->ipc_fd); | ||||
|     return -1; | ||||
|   } | ||||
| 
 | ||||
|   VisionPacket rp; | ||||
|   err = vipc_recv(s->ipc_fd, &rp); | ||||
|   if (err <= 0) { | ||||
|     close(s->ipc_fd); | ||||
|     return -1; | ||||
|   } | ||||
|   assert(rp.type = VIPC_STREAM_BUFS); | ||||
|   assert(rp.d.stream_bufs.type == type); | ||||
| 
 | ||||
|   s->bufs_info = rp.d.stream_bufs; | ||||
| 
 | ||||
|   s->num_bufs = rp.num_fds; | ||||
|   s->bufs = calloc(s->num_bufs, sizeof(VIPCBuf)); | ||||
|   assert(s->bufs); | ||||
| 
 | ||||
|   vipc_bufs_load(s->bufs, &rp.d.stream_bufs, s->num_bufs, rp.fds); | ||||
| 
 | ||||
|   if (out_bufs_info) { | ||||
|     *out_bufs_info = s->bufs_info; | ||||
|   } | ||||
| 
 | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| void visionstream_release(VisionStream *s) { | ||||
|   int err; | ||||
|   if (s->last_idx >= 0) { | ||||
|     VisionPacket rep = { | ||||
|       .type = VIPC_STREAM_RELEASE, | ||||
|       .d = { .stream_rel = { | ||||
|         .type = s->last_type, | ||||
|         .idx = s->last_idx, | ||||
|       }} | ||||
|     }; | ||||
|     err = vipc_send(s->ipc_fd, &rep); | ||||
|     s->last_idx = -1; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| VIPCBuf* visionstream_get(VisionStream *s, VIPCBufExtra *out_extra) { | ||||
|   int err; | ||||
| 
 | ||||
|   VisionPacket rp; | ||||
|   err = vipc_recv(s->ipc_fd, &rp); | ||||
|   if (err <= 0) { | ||||
|     return NULL; | ||||
|   } | ||||
|   assert(rp.type == VIPC_STREAM_ACQUIRE); | ||||
| 
 | ||||
|   if (s->last_idx >= 0) { | ||||
|     VisionPacket rep = { | ||||
|       .type = VIPC_STREAM_RELEASE, | ||||
|       .d = { .stream_rel = { | ||||
|         .type = s->last_type, | ||||
|         .idx = s->last_idx, | ||||
|       }} | ||||
|     }; | ||||
|     err = vipc_send(s->ipc_fd, &rep); | ||||
|     if (err <= 0) { | ||||
|       return NULL; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   s->last_type = rp.d.stream_acq.type; | ||||
|   s->last_idx = rp.d.stream_acq.idx; | ||||
|   assert(s->last_idx < s->num_bufs); | ||||
| 
 | ||||
|   if (out_extra) { | ||||
|     *out_extra = rp.d.stream_acq.extra; | ||||
|   } | ||||
| 
 | ||||
|   return &s->bufs[s->last_idx]; | ||||
| } | ||||
| 
 | ||||
| void visionstream_destroy(VisionStream *s) { | ||||
|   int err; | ||||
| 
 | ||||
|   if (s->last_idx >= 0) { | ||||
|     VisionPacket rep = { | ||||
|       .type = VIPC_STREAM_RELEASE, | ||||
|       .d = { .stream_rel = { | ||||
|         .type = s->last_type, | ||||
|         .idx = s->last_idx, | ||||
|       }} | ||||
|     }; | ||||
|     err = vipc_send(s->ipc_fd, &rep); | ||||
|     s->last_idx = -1; | ||||
|   } | ||||
| 
 | ||||
|   for (int i=0; i<s->num_bufs; i++) { | ||||
|     if (s->bufs[i].addr) { | ||||
|       munmap(s->bufs[i].addr, s->bufs[i].len); | ||||
|       s->bufs[i].addr = NULL; | ||||
|       close(s->bufs[i].fd); | ||||
|     } | ||||
|   } | ||||
|   if (s->bufs) free(s->bufs); | ||||
|   close(s->ipc_fd); | ||||
| } | ||||
| @ -0,0 +1,113 @@ | ||||
| #ifndef VISIONIPC_H | ||||
| #define VISIONIPC_H | ||||
| 
 | ||||
| #include <stddef.h> | ||||
| #include <stdint.h> | ||||
| #include <stdbool.h> | ||||
| 
 | ||||
| #define VIPC_SOCKET_PATH "/tmp/vision_socket" | ||||
| #define VIPC_MAX_FDS 64 | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| typedef enum VisionIPCPacketType { | ||||
|   VIPC_INVALID = 0, | ||||
|   VIPC_STREAM_SUBSCRIBE, | ||||
|   VIPC_STREAM_BUFS, | ||||
|   VIPC_STREAM_ACQUIRE, | ||||
|   VIPC_STREAM_RELEASE, | ||||
| } VisionIPCPacketType; | ||||
| 
 | ||||
| typedef enum VisionStreamType { | ||||
|   VISION_STREAM_RGB_BACK, | ||||
|   VISION_STREAM_RGB_FRONT, | ||||
|   VISION_STREAM_YUV, | ||||
|   VISION_STREAM_YUV_FRONT, | ||||
|   VISION_STREAM_MAX, | ||||
| } VisionStreamType; | ||||
| 
 | ||||
| typedef struct VisionUIInfo { | ||||
|   int big_box_x, big_box_y; | ||||
|   int big_box_width, big_box_height; | ||||
|   int transformed_width, transformed_height; | ||||
| 
 | ||||
|   int front_box_x, front_box_y; | ||||
|   int front_box_width, front_box_height; | ||||
| } VisionUIInfo; | ||||
| 
 | ||||
| typedef struct VisionStreamBufs { | ||||
|   VisionStreamType type; | ||||
| 
 | ||||
|   int width, height, stride; | ||||
|   size_t buf_len; | ||||
| 
 | ||||
|   union { | ||||
|     VisionUIInfo ui_info; | ||||
|   } buf_info; | ||||
| } VisionStreamBufs; | ||||
| 
 | ||||
| typedef struct VIPCBufExtra { | ||||
|   // only for yuv
 | ||||
|   uint32_t frame_id; | ||||
|   uint64_t timestamp_eof; | ||||
| } VIPCBufExtra; | ||||
| 
 | ||||
| typedef union VisionPacketData { | ||||
|   struct { | ||||
|     VisionStreamType type; | ||||
|     bool tbuffer; | ||||
|   } stream_sub; | ||||
|   VisionStreamBufs stream_bufs; | ||||
|   struct { | ||||
|     VisionStreamType type; | ||||
|     int idx; | ||||
|     VIPCBufExtra extra; | ||||
|   } stream_acq; | ||||
|   struct { | ||||
|     VisionStreamType type; | ||||
|     int idx; | ||||
|   } stream_rel; | ||||
| } VisionPacketData; | ||||
| 
 | ||||
| typedef struct VisionPacket { | ||||
|   int type; | ||||
|   VisionPacketData d; | ||||
|   int num_fds; | ||||
|   int fds[VIPC_MAX_FDS]; | ||||
| } VisionPacket; | ||||
| 
 | ||||
| int vipc_connect(void); | ||||
| int vipc_recv(int fd, VisionPacket *out_p); | ||||
| int vipc_send(int fd, const VisionPacket *p); | ||||
| 
 | ||||
| typedef struct VIPCBuf { | ||||
|   int fd; | ||||
|   size_t len; | ||||
|   void* addr; | ||||
| } VIPCBuf; | ||||
| void vipc_bufs_load(VIPCBuf *bufs, const VisionStreamBufs *stream_bufs, | ||||
|                      int num_fds, const int* fds); | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| typedef struct VisionStream { | ||||
|   int ipc_fd; | ||||
|   int last_idx; | ||||
|   int last_type; | ||||
|   int num_bufs; | ||||
|   VisionStreamBufs bufs_info; | ||||
|   VIPCBuf *bufs; | ||||
| } VisionStream; | ||||
| 
 | ||||
| int visionstream_init(VisionStream *s, VisionStreamType type, bool tbuffer, VisionStreamBufs *out_bufs_info); | ||||
| void visionstream_release(VisionStream *s); | ||||
| VIPCBuf* visionstream_get(VisionStream *s, VIPCBufExtra *out_extra); | ||||
| void visionstream_destroy(VisionStream *s); | ||||
| 
 | ||||
| #ifdef __cplusplus | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,124 @@ | ||||
| 
 | ||||
| 
 | ||||
| int visionstream_init(VisionStream *s, VisionStreamType type, bool tbuffer, VisionStreamBufs *out_bufs_info) { | ||||
|   int err; | ||||
| 
 | ||||
|   memset(s, 0, sizeof(*s)); | ||||
| 
 | ||||
|   s->last_idx = -1; | ||||
| 
 | ||||
|   s->ipc_fd = vipc_connect(); | ||||
|   if (s->ipc_fd < 0) return -1; | ||||
| 
 | ||||
|   VisionPacket p = { | ||||
|     .type = VIPC_STREAM_SUBSCRIBE, | ||||
|     .d = { .stream_sub = { | ||||
|       .type = type, | ||||
|       .tbuffer = tbuffer, | ||||
|     }, }, | ||||
|   }; | ||||
|   err = vipc_send(s->ipc_fd, &p); | ||||
|   if (err < 0) { | ||||
|     close(s->ipc_fd); | ||||
|     return -1; | ||||
|   } | ||||
| 
 | ||||
|   VisionPacket rp; | ||||
|   err = vipc_recv(s->ipc_fd, &rp); | ||||
|   if (err <= 0) { | ||||
|     close(s->ipc_fd); | ||||
|     return -1; | ||||
|   } | ||||
|   assert(rp.type = VIPC_STREAM_BUFS); | ||||
|   assert(rp.d.stream_bufs.type == type); | ||||
| 
 | ||||
|   s->bufs_info = rp.d.stream_bufs; | ||||
| 
 | ||||
|   s->num_bufs = rp.num_fds; | ||||
|   s->bufs = calloc(s->num_bufs, sizeof(VIPCBuf)); | ||||
|   assert(s->bufs); | ||||
| 
 | ||||
|   vipc_bufs_load(s->bufs, &rp.d.stream_bufs, s->num_bufs, rp.fds); | ||||
| 
 | ||||
|   if (out_bufs_info) { | ||||
|     *out_bufs_info = s->bufs_info; | ||||
|   } | ||||
| 
 | ||||
|   return 0; | ||||
| } | ||||
| 
 | ||||
| void visionstream_release(VisionStream *s) { | ||||
|   int err; | ||||
|   if (s->last_idx >= 0) { | ||||
|     VisionPacket rep = { | ||||
|       .type = VIPC_STREAM_RELEASE, | ||||
|       .d = { .stream_rel = { | ||||
|         .type = s->last_type, | ||||
|         .idx = s->last_idx, | ||||
|       }} | ||||
|     }; | ||||
|     err = vipc_send(s->ipc_fd, &rep); | ||||
|     s->last_idx = -1; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| VIPCBuf* visionstream_get(VisionStream *s, VIPCBufExtra *out_extra) { | ||||
|   int err; | ||||
| 
 | ||||
|   VisionPacket rp; | ||||
|   err = vipc_recv(s->ipc_fd, &rp); | ||||
|   if (err <= 0) { | ||||
|     return NULL; | ||||
|   } | ||||
|   assert(rp.type == VIPC_STREAM_ACQUIRE); | ||||
| 
 | ||||
|   if (s->last_idx >= 0) { | ||||
|     VisionPacket rep = { | ||||
|       .type = VIPC_STREAM_RELEASE, | ||||
|       .d = { .stream_rel = { | ||||
|         .type = s->last_type, | ||||
|         .idx = s->last_idx, | ||||
|       }} | ||||
|     }; | ||||
|     err = vipc_send(s->ipc_fd, &rep); | ||||
|     if (err <= 0) { | ||||
|       return NULL; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   s->last_type = rp.d.stream_acq.type; | ||||
|   s->last_idx = rp.d.stream_acq.idx; | ||||
|   assert(s->last_idx < s->num_bufs); | ||||
| 
 | ||||
|   if (out_extra) { | ||||
|     *out_extra = rp.d.stream_acq.extra; | ||||
|   } | ||||
| 
 | ||||
|   return &s->bufs[s->last_idx]; | ||||
| } | ||||
| 
 | ||||
| void visionstream_destroy(VisionStream *s) { | ||||
|   int err; | ||||
| 
 | ||||
|   if (s->last_idx >= 0) { | ||||
|     VisionPacket rep = { | ||||
|       .type = VIPC_STREAM_RELEASE, | ||||
|       .d = { .stream_rel = { | ||||
|         .type = s->last_type, | ||||
|         .idx = s->last_idx, | ||||
|       }} | ||||
|     }; | ||||
|     err = vipc_send(s->ipc_fd, &rep); | ||||
|     s->last_idx = -1; | ||||
|   } | ||||
| 
 | ||||
|   for (int i=0; i<s->num_bufs; i++) { | ||||
|     if (s->bufs[i].addr) { | ||||
|       munmap(s->bufs[i].addr, s->bufs[i].len); | ||||
|       s->bufs[i].addr = NULL; | ||||
|       close(s->bufs[i].fd); | ||||
|     } | ||||
|   } | ||||
|   if (s->bufs) free(s->bufs); | ||||
|   close(s->ipc_fd); | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue