获取OpenGL ES使用Emscripten将反馈数据转换回主机

时间:2018-02-15 13:56:20

标签: c++ opengl-es glfw emscripten

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

$ g++ tf.cpp -lGL -lglfw

但是,如果我使用Emscripten,则会抛出异常,表示该异常 仅在访问glMapBufferRange时才支持MAP_WRITE|INVALIDATE_BUFFER。我确实想阅读而不是写作,所以也许我不应该使用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;
}

2 个答案:

答案 0 :(得分:1)

WebGL2不支持MapBufferRange,因为这将是一场安全噩梦。相反,它支持getBufferSubData。我不知道是否在emscripten中暴露给WebAssembly。对于您使用MapBufferRange提到的案例,Emscripten正在模拟bufferData。请参阅herehere

如果不支持getBufferSubData,您可以添加它。请参阅library_gl.js中的代码,了解readPixels的实现方式,并将其作为如何向WebAssembly公开getBufferSubData的灵感。或者使用_EM_ASM添加内联JavaScript。我还没有完成它,但谷歌搜索&#34; getBufferSubData emscripten&#34;买了this gist

答案 1 :(得分: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()。