У чтения из GL_TEXTURE_EXTERNAL_OES до GL_TEXTURE_2D есть проблемы с производительностью и глюки
Мне нужно отправить данные из GL_TEXTURE_EXTERNAL_OES в простой GL_TEXTURE_2D (рендеринг изображения с Android-плеера в текстуру Unity) и в настоящее время делать это через считанные пиксели из буфера с прикрепленной исходной текстурой. Этот процесс работает правильно на моем телефоне OnePlus 5, но у него есть глюки с изображением на телефонах, таких как xiaomi note 4, mi a2 и т. Д. (Например, изображение очень зеленое), а также есть проблемы с производительностью, потому что этот процесс работает каждый кадр, а затем больше пикселей для чтения, чем хуже производительность (даже у моего телефона низкий fps при разрешении 4k). Есть идеи, как оптимизировать этот процесс или как-то иначе?
Спасибо и всего наилучшего!
GLuint FramebufferName;
glGenFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, g_ExtTexturePointer, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
LOGD("%s", "Error: Could not setup frame buffer.");
}
unsigned char* data = new unsigned char[g_SourceWidth * g_SourceHeight * 4];
glReadPixels(0, 0, g_SourceWidth, g_SourceHeight, GL_RGBA, GL_UNSIGNED_BYTE, data);
glBindTexture(GL_TEXTURE_2D, g_TexturePointer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, g_SourceWidth, g_SourceHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glDeleteFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
delete[] data;
ОБНОВИТЬ. Функция, которая содержит этот код и функция, которая вызывает его со стороны Unity
static void UNITY_INTERFACE_API OnRenderEvent(int eventID) { ... }
extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UMDGetRenderEventFunc()
{
return OnRenderEvent;
}
Который вызывается из функции обновления Unity следующим образом:
[DllImport("RenderingPlugin")]
static extern IntPtr UMDGetRenderEventFunc();
IEnumerator UpdateVideoTexture()
{
while (true)
{
...
androidPlugin.UpdateSurfaceTexture();
GL.IssuePluginEvent(UMDGetRenderEventFunc, 1);
}
}
И плагин Android делает это на своей стороне (surfaceTexture его текстура, которая содержит эту внешнюю текстуру, на которой ExoPlayer рендерит видео)
public void exportUpdateSurfaceTexture() {
synchronized (this) {
if (this.mIsStopped) {
return;
}
surfaceTexture.updateTexImage();
}
}
2 ответа
На стороне C++:
Вы создаете и уничтожаете данные пикселей каждый кадр, когда вы делаете new unsigned char[g_SourceWidth * g_SourceHeight * 4];
а также delete[] data
и это дорого в зависимости от размера текстуры. Создайте данные текстуры один раз, а затем снова используйте их.
Один из способов сделать это - иметь static
переменные на стороне C++ содержат информацию о текстуре, а затем функцию для инициализации этих переменных:
static void* pixelData = nullptr;
static int _x;
static int _y;
static int _width;
static int _height;
void initPixelData(void* buffer, int x, int y, int width, int height) {
pixelData = buffer;
_x = x;
_y = y;
_width = width;
_height = height;
}
Тогда ваша функция захвата должна быть переписана для удаления new unsigned char[g_SourceWidth * g_SourceHeight * 4];
а также delete[] data
но используйте статические переменные.
static void UNITY_INTERFACE_API OnRenderEvent(int eventID)
{
if (pixelData == nullptr) {
//Debug::Log("Pointer is null", Color::Red);
return;
}
GLuint FramebufferName;
glGenFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_EXTERNAL_OES, g_ExtTexturePointer, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
LOGD("%s", "Error: Could not setup frame buffer.");
}
glReadPixels(_x, _y, _width, _height, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);
glBindTexture(GL_TEXTURE_2D, g_TexturePointer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);
glDeleteFramebuffers(1, &FramebufferName);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
}
extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API
UMDGetRenderEventFunc()
{
return OnRenderEvent;
}
На стороне C#:
[DllImport("RenderingPlugin", CallingConvention = CallingConvention.Cdecl)]
public static extern void initPixelData(IntPtr buffer, int x, int y, int width, int height);
[DllImport("RenderingPlugin", CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr UMDGetRenderEventFunc();
Создайте информацию о текстуре, закрепите ее и отправьте указатель на C++:
int width = 500;
int height = 500;
//Where Pixel data will be saved
byte[] screenData;
//Where handle that pins the Pixel data will stay
GCHandle pinHandler;
//Used to test the color
public RawImage rawImageColor;
private Texture2D texture;
// Use this for initialization
void Awake()
{
Resolution res = Screen.currentResolution;
width = res.width;
height = res.height;
//Allocate array to be used
screenData = new byte[width * height * 4];
texture = new Texture2D(width, height, TextureFormat.RGBA32, false, false);
//Pin the Array so that it doesn't move around
pinHandler = GCHandle.Alloc(screenData, GCHandleType.Pinned);
//Register the screenshot and pass the array that will receive the pixels
IntPtr arrayPtr = pinHandler.AddrOfPinnedObject();
initPixelData(arrayPtr, 0, 0, width, height);
StartCoroutine(UpdateVideoTexture());
}
Затем, чтобы обновить текстуру, см. Образец ниже. Обратите внимание, что есть два способа обновления текстуры, как показано в коде ниже. Если у вас возникнут проблемы с Method1, закомментируйте две строки, которые используют texture.LoadRawTextureData
а также texture.Apply
и откомментируйте код Method2, который использует ByteArrayToColor
, texture.SetPixels
а также texture.Apply
функция:
IEnumerator UpdateVideoTexture()
{
while (true)
{
//Take screenshot of the screen
GL.IssuePluginEvent(UMDGetRenderEventFunc(), 1);
//Update Texture Method1
texture.LoadRawTextureData(screenData);
texture.Apply();
//Update Texture Method2. Use this if the Method1 above crashes
/*
ByteArrayToColor();
texture.SetPixels(colors);
texture.Apply();
*/
//Test it by assigning the texture to a raw image
rawImageColor.texture = texture;
//Wait for a frame
yield return null;
}
}
Color[] colors = null;
void ByteArrayToColor()
{
if (colors == null)
{
colors = new Color[screenData.Length / 4];
}
for (int i = 0; i < screenData.Length; i += 4)
{
colors[i / 4] = new Color(screenData[i],
screenData[i + 1],
screenData[i + 2],
screenData[i + 3]);
}
}
Открепите массив, когда закончите или когда скрипт будет уничтожен:
void OnDisable()
{
//Unpin the array when disabled
pinHandler.Free();
}
Призвание glReadPixels
всегда будет медленным; Процессоры плохо справляются с массовой передачей данных.
В идеале вам удалось бы убедить Unity принять дескриптор внешнего изображения и выполнить нулевую копию всего процесса, но не получилось, что я использовал бы рендеринг в текстуру с помощью графического процессора и шейдер для переноса с внешнего изображения на поверхность RGB.,