Converting Render Textures to RBGA byte array via native plugin

I know that there are other questions asking this, but I haven’t been able to find one with an answer that works. The program I’m writing requires me to take a Unity RenderTexture object and convert it into an RGB(A) buffer, and be able to do that in as little time as possible (at absolute most 15 ms, I’d love to go far below that). I know that the fastest way to do that is going to be with a native plugin, but I can’t for the life of me get a native plugin to read a rendertexture into a byte buffer with OpenGL. Here’s my code, can anyone explain 1) why the pixels array is just a string of zeroes and 2) how to make it the image contents of my RenderTexture object.

C# Code

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using System.Runtime.InteropServices;

public class VimeoCaptureRunner : MonoBehaviour {

[DllImport("VimeoCapture")]
private static extern void SetTextureFromUnity (IntPtr t, int w, int h);

[DllImport("VimeoCapture")]
private static extern IntPtr RenderEvent();

private int resolutionX = 1280;
private int resolutionY = 720;

private Camera cam;
private RenderTexture mainRT;

// Use this for initialization
void Start () {
cam = GetComponent<Camera>();
mainRT = new RenderTexture(resolutionX, resolutionY, 0, RenderTextureFormat.ARGB32);
mainRT.Create();
SetTextureFromUnity(mainRT.GetNativeTexturePtr(), mainRT.width, mainRT.height);
}

void OnPostRender(){
GL.IssuePluginEvent(RenderEvent(), 0);
}
}

This is the runner that interfaces with the c# script. Everything is being called properly, and the s_CurrentAPI variable is being set correctly.

[Set-up work taken from Native Plugin example]

void SetTextureFromUnity(void* textureHandle, int w, int h)
{
g_TextureHandle = textureHandle;
g_TextureWidth = w;
g_TextureHeight = h;
}

void CaptureFrame(int i){
if (!g_TextureHandle){
return;
}
if (s_CurrentAPI)
{
pixels = (unsigned char *) malloc(g_TextureWidth * g_TextureHeight * 4);
s_CurrentAPI->save(g_TextureHandle, g_TextureWidth, g_TextureHeight, pixels);
// Local function to print pixels. It's all zeroes.
free(pixels);
}
}

UnityRenderingEvent RenderEvent(){
return CaptureFrame;
}

What follows is the offending piece of code. I don’t really know what the right way to do this is. I’ve tried four or five, all of them left the byte array black. If you have a better way, please let me know. This is someone else’s code, that I found in a post from 2013 I can’t find again.

void CaptureAPI_OpenGLCore::save(void* textureHandle, int width, int height, unsigned char* pxls){
GLuint gltex = (GLuint)(size_t)(textureHandle);
GLubyte *pixels = (GLubyte *) pxls;

GLuint offscreen_framebuffer;
GLint screen_framebuffer;
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &screen_framebuffer);
glGenFramebuffers(1, &offscreen_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, offscreen_framebuffer);

glBindTexture(GL_TEXTURE_2D, gltex);

// This line throws the following error: "OPENGL NATIVE PLUG-IN ERROR: GL_INVALID_OPERATION: Operation illegal in current state"
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

//Bind the texture to your FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gltex, 0);

//Test if everything failed
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if(status != GL_FRAMEBUFFER_COMPLETE) {
printf("failed to make complete framebuffer object %x", status);
}

//Bind the FBO
glBindFramebuffer(GL_FRAMEBUFFER, offscreen_framebuffer);
// set the viewport as the FBO won't be the same dimension as the screen
glViewport(0, 0, width, height);

glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

//Bind your main FBO again
glBindFramebuffer(GL_FRAMEBUFFER, screen_framebuffer);
// set the viewport as the FBO won't be the same dimension as the screen
glViewport(0, 0, width, height);
}

Thank you for taking the time to read this, and thank you for helping me.

Wow, that is a lot of code for something like that. Problem is that the RenderTexture is in GPU memory, so you should first move it back to main memory. You can do that into a Texture2D using ReadPixels. This does take some time, but that’s just how it is. The only way to speed it up is to compress the RenderTexture on the GPU so the amount of data to transfer is less.