OpenGL ES 中的跨平台渲染器

发布于 2024-12-27 17:24:37 字数 173 浏览 1 评论 0原文

我正在编写一个跨平台渲染器。我想在 Windows、Linux、Android、iOS 上使用它。

您认为避免绝对抽象并直接在 OpenGL ES 2.0 中编写是一个好主意吗?

据我所知,我应该能够在 PC 上针对标准 OpenGL 进行编译,只需对处理上下文和与窗口系统的连接的代码进行少量更改。

I'm writing an cross-platform renderer. I want to use it on Windows, Linux, Android, iOS.

Do you think that it is a good idea to avoid absolute abstraction and write it directly in OpenGL ES 2.0?

As far as I know I should be able to compile it on PC against standard OpenGL, with only a small changes in code that handles context and connection to windowing system.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

无法言说的痛 2025-01-03 17:24:37

您认为避免绝对抽象并直接在 OpenGL ES 2.0 中编写是一个好主意吗?

您的主要困难将是处理 ES 2.0 规范中实际上与 OpenGL 2.1 不同的部分。

例如,您无法通过桌面 GLSL 1.20 编译器推送 ES 2.0 着色器。在 ES 2.0 中,您可以使用诸如指定精度之类的东西;这些是 GLSL 1.20 中的非法构造。

然而,您可以围绕它们#define,但这需要一些手动干预。您必须将#ifdef插入到着色器源文件中。您可以使用一些着色器编译技巧来使这变得更容易。

事实上,由于 GL ES 使用一组完全不同的扩展(尽管有些是桌面 GL 扩展的镜像和子集),您可能想要这样做。

每个 GLSL 着色器(桌面或 ES)都需要有一个“序言”。着色器中的第一个非注释必须是 #version 声明。幸运的是,桌面 GL 2.1 和 GL ES 2.0 之间的版本是相同的:#version 1.20。问题是接下来发生的事情:#extension 列表(如果有的话)。这可以实现着色器所需的扩展。

由于 GL ES 使用与桌面 GL 不同的扩展,因此您需要更改此扩展列表。由于可能性很大,您将需要比桌面 GL 2.1 扩展更多的 GLSL ES 扩展,因此这些列表将不仅仅是 1:1 映射,而是完全不同的列表。

我的建议是利用为 GLSL 着色器提供多个字符串的功能。也就是说,您的实际着色器文件没有有任何序言内容。它们具有实际的定义和功能。着色器的主体。

在 GL ES 上运行时,您有一个全局前导码,您可以将其附加到着色器的开头。您将在桌面 GL 中看到不同的全局序言。代码如下所示:

GLuint shader = glCreateShader(/*shader type*/);
const char *shaderList[2];
shaderList[0] = GetGlobalPreambleString(); //Gets preamble for the right platform
shaderList[1] = LoadShaderFile(); //Get the actual shader file
glShaderSource(shader, 2, shaderList, NULL);

前导码还可以包含特定于平台的#define。当然是用户定义的。这样,您就可以#ifdef针对不同平台进行编码。

两者之间还存在其他差异。例如,虽然有效的 ES 2.0 纹理上传函数调用在桌面 GL 2.1 中可以正常工作,但它们不一定是最佳的。像所有移动系统一样,在大端机器上可以正常上传的东西将需要小端桌面机器中的驱动程序进行一些调整。因此,您可能希望有一种方法可以在 GL ES 和桌面 GL 上指定不同的像素传输参数。

此外,ES 2.0 和桌面 GL 2.1 中还有不同的扩展集可供您利用。虽然其中许多尝试相互镜像(OES_framebuffer_object 是 EXT_framebuffer_object 的子集),但您可能会遇到类似的“不完全是子集”问题,如上所述。

Do you think that it is a good idea to avoid absolute abstraction and write it directly in OpenGL ES 2.0?

Your principle difficulties with this will be dealing with those parts of the ES 2.0 specification which are not actually the same as OpenGL 2.1.

For example, you just can't shove ES 2.0 shaders through a desktop GLSL 1.20 compiler. In ES 2.0, you use things like specifying precision; those are illegal constructs in GLSL 1.20.

You can however #define around them, but this requires a bit of manual intervention. You will have to insert a #ifdef into the shader source file. There are shader compilation tricks you can do to make this a bit easier.

Indeed, because GL ES uses a completely different set of extensions (though some are mirrors and subsets of desktop GL extensions), you may want to do this.

Every GLSL shader (desktop or ES) needs to have a "preamble". The first non-comment thing in a shader needs to be a #version declaration. Fortunately for you, the version is the same between desktop GL 2.1 and GL ES 2.0: #version 1.20. The problem is what comes next: the #extension list (if any). This enables extensions needed by the shader.

Since GL ES uses different extensions from desktop GL, you will need to change this extension list. And since odds are good you're going to need more GLSL ES extensions than desktop GL 2.1 extensions, these lists won't just be 1:1 mapping, but completely different lists.

My suggestion is to employ the ability to give GLSL shaders multiple strings. That is, your actual shader files do not have any preamble stuff. They only have the actual definitions and functions. The main body of the shader.

When running on GL ES, you have a global preamble that you will affix to the beginning of the shader. You will have a different global preamble in desktop GL. The code would look like this:

GLuint shader = glCreateShader(/*shader type*/);
const char *shaderList[2];
shaderList[0] = GetGlobalPreambleString(); //Gets preamble for the right platform
shaderList[1] = LoadShaderFile(); //Get the actual shader file
glShaderSource(shader, 2, shaderList, NULL);

The preamble can also include a platform-specific #define. User-defined of course. That way, you can #ifdef code for different platforms.

There are other differences between the two. For example, while valid ES 2.0 texture uploading function calls will work fine in desktop GL 2.1, they will not necessarily be optimal. Things that would upload fine on big-endian machines like all mobile systems will require some bit twiddling from the driver in little-endian desktop machines. So you may want to have a way to specify different pixel transfer parameters on GL ES and desktop GL.

Also, there are different sets of extensions in ES 2.0 and desktop GL 2.1 that you will want to take advantage of. While many of them try to mirror one another (OES_framebuffer_object is a subset of EXT_framebuffer_object), you may run afoul of similar "not quite a subset" issues like those mentioned above.

北笙凉宸 2025-01-03 17:24:37

根据我的经验,满足此类需求的最佳方法是以纯 C 风格开发引擎,而不需要任何额外的层。

我是 PATRIA 3D 引擎的主要开发人员,该引擎基于您刚才提到的可移植性方面的基本原则,我们通过在基本标准库上开发该工具来实现这一目标。

然后在不同平台上编译代码的工作量非常小。

移植整个解决方案的实际工作量可以根据您想要嵌入到引擎中的组件来计算。

例如:


标准 C:

引擎 3D

游戏逻辑

游戏 AI

物理


+


窗口界面(GLUT、EGL 等) - 取决于平台,无论如何,桌面设备可能是 GLUT,移动设备可能是 EGL。

人机界面 - 取决于移植,Android 为 Java,IOS 为 OC,无论什么版本桌面

声音管理器 - 取决于移植

市场服务 - 取决于移植


这样,您可以无缝地重复使用 95% 的努力方式。

我们的引擎采用了这个解决方案,到目前为止,它确实值得初期投资。

In my humble experience, the best approach for this kind of requirements is to develop your engine in a pure C flavor, with no additional layers on it.

I am the main developer of PATRIA 3D engine which is based on the basic principle you just mentioned in terms of portability and we have achieved this by just developing the tool on basic standard libraries.

The effort to compile your code then on the different platforms is very minimal.

The actual effort to port the entire solution can be calculated depending on the components you want to embed in your engine.

For example:


Standard C:

Engine 3D

Game Logic

Game AI

Physics


+


Window interface (GLUT, EGL etc) - Depends on the platform, anyway could be GLUT for desktop and EGL for mobile devices.

Human Interface - depends on the porting, Java for Android, OC for IOS, whatever version desktop

Sound manager - depends on the porting

Market services - depends on the porting


In this way, you can re-use 95% of your efforts in a seamless way.

we have adopted this solution for our engine and so far it is really worth the initial investment.

乖乖 2025-01-03 17:24:37

以下是我为运行我的商业映射和路由库的各种平台实现 OpenGL ES 2.0 支持的经验结果。

渲染类被设计为在单独的线程中运行。它具有对包含地图数据和当前视图信息的对象的引用,并使用互斥体来避免在绘制时读取该信息时发生冲突。它在图形内存中维护 OpenGL ES 矢量数据的缓存。

所有渲染逻辑均用 C++ 编写,并在以下所有平台上使用。

Windows (MFC)

使用 ANGLE 库:链接到 libEGL.lib 和 libGLESv2.lib,并确保可执行文件有权访问 DLL libEGL.dll 和 libGLESv2.dll。 C++ 代码创建一个线程,以适当的速率(例如,每秒 25 次)重绘图形。

Windows(.NET 和 WPF)

使用 C++/CLI 包装器创建 EGL 上下文并调用直接在 MFC 实现中使用的 C++ 呈现代码。 C++ 代码创建一个线程,以适当的速率(例如,每秒 25 次)重绘图形。

Windows (UWP)

在 UWP 应用代码中创建 EGL 上下文,并通过 C++/CXX 包装器调用 C++ 渲染代码。您将需要使用 SwapChainPanel 并创建在不同线程中运行的自己的渲染循环。请参阅 GLUWP 项目获取示例代码。

Windows、Linux 和 Mac OS 上的 Qt

使用 QOpenGLWidget 作为您的窗口。使用 Qt OpenGL ES 包装器创建 EGL 上下文,然后在 PaintGL() 函数中调用 C++ 渲染代码。

Android

创建一个实现 android.opengl.GLSurfaceView.Renderer 的渲染器类。为 C++ 渲染对象创建 JNI 包装器。在 onSurfaceCreated() 函数中创建 C++ 渲染对象。在 onDrawFrame() 函数中调用 C++ 渲染对象的绘图函数。您需要为渲染器类导入以下库:

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;

创建一个从 GLSurfaceView 派生的视图类。在视图类的构造函数中,首先设置 EGL 配置:

setEGLContextClientVersion(2); // use OpenGL ES 2.0
setEGLConfigChooser(8,8,8,8,24,0);

然后创建渲染器类的实例并调用 setRenderer 来安装它。

iOS

使用 METALAngle 库,而不是 GLKit,Apple 已弃用该库并将最终不再支持。

创建一个 Objective C++ 渲染器类来调用您的 C++ OpenGL ES 绘图逻辑。

创建一个从 MGLKView 派生的视图类。在视图类的 drawRect() 函数中,创建一个渲染器对象(如果尚不存在),然后调用其绘图函数。也就是说,您的 drawRect 函数应该类似于:

-(void)drawRect:(CGRect)rect
    {
    if (m_renderer == nil && m_my_other_data != nil)
        m_renderer = [[MyRenderer alloc] init:m_my_other_data];
    if (m_renderer)
        [m_renderer draw];
    }

在您的应用程序中,您需要一个视图控制器类来创建 OpenGL 上下文并对其进行设置,使用如下代码:

MGLContext* opengl_context = [[MGLContext alloc] initWithAPI:kMGLRenderingAPIOpenGLES2];
m_view = [[MyView alloc] initWithFrame:aBounds context:opengl_context];
m_view.drawableDepthFormat = MGLDrawableDepthFormat24;
self.view = m_view;
self.preferredFramesPerSecond = 30;

Linux

最简单的方法是在 Linux 上使用 Qt(见上文),但也可以使用 GLFW 框架。在应用程序类的构造函数中,调用 glfwCreateWindow 创建一个窗口并将其存储为数据成员。调用 glfwMakeContextCurrent 使 EGL 上下文成为当前上下文,然后创建一个包含渲染器类实例的数据成员;像这样的事情:

m_window = glfwCreateWindow(1024,1024,"My Window Title",nullptr,nullptr);
glfwMakeContextCurrent(m_window);
m_renderer = std::make_unique<CMyRenderer>();

将 Draw 函数添加到您的应用程序类中:

bool MapWindow::Draw()
    {
    if (glfwWindowShouldClose(m_window))
        return false;
    m_renderer->Draw();
    /* Swap front and back buffers */
    glfwSwapBuffers(m_window);
    return true;
    }

您的 main() 函数将是:

int main(void)
    {
    /* Initialize the library */
    if (!glfwInit())
        return -1;

    // Create the app.
    MyApp app;

    /* Draw continuously until the user closes the window */
    while (app.Draw())
        {

        /* Poll for and process events */
        glfwPollEvents();
        }

    glfwTerminate();
    return 0;
    }

着色器不兼容性

各种 OpenGL ES 2.0 实现所接受的着色器语言中存在不兼容性。我在 CompileShader 函数中使用以下条件编译代码在 C++ 代码中克服了这些问题:

const char* preamble = "";

#if defined(_POSIX_VERSION) && !defined(ANDROID) && !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
// for Ubuntu using Qt or GLFW
preamble = "#version 100\n";
#elif defined(USING_QT) && defined(__APPLE__)
// On the Mac #version doesn't work so the precision qualifiers are suppressed.
preamble = "#define lowp\n#define mediump\n#define highp\n";
#endif

然后将 前导码 作为着色器代码的前缀。

Here are the results of my experience implementing OpenGL ES 2.0 support for various platforms on which my commercial mapping and routing library runs.

The rendering class is designed to run in a separate thread. It has a reference to the object containing the map data and the current view information, and uses mutexes to avoid conflicts when reading that information at the time of drawing. It maintains a cache of OpenGL ES vector data in graphics memory.

All the rendering logic is written in C++ and is used on all the following platforms.

Windows (MFC)

Use the ANGLE library: link to libEGL.lib and libGLESv2.lib and ensure that the executable has access to the DLLs libEGL.dll and libGLESv2.dll. The C++ code creates a thread that redraws the graphics at a suitable rate (e.g., 25 times a second).

Windows (.NET and WPF)

Use a C++/CLI wrapper to create an EGL context and to call the C++ rendering code that is used directly in the MFC implementation. The C++ code creates a thread that redraws the graphics at a suitable rate (e.g., 25 times a second).

Windows (UWP)

Create the EGL context in the UWP app code and call the C++ rendering code via the a a C++/CXX wrapper. You will need to use a SwapChainPanel and create your own render loop running in a different thread. See the GLUWP project for sample code.

Qt on Windows, Linux and Mac OS

Use a QOpenGLWidget as your windows. Use the Qt OpenGL ES wrapper to create the EGL context, then call the C++ rendering code in your paintGL() function.

Android

Create a renderer class implementing android.opengl.GLSurfaceView.Renderer. Create a JNI wrapper for the C++ rendering object. Create the C++ rendering object in your onSurfaceCreated() function. Call the C++ rendering object's drawing function in your onDrawFrame() function. You will need to import the following libraries for your renderer class:

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView.Renderer;

Create a view class derived from GLSurfaceView. In your view class's constructor first set up your EGL configuration:

setEGLContextClientVersion(2); // use OpenGL ES 2.0
setEGLConfigChooser(8,8,8,8,24,0);

then create an instance of your renderer class and call setRenderer to install it.

iOS

Use the METALAngle library, not GLKit, which Apple has deprecated and will eventually no longer support.

Create an Objective C++ renderer class to call your C++ OpenGL ES drawing logic.

Create a view class derived from MGLKView. In your view class's drawRect() function, create a renderer object if it doesn't yet exist, then call its drawing function. That is, your drawRect function should be something like:

-(void)drawRect:(CGRect)rect
    {
    if (m_renderer == nil && m_my_other_data != nil)
        m_renderer = [[MyRenderer alloc] init:m_my_other_data];
    if (m_renderer)
        [m_renderer draw];
    }

In your app you'll need a view controller class that creates the OpenGL context and sets it up, using code like this:

MGLContext* opengl_context = [[MGLContext alloc] initWithAPI:kMGLRenderingAPIOpenGLES2];
m_view = [[MyView alloc] initWithFrame:aBounds context:opengl_context];
m_view.drawableDepthFormat = MGLDrawableDepthFormat24;
self.view = m_view;
self.preferredFramesPerSecond = 30;

Linux

It is easiest to to use Qt on Linux (see above) but it's also possible to use the GLFW framework. In your app class's constructor, call glfwCreateWindow to create a window and store it as a data member. Call glfwMakeContextCurrent to make the EGL context current, then create a data member holding an instance of your renderer class; something like this:

m_window = glfwCreateWindow(1024,1024,"My Window Title",nullptr,nullptr);
glfwMakeContextCurrent(m_window);
m_renderer = std::make_unique<CMyRenderer>();

Add a Draw function to your app class:

bool MapWindow::Draw()
    {
    if (glfwWindowShouldClose(m_window))
        return false;
    m_renderer->Draw();
    /* Swap front and back buffers */
    glfwSwapBuffers(m_window);
    return true;
    }

Your main() function will then be:

int main(void)
    {
    /* Initialize the library */
    if (!glfwInit())
        return -1;

    // Create the app.
    MyApp app;

    /* Draw continuously until the user closes the window */
    while (app.Draw())
        {

        /* Poll for and process events */
        glfwPollEvents();
        }

    glfwTerminate();
    return 0;
    }

Shader incompatibilities

There are incompatibilities in the shader language accepted by the various OpenGL ES 2.0 implementations. I overcome these in the C++ code using the following conditionally compiled code in my CompileShader function:

const char* preamble = "";

#if defined(_POSIX_VERSION) && !defined(ANDROID) && !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
// for Ubuntu using Qt or GLFW
preamble = "#version 100\n";
#elif defined(USING_QT) && defined(__APPLE__)
// On the Mac #version doesn't work so the precision qualifiers are suppressed.
preamble = "#define lowp\n#define mediump\n#define highp\n";
#endif

The preamble is then prefixed to the shader code.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文