Search code examples
c++sdl-2sdl-image

SDL_Texture causes everything to render black


I have been working on a fairly large app in SDL for a while and recently noticed a strange bug; whenever a window is closed, everything on another window will render completely black. When I draw red a line, it will be black, and when I draw an SDL_Texture, it will draw a black rectangle in place of the image.

After a while, I managed to recreate the problem by making a simplified version of the app from the ground up. The program includes a window class that stores a window, its renderer, and an SDL_Texture. The window class also includes a render function to render things on to its window including the SDL_Texture. There is also a function that allows each window object to handle SDL events such as the window being closed. When the window is closed, the window, renderer, and texture are all destroyed.

class Window {
    SDL_Window *window;
    SDL_Renderer *renderer;
    Uint32 windowID;

    SDL_Texture *texture;

    void cleanup() {
        SDL_DestroyWindow(window);
        SDL_DestroyRenderer(renderer);
        SDL_DestroyTexture(texture);
    }

    bool running = true;
public:

    void init() {

        window = SDL_CreateWindow("Window", 50, 50, 721, 558, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
        renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
        windowID = SDL_GetWindowID(window);
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

        // load texture
        SDL_Surface* loadedSurface = IMG_Load("picture.png");
        texture = SDL_CreateTextureFromSurface(renderer, loadedSurface);
        SDL_FreeSurface(loadedSurface);

    }

    bool isRunning() {
        return running;
    }

    void render() {
        // clear the screen with a green color
        SDL_SetRenderDrawColor(renderer, 0x00, 0xFF, 0x00, 0xFF);
        SDL_RenderClear(renderer);

        // create a rectangle for drawing things
        SDL_Rect rect = {0, 0, 100, 100};

        // draw a red rectangle
        SDL_SetRenderDrawColor(renderer, 0xFF, 0x00, 0x00, 0xFF);
        SDL_RenderFillRect(renderer, &rect);

        // draw the texture/image on top of the rectangle
        if (SDL_RenderCopy(renderer, texture, NULL, &rect) < 0) {
            printf("Unable to render texture! Error: %s\n", SDL_GetError());
        }



        SDL_RenderPresent(renderer);
    }

    void events(SDL_Event &e) {
        if (e.window.windowID == windowID && e.window.event == SDL_WINDOWEVENT_CLOSE) {
            running = false;
            cleanup();
        }
    }
};

The main function manages multiple windows at a time, and separately feeds each one of them SDL events:

int main(int argc, const char * argv[]) {

    if (SDL_Init(SDL_INIT_VIDEO) < 0) { // initialize SDL and IMG
        printf("SDL could not initialize! Error: %s\n", SDL_GetError());
    } else if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) {
        printf("SDL_image could not initialize! Error: %s\n", IMG_GetError());
    } else {
        std::vector<Window*> windows; // vector of window objects

        // add window objects to the vector
        windows.push_back(new Window());
        windows.push_back(new Window());
        windows.push_back(new Window());
        windows.push_back(new Window());

        // initialize all windows
        for (int i = 0; i < windows.size(); i++) {
            windows[i]->init();
        }

        // game loop
        bool loop = true;
        while (loop) {
            SDL_Delay(50); // delay between each frame

            // render all windows
            for (int i = 0; i < windows.size(); i++) {
                windows[i]->render();
            }

            // handle new events
            SDL_Event e;
            while (SDL_PollEvent(&e)) {

                // loop backward through windows
                // in case one of them has to be
                // removed from the vector
                for (unsigned long i = windows.size(); i-- > 0;) {
                    if (windows[i]->isRunning()) {
                        windows[i]->events(e);
                    } else {
                        // delete a window if it has been closed
                        delete windows[i];
                        windows.erase(windows.begin() + i);
                    }
                }
            }

            if (windows.empty()) { // if all windows are closed,
                loop = false;      // stop the loop
            }
        }

    }

    return 0;
}

Now that I have shown my code, I will explain the issue in more detail. When the program starts, four window objects are created, and the windows each show up on the screen. Each of the windows render properly, with a PNG image overlaid on top of a red rectangle. The fourth window, or the last one to be initialized, will only render things such as images, rectangles, and lines in the color black after any other window is closed. The only thing that will render in a different color is SDL_RenderClear, which I used in the above code to render a green background, which exposes the black image and rectangle shapes.

I have tested to make sure the texture, renderer, and window are still valid after this problem occurs, and got no errors.

I would at least like to find what is causing the problem and if there is any possible solution.


Solution

  • You should not destroy window while renderer is still alive. Try executing methods inside of cleanup() in opposite order.

    void cleanup()
    {
        SDL_DestroyTexture(texture);
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
    }