example program for using a c++ program to stream a webcam in browser
This commit is contained in:
parent
52ef535eac
commit
f63cb2bfb2
|
@ -71,6 +71,9 @@ Test collision detection between a 2D sprite and other 2D sprites and boxes. Per
|
|||
|
||||
Map an image from a rectangle to a circle or from a circle to a rectangle using a shader program. Based on the elliptical grid mapping equations at http://squircular.blogspot.com/2015/09/mapping-circle-to-square.html
|
||||
|
||||
### browser webcam
|
||||
|
||||
An example for using a C++ program to display a webcam stream in the browser using Emscripten to translate the code from C++ to WebAssembly. Get the frame pixel data from a canvas element, read it into a SPACEBOX object, write the pixel data to an OpenGL texture, and use Emscripten to display the video.
|
||||
|
||||
Other libraries
|
||||
---------------
|
||||
|
@ -126,7 +129,7 @@ To build a WASM library that can be used to build an Emscripten version of a SPA
|
|||
$ find . -iname *.a
|
||||
./zbar/.libs/libzbar.a
|
||||
|
||||
There is a detailed tutorial on using Zbar with Web Assembly at https://barkeywolf.consulting/posts/barcode-scanner-webassembly/
|
||||
There is a detailed tutorial on using Zbar with WebAssembly at https://barkeywolf.consulting/posts/barcode-scanner-webassembly/
|
||||
|
||||
Font
|
||||
----
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# Browser Webcam Test
|
||||
|
||||
#######################
|
||||
# Location parameters #
|
||||
#######################
|
||||
|
||||
# Location of project specific source files
|
||||
SRC_DIR := ./
|
||||
|
||||
# Locations of [SPACEBOX] source and dependencies required to be compiled from source. These locations are configured to match the
|
||||
# structure of the [SPACEBOX] repository but can be modified as necessary.
|
||||
SB_DIR := ../../
|
||||
SB_SRC_DIR := $(SB_DIR)src/
|
||||
SB_LIB_DIR := $(SB_DIR)lib/
|
||||
SDLGFX2_DIR := $(SB_LIB_DIR)sdl2-gfx/
|
||||
GLEW_DIR := $(SB_LIB_DIR)glew/
|
||||
|
||||
# C and C++ compiler commands
|
||||
CC := clang
|
||||
CXX := clang++
|
||||
|
||||
# Location of SDL config program
|
||||
SDLCONFIG := $(HOME)/local/sdl/bin/sdl2-config
|
||||
|
||||
# Edit to point to the location of BPmono.ttf
|
||||
CREATE_FONT_SYMLINK := ln -nsf $(SB_DIR)"BPmono.ttf" .
|
||||
|
||||
#############################
|
||||
# Based on above parameters #
|
||||
#############################
|
||||
|
||||
SDL_CFLAGS = $(shell $(SDLCONFIG) --cflags)
|
||||
SDL_LFLAGS := $(shell $(SDLCONFIG) --libs)
|
||||
SB_H_FILES := $(wildcard $(addprefix $(SB_SRC_DIR),*.hpp))
|
||||
SB_O_FILES := $(filter-out $(addprefix $(SB_SRC_DIR),filesystem.o),$(SB_H_FILES:.hpp=.o))
|
||||
SRC_H_FILES := $(wildcard $(addprefix $(SRC_DIR),*.hpp))
|
||||
SRC_O_FILES := browser_webcam_test.o $(SRC_H_FILES:.hpp=.o)
|
||||
|
||||
#####################################################################
|
||||
# Targets for building [SPACE BOX], dependencies and project source #
|
||||
#####################################################################
|
||||
|
||||
$(SDLGFX2_DIR)%.o: $(SDLGFX2_DIR)%.c $(SDLGFX2_DIR)%.h
|
||||
$(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h
|
||||
$(CC) $< $(CFLAGS) -c -o $@
|
||||
|
||||
$(SB_SRC_DIR)extension.o : $(addprefix $(SB_SRC_DIR),Box.hpp Segment.hpp Color.hpp filesystem.hpp Pixels.hpp Log.hpp)
|
||||
$(SB_SRC_DIR)Node.o : $(addprefix $(SB_SRC_DIR),Game.hpp Configuration.hpp Delegate.hpp Display.hpp Input.hpp Box.hpp Audio.hpp Log.hpp)
|
||||
$(SB_SRC_DIR)Sprite.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Animation.hpp Color.hpp extension.hpp Pixels.hpp Log.hpp)
|
||||
$(SB_SRC_DIR)Game.o : $(addprefix $(SB_SRC_DIR),extension.hpp Node.hpp Sprite.hpp Recorder.hpp Input.hpp Configuration.hpp \
|
||||
Delegate.hpp Audio.hpp Log.hpp)
|
||||
$(SB_SRC_DIR)Animation.o : $(addprefix $(SB_SRC_DIR),Node.hpp Timer.hpp)
|
||||
$(SB_SRC_DIR)Recorder.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Configuration.hpp Delegate.hpp Animation.hpp extension.hpp)
|
||||
$(SB_SRC_DIR)Input.o : $(addprefix $(SB_SRC_DIR),Node.hpp Animation.hpp Configuration.hpp Delegate.hpp)
|
||||
$(SB_SRC_DIR)Configuration.o : $(addprefix $(SB_SRC_DIR),Node.hpp Animation.hpp Log.hpp)
|
||||
$(SB_SRC_DIR)Delegate.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Input.hpp)
|
||||
$(SB_SRC_DIR)Display.o : $(addprefix $(SB_SRC_DIR),Node.hpp Game.hpp Box.hpp Configuration.hpp Delegate.hpp Log.hpp)
|
||||
$(SB_SRC_DIR)Box.o : $(addprefix $(SB_SRC_DIR),extension.hpp Segment.hpp)
|
||||
$(SB_SRC_DIR)Segment.o : $(addprefix $(SB_SRC_DIR),extension.hpp Box.hpp)
|
||||
$(SB_SRC_DIR)Pixels.o : $(addprefix $(SB_SRC_DIR),Box.hpp extension.hpp Log.hpp utility.hpp)
|
||||
$(SB_SRC_DIR)Audio.o : $(addprefix $(SB_SRC_DIR),Node.hpp Display.hpp Configuration.hpp Box.hpp filesystem.hpp extension.hpp)
|
||||
$(SB_SRC_DIR)GLObject.o : $(addprefix $(SB_SRC_DIR),Log.hpp)
|
||||
$(SB_SRC_DIR)Texture.o : $(addprefix $(SB_SRC_DIR),GLObject.hpp filesystem.hpp Log.hpp)
|
||||
$(SB_SRC_DIR)VBO.o : $(addprefix $(SB_SRC_DIR),Log.hpp GLObject.hpp Attributes.hpp extension.hpp)
|
||||
$(SB_SRC_DIR)Attributes.o : $(addprefix $(SB_SRC_DIR),Log.hpp extension.hpp)
|
||||
$(SRC_DIR)Model.o : $(addprefix $(SB_SRC_DIR),extension.hpp Attributes.hpp Texture.hpp utility.hpp)
|
||||
$(SRC_DIR)*.o : $(SRC_H_FILES) $(SB_H_FILES)
|
||||
%.o : %.cpp %.hpp
|
||||
$(CXX) $(CXXFLAGS) $< -c -o $@
|
||||
|
||||
#############
|
||||
# Web build #
|
||||
#############
|
||||
|
||||
# Use Emscripten to output JavaScript and an HTML index page for running in the browser
|
||||
|
||||
EMSCRIPTENHOME = $(HOME)/ext/software/emsdk/upstream/emscripten
|
||||
EMSCRIPTEN_CFLAGS = -O1 -Wall -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS="['png', 'jpg']" -s USE_SDL_TTF=2 -s USE_SDL_MIXER=2 \
|
||||
-I $(SB_LIB_DIR) -I $(SB_SRC_DIR)
|
||||
EMSCRIPTEN_LFLAGS = -s MIN_WEBGL_VERSION=2 -s EXPORTED_FUNCTIONS="['_main']" -s ALLOW_MEMORY_GROWTH=1 -s FULL_ES3=1 \
|
||||
-s LLD_REPORT_UNDEFINED $(wildcard $(addprefix $(HOME)/ext/software/opencv-4.6.0/build_wasm/lib/,*.a)) $(HOME)/ext/software/ZBar/zbar/.libs/libzbar.a \
|
||||
--bind
|
||||
EMSCRIPTEN_PRELOADS = --preload-file "BPmono.ttf"@/ --preload-file "shaders/"@/"shaders/" --preload-file "config.json"@/
|
||||
|
||||
emscripten : CC = $(EMSCRIPTENHOME)/emcc
|
||||
emscripten : CXX = $(EMSCRIPTENHOME)/em++
|
||||
emscripten : CFLAGS = $(EMSCRIPTEN_CFLAGS)
|
||||
emscripten : CXXFLAGS = $(CFLAGS) --std=c++17
|
||||
emscripten : $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) $(SB_O_FILES) $(SRC_O_FILES)
|
||||
$(CREATE_FONT_SYMLINK)
|
||||
$(CXX) $^ $(CXXFLAGS) $(EMSCRIPTEN_LFLAGS) $(EMSCRIPTEN_PRELOADS) -o browser_webcam_test.js
|
||||
|
||||
#########################
|
||||
# Clean up object files #
|
||||
#########################
|
||||
|
||||
clean :
|
||||
-find $(SRC_DIR) -iname "*.o" -delete
|
||||
rm -f BPmono.ttf browser_webcam_test.data browser_webcam_test.js browser_webcam_test.wasm
|
||||
|
||||
clean-all : clean
|
||||
-find $(SB_SRC_DIR) -iname "*.o" -delete
|
||||
-find $(SB_LIB_DIR) -iname "*.o" -delete
|
|
@ -0,0 +1,166 @@
|
|||
/* Browser Webcam Test - "Model.cpp"
|
||||
*
|
||||
* This is a class for creating an OpenGL model (basically a collection of vertices, optional texture, and optional transformation).
|
||||
* It contains convenience functions for loading models into OpenGL.
|
||||
*
|
||||
* It is copied from the in-development game Gunkiss. It will eventually be merged into [SPACE BOX].
|
||||
*/
|
||||
|
||||
#include "Model.hpp"
|
||||
|
||||
/* Default constructor for Model */
|
||||
Model::Model() {};
|
||||
|
||||
/* Construct a Model, adding Attributes each already wrapped in a shared pointer. The attributes should
|
||||
* be passed as a map with each key being a name and each value being a shared pointer to attributes. */
|
||||
Model::Model(const std::map<std::string, std::shared_ptr<sb::Attributes>>& attributes_pack)
|
||||
{
|
||||
for (auto attributes : attributes_pack)
|
||||
{
|
||||
this->attributes(attributes.second, attributes.first);
|
||||
}
|
||||
}
|
||||
|
||||
/* Construct a Model, adding Attributes, which will each be wrapped in a shared pointer and stored in the
|
||||
* created object. The attributes should be passed as a map with each key being a name and each value being
|
||||
* an attributes object. */
|
||||
Model::Model(const std::map<std::string, sb::Attributes>& attributes_pack)
|
||||
{
|
||||
for (auto attributes : attributes_pack)
|
||||
{
|
||||
this->attributes(attributes.second, attributes.first);
|
||||
}
|
||||
}
|
||||
|
||||
/* Construct a new model object by passing a list of names which will be used to initialize
|
||||
* empty attributes objects with the given names */
|
||||
Model::Model(const std::initializer_list<std::string>& names)
|
||||
{
|
||||
for (const std::string& name : names)
|
||||
{
|
||||
this->attributes(sb::Attributes(), name);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the entire map of attributes, each wrapped in its shared pointer held by this object.
|
||||
* Can be used to iterate through the attributes. */
|
||||
std::map<std::string, std::shared_ptr<sb::Attributes>>& Model::attributes()
|
||||
{
|
||||
return model_attributes;
|
||||
}
|
||||
|
||||
/* Get the attributes under name, wrapped in the shared pointer held by this object. This
|
||||
* function uses the at method of std::map, so name must refer to attributes already
|
||||
* stored in this model. Use this function to share ownership of the attributes or to gain
|
||||
* access to the public interface of the attributes. */
|
||||
std::shared_ptr<sb::Attributes>& Model::attributes(const std::string& name)
|
||||
{
|
||||
return attributes().at(name);
|
||||
}
|
||||
|
||||
/* Get the attributes under name, wrapped in the shared pointer held by this object. This
|
||||
* function uses operator[] or std::map, so this can be used to add new attributes to the
|
||||
* object if they are wrapped in a shared pointer. */
|
||||
std::shared_ptr<sb::Attributes>& Model::operator[](const std::string& name)
|
||||
{
|
||||
auto element = attributes().find(name);
|
||||
/* add an empty Attributes at name if it doesn't exist yet */
|
||||
if (element == attributes().end())
|
||||
{
|
||||
attributes(sb::Attributes{}, name);
|
||||
}
|
||||
return attributes()[name];
|
||||
}
|
||||
|
||||
/* Assign name to attributes, copy and wrap in a shared pointer. The model can share
|
||||
* ownership of the created attribute memory with callers that request it. */
|
||||
void Model::attributes(const sb::Attributes& attributes, const std::string& name)
|
||||
{
|
||||
this->attributes(std::make_shared<sb::Attributes>(attributes), name);
|
||||
}
|
||||
|
||||
/* Assign name to attributes and share ownership. */
|
||||
void Model::attributes(const std::shared_ptr<sb::Attributes>& attributes, const std::string& name)
|
||||
{
|
||||
this->attributes()[name] = attributes;
|
||||
}
|
||||
|
||||
/* Enable all attributes. */
|
||||
void Model::enable()
|
||||
{
|
||||
for (const auto& attributes : this->attributes())
|
||||
{
|
||||
attributes.second->enable();
|
||||
}
|
||||
}
|
||||
|
||||
/* Disable all attributes. */
|
||||
void Model::disable()
|
||||
{
|
||||
for (const auto& attributes : this->attributes())
|
||||
{
|
||||
attributes.second->disable();
|
||||
}
|
||||
}
|
||||
|
||||
/* Return a reference to the texture container. */
|
||||
std::map<std::string, sb::Texture>& Model::textures()
|
||||
{
|
||||
return model_textures;
|
||||
}
|
||||
|
||||
/* Get the texture at name. This can be used to read the texture memory, share ownership of it, or
|
||||
* anything else a Texture object can be used for with direct calls to GL functions. */
|
||||
sb::Texture& Model::texture(const std::string& name)
|
||||
{
|
||||
return textures().at(name);
|
||||
}
|
||||
|
||||
/* Get the default texture. The default texture must have previously been set with the default key as
|
||||
* the name, which can be done using Model::texture(sb::Texture). */
|
||||
sb::Texture& Model::texture()
|
||||
{
|
||||
return texture(DEFAULT_TEXTURE_NAME);
|
||||
}
|
||||
|
||||
/* Assign name to texture and share ownership. */
|
||||
void Model::texture(const sb::Texture& texture, const std::string& name)
|
||||
{
|
||||
textures()[name] = texture;
|
||||
}
|
||||
|
||||
/* If no name is specified, use the default texture. This can be used to conveniently setup a model
|
||||
* with only one texture. */
|
||||
void Model::texture(const sb::Texture& texture)
|
||||
{
|
||||
this->texture(texture, DEFAULT_TEXTURE_NAME);
|
||||
}
|
||||
|
||||
/* Get the model's transformation matrix. */
|
||||
const glm::mat4& Model::transformation() const
|
||||
{
|
||||
return model_transformation;
|
||||
}
|
||||
|
||||
/* Set the model's transformation matrix. */
|
||||
void Model::transformation(const glm::mat4& transformation)
|
||||
{
|
||||
model_transformation = transformation;
|
||||
}
|
||||
|
||||
/* Return the size in bytes of the sum of the attributes. */
|
||||
std::size_t Model::size()
|
||||
{
|
||||
std::size_t sum = 0;
|
||||
for (const auto& attributes : this->attributes())
|
||||
{
|
||||
sum += attributes.second->size();
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/* Return the transformation matrix. */
|
||||
Model::operator glm::mat4() const
|
||||
{
|
||||
return model_transformation;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/* Browser Webcam Test - "Model.hpp"
|
||||
*
|
||||
* This is a class for creating an OpenGL model (basically a collection of vertices, optional texture, and optional transformation).
|
||||
* It contains convenience functions for loading models into OpenGL.
|
||||
*
|
||||
* It is copied from the in-development game Gunkiss. It will eventually be merged into [SPACE BOX].
|
||||
*/
|
||||
|
||||
#ifndef MODEL_H_
|
||||
#define MODEL_H_
|
||||
|
||||
/* GL functions */
|
||||
#if defined(__EMSCRIPTEN__)
|
||||
#include <GL/glew.h>
|
||||
#else
|
||||
#include "glew/glew.h"
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <iterator>
|
||||
#include "glm/glm.hpp"
|
||||
#include "Attributes.hpp"
|
||||
#include "Texture.hpp"
|
||||
|
||||
class Model
|
||||
{
|
||||
|
||||
private:
|
||||
|
||||
inline static const std::string DEFAULT_TEXTURE_NAME = "default";
|
||||
std::map<std::string, sb::Texture> model_textures;
|
||||
std::map<std::string, std::shared_ptr<sb::Attributes>> model_attributes;
|
||||
glm::mat4 model_transformation {1.0f};
|
||||
|
||||
public:
|
||||
|
||||
Model();
|
||||
Model(const std::map<std::string, std::shared_ptr<sb::Attributes>>&);
|
||||
Model(const std::map<std::string, sb::Attributes>&);
|
||||
Model(const std::initializer_list<std::string>&);
|
||||
std::map<std::string, std::shared_ptr<sb::Attributes>>& attributes();
|
||||
std::shared_ptr<sb::Attributes>& attributes(const std::string&);
|
||||
void attributes(const sb::Attributes&, const std::string&);
|
||||
void attributes(const std::shared_ptr<sb::Attributes>&, const std::string&);
|
||||
std::shared_ptr<sb::Attributes>& operator[](const std::string&);
|
||||
void enable();
|
||||
void disable();
|
||||
std::map<std::string, sb::Texture>& textures();
|
||||
sb::Texture& texture(const std::string&);
|
||||
sb::Texture& texture();
|
||||
void texture(const sb::Texture&, const std::string&);
|
||||
void texture(const sb::Texture&);
|
||||
const glm::mat4& transformation() const;
|
||||
void transformation(const glm::mat4&);
|
||||
std::size_t size();
|
||||
operator glm::mat4() const;
|
||||
|
||||
};
|
||||
|
||||
class Plane : public Model
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
inline const static std::shared_ptr<sb::Attributes> position = std::make_shared<sb::Attributes>(sb::Attributes{
|
||||
{-1.0f, 1.0f}, {1.0f, 1.0f}, {-1.0f, -1.0f},
|
||||
{1.0f, 1.0f}, {1.0f, -1.0f}, {-1.0f, -1.0f}
|
||||
});
|
||||
inline const static std::shared_ptr<sb::Attributes> uv = std::make_shared<sb::Attributes>(sb::Attributes{
|
||||
{0.0f, 1.0f}, {1.0f, 1.0f}, {0.0f, 0.0f},
|
||||
{1.0f, 1.0f}, {1.0f, 0.0f}, {0.0f, 0.0f}
|
||||
});
|
||||
|
||||
Plane() : Model(std::map<std::string, std::shared_ptr<sb::Attributes>>({{"position", position}, {"uv", uv}})) {}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,35 @@
|
|||
Emscripten webcam pixel data test
|
||||
=================================
|
||||
|
||||
This is a demo program for passing image data from an HTML5 canvas object in JavaScript to an OpenGL context in a C++ program. 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 and code to edit and display images on a web page.
|
||||
|
||||
It uses the [SPACEBOX][] engine to set up an SDL + GL environment and create a model conveniently, but it can be ported to just Emscripten.
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
### Emscripten
|
||||
|
||||
Install the latest Emscripten version to a directory and specify the directory in the Makefile
|
||||
|
||||
### SPACE BOX
|
||||
|
||||
The [SPACEBOX][] game and interactive application framework is required for setting up SDL + OpenGL. It is being used for convenience in setting up the rendering, but this technique can be used without it. Get it from https://git.nugget.fun/nugget/spacebox and specify the path to it in the Makefile.
|
||||
|
||||
Compiling
|
||||
---------
|
||||
|
||||
Run at the root of the directory after setting up Emscripten and SPACEBOX
|
||||
|
||||
make emscripten
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
Run at the root of the directory to create an HTTP server
|
||||
|
||||
python -m http.server
|
||||
|
||||
Browse to http://localhost:8000 to view the demo
|
||||
|
||||
[SPACEBOX]: https://git.nugget.fun/nugget/spacebox
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Browser Webcam Test by frank at shampoo.ooo
|
||||
*
|
||||
* Program for testing passing image data from an HTML5 canvas object in JavaScript to an OpenGL context in this C++ program.
|
||||
* This 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 and code to edit and display images on a web page.
|
||||
*
|
||||
* It uses the [SPACE BOX] engine (https://git.shampoo.ooo/nugget/spacebox) to set up an SDL + GL environment and create a model
|
||||
* conveniently, but it can be ported to just Emscripten.
|
||||
*/
|
||||
|
||||
#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()
|
||||
{
|
||||
/* [SPACE BOX] creates an SDL GL context and initializes GLEW. */
|
||||
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);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"display": {
|
||||
"render driver": "opengles3",
|
||||
"dimensions": [320, 240]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<body>
|
||||
<!-- WebGL output will be drawn here through Emscripten -->
|
||||
<canvas id="canvas"></canvas>
|
||||
|
||||
<!-- navigator.mediaDevices.getUserMedia will stream the webcam video directly here for testing -->
|
||||
<video id="webcam"></video>
|
||||
|
||||
<script>
|
||||
const FPS = 15;
|
||||
const BPP = 4;
|
||||
|
||||
// Direct output of webcam
|
||||
var video = document.getElementById("webcam");
|
||||
video.width = 320;
|
||||
video.height = 240;
|
||||
|
||||
// Undisplayed canvas which is used to draw the video frame and then read the pixel data directly
|
||||
var intermediate = document.createElement("canvas");
|
||||
intermediate.width = video.width;
|
||||
intermediate.height = video.height;
|
||||
var context = intermediate.getContext("2d");
|
||||
|
||||
// Indicates whether webcam is opened or not
|
||||
var streaming = false;
|
||||
|
||||
// Address of the webcam frame pixel data on the Emscripten heap
|
||||
var image_heap_address;
|
||||
|
||||
var Module = {
|
||||
onRuntimeInitialized: function()
|
||||
{
|
||||
|
||||
// Open the webcam and start displaying frames if successfully opened. Allocate space for 32-bit RGBA frame pixel data
|
||||
// on the Emscripten heap.
|
||||
navigator.mediaDevices.getUserMedia({video: {width: video.width, height: video.height}, audio: false})
|
||||
.then(function(stream) {
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
streaming = true;
|
||||
|
||||
// Get the memory address of the pixel data
|
||||
image_heap_address = Module._malloc(video.width * video.height * BPP);
|
||||
|
||||
// Pass the address to the C++ program
|
||||
Module.set_heap_offset(image_heap_address);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log('Camera Error: ' + err.name + ' ' + err.message);
|
||||
});
|
||||
|
||||
// This function will run continuously, drawing the webcam frame to the intermediate canvas, reading the pixel data,
|
||||
// storing the data on the heap, and setting the new frame available flag.
|
||||
function processVideo()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (streaming)
|
||||
{
|
||||
// Draw the webcam frame on a hidden canvas
|
||||
context.drawImage(video, 0, 0, video.width, video.height);
|
||||
|
||||
// Read the pixel data
|
||||
image_data = context.getImageData(0, 0, video.width, video.height).data;
|
||||
|
||||
// Get a memory view object that provides access to the heap at the previously allocated address
|
||||
image_heap_data = new Uint8Array(Module.HEAPU8.buffer, image_heap_address, video.width * video.height * BPP);
|
||||
|
||||
// Write the pixel data to the heap
|
||||
image_heap_data.set(image_data);
|
||||
|
||||
// Flag the C++ that new data is available
|
||||
Module.flag_frame();
|
||||
}
|
||||
|
||||
// Loop processVideo at roughly the FPS
|
||||
let begin = Date.now();
|
||||
let delay = 1000/FPS - (Date.now() - begin);
|
||||
setTimeout(processVideo, delay);
|
||||
}
|
||||
catch (err)
|
||||
{
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
processVideo();
|
||||
},
|
||||
|
||||
// Tell Emscripten to use this canvas for display
|
||||
canvas: document.getElementById("canvas")
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- This file is built by Emscripten when compiling the program -->
|
||||
<script src="browser_webcam_test.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,21 @@
|
|||
#version 300 es
|
||||
|
||||
/* Browser Webcam Test */
|
||||
|
||||
/* The precision declaration is required by OpenGL ES */
|
||||
precision mediump float;
|
||||
|
||||
/* Forwarded from the vertex shader */
|
||||
in vec2 uv;
|
||||
|
||||
/* The texture is provided by the program when it sets the uniform value. */
|
||||
uniform sampler2D base_texture;
|
||||
|
||||
/* Setting this to a color value will color the fragment in the output display */
|
||||
out vec4 myOutputColor;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
/* Get the color from the texture at the UV coordinates */
|
||||
myOutputColor = texture(base_texture, uv);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#version 300 es
|
||||
|
||||
/* Browser Webcam Test */
|
||||
|
||||
/* The precision declaration is required by OpenGL ES */
|
||||
precision mediump float;
|
||||
|
||||
/* Values which are bound the VBO. */
|
||||
in vec2 in_position;
|
||||
in vec2 vertex_uv;
|
||||
|
||||
/* Will be forwarded to the fragment shader */
|
||||
out vec2 uv;
|
||||
|
||||
/* Value is provided by the program when it sets the uniform value. */
|
||||
uniform mat4 transformation;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
/* Reflect 2D coordinates over the X-axis to flip the canvas coordinates into GL coordinates and return as a vec4 */
|
||||
gl_Position = vec4(vec2(1, -1) * in_position, 0, 1);
|
||||
|
||||
/* Forward the UV coordinates to the fragment shader. */
|
||||
uv = vertex_uv;
|
||||
}
|
Loading…
Reference in New Issue