LINUX.ORG.RU

Как рендерить несколько видеопотоков на OpenGL 3.1?

 , , ,


1

1

Есть несколько декодеров, работающих в разных потоках. Они декодируют видео с разной частотой кадров и вообще никак друг с другом не синхронизированы. Могут запускаться и останавливаться.

Нужно всё это дело рендерить в реальном времени в подобие видеостены - прямоугольной сетки, где в каждой ячейке свой видеопоток.

Как у меня сделано сейчас. Декодер хранит последний декодированный кадр. По таймеру поток, отвечающий за обновление экрана, собирает последние кадры от всех декодеров, через glTexSubImage2D обновляет OpenGL текстуры и рендерит их на экран. Чтобы потоки не конфликтовали, чтение и запись последнего декодированного кадра прикрыты мьютексом.

Текущая версия работает, но хочу попросить совета. Есть ли какие-нибудь мысли по оптимизации этого дела и вообще как правильно такое делать? Наверное, это похоже на работу композитора. Есть независимые источники, которые предоставляют готовые данные и их нужно свести воедино и отобразить на экране.

У меня здесь получается только один OpenGL контекст. Слышал что-то про многопоточный рендер. Применим ли он здесь?

OpenGL рендерится в окно Qt приложения через QOpenGLWindow. Версия OpenGL строго не старше 3.1. Нужна кроссплатформенность: топик и оффтопик, т.е. без платформозависимых фич типа dma-buf. Видеокарты в основном интел и нвидия.

★★★★★

Э, смотри, реальная видяшка всё равно будет синхронизироваться на отдачу видео по кадрам, остальные детали от лукавого. Главное с буферизацией вопрос реши, т.е. чтобы видео по кадрам рисовалось заранее. А то что ты будешь рисовать в буфере видеопамяти уже пофиг и то как тоже пофиг. Как удобно, так и делай.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от peregrine

чтобы видео по кадрам рисовалось заранее

Не понял этого. Кто, где и как должен рисовать? Декодер отдаёт кадр в виде буфера в обычной памяти. Его просто так нарисовать нельзя. Сначала нужно засунуть в OGL текстуру. Но я не могу обновлять текстуру просто так. Нужен активный OpenGL контекст. Вот я и спрашиваю про подробности как мне всё это правильно и быстро делать.

ox55ff ★★★★★
() автор топика

Вроде бы то что ты описал и твое решение выглядит вполне сносным. На сколько я знаю многопоточный рендер в OpenGL не реализован, только в Vulkan. Но можно что-то похожее сделать - формируешь команды в многопотоке для рендеринга и уже в рендер потоке читаешь сформированные команды и рендеришь основываясь на них. Под командами я имею ввиду свое API для работы с графикой, просто данные, которые можно будет потом преобразовать в OpenGL вызовы.

Int64 ★★★
()
Ответ на: комментарий от ox55ff

Нужен активный OpenGL контекст.

Если у тебя его нет, то как ты вызываешь glTexSubImage2D?

По теме, многопоточность в OpenGL реализуется больно и сложно. Для твоего юзкейса, мне кажется, можно применить glMapBuffer. Создать несколько буферов, каждый отобразить в соответствующий поток, параллельно заполнить, отрисовать. Отрисовывать придется последовательно, но хотя бы заполнение буферов получится параллельным.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Если у тебя его нет, то как ты вызываешь glTexSubImage2D?

Контекст есть только в потоке (назовём его основным), который обновляет окно. Потоки, которые декодируют видео его не имеют и, соответственно, не могут сами без помощи основного потока залить в OGL текстуру новые данные.

параллельно заполнить, отрисовать.

Тут возникает проблема синхронизации. Что будет, если во время обновления текстуры в одном потоке, главный поток начнёт её рендерить на экран? Думаю, что или блокировка или мусор на экране. Не хорошо.

ox55ff ★★★★★
() автор топика

На qml элементарно

import QtQuick 2.0
import QtMultimedia 5.4

Item {
	Rectangle {
		width: parent.width/2
		height: parent.height/2
		anchors.left: parent.left
		anchors.top: parent.top

		MediaPlayer {
			id: player0
			source: "imxv4l2:///dev/video0"
			autoPlay: true
		}

		VideoOutput {
			source: player0
			fillMode: VideoOutput.Stretch
			anchors.fill: parent
		}
	}

	Rectangle {
		width: parent.width/2
		height: parent.height/2
		anchors.right: parent.right
		anchors.top: parent.top

		MediaPlayer {
			id: player1
			source: "imxv4l2:///dev/video1"
			autoPlay: true
		}

		VideoOutput {
			source: player1
			fillMode: VideoOutput.Stretch
			anchors.fill: parent
		}
	}

	Rectangle {
		width: parent.width/2
		height: parent.height/2
		anchors.left: parent.left
		anchors.bottom: parent.bottom

		MediaPlayer {
			id: player2
			source: "imxv4l2:///dev/video2"
			autoPlay: true
		}

		VideoOutput {
			source: player2
			fillMode: VideoOutput.Stretch
			anchors.fill: parent
		}
	}

	Rectangle {
		width: parent.width/2
		height: parent.height/2
		anchors.right: parent.right
		anchors.bottom: parent.bottom

		MediaPlayer {
			id: player3
			source: "imxv4l2:///dev/video3"
			autoPlay: true
		}

		VideoOutput {
			source: player3
			fillMode: VideoOutput.Stretch
			anchors.fill: parent
		}
	}
}
spbob
()
Ответ на: комментарий от Int64

Вулкан слишком сильно сузит количество железа, на котором всё это сможет работать. У меня вилы, никак нельзя выше OGL 3.1. Сначала сделал на 3.3, началось нытьё «у меня не работает». Пришлось переписывать на 3.1 с костылями.

ox55ff ★★★★★
() автор топика
Ответ на: комментарий от spbob

У меня до 100 каналов. QML умрёт. Я проводил сравнения пару лет назад qml vs qwidget. Всё очень плохо. Плюс у меня свой конвейер декодирования. У меня не просто просмотр видеопотока от вебки или ip камеры.

ox55ff ★★★★★
() автор топика
Ответ на: комментарий от ox55ff

Тут возникает проблема синхронизации.

Да. Обновление буферов параллельное, но поток с рендером на экран в этот момент простаивает.

Siborgium ★★★★★
()

Ищи shared_contest Ищи multiple_target_renderer

anonymous
()
Ответ на: комментарий от anonymous

Для каждого потока с декодером создать OGL контекст и расшарить от основного? Я так делал. На Nvidia работает вроде бы хорошо. Но на встройке интела жесточайшие тормоза. Там околонулевая производительность была. Отказался.

ox55ff ★★★★★
() автор топика
Ответ на: комментарий от Siborgium

Обновление буферов параллельное, но поток с рендером на экран в этот момент простаивает.

А кто гарантирует, что поток с рендером будет простаивать? Он у меня по таймеру вызывается. И никак не синхронизирован с потоками декодеров.

ox55ff ★★★★★
() автор топика
Последнее исправление: ox55ff (всего исправлений: 1)
Ответ на: комментарий от Siborgium

Тут возникает проблема синхронизации.

Multiple target renderer. Буфер зааттаченных текстур 1, но в нём несколько текстур, шейдер может писать сразу в несколько или в одну из. Каждый поток пишет через 1 шейдер когда захочет в свою текстуру и всё. man gl_FragData.

Но судя по твоим словам у тебя уже написано много и архитектурно пол это не заточено.

anonymous
()
Ответ на: комментарий от ox55ff

Никто не гарантирует, нужно будет писать код, предотвращающий такую ситуацию.

Если заполнение буферов не закончилось к моменту вызова рендера, можно:

  • Отложить рендер, пока не закончится заполнение;
  • Хранить последний законченый кадр, рендерить его, если актуальный не готов;
  • Отдавать текущий кадр (не забудь glUnmapBuffer вызвать), кадр будет битый, но если такие простои редкие, то, возможно, это будет лучшим решением;
  • ?
Siborgium ★★★★★
()
Ответ на: комментарий от anonymous

А как шейдер получит доступ к данным, которые он будет писать в текстуры? Данные то в обычной RAM, не в GPU. Опять копировать куда то. Это плюс минус то же самое, что у меня есть.

Но судя по твоим словам у тебя уже написано много и архитектурно пол это не заточено.

Кстати говоря. У меня атласы. Переключение текстур дорогое. Поэтому я создаю одну большую, которая содержит кадры от нескольких декодеров. Потом сортирую команды загрузки данных и отрисовки, чтобы было меньше переключений. Ещё у меня декодеры отдают изображения в формате I420 и NV12. Там несколько plane. Проблем хватает.

ox55ff ★★★★★
() автор топика
Ответ на: комментарий от anonymous

shared context это хорошо, и я, на самом деле, считаю это более хорошим решением, но поддержка в драйверах сильно варьируется.

Siborgium ★★★★★
()
Ответ на: комментарий от ox55ff

Нет 1 контекст на все потоки. Ты создаёшь контекст и говоришь что разрешаешь его отдавать другим процессам и всё в каждом процессе можешь свою gl текстуру забиндить и в неё писать, а когда время будет выводить на экран отдавать основному процессу просто id чиселко текстуры это id или эти id указываются в uniform шейдера он уже тебе напрямую или через multiple target renderer рисует тоест на экран или на внеэкранный текстурный буффер. В реальности может быть от вауууу круто до пилять блокировки, тормоза, очереди. Погугли, почитай, поищи готовые примеры что-бы просто понять оно или не оно. Я оооооооооооооооооооооооооочень давно это тыкал, работало, не без проблем. Ну что вспомнил сказал, может у кого память свежнее подскажут более точечно.

anonymous
()
Ответ на: комментарий от Siborgium

но поддержка в драйверах сильно варьируется.

Как бы да, я выше (или уже ниже?) написал что если коротко «как повезёт»

anonymous
()
Ответ на: комментарий от ox55ff

А как шейдер получит доступ к данным, которые он будет писать в текстуры?

Через uniform передашь в шейдер в шейдере задаёшь 1 бальшую тестуру где по смещению UV через gl_FragData будешь писать результат, стоп……..

У меня атласы

Тоесть у тебя 4 кадра на текстуру можно, ну норм если влезают так и надо.

В таком случае мимо, либо ты пишешь в несколько в разных потоках, но опять же ты же понимаешь что конвеер гпу один хрен это в очередь поставит. Либо готовь кадры в памяти обычной молотя цпу и когда готово отдавай их шейдеру который уже выведет. Если ты пишешь в 1 текстуру несолько ихображений просто смещая UV то ты никак не сможешь в нескольких потоках писать в 1 текстуру.

Вынужден отклонится, гугли и ищи «как в нескольких потоках писать в текстуры и потом эти результаты объединить для вывода»

У тебя и так и так надо будет всё время гонять данные RAM -> GPURAM и

anonymous
()

Вроде раньше рекомендовали использовать Pixel Buffer Objects для обновления текстур. Для каждого видео заводишь по одной текстуре и один-два PBO. В рисующем потоке привязываешь PBO к текстуре, мапишь, полученный указатель отдаёшь декодирующему потоку, что он в писал сразу в буфер. Два PBO используют, чтобы переключаться между ними, давая драйверу использовать DMA.

i-rinat ★★★★★
()
Ответ на: комментарий от ox55ff

Может просто держать этот атлас в оперативной памяти, каждый из потоков записывает в свой отведенный кусок памяти в текстуре, возможно можно без блокировок и раз в n кадров блокировать этот атлас мьютексом и отправлять на GPU.

Int64 ★★★
()

Тут анон много сказал, но вот у меня 2 видеопотока в 1 окне https://www.youtube.com/watch?v=vEN_W6g_CD8& я просто 2 render targeta делаю цпу молотит картинку я её каждый кадр заливаю в текстуру задаю viewport и вывожу и так 2 раза за кадр. Всё тупо и просто их даже можно двигать , ну видео эти. И у меня никакой многопоточности, хотя подготовку кадров можно бы и вынести, но лень было а у тебя сколько вилеопотоков?

LINUX-ORG-RU

anonymous
()
Ответ на: комментарий от i-rinat

Тоже слышал про PBO, но боюсь, что не хватит памяти. Как я помню, там память под PBO не простая, а в зоне, где может работать DMA. У меня до 100 каналов. Каждый примерно до 2 мегапикселя. Надо будет посмотреть

ox55ff ★★★★★
() автор топика

Использовать opengl многопоточно, переключая активный контекст, хоть и возможно, но с точки зрения производительности ничего не дает в большинстве случаев, одни проблемы. Оптимизировать в данном случае стоит только подготовку кадров и заполнение очереди. А opengl дергать из одного рендер потока. Что в нем можно оптимизировать - так это: перейти на создание текстур через pbo, и вообще поравьювить свой менеджмент текстур - хорошо иметь текстурный пул для обновления данных в неактуальных текстурах, а не убивать и создавать текстуры заново.

CatsCantFly
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.