Vuo  2.0.2
VuoSceneObjectRenderer.cc
Go to the documentation of this file.
1 
10 #include "VuoSceneObjectRenderer.h"
11 
12 #include <Block.h>
13 #include <CoreServices/CoreServices.h>
14 #include <OpenGL/CGLMacro.h>
16 #define glGenVertexArrays glGenVertexArraysAPPLE
17 #define glBindVertexArray glBindVertexArrayAPPLE
18 #define glDeleteVertexArrays glDeleteVertexArraysAPPLE
19 
21 #include "module.h"
22 
23 extern "C"
24 {
25 #ifdef VUO_COMPILER
27  "title" : "VuoSceneObjectRenderer",
28  "dependencies" : [
29  "VuoSceneObject",
30  "VuoShader",
31  "VuoGlContext",
32  "VuoGlPool",
33  "OpenGL.framework"
34  ]
35  });
36 #endif
37 }
38 
42 typedef struct
43 {
44  GLint position;
45  GLint normal;
46  GLint textureCoordinate;
47  GLint color;
48 
49  unsigned int expectedOutputPrimitiveCount;
50  bool mayChangeOutputPrimitiveCount;
52 
57 {
58  VuoShader shader;
59 
60  GLuint shamTexture;
61  GLuint shamFramebuffer;
62 
63  GLuint query;
64 
65  GLuint vertexArray;
66 
67  VuoSceneObjectRenderer_Attributes pointAttributes;
68  VuoSceneObjectRenderer_Attributes lineAttributes;
69  VuoSceneObjectRenderer_Attributes triangleAttributes;
70 };
71 
73 
80 {
81  if (!VuoShader_isTransformFeedback(shader))
82  {
83  VUserLog("Error '%s' is not a transform feedback shader.", shader->name);
84  return NULL;
85  }
86 
87  __block struct VuoSceneObjectRendererInternal *sceneObjectRenderer = (struct VuoSceneObjectRendererInternal *)malloc(sizeof(struct VuoSceneObjectRendererInternal));
88  VuoRegister(sceneObjectRenderer, VuoSceneObjectRenderer_destroy);
89 
91  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
92  // Fetch the shader's attribute locations
93  bool havePoints = VuoShader_getAttributeLocations(shader, VuoMesh_Points, cgl_ctx, &sceneObjectRenderer->pointAttributes.position, &sceneObjectRenderer->pointAttributes.normal, &sceneObjectRenderer->pointAttributes.textureCoordinate, &sceneObjectRenderer->pointAttributes.color );
94  bool haveLines = VuoShader_getAttributeLocations(shader, VuoMesh_IndividualLines, cgl_ctx, &sceneObjectRenderer->lineAttributes.position, &sceneObjectRenderer->lineAttributes.normal, &sceneObjectRenderer->lineAttributes.textureCoordinate, &sceneObjectRenderer->lineAttributes.color );
95  bool haveTriangles = VuoShader_getAttributeLocations(shader, VuoMesh_IndividualTriangles, cgl_ctx, &sceneObjectRenderer->triangleAttributes.position, &sceneObjectRenderer->triangleAttributes.normal, &sceneObjectRenderer->triangleAttributes.textureCoordinate, &sceneObjectRenderer->triangleAttributes.color);
96  if (!havePoints || !haveLines || !haveTriangles)
97  {
98  VUserLog("Error: '%s' is missing programs for: %s %s %s", shader->name, havePoints? "" : "points", haveLines? "" : "lines", haveTriangles? "" : "triangles");
99  free(sceneObjectRenderer);
100  sceneObjectRenderer = NULL;
101  return;
102  }
103 
104  sceneObjectRenderer->pointAttributes.expectedOutputPrimitiveCount = VuoShader_getExpectedOutputPrimitiveCount (shader, VuoMesh_Points);
105  sceneObjectRenderer->pointAttributes.mayChangeOutputPrimitiveCount = VuoShader_getMayChangeOutputPrimitiveCount(shader, VuoMesh_Points);
106 
107  sceneObjectRenderer->lineAttributes.expectedOutputPrimitiveCount = VuoShader_getExpectedOutputPrimitiveCount (shader, VuoMesh_IndividualLines);
108  sceneObjectRenderer->lineAttributes.mayChangeOutputPrimitiveCount = VuoShader_getMayChangeOutputPrimitiveCount(shader, VuoMesh_IndividualLines);
109 
110  sceneObjectRenderer->triangleAttributes.expectedOutputPrimitiveCount = VuoShader_getExpectedOutputPrimitiveCount (shader, VuoMesh_IndividualTriangles);
111  sceneObjectRenderer->triangleAttributes.mayChangeOutputPrimitiveCount = VuoShader_getMayChangeOutputPrimitiveCount(shader, VuoMesh_IndividualTriangles);
112 
113  sceneObjectRenderer->shader = shader;
114  VuoRetain(sceneObjectRenderer->shader);
115 
116  glGenVertexArrays(1, &sceneObjectRenderer->vertexArray);
117 
118  // https://stackoverflow.com/questions/24112671/transform-feedback-without-a-framebuffer
119  sceneObjectRenderer->shamTexture = VuoGlTexturePool_use(cgl_ctx, VuoGlTexturePool_Allocate, GL_TEXTURE_2D, GL_RGBA, 1, 1, GL_BGRA, NULL);
120  VuoGlTexture_retain(sceneObjectRenderer->shamTexture, NULL, NULL);
121  glGenFramebuffers(1, &sceneObjectRenderer->shamFramebuffer);
122  glBindFramebuffer(GL_FRAMEBUFFER, sceneObjectRenderer->shamFramebuffer);
123  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sceneObjectRenderer->shamTexture, 0);
124  glBindFramebuffer(GL_FRAMEBUFFER, 0);
125 
126  glGenQueries(1, &sceneObjectRenderer->query);
127  });
128 
129  return (VuoSceneObjectRenderer)sceneObjectRenderer;
130 }
131 
136 static void VuoSceneObjectRenderer_drawSingle(CGLContextObj cgl_ctx, struct VuoSceneObjectRendererInternal *sceneObjectRenderer, VuoSceneObject sceneObject, float modelviewMatrix[16])
137 {
138  VuoMesh mesh = VuoSceneObject_getMesh(sceneObject);
139  if (!mesh)
140  return;
141 
143  GLuint outputPrimitiveGlMode;
145  int primitiveVertexMultiplier;
146  if (outputPrimitiveMode == VuoMesh_IndividualTriangles)
147  {
148  outputPrimitiveGlMode = GL_TRIANGLES;
149  attributes = sceneObjectRenderer->triangleAttributes;
150  primitiveVertexMultiplier = 3;
151  }
152  else if (outputPrimitiveMode == VuoMesh_IndividualLines)
153  {
154  outputPrimitiveGlMode = GL_LINES;
155  attributes = sceneObjectRenderer->lineAttributes;
156  primitiveVertexMultiplier = 2;
157  }
158  else // if (submesh.elementAssemblyMethod == VuoMesh_Points)
159  {
160  outputPrimitiveGlMode = GL_POINTS;
161  attributes = sceneObjectRenderer->pointAttributes;
162  primitiveVertexMultiplier = 1;
163  }
164 
165  unsigned int vertexCount, combinedBuffer, elementCount, elementBuffer;
166  void *normalOffset, *textureCoordinateOffset, *colorOffset;
167  VuoMesh_getGPUBuffers(mesh, &vertexCount, &combinedBuffer, &normalOffset, &textureCoordinateOffset, &colorOffset, &elementCount, &elementBuffer);
168 
169 
170  // Attach the input combinedBuffer for rendering.
171  glBindBuffer(GL_ARRAY_BUFFER, combinedBuffer);
172 
173  VuoGlProgram program;
174  if (!VuoShader_activate(sceneObjectRenderer->shader, VuoMesh_getElementAssemblyMethod(mesh), cgl_ctx, &program))
175  {
176  VUserLog("Shader activation failed.");
177  return;
178  }
179 
180 
181  GLint modelviewMatrixUniform = VuoGlProgram_getUniformLocation(program, "modelviewMatrix");
182  if (modelviewMatrixUniform != -1)
183  glUniformMatrix4fv(modelviewMatrixUniform, 1, GL_FALSE, modelviewMatrix);
184 
185 
186  GLint modelviewMatrixInverseUniform = VuoGlProgram_getUniformLocation(program, "modelviewMatrixInverse");
187  if (modelviewMatrixInverseUniform != -1)
188  {
189  float modelviewMatrixInverse[16];
190  VuoTransform_invertMatrix4x4(modelviewMatrix, modelviewMatrixInverse);
191  glUniformMatrix4fv(modelviewMatrixInverseUniform, 1, GL_FALSE, modelviewMatrixInverse);
192  }
193 
194 
195  int stride = sizeof(float) * 3;
196  glEnableVertexAttribArray(attributes.position);
197  glVertexAttribPointer(attributes.position, 3 /* XYZ */, GL_FLOAT, GL_FALSE, stride, (void*)0);
198  bool hasNormals = normalOffset && attributes.normal >= 0;
199  if (hasNormals)
200  {
201  glEnableVertexAttribArray(attributes.normal);
202  glVertexAttribPointer(attributes.normal, 3 /* XYZ */, GL_FLOAT, GL_FALSE, stride, normalOffset);
203  }
204  bool hasTextureCoordinates = textureCoordinateOffset && attributes.textureCoordinate >= 0;
205  if (hasTextureCoordinates)
206  {
207  glEnableVertexAttribArray(attributes.textureCoordinate);
208  glVertexAttribPointer(attributes.textureCoordinate, 2 /* XY */, GL_FLOAT, GL_FALSE, sizeof(float) * 2, textureCoordinateOffset);
209  }
210  bool hasColors = colorOffset && attributes.color >= 0;
211  if (hasColors)
212  {
213  glEnableVertexAttribArray(attributes.color);
214  glVertexAttribPointer(attributes.color, 4 /* RGBA */, GL_FLOAT, GL_FALSE, sizeof(float) * 4, colorOffset);
215  }
216 
217 
218  // Attach the input elementBuffer for rendering.
219  if (elementBuffer)
220  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer);
221 
222 
223  // Create and attach the output combinedBuffer.
224  // The output buffer will always contain all 4 vertex attributes (position, normal, textureCoordinate, color).
225  unsigned long outputVertexCount = VuoMesh_getSplitVertexCount(mesh) * attributes.expectedOutputPrimitiveCount;
226  unsigned long combinedOutputBufferSize = sizeof(float) * (3 + 3 + 2 + 4) * outputVertexCount;
227  GLuint combinedOutputBuffer = VuoGlPool_use(cgl_ctx, VuoGlPool_ArrayBuffer, combinedOutputBufferSize);
228  VuoGlPool_retain(combinedOutputBuffer);
229  glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, combinedOutputBuffer);
230 // glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, combinedOutputBufferSize, NULL, GL_STATIC_READ);
231 // glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0, combinedOutputBuffer);
232 
233  int offset = 0;
234  int size = sizeof(float) * 3 * outputVertexCount;
235  glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0, combinedOutputBuffer, 0, size);
236  offset += size;
237  glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 1, combinedOutputBuffer, offset, size);
238  offset += size;
239  size = sizeof(float) * 2 * outputVertexCount;
240  glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 2, combinedOutputBuffer, offset, size);
241  offset += size;
242  size = sizeof(float) * 4 * outputVertexCount;
243  glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 3, combinedOutputBuffer, offset, size);
244 
245 
246 
247  // Execute the filter.
248  GLenum mode = VuoMesh_getGlMode(mesh);
249  if (attributes.mayChangeOutputPrimitiveCount)
250  glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT, sceneObjectRenderer->query);
251  glBeginTransformFeedbackEXT(outputPrimitiveGlMode);
252 
253 #ifdef VUO_PROFILE
254  GLuint timeElapsedQuery;
255  glGenQueries(1, &timeElapsedQuery);
256  glBeginQuery(GL_TIME_ELAPSED_EXT, timeElapsedQuery);
257 #endif
258 
259  unsigned long completeInputElementCount = VuoMesh_getCompleteElementCount(mesh);
260  if (elementCount)
261  glDrawElements(mode, completeInputElementCount, GL_UNSIGNED_INT, (void*)0);
262  else if (vertexCount)
263  glDrawArrays(mode, 0, completeInputElementCount);
264 
265 #ifdef VUO_PROFILE
266  double seconds;
267  glEndQuery(GL_TIME_ELAPSED_EXT);
268  GLuint nanoseconds;
269  glGetQueryObjectuiv(timeElapsedQuery, GL_QUERY_RESULT, &nanoseconds);
270  seconds = ((double)nanoseconds) / NSEC_PER_SEC;
271  glDeleteQueries(1, &timeElapsedQuery);
272 
273  double objectPercent = seconds / (1./60.) * 100.;
274  VLog("%6.2f %% of 60 Hz frame %s (%s)", objectPercent, sceneObject->name, sceneObjectRenderer->shader->name);
275 #endif
276 
277 
278  glEndTransformFeedbackEXT();
279  if (attributes.mayChangeOutputPrimitiveCount)
280  glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT);
281 
282  glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0);
283 
284  if (elementBuffer)
285  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
286 
287  if (hasColors)
288  glDisableVertexAttribArray(attributes.color);
289  if (hasTextureCoordinates)
290  glDisableVertexAttribArray(attributes.textureCoordinate);
291  if (hasNormals)
292  glDisableVertexAttribArray(attributes.normal);
293  glDisableVertexAttribArray(attributes.position);
294 
295  VuoShader_deactivate(sceneObjectRenderer->shader, VuoMesh_getElementAssemblyMethod(mesh), cgl_ctx);
296 
297 
298  GLuint actualVertexCount = 0;
299  if (attributes.mayChangeOutputPrimitiveCount)
300  {
301  glGetQueryObjectuiv(sceneObjectRenderer->query, GL_QUERY_RESULT, &actualVertexCount);
302  actualVertexCount *= primitiveVertexMultiplier;
303  }
304  else
305  actualVertexCount = outputVertexCount;
306 
307 
308 #if 0 // NOCOMMIT
309  // Print out the result of the filter, for debugging.
310  VLog("inputElements=%lu actualVertexCount=%d normals=%d textureCoordinates=%d colors=%d", completeInputElementCount, actualVertexCount, hasNormals, hasTextureCoordinates, hasColors);
311  glFlush();
312  GLfloat feedback[actualVertexCount * (3 + 3 + 2 + 4)];
313  glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, combinedOutputBuffer);
314  glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0, sizeof(feedback), feedback);
315  for (int i = 0; i < actualVertexCount; ++i)
316  fprintf(stderr, "\t%3d = pos %5.2f %5.2f %5.2f normal %5.2f %5.2f %5.2f tc %5.2f %5.2f color %5.2f %5.2f %5.2f %5.2f\n", i,
317  feedback[i * 3], feedback[i * 3 + 1], feedback[i * 3 + 2],
318  feedback[(outputVertexCount + i) * 3], feedback[(outputVertexCount + i) * 3 + 1], feedback[(outputVertexCount + i) * 3 + 2],
319  feedback[outputVertexCount * (3 + 3) + i * 2], feedback[outputVertexCount * (3 + 3) + i * 2 + 1],
320  feedback[outputVertexCount * (3 + 3 + 2) + i * 4], feedback[outputVertexCount * (3 + 3 + 2) + i * 4 + 1], feedback[outputVertexCount * (3 + 3 + 2) + i * 4 + 2], feedback[outputVertexCount * (3 + 3 + 2) + i * 4 + 3]);
321  glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0);
322 #endif
323 
324  // https://vuo.org/node/1571
325  // https://b33p.net/kosada/node/12431
326  // The output buffer will always contain all 4 vertex attributes,
327  // though (depending on input) some might not contain contain useful data.
328 
330  actualVertexCount, combinedOutputBuffer, combinedOutputBufferSize,
331  (void *)(sizeof(float) * (3 ) * outputVertexCount),
332  (void *)(sizeof(float) * (3 + 3 ) * outputVertexCount),
333  (void *)(sizeof(float) * (3 + 3 + 2) * outputVertexCount),
334  0, 0, 0, // Since transform feedback doesn't provide an elementBuffer, render this submesh using glDrawArrays().
335  outputPrimitiveMode);
338 
339  VuoSceneObject_setMesh(sceneObject, newMesh);
340 }
341 
343 #define ELEM(i) (elementCount ? elements[i] : i)
344 
348 static inline void VuoSceneObjectRenderer_copyElement(VuoMesh mesh, int start, float *source, unsigned int elementCount, unsigned int *elements, VuoMesh_ElementAssemblyMethod elementAssemblyMethod, int verticesPerPrimitive, int floatsPerVertex, float *destination, float *defaultValues)
349 {
350  if (!source)
351  {
352  for (int i = 0; i < verticesPerPrimitive * floatsPerVertex; ++i)
353  destination[i] = defaultValues[i % floatsPerVertex];
354  return;
355  }
356 
357  if (elementAssemblyMethod == VuoMesh_IndividualTriangles
358  || elementAssemblyMethod == VuoMesh_IndividualLines
359  || elementAssemblyMethod == VuoMesh_Points)
360  {
361  if (elementCount)
362  {
363  unsigned int *e = elements + start;
364  for (int i = 0; i < verticesPerPrimitive; ++i)
365  for (int j = 0; j < floatsPerVertex; ++j)
366  destination[i * floatsPerVertex + j] = source[*(e + i) * floatsPerVertex + j];
367  }
368  else
369  {
370  for (int i = 0; i < verticesPerPrimitive; ++i)
371  for (int j = 0; j < floatsPerVertex; ++j)
372  destination[i * floatsPerVertex + j] = source[(start + i) * floatsPerVertex + j];
373  }
374  }
375  else if (elementAssemblyMethod == VuoMesh_TriangleStrip)
376  {
377  // Expand the triangle strip to individual triangles.
378  if ((start / 3) % 2 == 0)
379  {
380  for (int i = 0; i < verticesPerPrimitive; ++i)
381  for (int j = 0; j < floatsPerVertex; ++j)
382  destination[i * floatsPerVertex + j] = source[ELEM(start/3 + i) * floatsPerVertex + j];
383  }
384  else
385  {
386  for (int j = 0; j < floatsPerVertex; ++j)
387  {
388  destination[0 * floatsPerVertex + j] = source[ELEM(start/3 + 1) * floatsPerVertex + j];
389  destination[1 * floatsPerVertex + j] = source[ELEM(start/3 ) * floatsPerVertex + j];
390  destination[2 * floatsPerVertex + j] = source[ELEM(start/3 + 2) * floatsPerVertex + j];
391  }
392  }
393  }
394  else if (elementAssemblyMethod == VuoMesh_TriangleFan)
395  {
396  // Expand the triangle fan to individual triangles.
397  for (int j = 0; j < floatsPerVertex; ++j)
398  {
399  destination[0 * floatsPerVertex + j] = source[ELEM(0 ) * floatsPerVertex + j];
400  destination[1 * floatsPerVertex + j] = source[ELEM(start/3 + 1) * floatsPerVertex + j];
401  destination[2 * floatsPerVertex + j] = source[ELEM(start/3 + 2) * floatsPerVertex + j];
402  }
403  }
404  else if (elementAssemblyMethod == VuoMesh_LineStrip)
405  {
406  // Expand the line strip to individual lines.
407  for (int j = 0; j < floatsPerVertex; ++j)
408  {
409  destination[0 * floatsPerVertex + j] = source[ELEM(start/2 ) * floatsPerVertex + j];
410  destination[1 * floatsPerVertex + j] = source[ELEM(start/2 + 1) * floatsPerVertex + j];
411  }
412  }
413 }
414 
419 static void VuoSceneObjectRenderer_drawSingleOnCPU(VuoSceneObject sceneObject, float modelviewMatrix[16], VuoSceneObjectRenderer_CPUGeometryOperator cpuGeometryOperator)
420 {
421  VuoMesh mesh = VuoSceneObject_getMesh(sceneObject);
422  if (!mesh)
423  return;
424 
425  float *inputPositions, *inputNormals, *inputTextureCoordinates, *inputColors;
426  unsigned int elementCount, *elements;
427  VuoMesh_getCPUBuffers(mesh, nullptr, &inputPositions, &inputNormals, &inputTextureCoordinates, &inputColors, &elementCount, &elements);
428 
429  float modelMatrixInverse[16];
430  VuoTransform_invertMatrix4x4(modelviewMatrix, modelMatrixInverse);
431 
432 
433 
434 // unsigned int vertexCount;
435 // VuoMesh_getCPUBuffers(mesh, &vertexCount, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr);
436 // for (int i = 0; i < vertexCount; ++i)
437 // VLog(" input[%2d] = %f",i,inputPositions[i]);
438 
439 
440 
441 
442  unsigned int inputCount = VuoMesh_getSplitVertexCount(mesh);
443 
444  unsigned int allocatedVertices = inputCount;
445  float *newPositions, *newNormals, *newTextureCoordinates, *newColors;
446  VuoMesh_allocateCPUBuffers(allocatedVertices, &newPositions, &newNormals, &newTextureCoordinates, &newColors, 0, nullptr);
447  unsigned int newVertexCount = 0;
448 
449  int verticesPerPrimitive;
451  if (elementAssemblyMethod == VuoMesh_IndividualTriangles
452  || elementAssemblyMethod == VuoMesh_TriangleStrip
453  || elementAssemblyMethod == VuoMesh_TriangleFan)
454  verticesPerPrimitive = 3;
455  else if (elementAssemblyMethod == VuoMesh_IndividualLines
456  || elementAssemblyMethod == VuoMesh_LineStrip)
457  verticesPerPrimitive = 2;
458  else if (elementAssemblyMethod == VuoMesh_Points)
459  verticesPerPrimitive = 1;
460 
461  float defaultPosition[3] = {0,0,0};
462  float defaultNormal[3] = {0,0,1};
463  float defaultColor[4] = {1,1,1,1};
464 
465  for (unsigned int e = 0; e < inputCount; e += verticesPerPrimitive)
466  {
467  if (newVertexCount + VuoSceneObjectRenderer_maxOutputVertices > allocatedVertices)
468  {
469  allocatedVertices *= 2;
470  newPositions = (float *)realloc(newPositions, sizeof(float) * 3 * allocatedVertices);
471  newNormals = (float *)realloc(newNormals, sizeof(float) * 3 * allocatedVertices);
472  newTextureCoordinates = (float *)realloc(newTextureCoordinates, sizeof(float) * 2 * allocatedVertices);
473  newColors = (float *)realloc(newColors, sizeof(float) * 4 * allocatedVertices);
474  }
475 
476  float *positions = newPositions + newVertexCount * 3;
477  float *normals = newNormals + newVertexCount * 3;
478  float *textureCoordinates = newTextureCoordinates + newVertexCount * 2;
479  float *colors = newColors + newVertexCount * 4;
480  VuoSceneObjectRenderer_copyElement(mesh, e, inputPositions, elementCount, elements, elementAssemblyMethod, verticesPerPrimitive, 3, positions, defaultPosition);
481  VuoSceneObjectRenderer_copyElement(mesh, e, inputNormals, elementCount, elements, elementAssemblyMethod, verticesPerPrimitive, 3, normals, defaultNormal);
482  VuoSceneObjectRenderer_copyElement(mesh, e, inputTextureCoordinates, elementCount, elements, elementAssemblyMethod, verticesPerPrimitive, 2, textureCoordinates, defaultPosition);
483  VuoSceneObjectRenderer_copyElement(mesh, e, inputColors, elementCount, elements, elementAssemblyMethod, verticesPerPrimitive, 4, colors, defaultColor);
484 
485  int vertexCount = verticesPerPrimitive;
486 
487 // for (int i = 0; i < vertexCount; ++ i)
488 // VLog("prim %3d vertex %d pos %f,%f,%f norm=%f,%f,%f tc=%f,%f color=%f,%f,%f,%f",e,i,
489 // positions[i*3],positions[i*3+1],positions[i*3+2],
490 // normals[i*3],normals[i*3+1],normals[i*3+2],
491 // textureCoordinates[i*2],textureCoordinates[i*2+1],
492 // colors[i*4],colors[i*4+1],colors[i*4+2],colors[i*4+3]);
493 
494  cpuGeometryOperator(modelviewMatrix, modelMatrixInverse, &vertexCount, positions, normals, textureCoordinates, colors);
495 
496 // for (int i = 0; i < vertexCount; ++ i)
497 // VLog("prim %3d vertex %d pos %f,%f,%f norm=%f,%f,%f tc=%f,%f color=%f,%f,%f,%f",e,i,
498 // positions[i*3],positions[i*3+1],positions[i*3+2],
499 // normals[i*3],normals[i*3+1],normals[i*3+2],
500 // textureCoordinates[i*2],textureCoordinates[i*2+1],
501 // colors[i*4],colors[i*4+1],colors[i*4+2],colors[i*4+3]);
502 
503  if (vertexCount < 0 || vertexCount > VuoSceneObjectRenderer_maxOutputVertices)
504  {
505  VUserLog("Error: cpuGeometryOperator must output between 0 and %d vertices.", VuoSceneObjectRenderer_maxOutputVertices);
506  return;
507  }
508 
509  if (vertexCount % verticesPerPrimitive)
510  {
511  VUserLog("Error: When %d vertices are input to cpuGeometryOperator, it must output a multiple of %d vertices.", verticesPerPrimitive, verticesPerPrimitive);
512  return;
513  }
514 
515  newVertexCount += vertexCount;
516  }
517 
518 
519 // for (int i = 0; i < newVertexCount; ++i)
520 // VLog("output[%2d] = %f",i,newPositions[i]);
521 
522 
523  bool originalMeshHasTextureCoordinates = inputTextureCoordinates;
524  VuoMesh newMesh = VuoMesh_makeFromCPUBuffers(newVertexCount, newPositions, newNormals, originalMeshHasTextureCoordinates ? newTextureCoordinates : nullptr, newColors,
525  0, nullptr,
526  elementAssemblyMethod);
527  if (!originalMeshHasTextureCoordinates)
528  free(newTextureCoordinates);
531 
532  VuoSceneObject_setMesh(sceneObject, newMesh);
533 }
534 
544 {
545  return Block_copy(^(float *modelMatrix, float *modelMatrixInverse, int *vertexCount, float *positions, float *normals, float *textureCoordinates, float *colors) {
546  VuoPoint3d positionInScene[3];
547  VuoPoint3d deformedPositionInScene[3];
548  VuoPoint3d normalInScene[3];
549  VuoPoint2d textureCoordinate[3];
550  for (int i = 0; i < *vertexCount; ++i)
551  {
552  // Keep this in sync with deform.glsl.
553 
554  // Position ============================================================
555 
556  // Transform into worldspace.
557  VuoPoint3d position = VuoPoint3d_makeFromArray(&positions[i * 3]);
558  VuoPoint3d normal = VuoPoint3d_makeFromArray(&normals[i * 3]);
559  textureCoordinate[i] = VuoPoint2d_makeFromArray(&textureCoordinates[i * 2]);
560  positionInScene[i] = VuoTransform_transformPoint(modelMatrix, position);
561  normalInScene[i] = VuoPoint3d_normalize(VuoTransform_transformVector(modelMatrix, normal));
562 
563 // VLog("vertex %d pos %f,%f,%f norm=%f,%f,%f",i,
564 // positions[i*3],positions[i*3+1],positions[i*3+2],
565 // normals[i*3],normals[i*3+1],normals[i*3+2]);
566 
567  // Apply the deformation.
568  VuoPoint3d deformedPosition = deform(positionInScene[i],
569  normalInScene[i],
570  textureCoordinate[i]);
571  deformedPositionInScene[i] = deformedPosition;
572 
573  // Transform back into modelspace.
574  VuoPoint3d positionInModelspace = VuoTransform_transformPoint(modelMatrixInverse, deformedPosition);
575  VuoPoint3d_setArray(&positions[i * 3], positionInModelspace);
576  }
577 
578 
579  // Normal ==============================================================
580 
581  // First, find the tangent and bitangent vectors at the original (pre-deformation) position.
582  VuoPoint3d tangentInScene[3], bitangentInScene[3];
583  if (*vertexCount == 3)
584  {
585  // Based on "Computing Tangent Space Basis Vectors for an Arbitrary Mesh" by Eric Lengyel,
586  // Terathon Software 3D Graphics Library, 2001.
587  // https://web.archive.org/web/20160306000702/http://www.terathon.com/code/tangent.html
588 
589  VuoPoint3d tan1[3], tan2[3];
590  bzero(tan1, sizeof(VuoPoint3d) * 3);
591  bzero(tan2, sizeof(VuoPoint3d) * 3);
592 
593  VuoPoint3d v1 = positionInScene[0];
594  VuoPoint3d v2 = positionInScene[1];
595  VuoPoint3d v3 = positionInScene[2];
596 
597  VuoPoint2d w1 = textureCoordinate[0];
598  VuoPoint2d w2 = textureCoordinate[1];
599  VuoPoint2d w3 = textureCoordinate[2];
600 
601  float x1 = v2.x - v1.x;
602  float x2 = v3.x - v1.x;
603  float y1 = v2.y - v1.y;
604  float y2 = v3.y - v1.y;
605  float z1 = v2.z - v1.z;
606  float z2 = v3.z - v1.z;
607 
608  float s1 = w2.x - w1.x;
609  float s2 = w3.x - w1.x;
610  float t1 = w2.y - w1.y;
611  float t2 = w3.y - w1.y;
612 
613  float r = 1.0F / (s1 * t2 - s2 * t1);
614  VuoPoint3d sdir = (VuoPoint3d){
615  (t2 * x1 - t1 * x2) * r,
616  (t2 * y1 - t1 * y2) * r,
617  (t2 * z1 - t1 * z2) * r};
618  VuoPoint3d tdir = (VuoPoint3d){
619  (s1 * x2 - s2 * x1) * r,
620  (s1 * y2 - s2 * y1) * r,
621  (s1 * z2 - s2 * z1) * r};
622 
623  tan1[0] += sdir;
624  tan1[1] += sdir;
625  tan1[2] += sdir;
626 
627  tan2[0] += tdir;
628  tan2[1] += tdir;
629  tan2[2] += tdir;
630 
631  for (int i = 0; i < *vertexCount; ++i)
632  {
633  VuoPoint3d n = normalInScene[i];
634  VuoPoint3d t = tan1[i];
635  VuoPoint3d t2 = tan2[i];
636 
637  // Gram-Schmidt orthogonalize
638  tangentInScene[i] = VuoPoint3d_normalize(t - n * VuoPoint3d_dotProduct(n, t));
639  bitangentInScene[i] = VuoPoint3d_normalize(t2 - n * VuoPoint3d_dotProduct(n, t2));
640  }
641  }
642  else
643  for (int i = 0; i < *vertexCount; ++i)
644  {
645  tangentInScene[i] = (VuoPoint3d){1,0,0};
646  bitangentInScene[i] = (VuoPoint3d){0,1,0};
647  }
648 
649  // Next, apply the deformation to neighboring positions.
650  // Based on https://web.archive.org/web/20170202071451/https://http.developer.nvidia.com/GPUGems/gpugems_ch42.html
651  // section "An Approximate Numerical Technique".
652  for (int i = 0; i < *vertexCount; ++i)
653  {
654  const float scale = .01;
655  VuoPoint3d deformedAlongTangent = deform(positionInScene[i] + tangentInScene[i] * scale,
656  normalInScene[i],
657  textureCoordinate[i] + (VuoPoint2d){scale, 0});
658  VuoPoint3d deformedAlongBitangent = deform(positionInScene[i] + bitangentInScene[i] * scale,
659  normalInScene[i],
660  textureCoordinate[i] + (VuoPoint2d){0, scale});
661 
662  VuoPoint3d deformedPosition = deformedPositionInScene[i];
663 
664  // Calculate the orthonormal basis of the tangent plane at deformedPosition.
665  VuoPoint3d deformedTangent = deformedAlongTangent - deformedPosition;
666  VuoPoint3d deformedBitangent = deformedAlongBitangent - deformedPosition;
667  VuoPoint3d deformedNormal = VuoPoint3d_crossProduct(deformedTangent, deformedBitangent);
668 
669  // Transform back into modelspace.
670  VuoPoint3d deformedNormalInModelspace = VuoPoint3d_normalize(VuoTransform_transformVector(modelMatrixInverse, deformedNormal));
671 
672  VuoPoint3d_setArray(&normals[i * 3], deformedNormalInModelspace);
673  }
674  });
675 }
676 
683 {
684  static bool gpuTransformFeedback;
685  static dispatch_once_t once = 0;
686  dispatch_once(&once, ^{
687  // If the user set the `gpuTransformFeedback` preference, use it.
688  Boolean overridden = false;
689  gpuTransformFeedback = (int)CFPreferencesGetAppIntegerValue(CFSTR("gpuTransformFeedback"), CFSTR("org.vuo.Editor"), &overridden);
690 
691  if (!overridden)
692  {
693  // https://b33p.net/kosada/node/13622
694  // Some GPUs / GPU drivers have broken support for transform feedback,
695  // so use the CPU fallbacks instead.
696  __block const char *renderer;
697  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
698  renderer = (const char *)glGetString(GL_RENDERER);
699  });
700 
701  if (strncmp(renderer, "Intel", 5) == 0
702  || strncmp(renderer, "AMD ", 4) == 0
703  || strncmp(renderer, "NVIDIA GeForce 9400M", 4) == 0
704  || strncmp(renderer, "ATI ", 4) == 0)
705  gpuTransformFeedback = false;
706  else
707  // NVIDIA (except 9400M) seems to be the only GPU whose transform feedback works consistently.
708  gpuTransformFeedback = true;
709  }
710 
711  VDebugLog("gpuTransformFeedback = %d", gpuTransformFeedback);
712  });
713 
714  return gpuTransformFeedback;
715 }
716 
734 {
735  if (!sor)
736  return nullptr;
737 
739  {
740  __block VuoSceneObject sceneObjectCopy = VuoSceneObject_copy(sceneObject);
741 
742  struct VuoSceneObjectRendererInternal *sceneObjectRenderer = (struct VuoSceneObjectRendererInternal *)sor;
743  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
744  glEnable(GL_RASTERIZER_DISCARD_EXT);
745  glBindVertexArray(sceneObjectRenderer->vertexArray);
746  glBindFramebuffer(GL_FRAMEBUFFER, sceneObjectRenderer->shamFramebuffer);
747 
748  VuoSceneObject_apply(sceneObjectCopy, ^(VuoSceneObject currentObject, float modelviewMatrix[16]) {
749  VuoSceneObjectRenderer_drawSingle(cgl_ctx, sceneObjectRenderer, currentObject, modelviewMatrix);
750  });
751 
752  glBindBuffer(GL_ARRAY_BUFFER, 0);
753  glBindFramebuffer(GL_FRAMEBUFFER, 0);
755  glDisable(GL_RASTERIZER_DISCARD_EXT);
756 
757  // Ensure commands are submitted before we try to use the generated object on another context.
758  // https://b33p.net/kosada/node/10467
759  glFlushRenderAPPLE();
760  });
761 
762  return sceneObjectCopy;
763  }
764 
765  else
766  {
767  VuoSceneObject sceneObjectCopy = VuoSceneObject_copy(sceneObject);
768 
769  VuoSceneObject_apply(sceneObjectCopy, ^(VuoSceneObject currentObject, float modelviewMatrix[16]) {
770  VuoSceneObjectRenderer_drawSingleOnCPU(currentObject, modelviewMatrix, cpuGeometryOperator);
771  });
772 
773  return sceneObjectCopy;
774  }
775 }
776 
783 {
784  struct VuoSceneObjectRendererInternal *sceneObjectRenderer = (struct VuoSceneObjectRendererInternal *)sor;
785 
787  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
788  VuoRelease(sceneObjectRenderer->shader);
789 
790  glBindFramebuffer(GL_FRAMEBUFFER, sceneObjectRenderer->shamFramebuffer);
791  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
792  VuoGlTexture_release(VuoGlTexturePool_Allocate, GL_TEXTURE_2D, GL_RGBA, 1, 1, sceneObjectRenderer->shamTexture);
793 // glBindFramebuffer(GL_FRAMEBUFFER, 0); // handled by glDeleteFramebuffers
794  glDeleteFramebuffers(1, &sceneObjectRenderer->shamFramebuffer);
795 
796  glDeleteVertexArrays(1, &sceneObjectRenderer->vertexArray);
797 
798  glDeleteQueries(1, &sceneObjectRenderer->query);
799  });
800 
801  free(sceneObjectRenderer);
802 }