163 lines
6.3 KiB
C++
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);
|
|
}
|