在Linux中使用OpenGL而无需X.org

43

我希望在Linux系统中不依赖X环境打开OpenGL上下文。是否有任何方法可以实现?

我知道对于集成了英特尔显卡的硬件来说,这是可能的,但大多数人的系统都配备了Nvidia显卡。我希望能找到适用于Nvidia显卡的解决方案。

如果没有其他方法,只能通过集成了英特尔硬件来实现,那么我想知道如何使用这些硬件进行操作。

X11协议本身过于庞大和复杂。它提供的鼠标/键盘/触控板输入复用功能对于现代程序来说太过简单了。我认为这是阻碍Linux桌面系统改进的最大障碍,因此我正在寻找替代方法。


2
@nos:libSDL采用与X11相同的限制。例如:Wacom平板电脑被限制在显示分辨率内,而该平板电脑本身的分辨率比显示器高十倍!我听说大dpi鼠标也有类似的问题。 - Cheery
2
@Cheery:关于你的问题,看这里:http://superuser.com/questions/115330/on-linux-can-i-get-3d-acceleration-with-a-nvidia-card-w-o-x。如果你想尝试制作桌面环境,我建议使用SDL制作游戏GUI系统。它会非常接近“真实的东西”,而且不会有很多麻烦。请记住,即使你制作了一个不错的X替代品,也需要几年时间才能被采用。 - SigTerm
2
透明网络是大多数人永远不需要的一个领域。如果你真的需要这样的东西,你会使用Plan9。 - Cheery
1
在我看来,如果您只是分开透明网络和帧缓存处理的关注点,那会更好。如果您确实需要一个网络透明的GUI,请在该帧缓存上创建自己的GUI!这样,您可以同时使用多个。 - Cheery
2
Qt 4有一个名为“QWS”的虚拟帧缓冲区,它不需要X11并支持OpenGL。我想值得一试。 - bparker
显示剩余13条评论
6个回答

43

更新(2017年9月17日):

NVIDIA最近发布了一篇文章详细介绍了如何在无头系统上使用OpenGL,这与问题描述的用例非常相似。

总结如下:

  • 链接到libOpenGL.solibEGL.so,而不是libGL.so。(因此您的链接器选项应为-lOpenGL -lEGL
  • 调用eglGetDisplay,然后调用eglInitialize来初始化EGL。
  • 调用具有配置属性EGL_SURFACE_TYPEeglChooseConfig,然后跟随EGL_PBUFFER_BIT
  • 调用eglCreatePbufferSurface,然后调用eglBindApi(EGL_OPENGL_API);,然后调用eglCreateContexteglMakeCurrent

从那时起,像往常一样进行OpenGL渲染,您可以在任何地方复制您的像素缓冲区表面。NVIDIA的这篇补充文章包括一个基本示例和多个GPU的示例。根据应用程序需求,PBuffer表面也可以被替换为窗口表面或像素图表面。

我很遗憾之前在我的编辑中没有做更多的研究,但是好吧。更好的答案就是更好的答案。


自2010年回答以来,Linux图形空间发生了许多重大变化。因此,更新的答案如下:

今天,nouveau和其他DRI驱动程序已经成熟到使OpenGL软件在一般情况下稳定并且性能合理。随着在Mesa中引入EGL API,现在甚至可以在Linux桌面上编写OpenGL和OpenGL ES应用程序。

您可以编写应用程序,以针对EGL进行目标设置,而无需存在窗口管理器或复合器。要做到这一点,您将调用eglGetDisplayeglInitialize,最终调用eglCreateContexteglMakeCurrent,而不是通常使用GLX调用来完成相同的操作。
我不知道在没有显示服务器的情况下如何工作的具体代码路径,但EGL接受X11显示和Wayland显示,并且我知道它可以在没有显示服务器的情况下运行。您可以创建GL ES 1.1、ES 2.0、ES 3.0 (如果您有Mesa 9.1或更高版本)和OpenGL 3.1 (Mesa 9.0或更高版本)上下文。截至2013年9月,Mesa尚未实现OpenGL 3.2核心。
值得注意的是,在Raspberry Pi和Android上,默认支持EGL和GL ES 2.0(Android < 3.0时为1.1)。在树莓派上,我认为Wayland尚未起作用(截至2013年9月),但您可以使用附带的二进制驱动程序获取不带显示服务器的EGL。如果您感兴趣,您的EGL代码也应该是可移植的(需要最少修改)到iOS。
以下是过时的、先前接受的帖子:
我想在Linux中打开一个不带X的OpenGL上下文。有没有任何办法可以做到这一点?
我相信Mesa提供了framebuffer目标。如果它提供了任何硬件加速,那只能是使用已经适应支持这种用途的开源驱动程序的硬件。
Gallium3D也不成熟,据我所知,它甚至没有在路线图上支持此功能。
我想找到与Nvidia卡兼容的解决方案。
并没有这样的解决方案。没得商量。

NVIDIA只提供X驱动程序,而Nouveau项目仍不成熟,并且不支持您正在寻找的那种用途,因为它们目前仅专注于X11驱动程序。


1
我认为这不是一个好的答案,因为它已经不再正确,并且没有提供解决方案。 Weston/wayland 可以与 nouveau 一起使用,因此 opengl 可以在 NVIDIA 显卡上无需 X 运行。而对于正在努力发展的项目的评论往往也会很快过时。 - elmarco
1
@elmarco 我会在今天稍后有时间时修改我的回答。你是对的;我的回答相当过时。但公平起见,我是在2010年中期发布的,那时Wayland还只是一个原型。 - greyfade

8

4

4

1
您可以查看Android如何解决这些问题。请参见Android-x86项目。
Android使用具有EGL和OpenGLES的Mesa。 Android有自己简单的Gralloc组件用于模式设置和图形分配。在此基础上,他们还拥有SurfaceFlinger组件,它是一个合成引擎,使用OpenGLES进行加速。
看不出为什么不能以类似的方式使用这些组件,甚至重用Android粘合剂代码。

0
你可以使用SRM库(Simple Rendering Manager),这是一个专为在KMS/DRM环境中使用OpenGL ES 2.0进行渲染而设计的C库(无需Xorg或Wayland)。它的一个重要优势是为您简化了所有DRM/KMS配置。此外,它还方便了在多GPU设置中从单个分配中共享OpenGL纹理。
它已在Intel GPU(i915驱动程序)、Nvidia GPU(nouveau和nvidia-drm驱动程序)以及Mali GPU(Lima驱动程序)上进行了测试。
以下是一个基本示例,它仅使用glClear()函数更改所有连接屏幕的屏幕颜色:
#include <SRMCore.h>
#include <SRMDevice.h>
#include <SRMConnector.h>
#include <SRMConnectorMode.h>
#include <SRMListener.h>

#include <SRMList.h>
#include <SRMLog.h>

#include <GLES2/gl2.h>

#include <math.h>
#include <fcntl.h>
#include <unistd.h>

float color = 0.f;

/* Opens a DRM device */
static int openRestricted(const char *path, int flags, void *userData)
{
    SRM_UNUSED(userData);

    // Here something like libseat could be used instead
    return open(path, flags);
}

/* Closes a DRM device */
static void closeRestricted(int fd, void *userData)
{
    SRM_UNUSED(userData);
    close(fd);
}

static SRMInterface srmInterface =
{
    .openRestricted = &openRestricted,
    .closeRestricted = &closeRestricted
};

static void initializeGL(SRMConnector *connector, void *userData)
{
    SRM_UNUSED(userData);

    /* You must not do any drawing here as it won't make it to
     * the screen. */

    SRMConnectorMode *mode = srmConnectorGetCurrentMode(connector);

    glViewport(0, 
               0, 
               srmConnectorModeGetWidth(mode), 
               srmConnectorModeGetHeight(mode));

    // Schedule a repaint (this eventually calls paintGL() later, not directly)
    srmConnectorRepaint(connector);
}

static void paintGL(SRMConnector *connector, void *userData)
{
    SRM_UNUSED(userData);

    glClearColor((sinf(color) + 1.f) / 2.f,
                 (sinf(color * 0.5f) + 1.f) / 2.f,
                 (sinf(color * 0.25f) + 1.f) / 2.f,
                 1.f);

    color += 0.01f;

    if (color > M_PI*4.f)
        color = 0.f;

    glClear(GL_COLOR_BUFFER_BIT);
    srmConnectorRepaint(connector);
}

static void resizeGL(SRMConnector *connector, void *userData)
{
    /* You must not do any drawing here as it won't make it to
     * the screen.
     * This is called when the connector changes its current mode,
     * set with srmConnectorSetMode() */

    // Reuse initializeGL() as it only sets the viewport
    initializeGL(connector, userData);
}

static void pageFlipped(SRMConnector *connector, void *userData)
{
    SRM_UNUSED(connector);
    SRM_UNUSED(userData);

    /* You must not do any drawing here as it won't make it to
     * the screen.
     * This is called when the last rendered frame is now being
     * displayed on screen.
     * Google v-sync for more info. */
}

static void uninitializeGL(SRMConnector *connector, void *userData)
{
    SRM_UNUSED(connector);
    SRM_UNUSED(userData);

    /* You must not do any drawing here as it won't make it to
     * the screen.
     * Here you should free any resource created on initializeGL()
     * like shaders, programs, textures, etc. */
}

static SRMConnectorInterface connectorInterface =
{
    .initializeGL = &initializeGL,
    .paintGL = &paintGL,
    .resizeGL = &resizeGL,
    .pageFlipped = &pageFlipped,
    .uninitializeGL = &uninitializeGL
};

static void connectorPluggedEventHandler(SRMListener *listener, SRMConnector *connector)
{
    SRM_UNUSED(listener);

    /* This is called when a new connector is avaliable (E.g. Plugging an HDMI display). */

    /* Got a new connector, let's render on it */
    if (!srmConnectorInitialize(connector, &connectorInterface, NULL))
        SRMError("[srm-basic] Failed to initialize connector %s.",
                 srmConnectorGetModel(connector));
}

static void connectorUnpluggedEventHandler(SRMListener *listener, SRMConnector *connector)
{
    SRM_UNUSED(listener);
    SRM_UNUSED(connector);

    /* This is called when a connector is no longer avaliable (E.g. Unplugging an HDMI display). */

    /* The connnector is automatically uninitialized after this event (if initialized)
     * so calling srmConnectorUninitialize() is a no-op. */
}

int main(void)
{
    SRMCore *core = srmCoreCreate(&srmInterface, NULL);

    if (!core)
    {
        SRMFatal("[srm-basic] Failed to initialize SRM core.");
        return 1;
    }

    // Subscribe to Udev events
    SRMListener *connectorPluggedEventListener = srmCoreAddConnectorPluggedEventListener(core, &connectorPluggedEventHandler, NULL);
    SRMListener *connectorUnpluggedEventListener = srmCoreAddConnectorUnpluggedEventListener(core, &connectorUnpluggedEventHandler, NULL);

    // Find and initialize avaliable connectors

    // Loop each GPU (device)
    SRMListForeach (deviceIt, srmCoreGetDevices(core))
    {
        SRMDevice *device = srmListItemGetData(deviceIt);

        // Loop each GPU connector (screen)
        SRMListForeach (connectorIt, srmDeviceGetConnectors(device))
        {
            SRMConnector *connector = srmListItemGetData(connectorIt);

            if (srmConnectorIsConnected(connector))
            {
                if (!srmConnectorInitialize(connector, &connectorInterface, NULL))
                    SRMError("[srm-basic] Failed to initialize connector %s.",
                             srmConnectorGetModel(connector));
            }
        }
    }

    while (1)
    {
        /* Udev monitor poll DRM devices/connectors hotplugging events (-1 disables timeout).
         * To get a pollable FD use srmCoreGetMonitorFD() */

        if (srmCoreProccessMonitor(core, -1) < 0)
            break;
    }

    /* Unsubscribe to DRM events
     *
     * These listeners are automatically destroyed when calling srmCoreDestroy()
     * so there is no need to free them manually.
     * This is here just to show how to unsubscribe to events on the fly. */

    srmListenerDestroy(connectorPluggedEventListener);
    srmListenerDestroy(connectorUnpluggedEventListener);

    // Finish SRM
    srmCoreDestroy(core);

    return 0;
}
  1. 首先,创建一个SRMCore实例。
  2. 然后,遍历每个SRMDevice(GPU)。每个GPU都有一个SRMConnectors(显示器)列表。
  3. 使用srmConnectorInitialize()初始化所需的连接器。
  4. 库为每个连接器创建一个渲染线程,并调用典型的OpenGL事件,如initializeGL()paintGL()resizeGL()等。
  5. 要在连接器中安排新的重绘,请使用srmConnectorRepaint()
  6. 如果需要创建OpenGL纹理,请参考SRMBuffer文档
希望你能发现这个图书馆对你的特定使用场景有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接