SDL2 - Emulator slow on Ubuntu/Mac OS X

0

I wrote a Space Invaders emulator in C++ using SDL2 only for creating the game window and for playing sounds (sdl2_mixer). On Windows the emulator works at 60 FPS (I can change this value to whatever I want and it works without any problem) but if I build it on Ubuntu or Mac OS X the game is unplayable (maybe 10% of the wanted FPS).

Is there an explanation for this?

  • Renderer name on Ubuntu: opengl

  • Renderer name on Windows: direct3d

  • Renderer flags is 0x0a both on Ubuntu than Windows.

Compiled with:

g++ -std=c++11 main.cpp spaceinvadersmachine.cpp intel8080.cpp -lSDL2 -lSDL2_mixer -I/usr/include/SDL2 -I/usr/include/SDL2_mixer -Ofast -D_REENTRANT -o spaceinvaders.app

or:

g++ -std=c++11 main.cpp spaceinvadersmachine.cpp intel8080.cpp -lSDL2 -lSDL2_mixer -I/usr/include/SDL2 -I/usr/include/SDL2_mixer -O2 -D_REENTRANT -o spaceinvaders.app

Here some code:

// MAIN loop

while (!quitEmulator)
{
    // Handle events on queue
    while (SDL_PollEvent(&events) == 1)
    {
        switch (events.type)
        {
        case SDL_QUIT:
        {
            // Game's window has been closed
            quitEmulator = true;
            break;
        }
        case SDL_KEYDOWN:
        {
            // Set magnification to 1x
            if (events.key.keysym.sym == SDLK_1)
            {
                machine.magnificationFactor = 1.0;
                machine.magnificationRequest = true;
            }

            // Save game request
            if (events.key.keysym.sym == SDLK_2)
            {
                machine.saveGameRequest = true;
            }

            // Load game request
            if (events.key.keysym.sym == SDLK_3)
            {
                machine.loadGameRequest = true;
            }

            // Set magnification to 4x
            if (events.key.keysym.sym == SDLK_4)
            {
                machine.magnificationFactor = 2.0;
                machine.magnificationRequest = true;
            }

            // Set bases dipswitch request
            if (events.key.keysym.sym == SDLK_7)
            {
                machine.dipBasesRequest = true;
            }

            // Set bonus base dipswitch request
            if (events.key.keysym.sym == SDLK_8)
            {
                machine.dipBonusBaseRequest = true;
            }

            // Set magnification to 9x
            if (events.key.keysym.sym == SDLK_9)
            {
                machine.magnificationFactor = 3.0;
                machine.magnificationRequest = true;
            }

            // Set coin informations dipswitch request
            if (events.key.keysym.sym == SDLK_i)
            {
                machine.dipCoinInfoRequest = true;
            }

            // Game paused
            if (events.key.keysym.sym == SDLK_p)
            {
                gamePaused = !gamePaused;
                if (gamePaused)
                    cout << "INFO: Game paused!\n";
                else
                    cout << "INFO: Game resumed!\n";
            }

            // Reset request
            if (events.key.keysym.sym == SDLK_r)
            {
                machine.resetRequest = true;
            }

            // Change color mode
            if (events.key.keysym.sym == SDLK_c)
            {
                machine.coloredFrame = !machine.coloredFrame;

                if (machine.coloredFrame)
                    cout << "INFO: Color mode set to RGB\n";
                else
                    cout << "INFO: Color mode set to B/W\n";
            }

            break;
        }
        }
    }

    if (!gamePaused)
    {
        // Check keyboard inputs
        SDL_PumpEvents();
        machine.checkKeyboardInput();

        // Set bases dipswitch if requested
        if (machine.dipBasesRequest)
        {
            machine.setBasesDipswitch();
        }

        // Set bonus base dipswitch if requested
        if (machine.dipBonusBaseRequest)
        {
            machine.setBonusBaseDipswitch();
        }

        // Set coin informations dipswitch if requested
        if (machine.dipCoinInfoRequest)
        {
            machine.setCoinInfoDipswitch();
        }

        // Change magnification factor if requested
        if (machine.magnificationRequest)
        {
            machine.setMagnification();
        }

        // Check for interrupt
        if (CPUInterruptDeltaCycles >= cyclePerInterrupt)
        {
            CPUInterruptDeltaCycles = 0;

            machine.interruptRequest = true;

            if (machine.lastInterruptNumber == 1) // RST 1
            {
                machine.interruptNumber = 2; // RST 2
            }
            else // RST 2
            {
                machine.interruptNumber = 1; // RST 1
            }

            machine.lastInterruptNumber = machine.interruptNumber;
        }
        else
        {
            machine.interruptRequest = false;
        }

        // Execute next instruction
        CPU.executeROM(machine.interruptRequest, machine.interruptNumber);

        // Increments CPU's cycle counters
        CPUDeltaCycles += CPU.CPUCycles;
        CPUInterruptDeltaCycles += CPU.CPUCycles;

        // Check if OPCode is known
        if (CPU.unimplementedOPCode)
            quitEmulator = true;

        // Check for I/O
        machine.checkIO();

        // Check if a frame must be drawn
        // Save and Load if requested
        if (CPUDeltaCycles >= cyclePerFrame)
        {
            CPUDeltaCycles = 0;
            machine.createFrame();
            machine.showFrame();
            drewFrames += 1;

            while ((SDL_GetTicks() - lastFPSSynchronization) < frameTimeInterval)
            {
                // Waiting
                ;
            }

            lastFPSSynchronization = SDL_GetTicks();

            if (machine.saveGameRequest)
            {
                machine.saveGameRequest = false;
                machine.saveGame(CPUInterruptDeltaCycles);
            }

            if (machine.loadGameRequest)
            {
                machine.loadGameRequest = false;
                machine.loadGame(CPUInterruptDeltaCycles);

                // Remove pending keyboard events
                SDL_FlushEvent(SDL_KEYDOWN);
                SDL_FlushEvent(SDL_KEYUP);
                SDL_PumpEvents();
            }
        }

        // Reset if requested
        if (machine.resetRequest)
        {
            machine.resetRequest = false;
            machine.resetMachine();
        }
    }
}

// Show frame through SDL

void spaceInvadersMachine::showFrame()
{
    loadedSurface = SDL_CreateRGBSurfaceFrom(frameBuffer32, SCREEN_HEIGHT, SCREEN_WIDTH, 32, 4 * SCREEN_HEIGHT, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);

    if (loadedSurface == NULL)
    {
        printf("Unable to load video RAM! Error: %s\n", SDL_GetError());
    }
    else
    {
        // Create texture from surface
        gTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);

        // Delete surface
        SDL_FreeSurface(loadedSurface);

        if (gTexture == NULL)
        {
            printf("Failed to create texture image! Error: %s\n", SDL_GetError());
        }
        else
        {
            // Clear screen
            SDL_RenderClear(gRenderer);

            // Render texture to screen
            SDL_RenderCopyEx(gRenderer, gTexture, NULL, &destinationRectangle, 90, NULL, SDL_FLIP_NONE);

            // Delete texture (avoid memory leak)
            SDL_DestroyTexture(gTexture);

            // Update screen
            SDL_RenderPresent(gRenderer);
        }
    }
}

EDIT

I tried to comment out the part where the frame is created (so no frame update on the window) and the game remains slow (the audio is still playing), so maybe the problem is not inside frame creation function.

EDIT 2

I added

auto t1 = std::chrono::high_resolution_clock::now();

at the beginning of the main loop and

auto t2 = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 );

at the end. After the measurement I added this

if (CPUDeltaCycles == 0)
    cout << "Loop time: " << duration.count() << " microseconds\n";

in order to see the result only if a frame was drawn. It prints a value around 250, it is in hex, so 600 microseconds (0.6 ms). It's impossible, too fast for what I see, any idea on why <chrono> fails?

c++
c++11
sdl-2
asked on Stack Overflow Oct 1, 2019 by Francesco • edited Oct 2, 2019 by Francesco

1 Answer

0

You're creating a new surface and a new texture every frame. Yeah it's going to be slow, creating textures is a ridiculously slow operation. Don't do it so often.

If you need to write pixels directly, create a texture of the right size once (with SDL_TEXTUREACCESS_STREAMING) and then use SDL_LockTexture and SDL_UnlockTexture to update it (note: don't use SDL_UpdateTexture for this, it's not really much - if at all - better than recreating the texture performance wise). I'm guessing the windows version is fast "by accident" because your driver implementation notices you're doing something strange and quietly ignores what you're telling it to do and does something faster.

Of course without looking at the code it's hard to say this is the only problem with it, but this is pretty likely to be a (or maybe even the) bottleneck.

answered on Stack Overflow Oct 1, 2019 by Cubic • edited Oct 1, 2019 by Cubic

User contributions licensed under CC BY-SA 3.0