Beatmup
operation.cpp
Go to the documentation of this file.
1 /*
2  Beatmup image and signal processing library
3  Copyright (C) 2020, lnstadrum
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "operation.h"
20 
21 #define POS_FMT "%0.10f"
22 
23 using namespace Beatmup;
24 using namespace NNets;
25 
26 static const char
27  *UNIFORM_DELTA = "d", //!< uniform variable storing spatial deltas for SpatialFilteringMixin
28  *UNIFORM_SHIFT = "shift"; //!< uniform variable specifying an offset to apply to the sampling position in SpatialFilteringMixin
29 
31 
32 Size AbstractOperation::getOutputSize(int outputIndex) const {
33  throw RuntimeError("Operation " + name + " does not have output #" + std::to_string(outputIndex));
34 }
35 
36 
38  throw RuntimeError("Operation " + name + " does not take Storage View on output #" + std::to_string(index));
39 }
40 
41 
43  throw RuntimeError("Operation " + name + " does not take Vector on output #" + std::to_string(index));
44 }
45 
46 
48  throw RuntimeError("Operation " + name + " does not take TextureHandler on output #" + std::to_string(index));
49 }
50 
51 
53  throw RuntimeError("Operation " + name + " does not take Storage View on input #" + std::to_string(index));
54 }
55 
56 
58  throw RuntimeError("Operation " + name + " does not take Storage View on output #" + std::to_string(index));
59 }
60 
61 
63  throw RuntimeError("Operation " + name + " does not take Vector on input #" + std::to_string(index));
64 }
65 
66 
68  throw RuntimeError("Operation " + name + " does not take Vector on output #" + std::to_string(index));
69 }
70 
71 
73  throw RuntimeError("Operation " + name + " does not take TextureHandler on input #" + std::to_string(index));
74 }
75 
76 
78  throw RuntimeError("Operation " + name + " does not take TextureHandler on output #" + std::to_string(index));
79 }
80 
81 
82 
83 std::map<std::string, AbstractOperation::Deserializer*>& AbstractOperation::Deserializer::getDeserializersMap() {
84  static std::map<std::string, Deserializer*> map;
85  return map;
86 }
87 
88 
90  // add the instance to map
91 #ifdef BEATMUP_DEBUG
92  DebugAssertion::check(getDeserializersMap().count(opType) == 0, "A deserializer of a specific operation is already registered");
93 #endif
94  getDeserializersMap().emplace(opType, this);
95 }
96 
97 
98 SpatialFilteringMixin::SpatialFilteringMixin(const int nbSizeX, const int nbSizeY):
99  nbSizeX(nbSizeX), nbSizeY(nbSizeY), useUniformShift(false)
100 {
101  deltas = new float[2 * getDeltasSize()];
102 }
103 
104 
106  delete[] deltas;
107 }
108 
109 
110 void SpatialFilteringMixin::writeHeader(StringBuilder& code, bool useUniformShift) {
111  if (nbSizeX > 1 || nbSizeY > 1)
112  code.printf("uniform highp vec2 %s[%d];\n", UNIFORM_DELTA, getDeltasSize());
113  if (useUniformShift)
114  code.printf("uniform highp vec2 %s;\n", UNIFORM_SHIFT);
115  this->useUniformShift = useUniformShift;
116 }
117 
118 
119 void SpatialFilteringMixin::declare(StringBuilder& code, const char* datatype, bool inlineSampling) {
120  // declaring neighborhood samples
121  const int nbSize = std::max(nbSizeX, nbSizeY);
122  code.printf("%s i00", datatype);
123  for (int i = 1; i < nbSize * nbSize; ++i)
124  code.printf(", %s%d%d", SAMPLE_ID_PREFIX, i % nbSize, i / nbSize);
125  code.line(";");
126 
127  // declaring/computing neighborhood positions
128  const int mid = (nbSize - 1) / 2;
129  code.printf("highp vec2 p%d = %s", mid, GL::RenderingPrograms::TEXTURE_COORDINATES_ID);
130  if (useUniformShift)
131  code.printf(" + %s", UNIFORM_SHIFT);
132  for (int i = 0; i < nbSize; ++i) {
133  if (i != mid) code(", ");
134  if (i < mid)
135  code.printf("p%d = p%d - %s[%d]", i, mid, UNIFORM_DELTA, mid - i - 1);
136  else if (i > mid)
137  code.printf("p%d = p%d + %s[%d]", i, mid, UNIFORM_DELTA, i - mid - 1);
138  }
139 
140  // declaring neighborhood positions shifted for a current input channel
141  if (!inlineSampling) {
142  code(", cp0");
143  for (int i = 1; i < nbSize; ++i)
144  code.printf(", cp%d", i);
145  }
146  code.line(";");
147 }
148 
149 
150 void SpatialFilteringMixin::sample(StringBuilder& code, const char* inputName, const int inputIndex, const Point& shift, const bool isFirstSample, const char* suffix) {
151  // shift neighborhood positions to the channel origin
152  if (isFirstSample || this->shift != shift) {
153  const int nbSize = std::max(nbSizeX, nbSizeY);
154  for (int i = 0; i < nbSize; ++i)
155  if (shift == Point::ZERO)
156  code.printf("cp%d = p%d;\n", i, i);
157  else
158  code.printf("cp%d = p%d + vec2(" POS_FMT ", " POS_FMT ");\n", i, i, shift.x, shift.y);
159  this->shift = shift;
160  }
161 
162  // sample input
163  for (int y = 0; y < nbSizeY; ++y)
164  for (int x = 0; x < nbSizeX; ++x)
165  if (x == y)
166  code.printf("%s%d%d = texture2D(%s[%d], cp%d)%s;\n",
167  SAMPLE_ID_PREFIX, x, y, inputName, inputIndex, x, suffix);
168  else
169  code.printf("%s%d%d = texture2D(%s[%d], vec2(cp%d.x, cp%d.y))%s;\n",
170  SAMPLE_ID_PREFIX, x, y, inputName, inputIndex, x, y, suffix);
171 }
172 
173 
174 void SpatialFilteringMixin::sampleInline(StringBuilder& code, const char* inputName, const int inputIndex, const IntPoint& position, const Point& shift, const char* suffix) {
175 #ifdef BEATMUP_DEBUG
176  OutOfRange::check(position.x, 0, nbSizeX - 1, "neighbor X coordinate is out of range: %d");
177  OutOfRange::check(position.y, 0, nbSizeY - 1, "neighbor X coordinate is out of range: %d");
178 #endif
179 
180  // begin texture sampling statement
181  code.printf("texture2D(%s[%d], ", inputName, inputIndex);
182 
183  // print out texture coordinate
184  if (position.x == position.y)
185  code.printf("p%d", position.x);
186  else
187  code.printf("vec2(p%d.x, p%d.y)", position.x, position.y);
188 
189  if (shift != Point::ZERO)
190  code.printf(" + vec2(" POS_FMT ", " POS_FMT ")", shift.x, shift.y);
191 
192  // print the remainder
193  code.printf(")%s", suffix);
194 }
195 
196 
197 void SpatialFilteringMixin::setup(const int width, const int height) {
198  if (nbSizeX > 1 || nbSizeY > 1) {
199  const int deltaSize = getDeltasSize();
200  for (int i = 0; i < deltaSize; ++i) {
201  const float f = (float)(i + 1);
202  deltas[2*i ] = f / width;
203  deltas[2*i+1] = f / height;
204  }
205  }
206 }
207 
208 
209 void SpatialFilteringMixin::setUniformShift(GL::Program& program, const IntPoint& shift, const IntPoint& inputSize) {
210 #ifdef BEATMUP_DEBUG
211  DebugAssertion::check(useUniformShift, "Uniform shift is not enabled");
212 #endif
213  program.setVector2(UNIFORM_SHIFT, (float)shift.x / inputSize.x, (float)shift.y / inputSize.y);
214 }
215 
216 
218  if (nbSizeX > 1 || nbSizeY > 1)
220 }
221 
222 
224  const IntPoint halfKernel((nbSizeX - 1) / 2, (nbSizeY - 1) / 2);
225 
226  if (padding == Size::Padding::VALID) {
227  const IntPoint
228  // number of times the kernel is applied
229  n((size - IntPoint(nbSizeX, nbSizeY)) / stride + 1);
230 
231  return IntRectangle(halfKernel, halfKernel + (n - 1) * stride);
232  }
233 
234  else {
235  const IntPoint
236  // number of times the kernel is applied
237  n(ceili(size, stride)),
238 
239  // pixels participating in all the kernels applications (distance from border to border)
240  coverage = (n - 1) * stride + IntPoint(nbSizeX, nbSizeX),
241 
242  // excessive pixels to be padded on one side
243  ledge = (coverage - size) / 2,
244 
245  // the ledge part covered by kernel
246  kernelShift = std::min(halfKernel, halfKernel - ledge);
247 
248  return IntRectangle(kernelShift, kernelShift + (n - 1) * stride);
249  }
250 }
251 
252 
253 IntRectangle SpatialFilteringMixin::getSamplingArea(const Storage::View& storage, const int channel, const IntPoint& stride, const Size::Padding padding) const {
254  return getSamplingArea(
255  IntPoint(storage.getWidth(), storage.getHeight()),
256  stride, padding
257  ).translated(storage.getChannelOrigin(channel));
258 }
259 
260 
262  const Storage::View& storage,
263  const int channel,
264  const IntPoint& stride,
265  const Size::Padding padding,
266  const IntPoint& outputSize
267 ) const {
269  getSamplingArea(storage, channel, stride, padding),
270  IntPoint(storage.getTextureWidth(), storage.getTextureHeight()),
271  outputSize
272  );
273 }
274 
275 
277  const int mid = (std::max(nbSizeX, nbSizeY) - 1) / 2;
278  return "p" + std::to_string(mid);
279 }
280 
281 
282 void ActivationFunctionMixin::apply(StringBuilder& code, const char* inputVariable) {
283  switch (activationFunc) {
285  code.printf("gl_FragColor = %s;", inputVariable);
286  return;
288  code.printf("gl_FragColor = 0.167 * %s;", inputVariable);
289  return;
291  code.printf("gl_FragColor = clamp(0.1*(%s), -0.05, 0.05) + clamp(0.05*(%s), -0.125, 0.125) + 0.05*(%s) + 0.5;", inputVariable, inputVariable, inputVariable);
292  return;
293  default: Insanity::insanity("Invalid activation function");
294  }
295 }
296 
297 
299  const int num = thread.numThreads();
300  beforeExecute(gpu, num);
301  const int amount = getAmountOfWork();
302  thread.synchronize();
303 
304  // process the first hunk
305  execute(0, amount / num, 0, num);
306 
307  thread.synchronize();
308  afterExecute(num);
309 }
310 
311 
313  thread.synchronize();
314  if (thread.isTaskAborted())
315  return;
316  const int
317  amount = getAmountOfWork(),
318  idx = thread.currentThread(),
319  num = thread.numThreads();
320  execute(
321  idx * amount / num,
322  (idx + 1) * amount / num,
323  idx, num
324  );
325  thread.synchronize();
326 }
327 
328 
330  const std::string lc = StringUtils::lowercase(str);
331  if (lc == "default")
333  if (lc == "brelu6")
335  if (lc == "sigmoid_like")
337  throw InvalidArgument("Invalid activation function: " + str);
339 }
340 
341 
342 std::string std::to_string(ActivationFunction function) {
343  /** \page NNetsActivationFunctionsSerialization Activation functions serialization
344  In the serialized representation, activation functions are specified with a single keyword.
345  - `default`: identity clipped to 0..1 range
346  - `brelu6`: 0.167 times identity clipped to 0..1 range
347  - `sigmoid_like`: a piecewise-linear sigmoid function approximation
348  */
349  switch (function) {
350  case ActivationFunction::DEFAULT: return "default";
351  case ActivationFunction::BRELU6: return "brelu6";
352  case ActivationFunction::SIGMOID_LIKE: return "sigmoid_like";
353  }
354  Insanity::insanity("Invalid activation function");
355  return "";
356 }
static const CustomPoint ZERO
Definition: geometry.h:122
CustomRectangle translated(numeric x, numeric y) const
Returns a translated box.
Definition: geometry.h:235
void setVec2Array(const std::string &name, const float *xy, const int length)
Definition: program.cpp:458
void setVector2(const std::string &name, const float x, const float y)
Definition: program.cpp:362
Regular OpenGL program.
Definition: program.h:229
static const char * TEXTURE_COORDINATES_ID
Texture coordinates shader variable name in vertex shader.
Real-valued vector usable by GPU.
Internal low-level GPU control API.
Definition: pipeline.h:33
static Rectangle getTextureCoordinates(const Rectangle &area, const IntPoint &size, const IntPoint &sampling)
Computes floating-point texture coordinates for pixel-accurate sampling: a texture gets sampled exact...
Definition: pipeline.cpp:976
static void insanity(const char *message)
Definition: exception.h:136
Deserializer(const char *opType)
Registers a new deserializer capable of building an operation from a listing block.
Definition: operation.cpp:89
static std::map< std::string, Deserializer * > & getDeserializersMap()
Definition: operation.cpp:83
virtual Size getOutputSize(int outputIndex=0) const
Returns full size of a specific operation output.
Definition: operation.cpp:32
virtual void setInput(Storage::View &&storage, int index=0)
Definition: operation.cpp:52
virtual Storage::View getOutput(int index=0)
Returns a storage view bound to a specific operation output.
Definition: operation.cpp:37
virtual void setOutput(Storage::View &&storage, int index=0)
Definition: operation.cpp:57
const ActivationFunction activationFunc
Definition: operation.h:417
void apply(StringBuilder &code, const char *inputVariable)
Renders a GLSL code applying activation function to a specific variable and writing the result to gl_...
Definition: operation.cpp:282
void execute(TaskThread &thread, GraphicPipeline &gpu)
Executes the operation.
Definition: operation.cpp:298
virtual int getAmountOfWork() const =0
Returns amount of work in arbitrary units to be splitted among threads.
virtual void beforeExecute(GraphicPipeline &gpu, const int threadCount)
Called right before the operation is executed.
Definition: operation.h:450
virtual void afterExecute(const int threadCount)
Called right after the operation is executed.
Definition: operation.h:455
Operation 3D input/output size.
Definition: storage.h:37
Padding
Zero padding specification.
Definition: storage.h:45
Rectangle getTextureCoordinates(const Storage::View &storage, const int channel, const IntPoint &stride, const Size::Padding padding, const IntPoint &outputSize) const
Computes texture coordinates sampling a specific storage channel for given stride,...
Definition: operation.cpp:261
void sample(StringBuilder &code, const char *inputName, const int inputIndex, const Point &shift, const bool isFirstSample=true, const char *suffix="")
Samples a neighborhood of a given texture.
Definition: operation.cpp:150
void sampleInline(StringBuilder &code, const char *inputName, const int inputIndex, const IntPoint &position, const Point &shift, const char *suffix="")
Definition: operation.cpp:174
void setup(const int width, const int height)
Prepares the spatial filtering operation execution.
Definition: operation.cpp:197
static const char * SAMPLE_ID_PREFIX
prefix of variables declaring a neighbor sample
Definition: operation.h:285
Point shift
current static shift of the sampling position
Definition: operation.h:275
void writeHeader(StringBuilder &code, bool useUniformShift)
Writes out the very GLSL fragment shader header required for spatial neighborhood sampling.
Definition: operation.cpp:110
std::string getInputSamplingPos() const
Retrieves input sampling point position for the current fragment.
Definition: operation.cpp:276
void declare(StringBuilder &code, const char *datatype, bool inlineSampling=false)
Declares GLSL fragment shader main(..) code part required for spatial neighborhood sampling.
Definition: operation.cpp:119
void setupProgram(GL::Program &program)
Prepares a given program for spatial filtering.
Definition: operation.cpp:217
IntRectangle getSamplingArea(const IntPoint &size, const IntPoint &stride, const Size::Padding padding) const
Implements common padding policies by computing a rectangular area of positions the sampling kernel t...
Definition: operation.cpp:223
bool useUniformShift
if true, the sampling position can be shifted dynamically at every run
Definition: operation.h:277
SpatialFilteringMixin(const int nbSizeX, const int nbSizeY)
Initializes spatial filtering mixin.
Definition: operation.cpp:98
void setUniformShift(GL::Program &program, const IntPoint &shift, const IntPoint &inputSize)
Applies an offset to the sampling position at runtime.
Definition: operation.cpp:209
float * deltas
array storing pixel position differences for neighborhood sampling
Definition: operation.h:276
Maps a 3D tensor onto a storage.
Definition: storage.h:308
int getTextureHeight() const
Returns height in pixels of all the textures.
Definition: storage.h:375
IntPoint getChannelOrigin(int channel) const
Returns origin in pixels of a given channel within the texture containing it.
Definition: storage.cpp:509
int getTextureWidth() const
Returns width in pixels of all the textures.
Definition: storage.h:370
static void check(const datatype value, const datatype min, const datatype max, const char *message)
Definition: exception.h:86
Toolset to build a string content.
StringBuilder & line(const std::string &append)
StringBuilder & printf(const char *format,...)
Thread executing tasks.
Definition: parallelism.h:154
virtual ThreadIndex numThreads() const =0
virtual void synchronize()=0
Blocks until all the other threads running the same task reach the same point.
ThreadIndex currentThread() const
Definition: parallelism.h:165
virtual bool isTaskAborted() const =0
Returns true if the task is asked to stop from outside.
ActivationFunction activationFunctionFromString(const std::string &str)
Returns a zero padding value from a string.
Definition: operation.cpp:329
ActivationFunction
Activation function specification.
Definition: operation.h:401
@ SIGMOID_LIKE
piecewise-linear sigmoid approximation
@ DEFAULT
default activation: 0..1 bounded ReLU (identity clipped to 0..1 range)
@ BRELU6
0.167 times identity clipped to 0..1 range
std::string lowercase(const std::string &str)
Converts a string to lower case (latin letters only).
CustomRectangle< int > IntRectangle
Definition: geometry.h:630
CustomPoint< int > IntPoint
Definition: geometry.h:629
std::string to_string(Beatmup::NNets::ActivationFunction function)
CustomPoint< numeric > min(const CustomPoint< numeric > &a, const CustomPoint< numeric > &b)
Definition: geometry.h:724
CustomPoint< numeric > max(const CustomPoint< numeric > &a, const CustomPoint< numeric > &b)
Definition: geometry.h:728
#define POS_FMT
Definition: operation.cpp:21
static const char * UNIFORM_SHIFT
uniform variable specifying an offset to apply to the sampling position in SpatialFilteringMixin
Definition: operation.cpp:28
static const char * UNIFORM_DELTA
uniform variable storing spatial deltas for SpatialFilteringMixin
Definition: operation.cpp:27
#define ceili(x, y)
integer division x/y with ceiling
Definition: utils.hpp:21
jlong jint index
jobject jlong jint jint y
JNIEnv jlong jint jint count
jlong jint width
jlong jint jint height
jobject jlong jint x
jlong jobject size
int n
JNIEnv jobject jstring str