spacebox/demo/browser_webcam_test/browser_webcam_test.cpp

163 lines
6.3 KiB
C++

/*
* /\ +------------------------------------------------------+
* ____/ \____ /| - Open source game framework licensed to freely use, |
* \ / / | copy, modify and sell without restriction |
* +--\ ^__^ /--+ | |
* | ~/ \~ | | - created for <https://foam.shampoo.ooo> |
* | ~~~~~~~~~~~~ | +------------------------------------------------------+
* | 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 <emscripten/bind.h>
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#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<std::uint8_t*>(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<int>(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);
}