/* * /\ +------------------------------------------------------+ * ____/ \____ /| - Open source game framework licensed to freely use, | * \ / / | copy, modify and sell without restriction | * +--\ ^__^ /--+ | | * | ~/ \~ | | - created for | * | ~~~~~~~~~~~~ | +------------------------------------------------------+ * | SPACE ~~~~~ | / * | ~~~~~~~ BOX |/ * +--------------+ * * This tests passing image data from an HTML5 canvas object in JavaScript to an OpenGL context. It can be useful, for example, * for developing cross-platform applications that use the same codebase to export both desktop and web versions, or for using * C++ libraries to edit and display images on a web page. * * It was written to test transferring webcam image data to OpenCV. The OpenCV dependency was dropped since it isn't necessary just * for displaying the webcam, but there is an example line in the code where the OpenCV call could be substituted. */ #include #include #include #include #include "Game.hpp" #include "GLObject.hpp" #include "VBO.hpp" #include "Model.hpp" using namespace emscripten; /* These variables will be bound to JS. They are placed in the global scope, so they can be read and written by both * C++ and JS. The following functions are bound to JS so they can be used to write values to the variables. */ unsigned int emscripten_heap_offset = 0; bool new_frame_flag = false; void flag_frame() { new_frame_flag = true; } void set_heap_offset(int offset) { emscripten_heap_offset = offset; } class Browser_Webcam_Test : public Game { public: sb::VAO vao; sb::VBO vbo; GLuint flat_program, texture_uniform; Plane camera_frame_model; Browser_Webcam_Test() { /* The parent constructor initializes SDL and SDL extensions, sets GL attributes and creates a window. */ }; /* Load pixel data from the Emscripten heap into an OpenGL texture. This will be called whenever new frame data is added to the Emscripten heap */ void refresh() { /* Address of frame RGBA pixel data on the Emscripten heap (received as an unsigned int and cast to an unsigned 8-bit pointer) */ unsigned char* pos = reinterpret_cast(emscripten_heap_offset); /* Print the first four 8-bit values, which should be an RGBA color */ std::cout << "heap address " << emscripten_heap_offset << " first 4 bytes: "; for (std::size_t ii = 0; ii < 4; ii++) { std::cout << static_cast(pos[ii]) << " "; } std::cout << std::endl; // If OpenCV were being used, a cv::Mat could be created: // frame = cv::Mat(FW, FH, CV_8UC4, pos); /* Add a texture to the camera Plane for storing frame image data */ camera_frame_model.texture().load(pos, configuration()["display"]["dimensions"]); /* Indicate pixel data has finished loading */ new_frame_flag = false; } /* Set up GL buffers for attributes. Set up shaders and uniforms. Create the texture that will hold the camera pixel data. */ void load_gl_context() { /* [SPACEBOX] uses SDL to create GL context */ Game::load_gl_context(); /* Generate a vertex array object ID, bind it as current (requirement of OpenGL) */ vao.generate(); vao.bind(); /* Generate ID for the vertex buffer object that will hold all vertex data. Using one buffer for all attributes, data * will be copied in one after the other. */ vbo.generate(); vbo.bind(); /* Load, configure, and set GL to use the shader program */ GLuint vertex_shader = load_shader("shaders/flat.vert", GL_VERTEX_SHADER); GLuint fragment_shader = load_shader("shaders/flat.frag", GL_FRAGMENT_SHADER); flat_program = glCreateProgram(); glAttachShader(flat_program, vertex_shader); glAttachShader(flat_program, fragment_shader); Plane::position->bind(0, flat_program, "in_position"); Plane::uv->bind(1, flat_program, "vertex_uv"); link_shader(flat_program); glUseProgram(flat_program); /* Fill VBO with attribute data */ vbo.allocate(camera_frame_model.size(), GL_STATIC_DRAW); vbo.add(*Plane::position); vbo.add(*Plane::uv); /* Set the active texture unit to #0, Get the texture uniform from the shader and set it use texture #0. See * https://www.khronos.org/opengl/wiki/Sampler_(GLSL)#Binding_textures_to_samplers */ glActiveTexture(GL_TEXTURE0); texture_uniform = glGetUniformLocation(flat_program, "base_texture"); glUniform1i(texture_uniform, 0); /* Create a texture the size of the video resolution (defined as 320, 240 in config.json and index.html) */ camera_frame_model.texture(sb::Texture()); camera_frame_model.texture().generate(configuration()["display"]["dimensions"]); camera_frame_model.texture().bind(); camera_frame_model.enable(); } /* This gets called every frame by the parent class. Refresh the texture pixel data if a new frame is available. * Clear the screen, then draw the camera model, which will render the texture. */ void update() { /* This flag is set in JS whenever a camera frame is read and stored */ if (new_frame_flag) { refresh(); } glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, camera_frame_model.attributes("position")->count()); SDL_GL_SwapWindow(window()); } }; /* Emscripten will call this function. Create a game object, load its GL context, and run the game. */ int main() { Browser_Webcam_Test browser_webcam_test = Browser_Webcam_Test(); browser_webcam_test.load_gl_context(); browser_webcam_test.run(); browser_webcam_test.quit(); return 0; } /* This will bind the global functions at the beginning of the file to Emscripten so those values can be set and read by this program */ EMSCRIPTEN_BINDINGS(my_module) { function("flag_frame", &flag_frame); function("set_heap_offset", &set_heap_offset); }