├── .gitignore ├── LICENSE ├── include ├── engine.h └── internal │ └── engine.h ├── makefile ├── shader ├── test.fs └── test.vs └── src ├── egl_x11.c ├── engine.c └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | bin/ 3 | build/ 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Daniel Abrecht 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/engine.h: -------------------------------------------------------------------------------- 1 | #ifndef DENG_ENGINE_H 2 | #define DENG_ENGINE_H 3 | 4 | #include 5 | #include 6 | 7 | struct engine; 8 | struct dma_gl_texture; 9 | 10 | struct shader { 11 | GLuint fragment; 12 | GLuint vertex; 13 | GLuint program; 14 | }; 15 | 16 | struct engine_load_create_shader_program_params { 17 | const char* fragment_shader; 18 | const char* vertex_shader; 19 | }; 20 | 21 | int engine_init(struct engine* engine, int argc, char* argv[]); 22 | bool engine_main_loop(struct engine* engine); 23 | void engine_cleanup(struct engine* engine); 24 | 25 | void engine_private_set(struct engine* engine, void* x); 26 | void* engine_private_get(struct engine* engine); 27 | 28 | GLuint engine_load_shader(const char* path); 29 | GLuint engine_create_shader_program(GLuint shaders[]); 30 | int engine_load_create_shader_program(struct shader* result, struct engine_load_create_shader_program_params); // Convinience function 31 | #define engine_load_create_shader_program(X,...) engine_load_create_shader_program(X,(struct engine_load_create_shader_program_params){__VA_ARGS__}) 32 | 33 | struct dma_gl_texture* engine_v4l_texture_create(struct engine* engine, const char* v4l_device); 34 | void engine_dma_texture_destroy(struct dma_gl_texture* dgt); 35 | GLuint engine_dma_texture_get_gl_texture(struct dma_gl_texture* dgt); 36 | GLenum engine_dma_texture_get_gl_type(struct dma_gl_texture* dgt); 37 | void engine_dma_texture_bind(struct dma_gl_texture* dgt); 38 | void engine_dma_texture_play(struct dma_gl_texture* texture); 39 | void engine_dma_texture_pause(struct dma_gl_texture* texture); 40 | int engine_dma_texture_update(struct dma_gl_texture* texture); 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /include/internal/engine.h: -------------------------------------------------------------------------------- 1 | #ifndef DENG_I_ENGINE_H 2 | #define DENG_I_ENGINE_H 3 | 4 | #define GL_GLEXT_PROTOTYPES 5 | #define EGL_EGLEXT_PROTOTYPES 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #ifndef CONCAT 14 | #define CONCAT(A,B) A ## B 15 | #endif 16 | 17 | #ifndef CONCAT_EVAL 18 | #define CONCAT_EVAL(A,B) CONCAT(A,B) 19 | #endif 20 | 21 | #define ENGINE_REGISTER_DISPLAY_DRIVER(X) \ 22 | static void CONCAT_EVAL(erdd_reg_,__LINE__)(void) __attribute__((constructor)); \ 23 | static void CONCAT_EVAL(erdd_reg_,__LINE__)(void){ \ 24 | engine_i_register_display_driver((X)); \ 25 | } 26 | 27 | struct engine { 28 | struct engine_display_driver* driver; 29 | void* driver_private; 30 | EGLDisplay display; 31 | EGLConfig config; 32 | EGLContext context; 33 | EGLSurface surface; 34 | struct dma_gl_texture* textures; 35 | void* private; 36 | }; 37 | 38 | struct dma_gl_texture { 39 | struct engine* engine; 40 | GLuint texture; 41 | EGLImageKHR image[2]; 42 | bool current; 43 | int (*update_callback)(struct dma_gl_texture*); 44 | void (*destroy_callback)(struct dma_gl_texture*); 45 | union { 46 | long vlong; 47 | void* vptr; 48 | } update_param; 49 | bool autoupdate; 50 | struct dma_gl_texture *next, *last; 51 | }; 52 | 53 | struct engine_display_driver { 54 | const char* name; 55 | struct engine_display_driver* next; 56 | int(*init)(struct engine* engine); 57 | void(*before_drawing)(struct engine* engine); 58 | void(*after_drawing)(struct engine* engine); 59 | void(*destroy)(struct engine* engine); 60 | }; 61 | 62 | void engine_i_register_display_driver(struct engine_display_driver* driver); 63 | int engine_i_egl_x11_init(struct engine* engine); 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | 2 | SOURCES += src/main.c 3 | SOURCES += src/egl_x11.c 4 | SOURCES += src/engine.c 5 | 6 | OBJECTS = $(addprefix build/,$(addsuffix .o,$(SOURCES))) 7 | 8 | all: bin/test 9 | 10 | bin/test: $(OBJECTS) 11 | mkdir -p $(dir $@) 12 | gcc $^ -lGLESv2 -lEGL -lX11 -o $@ 13 | 14 | build/%.c.o: %.c 15 | mkdir -p $(dir $@) 16 | gcc -g -Og -I include -std=c11 -Wall -Wextra -pedantic -Werror $< -c -o $@ 17 | 18 | clean: 19 | rm -rf build bin 20 | -------------------------------------------------------------------------------- /shader/test.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | // Took me forever to figure out that the extension is requiered!!! 3 | #extension GL_OES_EGL_image_external : require 4 | 5 | precision mediump float; 6 | 7 | uniform samplerExternalOES texture; 8 | 9 | in vec2 f_texture_coordinate; 10 | 11 | out vec4 color; 12 | 13 | void main(){ 14 | color = vec4(vec3(texture2D(texture, f_texture_coordinate)), 1.0); 15 | } 16 | -------------------------------------------------------------------------------- /shader/test.vs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | precision highp float; 4 | 5 | in vec4 position; 6 | in vec2 texture_coordinate; 7 | 8 | out vec2 f_texture_coordinate; 9 | 10 | void main(){ 11 | f_texture_coordinate = texture_coordinate; 12 | gl_Position = position; 13 | } 14 | -------------------------------------------------------------------------------- /src/egl_x11.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct xdisplay { 10 | Window window; 11 | Display* display; 12 | }; 13 | 14 | static int init(struct engine* engine){ 15 | /* Allocating data structures */ 16 | struct xdisplay* xd = calloc(1, sizeof(struct xdisplay)); 17 | if(!xd){ 18 | perror("calloc failed"); 19 | return -1; 20 | } 21 | engine->driver_private = xd; 22 | 23 | /* Create window */ 24 | xd->display = XOpenDisplay(0); 25 | if(!xd->display){ 26 | fprintf(stderr, "Cannot open display\n"); 27 | return -1; 28 | } 29 | int screen = DefaultScreen(xd->display); 30 | xd->window = XCreateSimpleWindow(xd->display, RootWindow(xd->display, screen), 50, 50, 800, 600, true, BlackPixel(xd->display, screen), BlackPixel(xd->display, screen)); 31 | XSelectInput(xd->display, xd->window, ExposureMask | KeyPressMask); 32 | XMapWindow(xd->display, xd->window); 33 | 34 | /* initialise egl context */ 35 | 36 | engine->display = eglGetDisplay((EGLNativeDisplayType)xd->display); 37 | if( engine->display == EGL_NO_DISPLAY ){ 38 | fprintf(stderr, "Got no EGL display."); 39 | return -1; 40 | } 41 | 42 | if(!eglInitialize(engine->display, 0, 0)){ 43 | fprintf(stderr, "Unable to initialize EGL"); 44 | return -1; 45 | } 46 | 47 | EGLint attr[] = { 48 | EGL_BUFFER_SIZE, 16, 49 | EGL_RENDERABLE_TYPE, 50 | EGL_OPENGL_ES2_BIT, 51 | EGL_NONE 52 | }; 53 | 54 | EGLint num_config; 55 | if( !eglChooseConfig(engine->display, attr, &engine->config, 1, &num_config) ){ 56 | fprintf(stderr, "Failed to choose config (eglError: %d)\n", eglGetError()); 57 | return -1; 58 | } 59 | 60 | if(num_config != 1) { 61 | fprintf(stderr, "Didn't get exactly one config, but %d\n", num_config); 62 | return -1; 63 | } 64 | 65 | engine->surface = eglCreateWindowSurface(engine->display, engine->config, xd->window, 0); 66 | if( engine->surface == EGL_NO_SURFACE ){ 67 | fprintf(stderr, "Unable to create EGL surface (eglError: %d)\n", eglGetError()); 68 | return -1; 69 | } 70 | 71 | EGLint ctxattr[] = { 72 | EGL_CONTEXT_CLIENT_VERSION, 2, 73 | EGL_NONE 74 | }; 75 | engine->context = eglCreateContext(engine->display, engine->config, EGL_NO_CONTEXT, ctxattr); 76 | if( engine->context == EGL_NO_CONTEXT ){ 77 | fprintf(stderr, "Unable to create EGL context (eglError: %d)\n", eglGetError()); 78 | return -1; 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | void destroy(struct engine* engine){ 85 | struct xdisplay* xd = engine->driver_private; 86 | XDestroyWindow(xd->display, xd->window); 87 | XCloseDisplay(xd->display); 88 | } 89 | 90 | void before_drawing(struct engine* engine){ 91 | struct xdisplay* xd = engine->driver_private; 92 | // XEvent event; 93 | // XNextEvent(xd->display, &event); // includes keyboard input 94 | XWindowAttributes gwa; 95 | XGetWindowAttributes(xd->display, xd->window, &gwa); 96 | glViewport(0, 0, gwa.width, gwa.height); 97 | // TODO 98 | } 99 | 100 | static struct engine_display_driver display_driver = { 101 | .name = "X11", 102 | .init = init, 103 | .destroy = destroy, 104 | .before_drawing = before_drawing 105 | }; 106 | ENGINE_REGISTER_DISPLAY_DRIVER(&display_driver) 107 | -------------------------------------------------------------------------------- /src/engine.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #undef engine_load_create_shader_program 18 | 19 | 20 | struct engine_display_driver* display_driver_list; 21 | 22 | 23 | EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list) __attribute__((weak)); // May not be in libEGL symbol table, resolve manually :( 24 | EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list){ 25 | static PFNEGLCREATEIMAGEKHRPROC createImageProc = 0; 26 | if(!createImageProc) 27 | createImageProc = (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR"); 28 | return createImageProc(dpy, ctx, target, buffer, attrib_list); 29 | } 30 | 31 | EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR image) __attribute__((weak)); // May not be in libEGL symbol table, resolve manually :( 32 | EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR image){ 33 | static PFNEGLDESTROYIMAGEKHRPROC destroyImageProc = 0; 34 | if(!destroyImageProc) 35 | destroyImageProc = (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR"); 36 | return destroyImageProc(dpy, image); 37 | } 38 | 39 | void glDebugMessageCallbackKHR(GLDEBUGPROCKHR callback, const void *userParam) __attribute__((weak)); // May not be in libEGL symbol table, resolve manually :( 40 | void glDebugMessageCallbackKHR(GLDEBUGPROCKHR callback, const void *userParam){ 41 | static PFNGLDEBUGMESSAGECALLBACKKHRPROC debugMessageCallbackProc = 0; 42 | if(!debugMessageCallbackProc) 43 | debugMessageCallbackProc = (PFNGLDEBUGMESSAGECALLBACKKHRPROC)eglGetProcAddress("glDebugMessageCallbackKHR"); 44 | debugMessageCallbackProc(callback, userParam); 45 | } 46 | 47 | void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) __attribute__((weak)); // May not be in libEGL symbol table, resolve manually :( 48 | void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image){ 49 | static PFNGLEGLIMAGETARGETTEXTURE2DOESPROC imageTargetTexture2DOES = 0; 50 | if(!imageTargetTexture2DOES) 51 | imageTargetTexture2DOES = (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES"); 52 | imageTargetTexture2DOES(target, image); 53 | } 54 | 55 | void engine_i_register_display_driver(struct engine_display_driver* driver){ 56 | driver->next = display_driver_list; 57 | display_driver_list = driver; 58 | } 59 | 60 | int engine_load_create_shader_program(struct shader* shader, struct engine_load_create_shader_program_params params){ 61 | if(params.fragment_shader){ 62 | shader->fragment = engine_load_shader("shader/test.fs"); 63 | if(!shader->fragment){ 64 | fprintf(stderr, "engine_load_shader failed\n"); 65 | return -1; 66 | } 67 | } 68 | 69 | if(params.vertex_shader){ 70 | shader->vertex = engine_load_shader("shader/test.vs"); 71 | if(!shader->vertex){ 72 | fprintf(stderr, "engine_load_shader failed\n"); 73 | return -1; 74 | } 75 | } 76 | 77 | shader->program = engine_create_shader_program((GLuint[]){ 78 | shader->vertex, 79 | shader->fragment, 80 | 0}); 81 | if(!shader->program){ 82 | fprintf(stderr,"engine_create_shader_program failed\n"); 83 | return -1; 84 | } 85 | 86 | return 0; 87 | } 88 | 89 | static void print_info_log(GLuint shader, bool shader_or_program){ 90 | GLint length; 91 | if(shader_or_program){ 92 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length); 93 | }else{ 94 | glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &length); 95 | } 96 | if(!length) 97 | return; 98 | char* log = malloc(length+1); 99 | if(!log) return; 100 | if(shader_or_program){ 101 | glGetShaderInfoLog(shader, length+1, 0, log); 102 | }else{ 103 | glGetProgramInfoLog(shader, length+1, 0, log); 104 | } 105 | log[length] = 0; 106 | fputs(log , stderr); 107 | fputs("\n", stderr); 108 | free(log); 109 | } 110 | 111 | GLuint engine_load_shader(const char* path){ 112 | size_t path_length = path ? strlen(path) : 0; 113 | if(path_length < 3){ 114 | fprintf(stderr, "engine_load_shader called with invalid path\n"); 115 | return 0; 116 | } 117 | GLenum type = 0; 118 | if(!strcmp(path+path_length-3,".vs")){ 119 | type = GL_VERTEX_SHADER; 120 | }else if(!strcmp(path+path_length-3,".fs")){ 121 | type = GL_FRAGMENT_SHADER; 122 | }else{ 123 | fprintf(stderr, "Shader file has unknown extension. Currently supported are: .vs - vertex shader, .fs - fragment shader\n"); 124 | return 0; 125 | } 126 | 127 | void* mem = 0; 128 | int fd = open(path, O_RDONLY); 129 | if(fd == -1){ 130 | fprintf(stderr,"Failed to open file %s: %s\n",path,strerror(errno)); 131 | return 0; 132 | } 133 | 134 | struct stat s; 135 | if(fstat(fd, &s) != 0){ 136 | fprintf(stderr,"Failed to fstat file %s: %s\n",path,strerror(errno)); 137 | close(fd); 138 | return 0; 139 | } 140 | 141 | if(!s.st_size){ 142 | fprintf(stderr,"Shader file appears to be empty\n"); 143 | close(fd); 144 | return 0; 145 | } 146 | 147 | mem = mmap(0, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 148 | if(mem == MAP_FAILED){ 149 | fprintf(stderr, "mmap %s failed: %s\n", path, strerror(errno)); 150 | close(fd); 151 | return 0; 152 | } 153 | 154 | close(fd); 155 | 156 | while(glGetError() != GL_NO_ERROR); // Clear previouse errors 157 | 158 | GLuint shader = glCreateShader(type); 159 | if(!shader){ 160 | fprintf(stderr, "glCreateShader failed\n"); 161 | munmap(mem, s.st_size); 162 | return 0; 163 | } 164 | 165 | glShaderSource(shader, 1, &(const GLchar*){mem}, &(GLint){s.st_size}); 166 | glCompileShader(shader); 167 | print_info_log(shader, true); 168 | 169 | munmap(mem, s.st_size); 170 | 171 | GLint result = 0; 172 | glGetShaderiv(shader, GL_COMPILE_STATUS, &result); 173 | if(result != GL_TRUE){ 174 | fprintf(stderr,"Compiling shader failed\n"); 175 | glDeleteShader(shader); 176 | return 0; 177 | } 178 | 179 | return shader; 180 | } 181 | 182 | GLuint engine_create_shader_program(GLuint shaders[]){ 183 | GLuint shader_program = glCreateProgram(); 184 | 185 | if(!shader_program){ 186 | fprintf(stderr,"glCreateProgram failed\n"); 187 | return 0; 188 | } 189 | 190 | for(GLuint* it=shaders; *it; it++) 191 | glAttachShader(shader_program, *it); 192 | 193 | glLinkProgram(shader_program); 194 | print_info_log(shader_program, false); 195 | 196 | GLint result = 0; 197 | glGetProgramiv(shader_program, GL_LINK_STATUS, &result); 198 | if(result != GL_TRUE){ 199 | fprintf(stderr,"linking shader program failed\n"); 200 | glDeleteProgram(shader_program); 201 | return 0; 202 | } 203 | 204 | return shader_program; 205 | } 206 | 207 | 208 | struct dma_buffers { 209 | int fd[2]; 210 | struct v4l2_format fmt; 211 | }; 212 | 213 | static int device_init_get_dmabuf(int fd, struct dma_buffers* result){ 214 | 215 | { 216 | struct v4l2_capability cap; 217 | memset(&cap, 0, sizeof(cap)); 218 | if( ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1){ 219 | if(EINVAL == errno){ 220 | fprintf(stderr, "This isn't a V4L2 device\n"); 221 | }else{ 222 | perror("VIDIOC_QUERYCAP"); 223 | } 224 | return -1; 225 | } 226 | 227 | if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){ 228 | fprintf(stderr, "This is no video capture device\n"); 229 | return -1; 230 | } 231 | 232 | if(!(cap.capabilities & V4L2_CAP_STREAMING)){ 233 | fprintf(stderr, "no streaming i/o support\n"); 234 | return -1; 235 | } 236 | 237 | } 238 | 239 | { 240 | struct v4l2_cropcap cropcap; 241 | memset(&cropcap, 0, sizeof(cropcap)); 242 | cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 243 | if(ioctl(fd, VIDIOC_CROPCAP, &cropcap) == 0){ 244 | struct v4l2_crop crop; 245 | memset(&crop, 0, sizeof(crop)); 246 | crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 247 | crop.c = cropcap.defrect; /* reset to default */ 248 | ioctl(fd, VIDIOC_S_CROP, &crop); 249 | } 250 | } 251 | 252 | memset(&result->fmt,0,sizeof(result->fmt)); 253 | result->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 254 | 255 | if(ioctl(fd, VIDIOC_G_FMT, &result->fmt) == -1){ 256 | perror("VIDIOC_G_FMT failed (now trying VIDIOC_S_FMT)"); 257 | return -1; 258 | } 259 | 260 | unsigned int min = result->fmt.fmt.pix.width * 2; 261 | if(result->fmt.fmt.pix.bytesperline < min) 262 | result->fmt.fmt.pix.bytesperline = min; 263 | min = result->fmt.fmt.pix.bytesperline * result->fmt.fmt.pix.height; 264 | if(result->fmt.fmt.pix.sizeimage < min) 265 | result->fmt.fmt.pix.sizeimage = min; 266 | 267 | int count = 0; 268 | { 269 | struct v4l2_requestbuffers reqbuf; 270 | memset(&reqbuf, 0, sizeof(reqbuf)); 271 | reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 272 | reqbuf.memory = V4L2_MEMORY_MMAP; 273 | reqbuf.count = 2; 274 | int res; 275 | res = ioctl(fd, VIDIOC_REQBUFS, &reqbuf); 276 | if(res == -1 && errno == EINVAL){ 277 | reqbuf.count = 1; 278 | res = ioctl(fd, VIDIOC_REQBUFS, &reqbuf); 279 | } 280 | if(res == -1){ 281 | if(errno == EINVAL){ 282 | fprintf(stderr, "Video capturing or DMABUF streaming is not supported\n"); 283 | }else{ 284 | perror("VIDIOC_REQBUFS"); 285 | } 286 | return -1; 287 | } 288 | count = reqbuf.count; 289 | } 290 | if(count > 2) 291 | count = 2; 292 | 293 | for(int i=0; ifd[i] = expbuf.fd; 304 | } 305 | 306 | return 0; 307 | } 308 | 309 | static int start_capturing(int fd){ 310 | if(ioctl(fd, VIDIOC_STREAMON, &(enum v4l2_buf_type){V4L2_BUF_TYPE_VIDEO_CAPTURE})){ 311 | perror("VIDIOC_STREAMON"); 312 | return -1; 313 | } 314 | return 0; 315 | } 316 | 317 | void engine_dma_texture_play(struct dma_gl_texture* texture){ 318 | texture->autoupdate = true; 319 | } 320 | 321 | void engine_dma_texture_pause(struct dma_gl_texture* texture){ 322 | texture->autoupdate = false; 323 | } 324 | 325 | GLenum engine_dma_texture_get_gl_type(struct dma_gl_texture* dgt){ 326 | (void)dgt; 327 | return GL_TEXTURE_EXTERNAL_OES; 328 | } 329 | 330 | GLuint engine_dma_texture_get_gl_texture(struct dma_gl_texture* dgt){ 331 | return dgt->texture; 332 | } 333 | 334 | int engine_dma_texture_update(struct dma_gl_texture* dgt){ 335 | if(dgt->update_callback) 336 | return dgt->update_callback(dgt); 337 | return 0; 338 | } 339 | 340 | static int init(struct engine* engine, int argc, char* argv[]){ 341 | (void)argc; 342 | (void)argv; 343 | memset(engine, 0, sizeof(*engine)); 344 | for(struct engine_display_driver* it=display_driver_list; it; it=it->next){ 345 | if(it->init && it->init(engine) != -1){ 346 | engine->driver = it; 347 | break; 348 | } 349 | } 350 | if(!engine->driver){ 351 | fprintf(stderr,"failed to initialise any display driver"); 352 | return -1; 353 | } 354 | return 0; 355 | } 356 | 357 | // TODO: generalise format & make public 358 | static struct dma_gl_texture* engine_dma_texture_create(struct engine* engine, const struct dma_buffers* dma){ 359 | struct dma_gl_texture* dgt = calloc(1, sizeof(struct dma_gl_texture)); 360 | if(!dgt){ 361 | perror("calloc failed"); 362 | goto error; 363 | } 364 | dgt->autoupdate = true; 365 | dgt->image[0] = eglCreateImageKHR(engine->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)0, (EGLint[]){ 366 | EGL_WIDTH, dma->fmt.fmt.pix.width, 367 | EGL_HEIGHT, dma->fmt.fmt.pix.height, 368 | EGL_LINUX_DRM_FOURCC_EXT, dma->fmt.fmt.pix.pixelformat, 369 | EGL_DMA_BUF_PLANE0_FD_EXT, dma->fd[0], 370 | EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, // No bound checks in drm intel driver in kernel (4.14.90) !?! 371 | EGL_DMA_BUF_PLANE0_PITCH_EXT, dma->fmt.fmt.pix.bytesperline, 372 | EGL_NONE 373 | }); 374 | if( dgt->image[0] == EGL_NO_IMAGE_KHR ){ 375 | fprintf(stderr,"eglCreateImageKHR failed\n"); 376 | goto error_after_calloc; 377 | } 378 | if(dma->fd[1] != -1){ // If this fails, we jus don't have double buffering, so not a big deal 379 | dgt->image[1] = eglCreateImageKHR(engine->display, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, (EGLClientBuffer)0, (EGLint[]){ 380 | EGL_WIDTH, dma->fmt.fmt.pix.width, 381 | EGL_HEIGHT, dma->fmt.fmt.pix.height, 382 | EGL_LINUX_DRM_FOURCC_EXT, dma->fmt.fmt.pix.pixelformat, 383 | EGL_DMA_BUF_PLANE0_FD_EXT, dma->fd[1], 384 | EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, // No bound checks in drm intel driver in kernel (4.14.90) !?! 385 | EGL_DMA_BUF_PLANE0_PITCH_EXT, dma->fmt.fmt.pix.bytesperline, 386 | EGL_NONE 387 | }); 388 | }else{ 389 | dgt->image[1] = EGL_NO_IMAGE_KHR; 390 | } 391 | while(glGetError() != GL_NO_ERROR); // Clear error flags 392 | glGenTextures(1, &dgt->texture); 393 | if(glGetError() != GL_NO_ERROR) 394 | goto error_after_create_image; 395 | glEnable(GL_TEXTURE_EXTERNAL_OES); 396 | glBindTexture(GL_TEXTURE_EXTERNAL_OES, dgt->texture); 397 | glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 398 | glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 399 | /* glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 400 | glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);*/ 401 | glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, dgt->image[0]); 402 | if(glGetError() != GL_NO_ERROR){ 403 | fprintf(stderr,"creating gl texture failed\n"); 404 | goto error_after_gen_textures; 405 | } 406 | dgt->engine = engine; 407 | dgt->next = engine->textures; 408 | dgt->last = 0; 409 | engine->textures = dgt; 410 | return dgt; 411 | error_after_gen_textures: 412 | glDeleteTextures(1, &dgt->texture); 413 | error_after_create_image: 414 | if(dgt->image[1] != EGL_NO_IMAGE_KHR) 415 | eglDestroyImageKHR(engine->display, dgt->image[1]); 416 | eglDestroyImageKHR(engine->display, dgt->image[0]); 417 | error_after_calloc: 418 | free(dgt); 419 | error: 420 | return 0; 421 | } 422 | 423 | void engine_dma_texture_destroy(struct dma_gl_texture* dgt){ 424 | if(!dgt) 425 | return; 426 | if(dgt->next) 427 | dgt->next->last = dgt->last; 428 | if(!dgt->last) 429 | dgt->engine->textures = dgt->next; 430 | if(dgt->last) 431 | dgt->last->next = dgt->next; 432 | if(dgt->destroy_callback) 433 | dgt->destroy_callback(dgt); 434 | glDeleteTextures(1, &dgt->texture); 435 | if(dgt->image[1] != EGL_NO_IMAGE_KHR) 436 | eglDestroyImageKHR(dgt->engine->display, dgt->image[1]); 437 | eglDestroyImageKHR(dgt->engine->display, dgt->image[0]); 438 | memset(dgt, 0, sizeof(*dgt)); 439 | free(dgt); 440 | } 441 | 442 | static int open_device(const char* dev){ 443 | 444 | int fd = -1; 445 | if(strncmp(dev,"fd:",3) == 0){ 446 | if(sscanf(dev+3,"%d",&fd) != 1){ 447 | fprintf(stderr, "Cannot parse fd '%s'\n", dev); 448 | return -1; 449 | } 450 | }else{ 451 | fd = open(dev, O_RDWR | O_NONBLOCK); 452 | if(fd == -1){ 453 | fprintf(stderr, "Cannot open '%s': %d, %s\n", dev, errno, strerror(errno)); 454 | return -1; 455 | } 456 | } 457 | 458 | struct stat st; 459 | if(fstat(fd, &st) == -1){ 460 | fprintf(stderr, "Cannot identify '%s': %d, %s\n", dev, errno, strerror(errno)); 461 | return -1; 462 | } 463 | 464 | if(!S_ISCHR(st.st_mode)){ 465 | fprintf(stderr, "%s isn't a device file\n", dev); 466 | return -1; 467 | } 468 | 469 | return fd; 470 | } 471 | 472 | void v4l_dma_destroy(struct dma_gl_texture* dgt){ 473 | close(dgt->update_param.vlong); 474 | } 475 | 476 | int v4l_dma_update(struct dma_gl_texture* dgt){ 477 | int ret = 0; 478 | int dev = dgt->update_param.vlong; 479 | 480 | struct v4l2_buffer buf; 481 | memset(&buf, 0, sizeof(buf)); 482 | buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 483 | buf.memory = V4L2_MEMORY_MMAP; 484 | buf.index = dgt->image[1] != EGL_NO_IMAGE_KHR && !dgt->current; 485 | 486 | if(ioctl(dev, VIDIOC_QUERYBUF, &buf) == -1){ 487 | perror("VIDIOC_QUERYBUF"); 488 | return -1; 489 | } 490 | 491 | bool swapped = false; 492 | if(buf.flags & V4L2_BUF_FLAG_DONE){ 493 | if(dgt->image[1] != EGL_NO_IMAGE_KHR){ 494 | dgt->current = !dgt->current; 495 | swapped = true; 496 | } 497 | if(ioctl(dev, VIDIOC_DQBUF, &buf) == -1){ 498 | perror("VIDIOC_DQBUF"); 499 | return -1; 500 | } 501 | if(swapped) 502 | glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, dgt->image[dgt->current]); 503 | ret = 1; 504 | } 505 | 506 | if(swapped){ 507 | buf.index = !dgt->current; 508 | if(ioctl(dev, VIDIOC_QUERYBUF, &buf) == -1){ 509 | perror("VIDIOC_QUERYBUF"); 510 | return -1; 511 | } 512 | } 513 | 514 | if(!(buf.flags & V4L2_BUF_FLAG_QUEUED)){ 515 | if(ioctl(dev, VIDIOC_QBUF, &buf) == -1){ 516 | perror("VIDIOC_QBUF"); 517 | return -1; 518 | } 519 | } 520 | 521 | return ret; 522 | } 523 | 524 | struct dma_gl_texture* engine_v4l_texture_create(struct engine* engine, const char* v4l_device){ 525 | struct dma_gl_texture* result = 0; 526 | struct dma_buffers dma = { 527 | .fd = {-1, -1} 528 | }; 529 | int dev = -1; 530 | 531 | dev = open_device(v4l_device); 532 | if(dev == -1){ 533 | fprintf(stderr,"failed to open v4l device\n"); 534 | goto error; 535 | } 536 | 537 | if(device_init_get_dmabuf(dev, &dma) == -1){ 538 | fprintf(stderr,"device_init_get_dmabuf failed\n"); 539 | goto error; 540 | } 541 | 542 | if(start_capturing(dev) == -1){ 543 | fprintf(stderr,"failed to start video capturing\n"); 544 | goto error; 545 | } 546 | 547 | result = engine_dma_texture_create(engine, &dma); 548 | if(!result){ 549 | fprintf(stderr,"failed to create texture from dma buffer\n"); 550 | goto error; 551 | } 552 | 553 | result->update_callback = v4l_dma_update; 554 | result->destroy_callback = v4l_dma_destroy; 555 | result->update_param.vlong = dev; 556 | 557 | close(dma.fd[0]); 558 | close(dma.fd[1]); 559 | 560 | return result; 561 | 562 | error: 563 | close(dma.fd[0]); 564 | close(dma.fd[1]); 565 | close(dev); 566 | engine_dma_texture_destroy(result); 567 | return 0; 568 | } 569 | 570 | void main_loop(struct engine* engine){ 571 | while(true){ 572 | eglMakeCurrent(engine->display, engine->surface, engine->surface, engine->context); 573 | if(engine->driver->before_drawing) 574 | engine->driver->before_drawing(engine); 575 | for(struct dma_gl_texture* it=engine->textures; it; it=it->next) 576 | if(it->autoupdate) 577 | engine_dma_texture_update(it); 578 | if(!engine_main_loop(engine)) 579 | break; 580 | if(engine->driver->after_drawing) 581 | engine->driver->after_drawing(engine); 582 | eglSwapBuffers(engine->display, engine->surface); 583 | } 584 | } 585 | 586 | void cleanup(struct engine* engine){ 587 | while(engine->textures) 588 | engine_dma_texture_destroy(engine->textures); 589 | if(engine->driver->destroy) 590 | engine->driver->destroy(engine); 591 | eglDestroyContext(engine->display, engine->context); 592 | eglDestroySurface(engine->display, engine->surface); 593 | eglTerminate(engine->display); 594 | } 595 | 596 | void engine_private_set(struct engine* engine, void* x){ 597 | engine->private = x; 598 | } 599 | 600 | void* engine_private_get(struct engine* engine){ 601 | return engine->private; 602 | } 603 | 604 | void gl_debug_callback( 605 | GLenum source, 606 | GLenum type, 607 | GLuint id, 608 | GLenum severity, 609 | GLsizei length, 610 | const GLchar* message, 611 | const void* userParam 612 | ){ 613 | (void)source; 614 | (void)id; 615 | (void)length; 616 | (void)userParam; 617 | fprintf( stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", 618 | ( type == GL_DEBUG_TYPE_ERROR_KHR ? "** GL ERROR **" : "" ), 619 | type, severity, message ); 620 | } 621 | 622 | int main(int argc, char* argv[]){ 623 | struct engine engine; 624 | if(init(&engine, argc, argv) == -1) 625 | goto error; 626 | eglMakeCurrent(engine.display, engine.surface, engine.surface, engine.context); 627 | glEnable(GL_DEBUG_OUTPUT_KHR); 628 | glDebugMessageCallbackKHR(gl_debug_callback, 0); 629 | if(engine_init(&engine, argc, argv) == -1) 630 | goto error_after_init; 631 | main_loop(&engine); 632 | eglMakeCurrent(engine.display, engine.surface, engine.surface, engine.context); 633 | engine_cleanup(&engine); 634 | cleanup(&engine); 635 | return 0; 636 | error_after_init: 637 | cleanup(&engine); 638 | error: 639 | return -1; 640 | } 641 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | struct my_test_shader { 8 | struct shader shader; 9 | struct { 10 | GLint position; 11 | GLint texture_coordinate; 12 | } attribut; 13 | struct { 14 | GLint texture; 15 | } uniform; 16 | }; 17 | 18 | struct runtime { 19 | struct dma_gl_texture* camera; 20 | struct my_test_shader test_shader; 21 | }; 22 | 23 | int engine_init(struct engine* engine, int argc, char* argv[]){ 24 | (void)argc; 25 | (void)argv; 26 | 27 | /* Allocate some private date to store everything in */ 28 | struct runtime* runtime = calloc(1, sizeof(struct runtime)); 29 | if(!runtime){ 30 | perror("calloc failed"); 31 | goto error; 32 | } 33 | engine_private_set(engine, runtime); 34 | 35 | /* Load & compile shaders */ 36 | if(engine_load_create_shader_program( 37 | &runtime->test_shader.shader, 38 | .vertex_shader = "shader/test.vs", 39 | .fragment_shader = "shader/test.fs" 40 | ) == -1){ 41 | fprintf(stderr, "engine_load_create_shader_program failed\n"); 42 | goto error_after_calloc; 43 | } 44 | 45 | /* get shader attributes & uniforms */ 46 | runtime->test_shader.attribut.position = glGetAttribLocation(runtime->test_shader.shader.program, "position"); 47 | if(runtime->test_shader.attribut.position == -1){ 48 | fprintf(stderr,"position attribute not found in shader program\n"); 49 | goto error_after_calloc; 50 | } 51 | 52 | runtime->test_shader.attribut.texture_coordinate = glGetAttribLocation(runtime->test_shader.shader.program, "texture_coordinate"); 53 | if(runtime->test_shader.attribut.texture_coordinate == -1){ 54 | fprintf(stderr,"texture_coordinate attribute not found in shader program\n"); 55 | goto error_after_calloc; 56 | } 57 | 58 | runtime->test_shader.uniform.texture = glGetUniformLocation(runtime->test_shader.shader.program, "texture"); 59 | if(runtime->test_shader.uniform.texture == -1){ 60 | fprintf(stderr,"texture uniform not found in shader program\n"); 61 | goto error_after_calloc; 62 | } 63 | 64 | /* Create texture from v4l device */ 65 | runtime->camera = engine_v4l_texture_create(engine, "/dev/video0"); 66 | if(!runtime->camera){ 67 | fprintf(stderr, "engine_v4l_texture_create failed\n"); 68 | goto error_after_calloc; 69 | } 70 | 71 | return 0; 72 | 73 | error_after_calloc: 74 | free(runtime); 75 | error: 76 | return -1; 77 | } 78 | 79 | bool engine_main_loop(struct engine* engine){ 80 | struct runtime* runtime = engine_private_get(engine); 81 | 82 | glUseProgram(runtime->test_shader.shader.program); 83 | 84 | glClearColor(0.1,0.2,0.3,1); 85 | glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); 86 | 87 | float position[][3][3] = { 88 | { {-0.5, -0.5, 0.0}, { 0.5, -0.5, 0.0}, {-0.5, 0.5, 0.0} }, 89 | { {-0.5, 0.5, 0.0}, { 0.5, -0.5, 0.0}, { 0.5, 0.5, 0.0} } 90 | }; 91 | size_t position_count = sizeof(position)/sizeof(**position); 92 | 93 | float texture_coordinate[][3][2] = { 94 | { { 1.0, 1.0}, { 0.0, 1.0}, { 1.0, 0.0} }, 95 | { { 1.0, 0.0}, { 0.0, 1.0}, { 0.0, 0.0} } 96 | }; 97 | size_t texture_coordinate_count = sizeof(texture_coordinate)/sizeof(**texture_coordinate); 98 | 99 | assert(texture_coordinate_count == position_count); 100 | size_t vertex_count = position_count; 101 | 102 | glVertexAttribPointer(runtime->test_shader.attribut.position, 3, GL_FLOAT, false, 0, position); 103 | glEnableVertexAttribArray(runtime->test_shader.attribut.position); 104 | glVertexAttribPointer(runtime->test_shader.attribut.texture_coordinate, 2, GL_FLOAT, false, 0, texture_coordinate); 105 | glEnableVertexAttribArray(runtime->test_shader.attribut.texture_coordinate); 106 | 107 | glActiveTexture(GL_TEXTURE0); 108 | // glEnable(GL_TEXTURE_2D); 109 | // glEnable(engine_dma_texture_get_gl_type(runtime->camera)); 110 | glBindTexture( 111 | engine_dma_texture_get_gl_type(runtime->camera), 112 | engine_dma_texture_get_gl_texture(runtime->camera) 113 | ); 114 | glUniform1i(runtime->test_shader.uniform.texture, 0); // GL_TEXTURE0 115 | 116 | glDrawArrays(GL_TRIANGLES, 0, vertex_count); 117 | 118 | return true; 119 | } 120 | 121 | void engine_cleanup(struct engine* engine){ 122 | struct runtime* runtime = engine_private_get(engine); 123 | if(!runtime) 124 | return; 125 | // TODO 126 | } 127 | --------------------------------------------------------------------------------