【问题标题】:Getting OpenGL ES Transform Feedback data back to the host using Emscripten使用 Emscripten 将 OpenGL ES 变换反馈数据返回到主机
【发布时间】:2020-05-28 23:43:49
【问题描述】:

使用 GLFW,下面的 C++ OpenGL ES 代码计算五个数字的平方根并将它们输出到命令行。该代码使用了变换反馈。当我在 Ubuntu 17.10 上编译时,使用以下命令,我得到了我期望的结果:

$ g++ tf.cpp -lGL -lglfw

但是,如果我使用 Emscripten,则会引发异常,这表明 仅当访问权限为MAP_WRITE|INVALIDATE_BUFFER 时才支持glMapBufferRange。我确实想读而不是写,所以也许我不应该使用glMapBufferRange,但我可以用什么来代替?我在 Firefox 和 Chromium 上都试过了。我用 Emscripten 编译的命令是:

$ em++ -std=c++11 tf.cpp -s USE_GLFW=3 -s USE_WEBGL2=1 -s FULL_ES3=1 -o a.out.html

代码如下:

#include <iostream>

#define GLFW_INCLUDE_ES3
#include <GLFW/glfw3.h>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

static const GLuint WIDTH  = 800;
static const GLuint HEIGHT = 600;
static const GLchar *vertex_shader_src =
"#version 300 es\n"
"precision mediump float;\n"
"in  float inValue;\n"
"out float outValue;\n"
"void main() {\n"
"  outValue = sqrt(inValue);\n"
"}\n";

// Emscripten complains if there's no fragment shader
static const GLchar *fragment_shader_src =
"#version 300 es\n"
"precision mediump float;\n"
"out vec4 colour;\n"
"void main() {\n"
"  colour = vec4(1.0, 1.0, 0.0, 1.0);\n"
"}\n";

static const GLfloat vertices[] = {
   0.0f,  0.5f, 0.0f,
   0.5f, -0.5f, 0.0f,
  -0.5f, -0.5f, 0.0f,
};

GLint get_shader_prog(const char *vert_src, const char *frag_src = "")
{
  enum Consts { INFOLOG_LEN = 512 };
  GLchar infoLog[INFOLOG_LEN];
  GLint fragment_shader, vertex_shader, shader_program, success;
  shader_program = glCreateProgram();

  auto mk_shader = [&](GLint &shader, const GLchar **str, GLenum shader_type) {
    shader = glCreateShader(shader_type);
    glShaderSource(shader, 1, str, NULL);
    glCompileShader(shader);
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success) {
      glGetShaderInfoLog(shader, INFOLOG_LEN, NULL, infoLog);
      std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << '\n';
    }
    glAttachShader(shader_program, shader);
  };

  mk_shader(vertex_shader,   &vert_src, GL_VERTEX_SHADER);
  mk_shader(fragment_shader, &frag_src, GL_FRAGMENT_SHADER);

  const GLchar* feedbackVaryings[] = { "outValue" };
  glTransformFeedbackVaryings(shader_program, 1, feedbackVaryings,
                              GL_INTERLEAVED_ATTRIBS);

  glLinkProgram(shader_program);
  glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
  if (!success) {
    glGetProgramInfoLog(shader_program, INFOLOG_LEN, NULL, infoLog);
    std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << '\n';
  }
  glUseProgram(shader_program);

  glDeleteShader(vertex_shader);
  glDeleteShader(fragment_shader);
  return shader_program;
}

int main(int argc, char *argv[])
{
  glfwInit();
  glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  GLFWwindow *window = glfwCreateWindow(WIDTH, HEIGHT, __FILE__, NULL, NULL);
  glfwMakeContextCurrent(window);

  GLuint shader_prog = get_shader_prog(vertex_shader_src, fragment_shader_src);
  GLint inputAttrib = glGetAttribLocation(shader_prog, "inValue");

  glViewport(0, 0, WIDTH, HEIGHT);

  GLuint vao;
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);

  GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };

  GLuint vbo;
  glGenBuffers(1, &vbo);
  glBindBuffer(GL_ARRAY_BUFFER, vbo);
  glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);

  glVertexAttribPointer(inputAttrib, 1, GL_FLOAT, GL_FALSE, 0, 0);
  glEnableVertexAttribArray(inputAttrib);

  GLuint tbo;
  glGenBuffers(1, &tbo);
  glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, tbo);
  glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, sizeof(data),
               nullptr, GL_STATIC_READ);
  glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, tbo);
  glEnable(GL_RASTERIZER_DISCARD);

  glBeginTransformFeedback(GL_POINTS);
  glDrawArrays(GL_POINTS, 0, 5);
  glEndTransformFeedback();

  glDisable(GL_RASTERIZER_DISCARD);
  glFlush();

  GLfloat feedback[5]{1,2,3,4,5};
  void *void_buf = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER,0,
                                    sizeof(feedback), GL_MAP_READ_BIT);
  GLfloat *buf = static_cast<GLfloat *>(void_buf);
  for (int i = 0; i < 5; i++)
    feedback[i] = buf[i];
  glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);

  for (int i = 0; i < 5; i++)
    std::cout << feedback[i] << ' ';
  std::cout << std::endl;

  glDeleteBuffers(1, &vbo);
  glDeleteBuffers(1, &tbo);
  glDeleteVertexArrays(1, &vao);
  glfwTerminate();

  return 0;
}

【问题讨论】:

    标签: c++ opengl-es glfw emscripten


    【解决方案1】:

    正如另一个答案所提到的,WebGL 2.0 接近于 OpenGL ES 3.0,但令人困惑的是没有定义 glMapBufferRange() 函数,因此 Emscripten 尝试仅模拟该函数的部分功能。

    然而,Real WebGL 2.0 公开了glGetBufferSubData() 模拟,这在 OpenGL ES 中不存在,但在桌面 OpenGL 中存在。该方法可以通过EM_ASM_封装在代码中:

    void myGetBufferSubData (GLenum theTarget, GLintptr theOffset, GLsizeiptr theSize, void* theData)
    {
    #ifdef __EMSCRIPTEN__
      EM_ASM_(
      {
        Module.ctx.getBufferSubData($0, $1, HEAPU8.subarray($2, $2 + $3));
      }, theTarget, theOffset, theData, theSize);
    #else
      glGetBufferSubData (theTarget, theOffset, theSize, theData);
    #endif
    }
    

    在多平台代码中取回 VBO 数据很麻烦:

    1. OpenGL 1.5+
      • glGetBufferSubData():是的
      • glMapBufferRange():是的
    2. OpenGL ES 2.0
      • glGetBufferSubData():没有
      • glMapBufferRange():没有
    3. OpenGL ES 3.0+
      • glGetBufferSubData():没有
      • glMapBufferRange():是的
    4. WebGL 1.0
      • glGetBufferSubData():没有
      • glMapBufferRange():没有
    5. WebGL 2.0
      • glGetBufferSubData():是的
      • glMapBufferRange():没有

    这样:

    • 桌面 OpenGL 提供了最大的灵活性。
    • OpenGL ES 2.0 和 WebGL 1.0 没有机会取回数据。
    • OpenGL ES 3.0+ 仅提供映射缓冲区。
    • WebGL 2.0 仅提供 getBufferSubData()。

    【讨论】:

      【解决方案2】:

      WebGL2 不支持MapBufferRange,因为这将是一场安全噩梦。相反,它支持getBufferSubData。我不知道这是否暴露在 emscripten 中的 WebAssembly 中。 Emscripten 正在使用 bufferData 模拟您提到的情况下的 MapBufferRange。见herehere

      如果不支持getBufferSubData,您可以添加它。请参阅library_gl.js 中的代码了解如何实现readPixels,并将其作为如何将getBufferSubData 公开给WebAssembly 的灵感。要么使用 _EM_ASM 添加一些内联 JavaScript。我还没有这样做,但谷歌搜索“getBufferSubData emscripten”买了this gist

      【讨论】:

      • Emscripten 仍然没有通过eglGetProcAddress() 公开glGetBufferSubData(),这令人困惑,但可以理解——因为glGetBufferSubData() 未被OpenGL ES 及其任何扩展定义,并且仅存在于桌面OpenGL 上。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-01
      相关资源
      最近更新 更多