Многие из присутствующих еще не успели застать времена, когда иксы еще назывались хfree86 и потому не знают, кого благодарить за то, что у них нвидия без шмеля не работает. Рассказываю почему в линуксе все плохо с графикой там, где в винде просто берет и работает.
Когда-то видяхи были только PCI и немного ISA. ISA это вообще чтоб страдать, я помню у меня была аудиокарта ISA.... ооооо, как безбожно тормозило видео на k6-500 с этой аудяхой и как оно игралось мплеером с pciшной картой. Короче, ISA - это вообще зло которое надо забыть как страшный сон. Поэтому его забыли и в ведре все устройства растут из «шины PCI».
Но в те далекие времена видяхи точились под windows gdi(посмотрите hardware caps той же s3trio64v+) и поэтому дров под линукс для них не было. а там кстати были аппаратные шрифты, если кто не знает. поэтому в линуксе была только опция framebuffer и страдать. А чтоб совсем уж не тормозить, картинки гоняли через MIT-SHM.
Затем появилось ускорение, и работало оно на стороне сервера х86 и то - только по талонам и для своих. Плюс иксы тупо лезли своими лапами прямо в железо, да так, что для них даже вход со двора делали в ведре, и вход этот назывался DRI1.
«The legacy DRI1 drivers expose highly broken interfaces to user-space. No modern system should enable them, or you will effectively allow user-space to circumvent most of your kernel security measures. The DRI1 kernel APIs are simply broken. User-space can always use vesafb/efifb/simplefb and friends to get working graphics. Lets hide the old drivers behind CONFIG_BROKEN. In case they turn out to be still used (really?), we can easily revert this and figure out a way to move them out of sight (e.g., moving all DRI1 drivers to drivers/gpu/dri1/).»
Затем появился DRI2, а с ним пришли и гениальные в кавычках решения. Вот например кто был тот долбо* кто решил что объекты DRI надо сделать обязательно файловыми дескрипторами? Ну он хоть что-то на opengl писал? Знает, что там GLsync это void* и его в poll не сунуть? Если хочешь добавить фичу ждания, добавь функцию в struct file_operations когда символьное устройство регистрируешь. А кстати, из-за этого гениального решения каждый процесс может потырить данные из текстур другого процесса. А еще прикольное решение: авторизация в DRI2 - клиент открывает устройство, спрашивает у сервера «пароль», сервер сообщает драйверу «пароль» и передает его клиенту чтоб тот тоже ввел этот пароль. Вот скажите, в юниксе, где можно файловые дескрипторы через unix socket передавать, заранее зарядив в них пару ioctlов, это что, не рукожопие? Кстати, длина этого «пароля» - 32 бита.
Далее, у нас появилась шина AGP. А с нею в линукс пришел agp_gart и такая пакость как ttm. Великий и ужасный генератор паник и багов ttm! А всё с чего: памяти у видях было мало, метров 16-32, и кто-то умный не в кавычках придумал, что можно одноразовую память и прямо из системной брать. Для этого в AGP сделали самый настоящий iommu, только назвали почему-то GART. если вы полазаете в биосе старых мамок, наверняка увидите такую вещь как AGP aperture - вот это и есть колво мегабайт, которое AGP может пробросить в видяху.
А я напомню, что графическую подсистему пишут не скотинки, а умные гениальные люди, которые уже запилили псевдо-файловые дескрипторы в DRI2 и пароли из 32бит, зато никогда ни гуй и игорей не пилили. Поэтому какой-то мудак придумал, что TTM эти текстуры должен свопать! То есть у вас обычно всего лишь одно 3д-приложение активно чтоб по-серьёзному. это либо игорь, либо композитор. все остальные живут набегами на видеокарту и держат в ней максимум кэш из буферов. Его не надо отсвопывать, его надо изначально положить в памяти и никуда не двигать, а лишь туда перенацеливать GART. Типовая задача отрисовки состоит в том, чтоб положить в буфер все свои пиксели, иконки(вы подумайте как нибудь, что стоит отрисовать маненькие такие иконки), сказать где что лежит и отправить в полёт. Одна операция с точки зрения драйвера - вот буфер с кучей команд, возьми, поработай и положи обратно. Но мы же не ищем легких путей! жизнь должна быть болью, причем желательно в жопе, причем желательно у всех пользователей линукса. Поэтому сделали сложно и глючно.
Далее, видяхи стали комбинироваться. Пользователи нвидии, вы наверно слышали про «оптимус»? Болит, да? А хотите я вам скажу, что всё это время вы могли не страдать если бы не долбо*-разработчики? Бесплатные курсы 3д-графики на ЛОРе!
В OpenGL есть такое понятие как framebuffer. Немного погрубже под ним, есть такое понятие как swapchain, обычно вы его видите как double-buffer но может быть и тройной(он лучше, т.к. не долбит синхронизацией) и даже больше буферов(бывает нужно если видяха сильно запаздывает при сохранении фреймрейта). «Нулевой» фреймбуфер собственно и подключен к этому свопчейну и вы именно в этот свопчейн запихиваете картинку командой чототамSwapBuffers. Помимо него, вы можете накастовать своих фреймбуферов командов glGenFramebuffers, тоже красивых и модных но без фреймбуфера. Чтоб брать картинки оттуда вам надо симулировать свопчейн своими руками, а именно:
0) в самом начале создать свой swapchain c 2-3 текстурами типа RGBA. одну из них приаттачить к фреймбуферу. ну и depthbuffer с шаблоном, по требованию. кроме того надо сделать две очереди:
typedef struct {
GLsync* mRenderComplete;
GLuint mOutputTexture;
GLuint mPixelBuffer;
GLsync* mCopyPixelsComplete;
int mWidth, mHeight;
Image * mPixels;
} SwapItem;
SwapItem swapitems[N];
typedef std::deque<SwapItem*> SwapChain;
SwapChain in; //уже ненужные кадры
SwapChain out;//кадры на отправку
само окно создаете чисто 2Дшное.
1) в момент вызова kokokoSwapBuffer, создать объект типа GLsync, отцепить от фреймбуфера текущую текстуру GL_COLOR_ATTACHMENT0 и вдвоем положить их в очередь. GLsync кладется в mRenderComplete, текстура все время лежит в mOutputTexture
2) проверить, что в очереди in есть новые текстуры. если нет, ждать в первом в очереди элементе
2а)если !null SwapItem->mRenderComplete должен отсигналиться
2б)если !null SwapItem->mCopyPixelsComplete должен отсигналиться
2в) удалить все ненужные GLsync которые вышеописаны
3) зайти в каждый элемент out, проверить SwapItem->mRenderComplete если !null.
3а) если он отсигналился, mPixelBuffer это pixel buffer, который надо замапить в режиме GL_MAP_PERSISTENT|GL_MAP_UNSYNCHRONIZED и адрес положить в mPixels. Если размер окна изменился, этот буфер надо пересоздать и перемапить.
3б) запускате glPackPixels в этот буфер. ставите mCopyPixelsComplete.
3в) шаг 4 пропускаете для всех таких элементов
4) зайти в первый элемент out, проверить SwapItem->mCopyPixelsComplete если !null.
4а) если он отсигналился, mPixels это и есть ваша картинка. рисуете её средствами обычного 2D.
4б) Как только она вам не нужна, и у вас следующий элемент в out тоже отсигналился, вы просто перекладываете SwapItem из out в in.
5) PROFIT!!!!!!
И мое решение будет работать под SDL2, в Qt, Gtk, и так далее. Ему просто будет похеру. Потому что именно так и работает оптимус в винде. А то что он у вас в линуксе работает через боль - это потому, что графон вам пилят мудаки безрукие. А поскольку они умные а я нет - поцелуйте их в жёппу за то, что нулевой фреймбуфер они гвоздями прибили к DRI2-шному свопчейну и вышеописаный фокус провернуть не удастся именно из-за этого.
Легко заявлять «фак ю нвидия», когда на стороне нвидии выступают бессловесные разработчики, которые (наверно как и в АМД) пилят дрова для линукса в режиме партизан, в то время как куроводство требует сосредоточиться на винде. Которые не могут ответить линуксу, что он сам должен быть хоть немного в ответе за свой курятник и факи свои должен был отнести в dri-devel местным долбозверям, в первую очередь дятлам что из интела.
Далее, это еще не всё. Продолжаем уроки OpenGL на ЛОРе. Самая главная фишка OpenGL4.4+ - это мапание буферов с GL_MAP_PERSISTENT|GL_MAP_UNSYNCHRONIZED, когда ты организуешь циклический буфер достаточно большого размер. Или несколько их. В один буфер кладешь новые данные, в другой команды для indirect draw, в третий - обновления для текстур и так засылаешь на GPU. Буферы эти статичны в процессе всей игры или жизни приложения, ну или почти статичны(возможно их надо будет время от времени уширять). Так вот, в плане драйвера весь этот процесс можно уместить в один общий для всех видях драйвер который стукает в серверный процесс, где сидят уже драйвера видях и не оттормаживая приложение и не засирая кэши отрисоывывают всё-всё-всё и сигналят GLsyncи, которые можно сделать через futexи. И это будет быстро, волшебно и безопасно! Хоть сколько видях напихай - все будут работать и дружить друг с другом. Более того, драйвер может сидеть вообще через UIO и ядро не трогать.
Но вместо этого вам приходится наблюдать цирк с libGL от разных производителей и пляски с бубном. Кому надо сказать за это спасибо? Конечно же мне, потому что я дурак, а те, кто этот цирк с графоном в линуксе соорудил они умные.