Vuo  2.0.0
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 tangent;
47  GLint bitangent;
48  GLint textureCoordinate;
49 
50  unsigned int expectedOutputPrimitiveCount;
51  bool mayChangeOutputPrimitiveCount;
53 
58 {
59  VuoShader shader;
60 
61  GLuint shamTexture;
62  GLuint shamFramebuffer;
63 
64  GLuint query;
65 
66  GLuint vertexArray;
67 
68  VuoSceneObjectRenderer_Attributes pointAttributes;
69  VuoSceneObjectRenderer_Attributes lineAttributes;
70  VuoSceneObjectRenderer_Attributes triangleAttributes;
71 };
72 
74 
81 {
82  if (!VuoShader_isTransformFeedback(shader))
83  {
84  VUserLog("Error '%s' is not a transform feedback shader.", shader->name);
85  return NULL;
86  }
87 
88  __block struct VuoSceneObjectRendererInternal *sceneObjectRenderer = (struct VuoSceneObjectRendererInternal *)malloc(sizeof(struct VuoSceneObjectRendererInternal));
89  VuoRegister(sceneObjectRenderer, VuoSceneObjectRenderer_destroy);
90 
92  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
93  // Fetch the shader's attribute locations
94  bool havePoints = VuoShader_getAttributeLocations(shader, VuoMesh_Points, cgl_ctx, &sceneObjectRenderer->pointAttributes.position, &sceneObjectRenderer->pointAttributes.normal, &sceneObjectRenderer->pointAttributes.tangent, &sceneObjectRenderer->pointAttributes.bitangent, &sceneObjectRenderer->pointAttributes.textureCoordinate );
95  bool haveLines = VuoShader_getAttributeLocations(shader, VuoMesh_IndividualLines, cgl_ctx, &sceneObjectRenderer->lineAttributes.position, &sceneObjectRenderer->lineAttributes.normal, &sceneObjectRenderer->lineAttributes.tangent, &sceneObjectRenderer->lineAttributes.bitangent, &sceneObjectRenderer->lineAttributes.textureCoordinate );
96  bool haveTriangles = VuoShader_getAttributeLocations(shader, VuoMesh_IndividualTriangles, cgl_ctx, &sceneObjectRenderer->triangleAttributes.position, &sceneObjectRenderer->triangleAttributes.normal, &sceneObjectRenderer->triangleAttributes.tangent, &sceneObjectRenderer->triangleAttributes.bitangent, &sceneObjectRenderer->triangleAttributes.textureCoordinate);
97  if (!havePoints || !haveLines || !haveTriangles)
98  {
99  VUserLog("Error: '%s' is missing programs for: %s %s %s", shader->name, havePoints? "" : "points", haveLines? "" : "lines", haveTriangles? "" : "triangles");
100  free(sceneObjectRenderer);
101  sceneObjectRenderer = NULL;
102  return;
103  }
104 
105  sceneObjectRenderer->pointAttributes.expectedOutputPrimitiveCount = VuoShader_getExpectedOutputPrimitiveCount (shader, VuoMesh_Points);
106  sceneObjectRenderer->pointAttributes.mayChangeOutputPrimitiveCount = VuoShader_getMayChangeOutputPrimitiveCount(shader, VuoMesh_Points);
107 
108  sceneObjectRenderer->lineAttributes.expectedOutputPrimitiveCount = VuoShader_getExpectedOutputPrimitiveCount (shader, VuoMesh_IndividualLines);
109  sceneObjectRenderer->lineAttributes.mayChangeOutputPrimitiveCount = VuoShader_getMayChangeOutputPrimitiveCount(shader, VuoMesh_IndividualLines);
110 
111  sceneObjectRenderer->triangleAttributes.expectedOutputPrimitiveCount = VuoShader_getExpectedOutputPrimitiveCount (shader, VuoMesh_IndividualTriangles);
112  sceneObjectRenderer->triangleAttributes.mayChangeOutputPrimitiveCount = VuoShader_getMayChangeOutputPrimitiveCount(shader, VuoMesh_IndividualTriangles);
113 
114  sceneObjectRenderer->shader = shader;
115  VuoRetain(sceneObjectRenderer->shader);
116 
117  glGenVertexArrays(1, &sceneObjectRenderer->vertexArray);
118 
119  // https://stackoverflow.com/questions/24112671/transform-feedback-without-a-framebuffer
120  sceneObjectRenderer->shamTexture = VuoGlTexturePool_use(cgl_ctx, VuoGlTexturePool_Allocate, GL_TEXTURE_2D, GL_RGBA, 1, 1, GL_BGRA, NULL);
121  VuoGlTexture_retain(sceneObjectRenderer->shamTexture, NULL, NULL);
122  glGenFramebuffers(1, &sceneObjectRenderer->shamFramebuffer);
123  glBindFramebuffer(GL_FRAMEBUFFER, sceneObjectRenderer->shamFramebuffer);
124  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, sceneObjectRenderer->shamTexture, 0);
125  glBindFramebuffer(GL_FRAMEBUFFER, 0);
126 
127  glGenQueries(1, &sceneObjectRenderer->query);
128  });
129 
130  return (VuoSceneObjectRenderer)sceneObjectRenderer;
131 }
132 
137 static void VuoSceneObjectRenderer_drawSingle(CGLContextObj cgl_ctx, struct VuoSceneObjectRendererInternal *sceneObjectRenderer, VuoSceneObject *sceneObject, float modelviewMatrix[16])
138 {
139  if (!sceneObject->mesh)
140  return;
141 
142  VuoRetain(sceneObject->mesh);
143 
144  VuoMesh newMesh = VuoMesh_make(sceneObject->mesh->submeshCount);
145 
146  for (unsigned int i = 0; i < sceneObject->mesh->submeshCount; ++i)
147  {
148  VuoSubmesh submesh = sceneObject->mesh->submeshes[i];
149 
150 
152  GLuint outputPrimitiveGlMode;
154  int primitiveVertexMultiplier;
155  if (outputPrimitiveMode == VuoMesh_IndividualTriangles)
156  {
157  outputPrimitiveGlMode = GL_TRIANGLES;
158  attributes = sceneObjectRenderer->triangleAttributes;
159  primitiveVertexMultiplier = 3;
160  }
161  else if (outputPrimitiveMode == VuoMesh_IndividualLines)
162  {
163  outputPrimitiveGlMode = GL_LINES;
164  attributes = sceneObjectRenderer->lineAttributes;
165  primitiveVertexMultiplier = 2;
166  }
167  else // if (submesh.elementAssemblyMethod == VuoMesh_Points)
168  {
169  outputPrimitiveGlMode = GL_POINTS;
170  attributes = sceneObjectRenderer->pointAttributes;
171  primitiveVertexMultiplier = 1;
172  }
173 
174 
175  // Attach the input combinedBuffer for rendering.
176  glBindBuffer(GL_ARRAY_BUFFER, submesh.glUpload.combinedBuffer);
177 
178  VuoGlProgram program;
179  if (!VuoShader_activate(sceneObjectRenderer->shader, submesh.elementAssemblyMethod, cgl_ctx, &program))
180  {
181  VUserLog("Shader activation failed.");
182  return;
183  }
184 
185 
186  GLint modelviewMatrixUniform = VuoGlProgram_getUniformLocation(program, "modelviewMatrix");
187  if (modelviewMatrixUniform != -1)
188  glUniformMatrix4fv(modelviewMatrixUniform, 1, GL_FALSE, modelviewMatrix);
189 
190 
191  GLint modelviewMatrixInverseUniform = VuoGlProgram_getUniformLocation(program, "modelviewMatrixInverse");
192  if (modelviewMatrixInverseUniform != -1)
193  {
194  float modelviewMatrixInverse[16];
195  VuoTransform_invertMatrix4x4(modelviewMatrix, modelviewMatrixInverse);
196  glUniformMatrix4fv(modelviewMatrixInverseUniform, 1, GL_FALSE, modelviewMatrixInverse);
197  }
198 
199 
200  int stride = VuoSubmesh_getStride(submesh);
201  glEnableVertexAttribArray(attributes.position);
202  glVertexAttribPointer(attributes.position, 4 /* XYZW */, GL_FLOAT, GL_FALSE, stride, (void*)0);
203  if (submesh.glUpload.normalOffset && attributes.normal >= 0)
204  {
205  glEnableVertexAttribArray(attributes.normal);
206  glVertexAttribPointer(attributes.normal, 4 /* XYZW */, GL_FLOAT, GL_FALSE, stride, submesh.glUpload.normalOffset);
207  }
208  bool hasTangents = submesh.glUpload.tangentOffset && attributes.tangent >= 0;
209  if (hasTangents)
210  {
211  glEnableVertexAttribArray(attributes.tangent);
212  glVertexAttribPointer(attributes.tangent, 4 /* XYZW */, GL_FLOAT, GL_FALSE, stride, submesh.glUpload.tangentOffset);
213  }
214  bool hasBitangents = submesh.glUpload.bitangentOffset && attributes.bitangent >= 0;
215  if (hasBitangents)
216  {
217  glEnableVertexAttribArray(attributes.bitangent);
218  glVertexAttribPointer(attributes.bitangent, 4 /* XYZW */, GL_FLOAT, GL_FALSE, stride, submesh.glUpload.bitangentOffset);
219  }
220  bool hasTextureCoordinates = submesh.glUpload.textureCoordinateOffset && attributes.textureCoordinate >= 0;
221  if (hasTextureCoordinates)
222  {
223  glEnableVertexAttribArray(attributes.textureCoordinate);
224  glVertexAttribPointer(attributes.textureCoordinate, 4 /* XYZW */, GL_FLOAT, GL_FALSE, stride, submesh.glUpload.textureCoordinateOffset);
225  }
226 
227 
228  // Attach the input elementBuffer for rendering.
229  if (submesh.glUpload.elementBuffer)
230  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, submesh.glUpload.elementBuffer);
231 
232 
233  // Create and attach the output combinedBuffer.
234  // The output buffer will always contain all 5 vertex attributes (position, normal, tangent, bitangent, textureCoordinate).
235  unsigned long outputVertexCount = VuoSubmesh_getSplitVertexCount(submesh) * attributes.expectedOutputPrimitiveCount;
236  unsigned long singleOutputBufferSize = sizeof(VuoPoint4d)*outputVertexCount;
237  unsigned long combinedOutputBufferSize = singleOutputBufferSize*5;
238  GLuint combinedOutputBuffer = VuoGlPool_use(cgl_ctx, VuoGlPool_ArrayBuffer, combinedOutputBufferSize);
239  VuoGlPool_retain(combinedOutputBuffer);
240  glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, combinedOutputBuffer);
241 // glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, combinedOutputBufferSize, NULL, GL_STATIC_READ);
242  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0, combinedOutputBuffer);
243 
244 
245  // Execute the filter.
246  GLenum mode = VuoSubmesh_getGlMode(submesh);
247  if (attributes.mayChangeOutputPrimitiveCount)
248  glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT, sceneObjectRenderer->query);
249  glBeginTransformFeedbackEXT(outputPrimitiveGlMode);
250 
251 #ifdef VUO_PROFILE
252  GLuint timeElapsedQuery;
253  glGenQueries(1, &timeElapsedQuery);
254  glBeginQuery(GL_TIME_ELAPSED_EXT, timeElapsedQuery);
255 #endif
256 
257  unsigned long completeInputElementCount = VuoSubmesh_getCompleteElementCount(submesh);
258  if (submesh.elementCount)
259  glDrawElements(mode, completeInputElementCount, GL_UNSIGNED_INT, (void*)0);
260  else if (submesh.vertexCount)
261  glDrawArrays(mode, 0, completeInputElementCount);
262 
263 #ifdef VUO_PROFILE
264  double seconds;
265  glEndQuery(GL_TIME_ELAPSED_EXT);
266  GLuint nanoseconds;
267  glGetQueryObjectuiv(timeElapsedQuery, GL_QUERY_RESULT, &nanoseconds);
268  seconds = ((double)nanoseconds) / NSEC_PER_SEC;
269  glDeleteQueries(1, &timeElapsedQuery);
270 
271  double objectPercent = seconds / (1./60.) * 100.;
272  VLog("%6.2f %% of 60 Hz frame %s (%s)", objectPercent, sceneObject->name, sceneObjectRenderer->shader->name);
273 #endif
274 
275 
276  glEndTransformFeedbackEXT();
277  if (attributes.mayChangeOutputPrimitiveCount)
278  glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN_EXT);
279 
280  glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0);
281 
282  if (submesh.glUpload.elementBuffer)
283  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
284 
285  if (hasTextureCoordinates)
286  glDisableVertexAttribArray(attributes.textureCoordinate);
287  if (hasBitangents)
288  glDisableVertexAttribArray(attributes.bitangent);
289  if (hasTangents)
290  glDisableVertexAttribArray(attributes.tangent);
291  if (submesh.glUpload.normalOffset && attributes.normal >= 0)
292  glDisableVertexAttribArray(attributes.normal);
293  glDisableVertexAttribArray(attributes.position);
294 
295  VuoShader_deactivate(sceneObjectRenderer->shader, submesh.elementAssemblyMethod, 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  glFlush();
311  GLfloat feedback[actualVertexCount*4*5];
312  glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, combinedOutputBuffer);
313  glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0, sizeof(feedback), feedback);
314  for (int buffer = 0; buffer < 5; ++buffer)
315  {
316  if (buffer == 0)
317  fprintf(stderr, "positions:\n");
318  else if (buffer == 1)
319  {
320  if (!submesh.glUpload.normalOffset)
321  continue;
322  fprintf(stderr, "normals:\n");
323  }
324  else if (buffer == 2)
325  {
326  if (!submesh.glUpload.tangentOffset)
327  continue;
328  fprintf(stderr, "tangents:\n");
329  }
330  else if (buffer == 3)
331  {
332  if (!submesh.glUpload.bitangentOffset)
333  continue;
334  fprintf(stderr, "bitangents:\n");
335  }
336  else if (buffer == 4)
337  {
338  if (!submesh.glUpload.textureCoordinateOffset)
339  continue;
340  fprintf(stderr, "texture coordinates:\n");
341  }
342 
343  for (int vertex = 0; vertex < actualVertexCount; vertex++)
344  {
345  for (int coordinate = 0; coordinate < 4; ++coordinate)
346  fprintf(stderr, "\t%f", feedback[vertex*4*5 + buffer*4 + coordinate]);
347  if (buffer >= 1 && buffer <= 3)
348  fprintf(stderr, " (length %f)", VuoPoint3d_magnitude((VuoPoint3d){feedback[vertex*4*5 + buffer*4], feedback[vertex*4*5 + buffer*4 + 1], feedback[vertex*4*5 + buffer*4 + 2]}));
349  fprintf(stderr, "\n");
350  }
351  }
352  glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER_EXT, 0);
353 #endif
354 
355  // https://vuo.org/node/1571
356  // https://b33p.net/kosada/node/12431
357  // The output buffer will always contain all 5 vertex attributes,
358  // though (depending on input) some might not contain contain useful data.
359  unsigned long combinedOutputBufferStride = sizeof(VuoPoint4d) * 5;
360 
361  newMesh->submeshes[i] = VuoSubmesh_makeGl(
362  actualVertexCount, combinedOutputBuffer, combinedOutputBufferSize, combinedOutputBufferStride,
363  submesh.glUpload.normalOffset ? (void*)(sizeof(VuoPoint4d)*1) : NULL,
364  submesh.glUpload.tangentOffset ? (void*)(sizeof(VuoPoint4d)*2) : NULL,
365  submesh.glUpload.bitangentOffset ? (void*)(sizeof(VuoPoint4d)*3) : NULL,
366  submesh.glUpload.textureCoordinateOffset ? (void*)(sizeof(VuoPoint4d)*4) : NULL,
367  0, 0, 0, // Since transform feedback doesn't provide an elementBuffer, render this submesh using glDrawArrays().
368  outputPrimitiveMode);
369  newMesh->submeshes[i].faceCullingMode = submesh.faceCullingMode;
370  newMesh->submeshes[i].primitiveSize = submesh.primitiveSize;
371  }
372 
373 // VuoRetain(newMesh);
374  VuoRelease(sceneObject->mesh);
375  sceneObject->mesh = newMesh;
376 }
377 
379 #define ELEM(i) (submesh.elementCount ? submesh.elements[i] : i)
380 
384 static inline void VuoSceneObjectRenderer_copyElement(VuoSubmesh submesh, int start, VuoPoint4d *source, int count, VuoPoint4d *destination)
385 {
386  if (!source)
387  {
388  for (int i = 0; i < count; ++i)
389  destination[i] = (VuoPoint4d){0,0,1,1};
390  return;
391  }
392 
394  {
395  if (submesh.elementCount)
396  {
397  unsigned int *e = submesh.elements + start;
398  destination[0] = source[*(e )];
399  destination[1] = source[*(e + 1)];
400  destination[2] = source[*(e + 2)];
401  }
402  else
403  {
404  VuoPoint4d *s = source + start;
405  destination[0] = *(s );
406  destination[1] = *(s + 1);
407  destination[2] = *(s + 2);
408  }
409  }
411  {
412  if (submesh.elementCount)
413  {
414  unsigned int *e = submesh.elements + start;
415  destination[0] = source[*(e )];
416  destination[1] = source[*(e + 1)];
417  }
418  else
419  {
420  VuoPoint4d *s = source + start;
421  destination[0] = *(s );
422  destination[1] = *(s + 1);
423  }
424  }
425  else if (submesh.elementAssemblyMethod == VuoMesh_Points)
426  {
427  if (submesh.elementCount)
428  destination[0] = source[submesh.elements[start]];
429  else
430  destination[0] = source[start];
431  }
432  else if (submesh.elementAssemblyMethod == VuoMesh_TriangleStrip)
433  {
434  // Expand the triangle strip to individual triangles.
435  if ((start / 3) % 2 == 0)
436  {
437  destination[0] = source[ELEM(start/3 )];
438  destination[1] = source[ELEM(start/3 + 1)];
439  destination[2] = source[ELEM(start/3 + 2)];
440  }
441  else
442  {
443  destination[0] = source[ELEM(start/3 + 1)];
444  destination[1] = source[ELEM(start/3 )];
445  destination[2] = source[ELEM(start/3 + 2)];
446  }
447  }
448  else if (submesh.elementAssemblyMethod == VuoMesh_TriangleFan)
449  {
450  // Expand the triangle fan to individual triangles.
451  destination[0] = source[ELEM(0 )];
452  destination[1] = source[ELEM(start/3 + 1)];
453  destination[2] = source[ELEM(start/3 + 2)];
454  }
455  else if (submesh.elementAssemblyMethod == VuoMesh_LineStrip)
456  {
457  // Expand the line strip to individual lines.
458  destination[0] = source[ELEM(start/2 )];
459  destination[1] = source[ELEM(start/2 + 1)];
460  }
461 }
462 
467 static void VuoSceneObjectRenderer_drawSingleOnCPU(VuoSceneObject *sceneObject, float modelviewMatrix[16], VuoSceneObjectRenderer_CPUGeometryOperator cpuGeometryOperator)
468 {
469  if (!sceneObject->mesh)
470  return;
471 
472  VuoRetain(sceneObject->mesh);
473 
474  VuoMesh newMesh = VuoMesh_make(sceneObject->mesh->submeshCount);
475 
476  float modelMatrixInverse[16];
477  VuoTransform_invertMatrix4x4(modelviewMatrix, modelMatrixInverse);
478 
479  for (unsigned int i = 0; i < sceneObject->mesh->submeshCount; ++i)
480  {
481  VuoSubmesh submesh = sceneObject->mesh->submeshes[i];
482  VuoSubmesh_download(&submesh);
483 
484  unsigned int inputCount = VuoSubmesh_getSplitVertexCount(submesh);
485 
486  unsigned int allocatedVertices = inputCount;
487  VuoPoint4d *newPositions = (VuoPoint4d *)malloc(sizeof(VuoPoint4d) * allocatedVertices);
488  VuoPoint4d *newNormals = (VuoPoint4d *)malloc(sizeof(VuoPoint4d) * allocatedVertices);
489  VuoPoint4d *newTangents = (VuoPoint4d *)malloc(sizeof(VuoPoint4d) * allocatedVertices);
490  VuoPoint4d *newBitangents = (VuoPoint4d *)malloc(sizeof(VuoPoint4d) * allocatedVertices);
491  VuoPoint4d *newTextureCoordinates = (VuoPoint4d *)malloc(sizeof(VuoPoint4d) * allocatedVertices);
492  unsigned int newVertexCount = 0;
493 
494  int verticesPerPrimitive;
498  verticesPerPrimitive = 3;
501  verticesPerPrimitive = 2;
502  else if (submesh.elementAssemblyMethod == VuoMesh_Points)
503  verticesPerPrimitive = 1;
504 
505  for (unsigned int e = 0; e < inputCount; e += verticesPerPrimitive)
506  {
507  if (newVertexCount + VuoSceneObjectRenderer_maxOutputVertices > allocatedVertices)
508  {
509  allocatedVertices *= 2;
510  newPositions = (VuoPoint4d *)realloc(newPositions, sizeof(VuoPoint4d) * allocatedVertices);
511  newNormals = (VuoPoint4d *)realloc(newNormals, sizeof(VuoPoint4d) * allocatedVertices);
512  newTangents = (VuoPoint4d *)realloc(newTangents, sizeof(VuoPoint4d) * allocatedVertices);
513  newBitangents = (VuoPoint4d *)realloc(newBitangents, sizeof(VuoPoint4d) * allocatedVertices);
514  newTextureCoordinates = (VuoPoint4d *)realloc(newTextureCoordinates, sizeof(VuoPoint4d) * allocatedVertices);
515  }
516 
517  VuoPoint4d *positions = newPositions + newVertexCount;
518  VuoPoint4d *normals = newNormals + newVertexCount;
519  VuoPoint4d *tangents = newTangents + newVertexCount;
520  VuoPoint4d *bitangents = newBitangents + newVertexCount;
521  VuoPoint4d *textureCoordinates = newTextureCoordinates + newVertexCount;
522  VuoSceneObjectRenderer_copyElement(submesh, e, submesh.positions, verticesPerPrimitive, positions);
523  VuoSceneObjectRenderer_copyElement(submesh, e, submesh.normals, verticesPerPrimitive, normals);
524  VuoSceneObjectRenderer_copyElement(submesh, e, submesh.tangents, verticesPerPrimitive, tangents);
525  VuoSceneObjectRenderer_copyElement(submesh, e, submesh.bitangents, verticesPerPrimitive, bitangents);
526  VuoSceneObjectRenderer_copyElement(submesh, e, submesh.textureCoordinates, verticesPerPrimitive, textureCoordinates);
527 
528  int vertexCount = verticesPerPrimitive;
529 
530  cpuGeometryOperator(modelviewMatrix, modelMatrixInverse, &vertexCount, positions, normals, tangents, bitangents, textureCoordinates);
531 
532  if (vertexCount < 0 || vertexCount > VuoSceneObjectRenderer_maxOutputVertices)
533  {
534  VUserLog("Error: cpuGeometryOperator must output between 0 and %d vertices.", VuoSceneObjectRenderer_maxOutputVertices);
535  return;
536  }
537 
538  if (vertexCount % verticesPerPrimitive)
539  {
540  VUserLog("Error: When %d vertices are input to cpuGeometryOperator, it must output a multiple of %d vertices.", verticesPerPrimitive, verticesPerPrimitive);
541  return;
542  }
543 
544  newVertexCount += vertexCount;
545  }
546 
547  bool originalMeshHasTextureCoordinates = submesh.textureCoordinates;
548  VuoSubmesh newSubmesh = VuoSubmesh_makeFromBuffers(newVertexCount, newPositions, newNormals, newTangents, newBitangents, originalMeshHasTextureCoordinates ? newTextureCoordinates : NULL,
549  0, nullptr,
550  submesh.elementAssemblyMethod);
551  newSubmesh.faceCullingMode = submesh.faceCullingMode;
552  newSubmesh.primitiveSize = submesh.primitiveSize;
553 
554  newMesh->submeshes[i] = newSubmesh;
555  }
556 
557  VuoRelease(sceneObject->mesh);
558  VuoMesh_upload(newMesh);
559  sceneObject->mesh = newMesh;
560 }
561 
571 {
572  return Block_copy(^(float *modelMatrix, float *modelMatrixInverse, int *vertexCount, VuoPoint4d *positions, VuoPoint4d *normals, VuoPoint4d *tangents, VuoPoint4d *bitangents, VuoPoint4d *textureCoordinates) {
573  for (int i = 0; i < *vertexCount; ++i)
574  {
575  // Keep this in sync with deform.glsl.
576 
577  // Position ============================================================
578 
579  // Transform into worldspace.
580  VuoPoint3d positionInScene = VuoTransform_transformPoint(modelMatrix, positions[i].xyz);
581  VuoPoint3d normalInScene = VuoPoint3d_normalize(VuoTransform_transformVector(modelMatrix, normals[i].xyz));
582 
583  // Apply the deformation.
584  VuoPoint3d deformedPosition = deform(positionInScene,
585  normalInScene,
586  (VuoPoint2d){textureCoordinates[i].x, textureCoordinates[i].y});
587 
588  // Transform back into modelspace.
589  positions[i] = VuoPoint3d_to4d1(VuoTransform_transformPoint(modelMatrixInverse, deformedPosition));
590 
591 
592  // Normal/Tangent/Bitangent ============================================
593 
594  // Transform the tangent and bitangent into worldspace.
595  // Since these are directional vectors, use the top 3x3 of the modelviewMatrix to apply just the rotation/scale (not the translation).
596  VuoPoint3d tangentInScene = VuoPoint3d_normalize(VuoTransform_transformVector(modelMatrix, tangents[i].xyz));
597  VuoPoint3d bitangentInScene = VuoPoint3d_normalize(VuoTransform_transformVector(modelMatrix, bitangents[i].xyz));
598 
599  // Apply the deformation to neighboring positions.
600  VuoPoint3d deformedAlongTangent = deform((VuoPoint3d){positionInScene.x + tangentInScene.x / 100.f,
601  positionInScene.y + tangentInScene.y / 100.f,
602  positionInScene.z + tangentInScene.z / 100.f},
603  normalInScene,
604  (VuoPoint2d){textureCoordinates[i].x + .01f,
605  textureCoordinates[i].y});
606  VuoPoint3d deformedAlongBitangent = deform((VuoPoint3d){positionInScene.x + bitangentInScene.x / 100.f,
607  positionInScene.y + bitangentInScene.y / 100.f,
608  positionInScene.z + bitangentInScene.z / 100.f},
609  normalInScene,
610  (VuoPoint2d){textureCoordinates[i].x,
611  textureCoordinates[i].y + .01f});
612 
613  // Calculate the orthonormal basis of the tangent plane at deformedPosition.
614  VuoPoint3d tangent = VuoPoint3d_subtract(deformedAlongTangent, deformedPosition);
615  VuoPoint3d bitangent = VuoPoint3d_subtract(deformedAlongBitangent, deformedPosition);
616  VuoPoint3d normal = VuoPoint3d_crossProduct(tangent, bitangent);
617 
618  tangents[i] = (VuoPoint4d){tangent.x, tangent.y, tangent.z, 1};
619  bitangents[i] = (VuoPoint4d){bitangent.x, bitangent.y, bitangent.z, 1};
620  normals[i] = (VuoPoint4d){normal.x, normal.y, normal.z, 1};
621  }
622  });
623 }
624 
631 {
632  static bool gpuTransformFeedback;
633  static dispatch_once_t once = 0;
634  dispatch_once(&once, ^{
635  // If the user set the `gpuTransformFeedback` preference, use it.
636  Boolean overridden = false;
637  gpuTransformFeedback = (int)CFPreferencesGetAppIntegerValue(CFSTR("gpuTransformFeedback"), CFSTR("org.vuo.Editor"), &overridden);
638 
639  if (!overridden)
640  {
641  // https://b33p.net/kosada/node/13622
642  // Some GPUs / GPU drivers have broken support for transform feedback,
643  // so use the CPU fallbacks instead.
644  __block const char *renderer;
645  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
646  renderer = (const char *)glGetString(GL_RENDERER);
647  });
648 
649  if (strncmp(renderer, "Intel", 5) == 0
650  || strncmp(renderer, "AMD ", 4) == 0
651  || strncmp(renderer, "NVIDIA GeForce 9400M", 4) == 0
652  || strncmp(renderer, "ATI ", 4) == 0)
653  gpuTransformFeedback = false;
654  else
655  // NVIDIA (except 9400M) seems to be the only GPU whose transform feedback works consistently.
656  gpuTransformFeedback = true;
657  }
658 
659  VDebugLog("gpuTransformFeedback = %d", gpuTransformFeedback);
660  });
661 
662  return gpuTransformFeedback;
663 }
664 
682 {
683  if (!sor)
684  return VuoSceneObject_makeEmpty();
685 
687  {
688  __block VuoSceneObject sceneObjectCopy = VuoSceneObject_copy(sceneObject);
689 
690  struct VuoSceneObjectRendererInternal *sceneObjectRenderer = (struct VuoSceneObjectRendererInternal *)sor;
691  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
692  glEnable(GL_RASTERIZER_DISCARD_EXT);
693  glBindVertexArray(sceneObjectRenderer->vertexArray);
694  glBindFramebuffer(GL_FRAMEBUFFER, sceneObjectRenderer->shamFramebuffer);
695 
696  VuoSceneObject_apply(&sceneObjectCopy, ^(VuoSceneObject *currentObject, float modelviewMatrix[16]) {
697  VuoSceneObjectRenderer_drawSingle(cgl_ctx, sceneObjectRenderer, currentObject, modelviewMatrix);
698  });
699 
700  glBindBuffer(GL_ARRAY_BUFFER, 0);
701  glBindFramebuffer(GL_FRAMEBUFFER, 0);
703  glDisable(GL_RASTERIZER_DISCARD_EXT);
704 
705  // Ensure commands are submitted before we try to use the generated object on another context.
706  // https://b33p.net/kosada/node/10467
707  glFlushRenderAPPLE();
708  });
709 
710  return sceneObjectCopy;
711  }
712 
713  else
714  {
715  VuoSceneObject sceneObjectCopy = VuoSceneObject_copy(sceneObject);
716 
717  VuoSceneObject_apply(&sceneObjectCopy, ^(VuoSceneObject *currentObject, float modelviewMatrix[16]) {
718  VuoSceneObjectRenderer_drawSingleOnCPU(currentObject, modelviewMatrix, cpuGeometryOperator);
719  });
720 
721  return sceneObjectCopy;
722  }
723 }
724 
731 {
732  struct VuoSceneObjectRendererInternal *sceneObjectRenderer = (struct VuoSceneObjectRendererInternal *)sor;
733 
735  VuoGlContext_perform(^(CGLContextObj cgl_ctx){
736  VuoRelease(sceneObjectRenderer->shader);
737 
738  glBindFramebuffer(GL_FRAMEBUFFER, sceneObjectRenderer->shamFramebuffer);
739  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
740  VuoGlTexture_release(VuoGlTexturePool_Allocate, GL_TEXTURE_2D, GL_RGBA, 1, 1, sceneObjectRenderer->shamTexture);
741 // glBindFramebuffer(GL_FRAMEBUFFER, 0); // handled by glDeleteFramebuffers
742  glDeleteFramebuffers(1, &sceneObjectRenderer->shamFramebuffer);
743 
744  glDeleteVertexArrays(1, &sceneObjectRenderer->vertexArray);
745 
746  glDeleteQueries(1, &sceneObjectRenderer->query);
747  });
748 
749  free(sceneObjectRenderer);
750 }