Skip to content

Instantly share code, notes, and snippets.

@rvega
Created April 9, 2018 18:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rvega/18ace0be4fefab3683ae4f5bf67349a9 to your computer and use it in GitHub Desktop.
Save rvega/18ace0be4fefab3683ae4f5bf67349a9 to your computer and use it in GitHub Desktop.
generating pixel texture with secondary thread
#include <nanogui/screen.h>
#include <nanogui/window.h>
#include <nanogui/glcanvas.h>
#include <nanogui/layout.h>
#include <nanogui/opengl.h> // includes most opengl definitions
#include <iostream>
#include <mutex>
#include <cstdint>
#include <thread>
// this is a "replacement" of vlc because I don't know how to use it
// so i'm just creating a synthetic texture that a thread will be sliding
// the pattern along
class ImageLoader {
public:
static constexpr int WIDTH = 320;
static constexpr int HEIGHT = 240;
ImageLoader() { }
~ImageLoader() { }
void setCallbacks(void (*f1)(char**, void*), void (*f2)(char**, void*), void* object) {
lockCallback = f1;
unlockCallback = f2;
callbackObject = object;
}
void play() {
playThread = new std::thread(&ImageLoader::doPlay, this);
}
static void doPlay(ImageLoader *instance) {
int blackRow = 0;
char* data;
while(1) {
instance->lockCallback(&data, instance->callbackObject);
int i = 0;
blackRow++;
blackRow = blackRow % HEIGHT;
for (int row = 0; row < HEIGHT; ++row) {
for (int col = 0; col < WIDTH; ++col) {
if (row == blackRow) {
data[i++] = 0x00; // black
data[i++] = 0x00;
data[i++] = 0x00;
}
else {
if (row < HEIGHT / 3) {
data[i++] = 0xFF; // red
data[i++] = 0x00;
data[i++] = 0x00;
}
else if (row < 2 * HEIGHT / 3) {
data[i++] = 0x00; // green
data[i++] = 0xFF;
data[i++] = 0x00;
}
else {
data[i++] = 0x00; // blue
data[i++] = 0x00;
data[i++] = 0xFF;
}
}
}
}
instance->unlockCallback(&data, instance->callbackObject);
std::this_thread::sleep_for(std::chrono::milliseconds(1000/24)); // 24 fps
}
}
int width() { return WIDTH; }
int height() { return HEIGHT; }
std::thread *playThread;
void (*lockCallback)(char**, void*);
void (*unlockCallback)(char**, void*);
void *callbackObject;
};
class VideoCanvas : public nanogui::GLCanvas {
protected:
nanogui::GLShader mTexShader;
int mWidth = 0;
int mHeight = 0;
GLuint mTex;// texture, used with glBindTexture
std::mutex mDataMutex;
char *mData = nullptr;
ImageLoader *imageLoader = nullptr;
bool needUpdate;
public:
VideoCanvas(nanogui::Widget *parent, int width, int height)
: nanogui::GLCanvas(parent)
, mWidth(width)
, mHeight(height) {
needUpdate = false;
imageLoader = new ImageLoader();
mData = new char[imageLoader->width() * imageLoader->height() * 3];
imageLoader->setCallbacks(&lockCallback, &unlockCallback, this);
initGL();
imageLoader->play();
}
~VideoCanvas() {
glDeleteTextures(1, &mTex);
}
void initGL() {
// create the OpenGL texture
glGenTextures(1, &mTex);
glBindTexture(GL_TEXTURE_2D, mTex);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// initialize the video feed shader
// create shader that will use to display textures
mTexShader.init(
/* registered name */
"video_shader",
/* vertex shader */
"#version 330\n"
"// inputs: should just be the vertices and uv's for a rectangle\n"
"in vec2 position;\n"
"in vec2 texcoord;\n"
"// outputs: interpolated texture coordinates for fragment shader\n"
"out vec2 pass_texcoord;\n"
"void main() {\n"
" pass_texcoord = texcoord;\n"
" gl_Position = vec4(position.xy, 0.0f, 1.0f);\n"
"}\n",
/* fragment shader */
"#version 330\n"
"in vec2 pass_texcoord;// interpolated texture coordinate\n"
"out vec4 outColor; // output color sampled from texture\n"
"uniform sampler2D passTex;// sampler to read the texture\n"
"void main() {\n"
" outColor = texture(passTex, pass_texcoord);\n"
"}\n"
);
nanogui::MatrixXu indices(3, 2);
indices.col(0) << 0, 1, 2;
indices.col(1) << 2, 3, 1;
nanogui::MatrixXf positions(2, 4);
positions.col(0) << -1.0f, 1.0f;
positions.col(1) << 1.0f, 1.0f;
positions.col(2) << -1.0f, -1.0f;
positions.col(3) << 1.0f, -1.0f;
nanogui::MatrixXf texcoords(2, 4);
texcoords.col(0) << 0.0f, 0.0f;
texcoords.col(1) << 1.0f, 0.0f;
texcoords.col(2) << 0.0f, 1.0f;
texcoords.col(3) << 1.0f, 1.0f;
mTexShader.bind();
mTexShader.uploadIndices(indices);
mTexShader.uploadAttrib("position", positions);
mTexShader.uploadAttrib("texcoord", texcoords);
}
void drawGL() override {
mTexShader.bind();
glActiveTexture(1);
glUniform1i(mTexShader.uniform("passTex", true), 1 - GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mTex);
// upload the image data (only needed when new data comes in from newFrame)
{
std::lock_guard<std::mutex> data_lock(mDataMutex);
if(needUpdate) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, mWidth, mHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, mData);
needUpdate = false;
}
}
mTexShader.drawIndexed(GL_TRIANGLES, 0, 2);
}
/* void newFrame(void *data) { */
/* std::lock_guard<std::mutex> data_lock(mDataMutex); */
/* mData = data; */
/* } */
static void lockCallback(char** data, void* object) {
VideoCanvas* c = (VideoCanvas *)object;
c->mDataMutex.lock();
*data = (char*)c->mData;
}
static void unlockCallback(char** data, void* object) {
VideoCanvas* c = (VideoCanvas *)object;
c->needUpdate = true;
c->mDataMutex.unlock();
}
};
class MyScreen : public nanogui::Screen {
public:
MyScreen() : nanogui::Screen({600, 600}, "Texture Testing") {
auto *window = new nanogui::Window(this, "Video Data");
window->setLayout(new nanogui::GroupLayout());
mVideoCanvas = new VideoCanvas(window, mImageLoader.width(), mImageLoader.height());
mVideoCanvas->setSize({mImageLoader.width(), mImageLoader.height()});
}
~MyScreen() { }
/* void drawContents() override { */
/* // update the texture. note that if you move your mouse around a lot over */
/* // the screen you can see faster updates. ideally you will run vlc on a */
/* // completely separate thread than the drawing thread. the setup of */
/* // video canvas above should let you call the `newFrame` method from a */
/* // different thread */
/* // */
/* // it changes because drawContents is triggered as events are fired */
/* // (if interested, see nanogui::Screen::drawAll method implementation) */
/* // so shaking the mouse triggers mouse motion events so then this code */
/* // is executed more often. AKA having the VLC stuff on the drawing thread */
/* // is probably not what you want. */
/* static int counter = 0; */
/* mImageLoader.updateTexture(counter++); */
/* // make sure this new texture is rendered */
/* mVideoCanvas->newFrame(mImageLoader.data()); */
/* } */
protected:
ImageLoader mImageLoader;
VideoCanvas *mVideoCanvas = nullptr;
};
int main(int argc, const char **argv) {
nanogui::init();
auto *screen = new MyScreen();
screen->performLayout();
screen->setVisible(true);
nanogui::mainloop();
nanogui::shutdown();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment