Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice
  3. Dismiss Notice

Question dmabuf for texture synchronisation with another OpenGL application

Discussion in 'Linux' started by ClayDome, Mar 22, 2023.

  1. ClayDome

    ClayDome

    Joined:
    Apr 8, 2021
    Posts:
    5
    Hello, I am looking for a way to share the content of a unity render texture between a Unity Linux app and another OpenGL application running concurrently.
    I looked into Native Plugins with the intention of using dmabuf.
    In order to make a proof of concept, I simply cloned the NativeRenderingPlugin repository
    https://github.com/Unity-Technologies/NativeRenderingPlugin/tree/master/PluginSource/source/Unity
    as well as a simple program demonstrating the use of dmabuf:
    https://gitlab.com/blaztinn/dma-buf-texture-sharing

    I can compile each one without any issue (the dmabuf program is working by itself and I can modify the unity plugin by itself too).
    I then tried integrating some dmabuf code into the Unity plugin to see how it would go.

    Here are the parts I deemed relevant (a longer portion of the code is below in case you would want more context, but I think the main part is here) :

    RenderingPlugin.cpp from the c++ plugin:
    Code (CSharp):
    1. //RenderingPlugin.cpp
    2. extern "C"  int share(int steps);
    3. extern "C"  void init();
    4.  
    5. static Display *x11_display;
    6. static Window x11_window;
    7. static EGLDisplay egl_display;
    8. static EGLContext egl_context;
    9. static EGLSurface egl_surface;
    10.  
    11. extern "C" void init()
    12. {
    13.     // Create X11 window
    14.     create_x11_window(is_server, &x11_display, &x11_window);
    15.     // Initialize EGL
    16.     initialize_egl(x11_display, x11_window, &egl_display, &egl_context, &egl_surface);
    17.     // Setup GL scene
    18.     gl_setup_scene();
    19. }
    20.  
    21. extern "C" int share(int genTex)
    22. {
    23.     // Server texture data
    24.     const size_t TEXTURE_DATA_WIDTH = 256;
    25.     const size_t TEXTURE_DATA_HEIGHT = TEXTURE_DATA_WIDTH;
    26.     const size_t TEXTURE_DATA_SIZE = TEXTURE_DATA_WIDTH * TEXTURE_DATA_HEIGHT;
    27.     int* texture_data = create_data(TEXTURE_DATA_SIZE);
    28.    
    29.     // GL texture that will be shared
    30.     GLuint texture;
    31.     if(genTex)
    32.     {
    33.         // GL: Create and populate the texture
    34.         glGenTextures(1, &texture);
    35.         glBindTexture(GL_TEXTURE_2D, texture);
    36.         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, TEXTURE_DATA_WIDTH, TEXTURE_DATA_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    37.         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, TEXTURE_DATA_WIDTH, TEXTURE_DATA_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, texture_data);
    38.         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    39.         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    40.     }
    41.     else
    42.     {
    43.         //Use the texture shared by unity
    44.         void* textureHandle = g_TextureHandle;
    45.         texture = (GLuint)(size_t)textureHandle;
    46.     }
    47.  
    48.     if(texture == 0)
    49.     {
    50.         //FAILS here when run with -headless AND we attempt to use unity's texture (genTex = 0)
    51.         return -4;
    52.     }
    53.     // EGL: Create EGL image from the GL texture
    54.     EGLImage image = eglCreateImage(egl_display,
    55.                                     egl_context,
    56.                                     EGL_GL_TEXTURE_2D,
    57.                                     (EGLClientBuffer)(uint64_t)texture,
    58.                                     NULL);
    59.  
    60.     if(image == EGL_NO_IMAGE)
    61.     {
    62.         //FAILS here when not run with -headless
    63.         return -1;
    64.     }
    65.     assert(image != EGL_NO_IMAGE);
    66.     //... (always fails before reaching here, unless running with -headless and generating the texture here (genTex = 1))
    67.  
    68. }

    UseRenderingPlugin.cs
    Code (CSharp):
    1. //UseRenderingPlugin.cs
    2.      public Texture2D tex;
    3.      IEnumerator Start()
    4.     {
    5. #if UNITY_WEBGL && !UNITY_EDITOR
    6.         RegisterPlugin();
    7. #endif
    8.         yield return new WaitForSeconds(2f);
    9.         init();//Call to the plugin
    10.         yield return new WaitForSeconds(5f);
    11.         CreateTextureAndPassToPlugin();
    12.         yield return new WaitForSeconds(5f);
    13.         int a = share(genTex);//Call to the plugin
    14.         Debug.Log($"{a}");//To see where it fails
    15.     }
    16.    
    17.     private void CreateTextureAndPassToPlugin()
    18.     {
    19.        
    20.         tex = new Texture2D(256,256,TextureFormat.ARGB32,false);
    21.         tex.filterMode = FilterMode.Point;
    22.         tex.Apply();
    23.         SetTextureFromUnity (tex.GetNativeTexturePtr(), tex.width, tex.height);
    24.     }
    25. //...

    I seems like I can't get eglCreateImage to work (it returns EGL_NO_IMAGE) unless I run the app with -headless. If I run the app with -headless though, it becomes impossible to send the native texture handle as Texture.GetNativeTexturePtr() then returns 0.

    I assumed it has something to do with the active OpenGL context at the time the code is executed, maybe the EGL context not being able to become the current context for its code (as even when the texture is generated inside the plugin, it wouldn't work if the unity program was not launched with '-headless').
    I'd love to know if there is some fundamental incompatibility between launching a Unity desktop app with OpenGL (not OpenGL ES as it does not seem to be available for desktop, unless I'm mistaken) and trying to use an EGL context inside the plugin, or if I just missed something.

    Thanks in advance.





    Here is the same code with more context:

    RenderingPlugin.cpp
    Code (CSharp):
    1. //C++
    2. //Addition to RenderingPlugin.cpp
    3. // Example low level rendering Unity plugin
    4.  
    5. //...
    6.  
    7. int* create_data(size_t size);
    8. void rotate_data(int* data, size_t size);
    9.  
    10. EXTERN  int share(int steps);
    11. EXTERN void init();
    12.  
    13. static Display *x11_display;
    14. static Window x11_window;
    15. static EGLDisplay egl_display;
    16. static EGLContext egl_context;
    17. static EGLSurface egl_surface;
    18.  
    19. static int is_server=1;
    20.  
    21. int main(int argc, char **argv)
    22. {
    23.     init();
    24.     share(1);
    25. }
    26.  
    27. EXTERN void init()
    28. {
    29.     // Create X11 window
    30.     create_x11_window(is_server, &x11_display, &x11_window);
    31.     // Initialize EGL
    32.     initialize_egl(x11_display, x11_window, &egl_display, &egl_context, &egl_surface);
    33.     // Setup GL scene
    34.     gl_setup_scene();
    35. }
    36.  
    37. EXTERN int share(int genTex)
    38. {
    39.     // Server texture data
    40.     const size_t TEXTURE_DATA_WIDTH = 256;
    41.     const size_t TEXTURE_DATA_HEIGHT = TEXTURE_DATA_WIDTH;
    42.     const size_t TEXTURE_DATA_SIZE = TEXTURE_DATA_WIDTH * TEXTURE_DATA_HEIGHT;
    43.     int* texture_data = create_data(TEXTURE_DATA_SIZE);
    44.  
    45.     // -----------------------------
    46.     // --- Texture sharing start ---
    47.     // -----------------------------
    48.  
    49.     // Socket paths for sending/receiving file descriptor and image storage data
    50.     const char *SERVER_FILE = "/tmp/test_server";
    51.     const char *CLIENT_FILE = "/tmp/test_client";
    52.     // Custom image storage data description to transfer over socket
    53.     struct texture_storage_metadata_t
    54.     {
    55.         int fourcc;
    56.         EGLuint64KHR modifiers;
    57.         EGLint stride;
    58.         EGLint offset;
    59.     };
    60.  
    61.  
    62.     //Code added to make sure the egl contexte is the current context, did not make a difference
    63.     //glFinish();
    64.     //eglMakeCurrent(egl_display,
    65.     //egl_surface,
    66.     //egl_surface,
    67.     //egl_context);
    68.    
    69.    
    70.     // GL texture that will be shared
    71.     GLuint texture;
    72.  
    73.     // The next `if` block contains server code in the `true` branch and client code in the `false` branch. The `true` branch is always executed first and the `false` branch after it (in a different process). This is because the server loops at the end of the branch until it can send a message to the client and the client blocks at the start of the branch until it has a message to read. This way the whole `if` block from top to bottom represents the order of events as they happen.
    74.     if (is_server)
    75.     {
    76.         if(genTex)
    77.         {
    78.             // GL: Create and populate the texture
    79.             glGenTextures(1, &texture);
    80.             glBindTexture(GL_TEXTURE_2D, texture);
    81.             glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, TEXTURE_DATA_WIDTH, TEXTURE_DATA_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    82.             glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, TEXTURE_DATA_WIDTH, TEXTURE_DATA_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, texture_data);
    83.             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    84.             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    85.         }
    86.         else
    87.         {
    88.             //Use the texture shared by unity
    89.             void* textureHandle = g_TextureHandle;
    90.             texture = (GLuint)(size_t)textureHandle;
    91.         }
    92.  
    93.         if(egl_display == NULL)
    94.         {
    95.             return -2;
    96.         }
    97.         if(egl_context == NULL)
    98.         {
    99.             return -3;
    100.         }
    101.         if(texture == 0)
    102.         {
    103.             //FAILS here when run with -headless AND we attempt to use unity's texture (genTex = 0)
    104.             return -4;
    105.         }
    106.  
    107.  
    108.         // EGL: Create EGL image from the GL texture
    109.         EGLImage image = eglCreateImage(egl_display,
    110.                                         egl_context,
    111.                                         EGL_GL_TEXTURE_2D,
    112.                                         (EGLClientBuffer)(uint64_t)texture,
    113.                                         NULL);
    114.  
    115.         if(image == EGL_NO_IMAGE)
    116.         {
    117.             //FAILS here when not run with -headless
    118.             return -1;
    119.         }
    120.         assert(image != EGL_NO_IMAGE);
    121.        
    122.         //... (always fails before reaching here, unless runnning -headless and generating the texture here (genTex = 1))
    123.  
    124.  
    125.         // The next line works around an issue in radeonsi driver (fixed in master at the time of writing). If you are
    126.         // having problems with texture rendering until the first texture update you can uncomment this line
    127.         // glFlush();
    128.  
    129.         // EGL (extension: EGL_MESA_image_dma_buf_export): Get file descriptor (texture_dmabuf_fd) for the EGL image and get its
    130.         // storage data (texture_storage_metadata)
    131.         int texture_dmabuf_fd;
    132.         struct texture_storage_metadata_t texture_storage_metadata;
    133.  
    134.         int num_planes;
    135.         PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC eglExportDMABUFImageQueryMESA =
    136.             (PFNEGLEXPORTDMABUFIMAGEQUERYMESAPROC)eglGetProcAddress("eglExportDMABUFImageQueryMESA");
    137.         EGLBoolean queried = eglExportDMABUFImageQueryMESA(egl_display,
    138.                                                            image,
    139.                                                            &texture_storage_metadata.fourcc,
    140.                                                            &num_planes,
    141.                                                            &texture_storage_metadata.modifiers);
    142.         assert(queried);
    143.         assert(num_planes == 1);
    144.         PFNEGLEXPORTDMABUFIMAGEMESAPROC eglExportDMABUFImageMESA =
    145.             (PFNEGLEXPORTDMABUFIMAGEMESAPROC)eglGetProcAddress("eglExportDMABUFImageMESA");
    146.         EGLBoolean exported = eglExportDMABUFImageMESA(egl_display,
    147.                                                        image,
    148.                                                        &texture_dmabuf_fd,
    149.                                                        &texture_storage_metadata.stride,
    150.                                                        &texture_storage_metadata.offset);
    151.         assert(exported);
    152.  
    153.         // Unix Domain Socket: Send file descriptor (texture_dmabuf_fd) and texture storage data (texture_storage_metadata)
    154.         int sock = create_socket(SERVER_FILE);
    155.         while (connect_socket(sock, CLIENT_FILE) != 0)
    156.             ;
    157.         write_fd(sock, texture_dmabuf_fd, &texture_storage_metadata, sizeof(texture_storage_metadata));
    158.         close(sock);
    159.         close(texture_dmabuf_fd);
    160.     }
    161.     else//client
    162.     {
    163.         //use another build of the original dmabuf program as a client
    164.     }
    165.  
    166.     // -----------------------------
    167.     // --- Texture sharing end ---
    168.     // -----------------------------
    169.  
    170.     time_t last_time = time(NULL);
    171.     while (1)
    172.     {
    173.         // Draw scene (uses shared texture)
    174.         gl_draw_scene(texture);
    175.         eglSwapBuffers(egl_display, egl_surface);
    176.  
    177.         // Update texture data each second to see that the client didn't just copy the texture and is indeed referencing
    178.         // the same texture data.
    179.         if (is_server)
    180.         {
    181.             time_t cur_time = time(NULL);
    182.             if (last_time < cur_time)
    183.             {
    184.                 last_time = cur_time;
    185.                 rotate_data(texture_data, TEXTURE_DATA_SIZE);
    186.                 glBindTexture(GL_TEXTURE_2D, texture);
    187.                 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, TEXTURE_DATA_WIDTH, TEXTURE_DATA_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, texture_data);
    188.             }
    189.         }
    190.  
    191.         // Check for errors
    192.         assert(glGetError() == GL_NO_ERROR);
    193.         assert(eglGetError() == EGL_SUCCESS);
    194.     }
    195.  
    196.     return 0;
    197. }
    198.  
    199. //Secondary, the way the data is generated in the dmabuf example
    200. int* create_data(size_t size)
    201. {
    202.     size_t edge = sqrt(size);
    203.     assert(edge * edge == size);
    204.     size_t half_edge = edge / 2;
    205.  
    206.     int* data = (int*) malloc(size * sizeof(int));
    207.  
    208.     // Paint the texture like so:
    209.     // RG
    210.     // BW
    211.     // where R - red, G - green, B - blue, W - white
    212.     int red = 0x000000FF;
    213.     int green = 0x0000FF00;
    214.     int blue = 0X00FF0000;
    215.     int white = 0x00FFFFFF;
    216.     for (size_t i = 0; i < size; i++) {
    217.         size_t x = i % edge;
    218.         size_t y = i / edge;
    219.  
    220.         if (x < half_edge) {
    221.             if (y < half_edge) {
    222.                 data[i] = red;
    223.             } else {
    224.                 data[i] = blue;
    225.             }
    226.         } else {
    227.             if (y < half_edge) {
    228.                 data[i] = green;
    229.             } else {
    230.                 data[i] = white;
    231.             }
    232.         }
    233.     }
    234.  
    235.     return data;
    236. }
    237.  
    238. void rotate_data(int* data, size_t size)
    239. {
    240.     size_t edge = sqrt(size);
    241.     assert(edge * edge == size);
    242.     size_t half_edge = edge / 2;
    243.  
    244.     for (size_t i = 0; i < half_edge * half_edge; i++) {
    245.         size_t x = i % half_edge;
    246.         size_t y = i / half_edge;
    247.  
    248.         int temp = data[x + y * edge];
    249.         data[x + y * edge] = data[(x + half_edge) + y * edge];
    250.         data[(x + half_edge) + y * edge] = data[(x + half_edge) + (y + half_edge) * edge];
    251.         data[(x + half_edge) + (y + half_edge) * edge] = data[x + (y + half_edge) * edge];
    252.         data[x + (y + half_edge) * edge] = temp;
    253.     }
    254. }
    255.  
    UseRenderingPlugin.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using System.Runtime.InteropServices;
    5. using UnityEngine.Rendering;
    6. public class UseRenderingPlugin : MonoBehaviour
    7. {
    8.     // Native plugin rendering events are only called if a plugin is used
    9.     // by some script. This means we have to DllImport at least
    10.     // one function in some active script.
    11.     // For this example, we'll call into plugin's SetTimeFromUnity
    12.     // function and pass the current time so the plugin can animate.
    13.  
    14. #if (UNITY_IOS || UNITY_TVOS || UNITY_WEBGL) && !UNITY_EDITOR
    15.     [DllImport ("__Internal")]
    16. #else
    17.     [DllImport ("RenderingPlugin")]
    18. #endif
    19.     private static extern void SetTimeFromUnity(float t);
    20.  
    21.  
    22.     // We'll also pass native pointer to a texture in Unity.
    23.     // The plugin will fill texture data from native code.
    24. #if (UNITY_IOS || UNITY_TVOS || UNITY_WEBGL) && !UNITY_EDITOR
    25.     [DllImport ("__Internal")]
    26. #else
    27.     [DllImport ("RenderingPlugin")]
    28. #endif
    29.     private static extern void SetTextureFromUnity(System.IntPtr texture, int w, int h);
    30.  
    31.     // We'll pass native pointer to the mesh vertex buffer.
    32.     // Also passing source unmodified mesh data.
    33.     // The plugin will fill vertex data from native code.
    34. #if (UNITY_IOS || UNITY_TVOS || UNITY_WEBGL) && !UNITY_EDITOR
    35.     [DllImport ("__Internal")]
    36. #else
    37.     [DllImport ("RenderingPlugin")]
    38. #endif
    39.     private static extern void SetMeshBuffersFromUnity (IntPtr vertexBuffer, int vertexCount, IntPtr sourceVertices, IntPtr sourceNormals, IntPtr sourceUVs);
    40.  
    41. #if (UNITY_IOS || UNITY_TVOS || UNITY_WEBGL) && !UNITY_EDITOR
    42.     [DllImport ("__Internal")]
    43. #else
    44.     [DllImport("RenderingPlugin")]
    45. #endif
    46.     private static extern IntPtr GetRenderEventFunc();
    47.  
    48. #if UNITY_WEBGL && !UNITY_EDITOR
    49.     [DllImport ("__Internal")]
    50.     private static extern void RegisterPlugin();
    51. #endif
    52.  
    53.  
    54.  
    55.  
    56. //////////////////////////////////NEW////////////////////////////////////////////
    57. #if (UNITY_IOS || UNITY_TVOS || UNITY_WEBGL) && !UNITY_EDITOR
    58.     [DllImport ("__Internal")]
    59. #else
    60.     [DllImport ("RenderingPlugin")]
    61. #endif
    62.     private static extern int share(int is_server);
    63.  
    64.  
    65. #if (UNITY_IOS || UNITY_TVOS || UNITY_WEBGL) && !UNITY_EDITOR
    66.     [DllImport ("__Internal")]
    67. #else
    68.     [DllImport ("RenderingPlugin")]
    69. #endif
    70.     private static extern void init();
    71.  
    72.     public int genTex;
    73.  
    74. //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\NEW\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
    75.  
    76.  
    77.     IEnumerator Start()
    78.     {
    79. #if UNITY_WEBGL && !UNITY_EDITOR
    80.         RegisterPlugin();
    81. #endif
    82.  
    83.         yield return new WaitForSeconds(2f);
    84.         init();////////NEW///////////
    85.         yield return new WaitForSeconds(5f);
    86.  
    87.         CreateTextureAndPassToPlugin();
    88.         SendMeshBuffersToPlugin();
    89.         StartCoroutine("CallPluginAtEndOfFrames");
    90.  
    91.         yield return new WaitForSeconds(5f);
    92.  
    93.         /////////////////////////////////NEW
    94.         int a = share(genTex);
    95.         Debug.Log($"{a}");
    96.         //\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\NEW
    97.     }
    98.     public Texture2D tex;
    99.     public GameObject target;
    100.    
    101.     private void CreateTextureAndPassToPlugin()
    102.     {
    103.        
    104.         tex = new Texture2D(256,256,TextureFormat.ARGB32,false);
    105.         // Set point filtering just so we can see the pixels clearly
    106.         tex.filterMode = FilterMode.Point;
    107.         // Call Apply() so it's actually uploaded to the GPU
    108.  
    109.         // Set texture onto our material
    110.  
    111.         var fillColorArray =  tex.GetPixels();
    112.        
    113.         for(var i = 0; i < fillColorArray.Length; ++i)
    114.         {
    115.             fillColorArray[i] = new Color(
    116.             1 -((float)i)/fillColorArray.Length,
    117.             1 -((float)i)/fillColorArray.Length,
    118.             ((float)i)/fillColorArray.Length,
    119.             1);
    120.         }
    121.          tex.SetPixels( fillColorArray );
    122.    
    123.         tex.Apply();
    124.  
    125.  
    126.  
    127.         target.GetComponent<Renderer>().material.mainTexture = tex;
    128.         // Pass texture pointer to the plugin
    129.         SetTextureFromUnity (tex.GetNativeTexturePtr(), tex.width, tex.height);
    130.         //SetTextureFromUnity (renderTex.GetNativeTexturePtr(), renderTex.width, renderTex.height);
    131.     }
    132.  
    133.  
    134.  
    135.  
    136.   /////////////////SECONDARY (the important part is before)//
    137.  
    138.     private void SendMeshBuffersToPlugin ()
    139.     {
    140.         var filter = target.GetComponent<MeshFilter> ();
    141.         var mesh = filter.mesh;
    142.  
    143.         // This is equivalent to MeshVertex in RenderingPlugin.cpp
    144.         var desiredVertexLayout = new[]
    145.         {
    146.             new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
    147.             new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3),
    148.             new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.Float32, 4),
    149.             new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2)
    150.         };
    151.  
    152.         // Let's be certain we'll get the vertex buffer layout we want in native code
    153.         mesh.SetVertexBufferParams(mesh.vertexCount, desiredVertexLayout);
    154.  
    155.         // The plugin will want to modify the vertex buffer -- on many platforms
    156.         // for that to work we have to mark mesh as "dynamic" (which makes the buffers CPU writable --
    157.         // by default they are immutable and only GPU-readable).
    158.         mesh.MarkDynamic ();
    159.  
    160.         // However, mesh being dynamic also means that the CPU on most platforms can not
    161.         // read from the vertex buffer. Our plugin also wants original mesh data,
    162.         // so let's pass it as pointers to regular C# arrays.
    163.         // This bit shows how to pass array pointers to native plugins without doing an expensive
    164.         // copy: you have to get a GCHandle, and get raw address of that.
    165.         var vertices = mesh.vertices;
    166.         var normals = mesh.normals;
    167.         var uvs = mesh.uv;
    168.         GCHandle gcVertices = GCHandle.Alloc (vertices, GCHandleType.Pinned);
    169.         GCHandle gcNormals = GCHandle.Alloc (normals, GCHandleType.Pinned);
    170.         GCHandle gcUV = GCHandle.Alloc (uvs, GCHandleType.Pinned);
    171.  
    172.         SetMeshBuffersFromUnity (mesh.GetNativeVertexBufferPtr (0), mesh.vertexCount, gcVertices.AddrOfPinnedObject (), gcNormals.AddrOfPinnedObject (), gcUV.AddrOfPinnedObject ());
    173.  
    174.         gcVertices.Free ();
    175.         gcNormals.Free ();
    176.         gcUV.Free ();
    177.     }
    178.  
    179.  
    180.  
    181.     private IEnumerator CallPluginAtEndOfFrames()
    182.     {
    183.         while (true) {
    184.             // Wait until all frame rendering is done
    185.             yield return new WaitForEndOfFrame();
    186.  
    187.             // Set time for the plugin
    188.             SetTimeFromUnity (Time.timeSinceLevelLoad);
    189.  
    190.             // Issue a plugin event with arbitrary integer identifier.
    191.             // The plugin can distinguish between different
    192.             // things it needs to do based on this ID.
    193.             // For our simple plugin, it does not matter which ID we pass here.
    194.             //GL.IssuePluginEvent(GetRenderEventFunc(), 1);
    195.  
    196.             GL.IssuePluginEvent(GetRenderEventFunc(), 1);
    197.             if(callShare)
    198.             {
    199.                 callShare=false;
    200.                 int a = share(genTex);
    201.                 Debug.Log($"{a}");
    202.             }
    203.         }
    204.     }
    205.     bool callShare = false;
    206.     void Update()
    207.     {
    208.         if(Input.GetKeyDown(KeyCode.Space))
    209.         {
    210.             callShare=true;
    211.         }
    212.     }
    213.  
    214. }
    215.