Search code examples
c++opengllinesdlpixel

OpenGL Scale Single Pixel Line


I would like to make a game that is internally 320x240, but renders to the screen at whole number multiples of this (640x480, 960,720, etc). I am going for retro 2D pixel graphics.

I have achieved this by setting the internal resolution via glOrtho():

glOrtho(0, 320, 240, 0, 0, 1);

And then I scale up the output resolution by a factor of 3, like this:

glViewport(0,0,960,720);
window = SDL_CreateWindow("Title", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 960, 720, SDL_WINDOW_OPENGL);

I draw rectangles like this:

glBegin(GL_LINE_LOOP);
glVertex2f(rect_x, rect_y);
glVertex2f(rect_x + rect_w, rect_y);
glVertex2f(rect_x + dst_w, dst_y + dst_h);
glVertex2f(rect_x, rect_y + rect_h);
glEnd();

It works perfectly at 320x240 (not scaled):

enter image description here

When I scale up to 960x720, the pixel rendering all works just fine! However it seems the GL_Line_Loop is not drawn on a 320x240 canvas and scaled up, but drawn on the final 960x720 canvas. The result is 1px lines in a 3px world :(

enter image description here

How do I draw lines to the 320x240 glOrtho canvas, instead of the 960x720 output canvas?


Solution

  • As I mentioned in my comment Intel OpenGL drivers has problems with direct rendering to texture and I do not know of any workaround that is working. In such case the only way around this is use glReadPixels to copy screen content into CPU memory and then copy it back to GPU as texture. Of coarse that is much much slower then direct rendering to texture. So here is the deal:

    1. set low res view

      do not change resolution of your window just the glViewport values. Then render your scene in the low res (in just a fraction of screen space)

    2. copy rendered screen into texture

    3. set target resolution view
    4. render the texture

      do not forget to use GL_NEAREST filter. The most important thing is that you swap buffers only after this not before !!! otherwise you would have flickering.

    Here C++ source for this:

    void gl_draw()
        {
        // render resolution and multiplier
        const int xs=320,ys=200,m=2;
    
        // [low res render pass]
        glViewport(0,0,xs,ys);
        glClearColor(0.0,0.0,0.0,1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_TEXTURE_2D);
        // 50 random lines
        RandSeed=0x12345678;
        glColor3f(1.0,1.0,1.0);
        glBegin(GL_LINES);
        for (int i=0;i<100;i++)
         glVertex2f(2.0*Random()-1.0,2.0*Random()-1.0);
        glEnd();
    
        // [multiply resiolution render pass]
        static bool _init=true;
        GLuint  txrid=0;        // texture id
        BYTE map[xs*ys*3];      // RGB
        // init texture
        if (_init)              // you should also delte the texture on exit of app ...
            {
            // create texture
            _init=false;
            glGenTextures(1,&txrid);
            glEnable(GL_TEXTURE_2D);
            glBindTexture(GL_TEXTURE_2D,txrid);
            glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);   // must be nearest !!!
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
            glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_COPY);
            glDisable(GL_TEXTURE_2D);
            }
        // copy low res screen to CPU memory
        glReadPixels(0,0,xs,ys,GL_RGB,GL_UNSIGNED_BYTE,map);
        // and then to GPU texture
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D,txrid);         
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, xs, ys, 0, GL_RGB, GL_UNSIGNED_BYTE, map);
        // set multiplied resolution view
        glViewport(0,0,m*xs,m*ys);
        glClear(GL_COLOR_BUFFER_BIT);
        // render low res screen as texture
        glBegin(GL_QUADS);
        glTexCoord2f(0.0,0.0); glVertex2f(-1.0,-1.0);
        glTexCoord2f(0.0,1.0); glVertex2f(-1.0,+1.0);
        glTexCoord2f(1.0,1.0); glVertex2f(+1.0,+1.0);
        glTexCoord2f(1.0,0.0); glVertex2f(+1.0,-1.0);
        glEnd();
        glDisable(GL_TEXTURE_2D);
    
        glFlush();
        SwapBuffers(hdc);   // swap buffers only here !!!
        }
    

    And preview:

    preview

    I tested this on some Intel HD graphics (god knows which version) I got at my disposal and it works (while standard render to texture approaches are not).