views:

746

answers:

5

I am making an application that does some custom image processing. The program will be driven by a simple menu in the console. The user will input the filename of an image, and that image will be displayed using openGL in a window. When the user selects some processing to be done to the image, the processing is done, and the openGL window should redraw the image.

My problem is that my image is never drawn to the window, instead the window is always black. I think it may have to do with the way I am organizing the threads in my program. The main execution thread handles the menu input/output and the image processing and makes calls to the Display method, while a second thread runs the openGL mainloop.

Here is my main code:

#include <iostream>
#include <GL/glut.h>
#include "ImageProcessor.h"
#include "BitmapImage.h"

using namespace std;

DWORD WINAPI openglThread( LPVOID param );
void InitGL();
void Reshape( GLint newWidth, GLint newHeight );
void Display( void );

BitmapImage* b;
ImageProcessor ip;


int main( int argc, char *argv[] ) {
    DWORD threadID;
    b = new BitmapImage();

    CreateThread( 0, 0, openglThread, NULL, 0, &threadID );

    while( true ) {
        char choice;
        string path = "TestImages\\";
        string filename;
        cout << "Enter filename: ";
        cin >> filename;
        path += filename;
        b = new BitmapImage( path );
        Display();

        cout << "1) Invert" << endl;
        cout << "2) Line Thin" << endl;
        cout << "Enter choice: ";
        cin >> choice;

        if( choice == '1' ) {
            ip.InvertColour( *b );
        }
        else {
            ip.LineThinning( *b );
        }
        Display();
    }

    return 0;
}


void InitGL() {
    int argc = 1;
    char* argv[1];
    argv[0] = new char[20];
    strcpy( argv[0], "main" );

    glutInit( &argc, argv );

    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowPosition( 0, 0 );
    glutInitWindowSize( 800, 600 );
    glutCreateWindow( "ICIP Program - Character recognition using line thinning, Hilbert curve, and wavelet approximation" );

    glutDisplayFunc( Display );
    glutReshapeFunc( Reshape );

    glClearColor(0.0,0.0,0.0,1.0);

    glEnable(GL_DEPTH_TEST);
}


void Reshape( GLint newWidth, GLint newHeight ) {
    /*  Reset viewport and projection parameters  */
    glViewport( 0, 0, newWidth, newHeight );
}


void Display( void ) {
    glClear (GL_COLOR_BUFFER_BIT); // Clear display window.
    b->Draw();
    glutSwapBuffers();
}


DWORD WINAPI openglThread( LPVOID param ) {
    InitGL();
    glutMainLoop();
    return 0;
}

Here is my draw method for BitmapImage:

void BitmapImage::Draw() {
    cout << "Drawing" << endl;
    if( _loaded ) {
        glBegin( GL_POINTS );
            for( unsigned int i = 0; i < _height * _width; i++ ) {
                glColor3f( _bitmap_image[i*3] / 255.0, _bitmap_image[i*3+1] / 255.0, _bitmap_image[i*3+2] / 255.0 );
                // invert the y-axis while drawing
                glVertex2i( i % _width, _height - (i / _width) );
            }
        glEnd();
    }
}

Any ideas as to the problem?

Edit: The problem was technically solved by starting a glutTimer from the openglThread which calls glutPostRedisplay() every 500ms. This is OK for now, but I would prefer a solution in which I only have to redisplay every time I make changes to the bitmap (to save on processing time) and one in which I don't have to run another thread (the timer is another thread im assuming). This is mainly because the main processing thread is going to be doing a lot of intensive work and I would like to dedicate most of the resources to this thread rather than anything else.

+2  A: 

I've had this problem before - it's pretty annoying. The problem is that all of your OpenGL calls must be done in the thread where you started the OpenGL context. So when you want your main (input) thread to change something in the OpenGL thread, you need to somehow signal to the thread that it needs to do stuff (set a flag or something).

Note: I don't know what your BitmapImage loading function (here, your constructor) does, but it probably has some OpenGL calls in it. The above applies to that too! So you'll need to signal to the other thread to create a BitmapImage for you, or at least to do the OpenGL-related part of creating the bitmap.

Jesse Beder
That's not entirely accurate, the requirement is that the OpenGL context can only be _current_ to any single thread. The context can however be created in a different thread than which it is current. This is useful for offscreen rendering for example.
codelogic
Just to clarify, the problem in this case is definitely the multithreaded rendering though, since the OP is making gl calls from different threads using the same context, or rather a non-existent context (in the main thread).
codelogic
Re comment #1, that's true, but I thought it was irrelevant (and unnecessary complication) for this particular example.
Jesse Beder
A: 

Your problem may be in Display() at the line

b->Draw();

I don't see where b is passed into the scope of Display().

jeffD
b is declared globally just above main()
MahlerFive
Yes, I see it now in the morning light.
jeffD
+1  A: 

A few points:

  • Generally, if you're going the multithreaded route, it's preferable if your main thread is your GUI thread i.e. it does minimal tasks keeping the GUI responsive. In your case, I would recommend moving the intensive image processing tasks into a thread and doing the OpenGL rendering in your main thread.

  • For drawing your image, you're using vertices instead of a textured quad. Unless you have a very good reason, it's much faster to use a single textured quad (the processed image being the texture). Check out glTexImage2D and glTexSubImage2D.

  • Rendering at a framerate of 2fps (500ms, as you mentioned) will have negligible impact on resources if you're using an OpenGL implementation that is accelerated, which is almost guaranteed on any modern system, and if you use a textured quad instead of a vertex per pixel.

codelogic
A: 

You need to make OpenGL calls on the thread in which context was created (glutInitDisplayMode). Hence glXX calls inside Display method which is on different thread will not be defined. You can see this easily by dumping the function address, hopefully it would be undefined or NULL.

Ketan
A: 

It sounds like the 500ms timer is calling Display() regularly, after 2 calls it fills the back-buffer and the front-buffer with the same rendering. Display() continues to be called until the user enters something, which the OpenGL thread never knows about, but, since, global variable b is now different, the thread blindly uses that in Display().

So how about doing what Jesse Beder says and use a global int, call it flag, to flag when the user entered something. For example:

set flag = 1; after you do the b = new BitmapImage( path );

then set flag = 0; after you call Display() from the OpenGL thread.

You loop on the timer, but, now check if flag = 1. You only need call glutPostRedisplay() when flag = 1, i.e. the user entered something.

Seems like a good way without using a sleep/wake mechanism. Accessing global variables among more than one thread can also be unsafe. I think the worst that can happen here is the OpenGL thread miss-reads flag = 0 when it should read flag = 1. It should then catch it after no more than a few iterations. If you get strange behavior go to synchronization.

With the code you show, you call Display() twice in main(). Actually, main() doesn't even need to call Display(), the OpenGL thread does it.

jeffD