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?
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.
User contributions licensed under CC BY-SA 3.0