JeVoisBase  1.20
JeVois Smart Embedded Machine Vision Toolkit Base Modules
Share this page:
FilterGPU.C
Go to the documentation of this file.
1 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2016 by Laurent Itti, the University of Southern
4 // California (USC), and iLab at USC. See http://iLab.usc.edu and http://jevois.org for information about this project.
5 //
6 // This file is part of the JeVois Smart Embedded Machine Vision Toolkit. This program is free software; you can
7 // redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
8 // Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
10 // License for more details. You should have received a copy of the GNU General Public License along with this program;
11 // if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
12 //
13 // Contact information: Laurent Itti - 3641 Watt Way, HNB-07A - Los Angeles, CA 90089-2520 - USA.
14 // Tel: +1 213 740 3527 - itti@pollux.usc.edu - http://iLab.usc.edu - http://jevois.org
15 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
16 /*! \file */
17 
19 
20 // ####################################################################################################
21 FilterGPU::FilterGPU(std::string const & instance) :
22  jevois::Component(instance), itsProgramChanged(false), itsQuadVertexBuffer(0), itsDisplay(EGL_NO_DISPLAY),
23  itsConfig(0), itsContext(0), itsSurface(0), itsFramebufferId(0), itsRenderbufferId(0),
24  itsRenderWidth(0), itsRenderHeight(0), itsRenderType(0)
25 { }
26 
27 // ####################################################################################################
28 void FilterGPU::initDisplay()
29 {
30  // Get an EGL display connection:
31  GL_CHECK(itsDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY));
32  if (itsDisplay == EGL_NO_DISPLAY) LFATAL("Could not get an OpenGL display");
33 
34  // Initialize the EGL display connection:
35  EGLint major, minor;
36  GL_CHECK_BOOL(eglInitialize(itsDisplay, &major, &minor););
37  LINFO("Initialized OpenGL-ES with EGL v" << major << '.' << minor);
38 
39  // Get an appropriate EGL configuration:
40  EGLint num_config;
41  static EGLint const cfg_attr[] =
42  { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE };
43  GL_CHECK_BOOL(eglChooseConfig(itsDisplay, cfg_attr, &itsConfig, 1, &num_config));
44  if (num_config < 1) LFATAL("Could not find a suitable OpenGL config");
45 
46  // Create a pbuffer surface:
47  GL_CHECK(itsSurface = eglCreatePbufferSurface(itsDisplay, itsConfig, NULL));
48 
49  // Bind to OpenGL-ES API:
50  GL_CHECK_BOOL(eglBindAPI(EGL_OPENGL_ES_API));
51 
52  // Create an EGL rendering context:
53  static EGLint const ctx_attr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
54  GL_CHECK(itsContext = eglCreateContext(itsDisplay, itsConfig, EGL_NO_CONTEXT, ctx_attr));
55  if (itsContext == EGL_NO_CONTEXT) LFATAL("Failed to create OpenGL context");
56 
57  // Bind the context to the surface:
58  GL_CHECK(eglMakeCurrent(itsDisplay, itsSurface, itsSurface, itsContext));
59 }
60 
61 // ####################################################################################################
63 {
64  // Kill our member variables before we close down OpenGL:
65  itsProgram.reset(); itsSrcTex.reset();
66 
67  if (itsRenderbufferId) glDeleteRenderbuffers(1, &itsRenderbufferId);
68  if (itsFramebufferId) glDeleteFramebuffers(1, &itsFramebufferId);
69 
70  glDeleteBuffers(1, &itsQuadVertexBuffer);
71 
72  // Delete surface, context, etc and close down:
73  eglMakeCurrent(itsDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
74  if (itsSurface) eglDestroySurface(itsDisplay, itsSurface);
75  eglDestroyContext(itsDisplay, itsContext);
76  eglTerminate(itsDisplay);
77 }
78 
79 // ####################################################################################################
80 void FilterGPU::setProgram(std::string const & vertex_shader, std::string const & frag_shader)
81 {
82  // We cannot load the program here as our display may not have been initialized yet, and we may be in a different
83  // thread. Let's just remember the file names and we will do the work in process():
84  std::lock_guard<std::mutex> _(itsMutex);
85  itsVshader = absolutePath(vertex_shader);
86  itsFshader = absolutePath(frag_shader);
87  itsProgramChanged = true;
88  itsProgramParams.clear();
89 }
90 
91 // ####################################################################################################
92 void FilterGPU::setProgramParam2f(std::string name, float val1, float val2)
93 {
94  std::lock_guard<std::mutex> _(itsMutex);
95  itsProgramParams[name] = { F2, { val1, val2 } };
96  itsProgramChanged = true;
97 }
98 
99 // ####################################################################################################
100 void FilterGPU::setProgramParam1f(std::string name, float val)
101 {
102  std::lock_guard<std::mutex> _(itsMutex);
103  itsProgramParams[name] = { F1, { val, 0.0F } };
104  itsProgramChanged = true;
105 }
106 
107 // ####################################################################################################
108 void FilterGPU::setProgramParam2i(std::string name, int val1, int val2)
109 {
110  std::lock_guard<std::mutex> _(itsMutex);
111  itsProgramParams[name] = { I2, { float(val1), float(val2) } };
112  itsProgramChanged = true;
113 }
114 
115 // ####################################################################################################
116 void FilterGPU::setProgramParam1i(std::string name, int val)
117 {
118  std::lock_guard<std::mutex> _(itsMutex);
119  itsProgramParams[name] = { I2, { float(val), 0.0F } };
120  itsProgramChanged = true;
121 }
122 
123 // ####################################################################################################
124 void FilterGPU::process(cv::Mat const & src, cv::Mat & dst)
125 {
126  // We init the display here so that it is in the same thread as the subsequent processing, as OpenGL is not very
127  // thread-friendly. Yet, see here for an alternative, which is basically to create some sort of shadow context in the
128  // process() thread after having created the context in the constructor or init() thread:
129  // http://stackoverflow.com/questions/11726650/egl-can-context-be-shared-between-threads
130  if (itsDisplay == EGL_NO_DISPLAY) initDisplay();
131 
132  if (src.type() != CV_8UC1 && src.type() != CV_8UC4) LFATAL("Source pixel format must be CV_8UC1 or CV_8UC4");
133  if (dst.type() != CV_8UC2 && dst.type() != CV_8UC4) LFATAL("Dest pixel format must be CV_8UC2 or CV_8UC4");
134  GLuint const srcformat = (src.channels() == 4 ? GL_RGBA : GL_LUMINANCE); // GL_ALPHA also works
135  GLuint const dstformat = (dst.channels() == 4 ? GL_RGBA4 : GL_RGB565);
136 
137  // Allocate our textures if needed, eg, first time we are called or sizes have changed:
138  if (!itsSrcTex || itsSrcTex->Width != src.cols || itsSrcTex->Height != src.rows || itsSrcTex->Format != srcformat)
139  {
140  itsSrcTex.reset(new GPUtexture(src.cols, src.rows, srcformat, false));
141  LINFO("Input texture " << itsSrcTex->Width << 'x' << itsSrcTex->Height << ' ' <<
142  (srcformat == GL_RGBA ? "RGBA" : "LUMINANCE") << " ready.");
143  }
144 
145  // Create our framebuffer and renderbuffer if needed:
146  if (itsRenderbufferId == 0 || int(itsRenderWidth) != dst.cols || int(itsRenderHeight) != dst.rows ||
147  itsRenderType != dst.type())
148  {
149  if (itsRenderbufferId) glDeleteRenderbuffers(1, &itsRenderbufferId);
150  if (itsFramebufferId) glDeleteFramebuffers(1, &itsFramebufferId);
151 
152  GL_CHECK(glGenFramebuffers(1, &itsFramebufferId));
153  GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, itsFramebufferId));
154 
155  GL_CHECK(glGenRenderbuffers(1, &itsRenderbufferId));
156  GL_CHECK(glBindRenderbuffer(GL_RENDERBUFFER, itsRenderbufferId));
157  GL_CHECK(glRenderbufferStorage(GL_RENDERBUFFER, dstformat, dst.cols, dst.rows));
158  GL_CHECK(glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, itsRenderbufferId));
159 
160  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
161  LFATAL("Framebuffer creation failed");
162 
163  itsRenderWidth = dst.cols; itsRenderHeight = dst.rows; itsRenderType = dst.type();
164  LINFO("Render buffer " << itsRenderWidth << 'x' << itsRenderHeight << ' ' <<
165  (dst.channels() == 2 ? "RGB565" : "RGBA") << " ready.");
166  }
167 
168  // Create our vertex buffer if needed:
169  if (itsQuadVertexBuffer == 0)
170  {
171  // Create an ickle vertex buffer:
172  static GLfloat const qv[] =
173  { 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
174 
175  GL_CHECK(glGenBuffers(1, &itsQuadVertexBuffer));
176  glBindBuffer(GL_ARRAY_BUFFER, itsQuadVertexBuffer);
177  glBufferData(GL_ARRAY_BUFFER, sizeof(qv), qv, GL_STATIC_DRAW);
178  GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, 0));
179  }
180 
181  // Set background color and clear buffers:
182  glClearColor(0.15f, 0.25f, 0.35f, 1.0f);
183  glClear(GL_COLOR_BUFFER_BIT);
184 
185  // Copy source pixel data to source texture:
186  itsSrcTex->setPixels(src.data);
187 
188  // Tell OpenGL to render into our destination framebuffer:
189  GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, itsFramebufferId));
190  GL_CHECK(glViewport(0, 0, itsRenderWidth, itsRenderHeight));
191 
192  // Load the shader program if needed:
193  if (itsProgramChanged)
194  {
195  std::lock_guard<std::mutex> _(itsMutex);
196 
197  // Nuke any old program first:
198  itsProgram.reset();
199 
200  // Create, load and compile the program:
201  itsProgram.reset(new GPUprogram(itsVshader.c_str(), itsFshader.c_str()));
202  itsProgramChanged = false;
203 
204  // Tell OpenGL to use our program:
205  glUseProgram(itsProgram->id());
206 
207  // Set all the parameters:
208  GLuint i = itsProgram->id();
209  for (auto const & p : itsProgramParams)
210  {
211  char const * n = p.first.c_str();
212  float const * v = &p.second.val[0];
213  switch (p.second.type)
214  {
215  case F2: GL_CHECK(glUniform2f(glGetUniformLocation(i, n), v[0], v[1])); break;
216  case F1: GL_CHECK(glUniform1f(glGetUniformLocation(i, n), v[0])); break;
217  case I2: GL_CHECK(glUniform2i(glGetUniformLocation(i, n), int(v[0]), int(v[1]))); break;
218  case I1: GL_CHECK(glUniform1i(glGetUniformLocation(i, n), int(v[0]))); break;
219  default: LFATAL("Unsupported GPU program parameter type " << p.second.type);
220  }
221  }
222  }
223 
224  if (!itsProgram) LFATAL("You need to set a program before processing frames");
225 
226  // Set program parameters that all programs should always use, ignore any error:
227  glUniform2f(glGetUniformLocation(itsProgram->id(), "texelsize"), 1.0f / dst.cols, 1.0f / dst.rows);
228  glUniform1i(glGetUniformLocation(itsProgram->id(), "tex"), 0);
229 
230  // Draw the texture onto the triangle strip, applying the program:
231  GL_CHECK(glBindBuffer(GL_ARRAY_BUFFER, itsQuadVertexBuffer));
232  GL_CHECK(glActiveTexture(GL_TEXTURE0));
233  GL_CHECK(glBindTexture(GL_TEXTURE_2D, itsSrcTex->Id));
234 
235  GLuint loc = glGetAttribLocation(itsProgram->id(), "vertex");
236  GL_CHECK(glVertexAttribPointer(loc, 4, GL_FLOAT, 0, 16, 0));
237  GL_CHECK(glEnableVertexAttribArray(loc));
238  GL_CHECK(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
239 
240  // Copy the rendered pixels to destination image:
241  GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, itsFramebufferId));
242  if (dstformat == GL_RGBA4) GL_CHECK(glReadPixels(0, 0, dst.cols, dst.rows, GL_RGBA, GL_UNSIGNED_BYTE, dst.data));
243  else GL_CHECK(glReadPixels(0, 0, dst.cols, dst.rows, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, dst.data));
244  GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, 0));
245 }
246 
FilterGPU::setProgramParam2i
void setProgramParam2i(std::string name, int val1, int val2)
Set some program parameters, 2 int version.
Definition: FilterGPU.C:108
FilterGPU::setProgramParam1f
void setProgramParam1f(std::string name, float val)
Set some program parameters, 1 float version.
Definition: FilterGPU.C:100
FilterGPU::setProgram
void setProgram(std::string const &vertex_shader, std::string const &frag_shader)
Set an image processing program.
Definition: FilterGPU.C:80
GL_CHECK
#define GL_CHECK(stmt)
FilterGPU.H
GL_CHECK_BOOL
#define GL_CHECK_BOOL(stmt)
jevois
FilterGPU::setProgramParam1i
void setProgramParam1i(std::string name, int val)
Set some program parameters, 1 int version.
Definition: FilterGPU.C:116
FilterGPU::FilterGPU
FilterGPU(std::string const &instance)
Constructor.
Definition: FilterGPU.C:21
FilterGPU::setProgramParam2f
void setProgramParam2f(std::string name, float val1, float val2)
Set some program parameters, 2 float version.
Definition: FilterGPU.C:92
LFATAL
#define LFATAL(msg)
FilterGPU::process
void process(cv::Mat const &src, cv::Mat &dst)
Process an image. The dst image should be allocated with correct image size and pixel type.
Definition: FilterGPU.C:124
GPUprogram
Simple class to load and compile some OpenGL-ES program.
Definition: GPUprogram.H:23
GPUtexture
Simple class to hold an OpenGL texture.
Definition: GPUtexture.H:26
demo.float
float
Definition: demo.py:39
jevois::Component::absolutePath
std::filesystem::path absolutePath(std::filesystem::path const &path="")
FilterGPU::~FilterGPU
~FilterGPU()
Destructor.
Definition: FilterGPU.C:62
name
std::string name
LINFO
#define LINFO(msg)