2
\$\begingroup\$

I'm currently working on a 2D-Game and i want to draw and calculate the whole screen myself through a pixel array. I'm programming in C++, using SDL 2 and OpenGL specifically a OpenGL Texture which is generated through my manipulated pixel array. My rendering System basically works like this:

  1. Pixelvalues from a spritesheet are getting put into the pixel array (those are all the tiles that should be rendered).
  2. The RGB values of every pixel are getting multiplied by a certain value to get the effect of lighting.
  3. The manipulated pixel array is being passed to OpenGL to generate a Texture which is then drawn to the screen.

And the problem is that especially the lighting calculations cost a lot of time since its all being calculated on the CPU and even though all i'm doing is drawing the very basic things my FPS is maxed out at like 160 (on a quite good computer). And after profiling my code i also got to the conclusion that mostly the lighting function takes up a lot of time.

Lighting function:

    void PixelRenderer::applyLight(Uint32& color, Uint32 factor){
    Uint32 red = (((color >> 16) & 0xff)* factor) >> 8;
    Uint32 green = (((color >> 8) & 0xff)* factor) >> 8;
    Uint32 blue = (((color) & 0xff)* factor) >> 8;

    if (red > 255) red = 255;
    if (green > 255) green = 255;
    if (blue > 255) blue = 255;

    color = (color & 0xFF000000) | red << 16 | green << 8 | blue;
}

and this is done for every single pixel on the screen (1024*576 resolution and 32x32 tileSize)

So my question basically is if theres any way i can do those calculations on the GPU to improve my performance.

\$\endgroup\$
  • \$\begingroup\$ You want a pixel shader. \$\endgroup\$ – Vaughan Hilts Feb 19 '15 at 19:03
1
\$\begingroup\$

Yes!

This is exactly the sort of thing that can be done very well on the GPU, since it is processing one pixel at a time.

There's two ways you might address this:

  1. Incorporate this processing directly in your shader, so you don't need to preprocess the texture at all. just process it on the fly as you apply it. To do this, you'd incorporate your applyLight calculations into the fragment shader for the thing you're texturing.

  2. Preprocess it as you do now, but on the GPU.

I'd probably lean towards Way #1, as the calculations seem not very expensive.

For interest, though, I'll sketch out Way #2, too.

Way #2, Preprocess on GPU

You'll need to write an OpenGL program (vertex & fragment shaders).

The general approach will be:

  • Create a geometry of two triangles to fill the screen.
  • Vertex shader pass-through
  • Fragment shader looks almost identical to your applyLight method, but using floating point and four-element vectors.
  • Render to a frame buffer object
  • The pixel data of the frame buffer object can be used directly as a texture next.

I'm attaching an excerpt of my one code which deals with creating an OpenGL frame buffer object; it's not standalone, I'm afraid, but perhaps provides a good sketch of how to get your frame buffer going.

MeFrameBufferGl::MeFrameBufferGl(int width, int height, EMeTextureKind kind) : MeObjectBase("MeFrameBufferGl")
{
    this->width = width;
    this->height = height;

    this->colorTexture = // created with glTexImage2D, width, height
    this->depthTexture = // created with glTexImage2D, width, height

    glGenFramebuffers(1, &this->frameBufferName);
    glBindFramebuffer(GL_FRAMEBUFFER, this->frameBufferName);

    //Attach 2D texture to this FBO
    GLuint colorTextureName = this->colorTexture->getGlName();
    GLuint depthTextureName = this->depthTexture->getGlName();
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTextureName, 0);
    SHOW_GL_ERROR;
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTextureName, 0);
    SHOW_GL_ERROR;

    //-------------------------
    //Does the GPU support current FBO configuration?
    GLenum status;
    status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    switch(status)
    {
        case GL_FRAMEBUFFER_COMPLETE:
            meLogInfo("framebuffer is good");
            break;
        default:
            meLogError("framebuffer is bad: 0x%04x %d\n", status, status);
            break;
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
\$\endgroup\$
  • \$\begingroup\$ So with your first method i will have to write my own shader which basically does what my applyLight method does right now? \$\endgroup\$ – heap_trouble Feb 19 '15 at 19:14
  • \$\begingroup\$ With either method, yes, a shader to do what applyLight does. The only difference is using it as a preprocess on your texture, or on the fly. \$\endgroup\$ – david van brink Feb 19 '15 at 19:26
  • \$\begingroup\$ Alright i will read up on how to implement that then, thank you. \$\endgroup\$ – heap_trouble Feb 19 '15 at 19:28
  • \$\begingroup\$ And also since getting the pixelvalues from my spritesheet array and assigning them to my "screen array" does take quite a lot of time aswell, if i want to do that on the GPU do i use shaders aswell? \$\endgroup\$ – heap_trouble Feb 19 '15 at 19:37
  • \$\begingroup\$ Generally yes, moving pixels around from textures to other textures is great for GPUs. You might imagine this in terms of a rectangle (triangle pair) per tile, drawing from the source texture. It's quite performant, but the implementation will seem (at first) rather more roundabout than copying rectangles of pixels between arrays. \$\endgroup\$ – david van brink Feb 19 '15 at 20:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.