Created
April 9, 2018 18:37
-
-
Save rvega/18ace0be4fefab3683ae4f5bf67349a9 to your computer and use it in GitHub Desktop.
generating pixel texture with secondary thread
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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