Mercurial > hg > dmlib
view src/dmsimple.c @ 2208:90ec1ec89c56
Revamp the palette handling in lib64gfx somewhat, add helper functions to
lib64util for handling external palette file options and add support for
specifying one of the "internal" palettes or external (.act) palette file to
gfxconv and 64vw.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 14 Jun 2019 05:01:12 +0300 |
parents | 78a0f44aa8b5 |
children | 69a5af2eb1ea |
line wrap: on
line source
/* * dmlib * -- Demo engine "player" code * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012-2015 Tecnic Software productions (TNSP) */ #include <SDL.h> #include "dmzlib.h" #include "dmengine.h" #include "dmargs.h" #include "dmtext.h" #include "dmimage.h" #include "setupfont.h" #ifdef DM_BUILT_IN_SETUP # include "setupimage.h" # include "setupmenubar.h" #endif // Setup specifics #define setupTextFieldLen 64 static const char *setupDataName = "SetupData.txt"; static const char *setupFontName = "SetupFont.dmf"; static DMVector setupMenuPos, setupMenuDim, setupText1Pos, setupText2Pos, setupMenuBarOffs, setupMenuBarDimAdj; static BOOL setupMenuCenter, setupTextCondensed; static char setupTextFullscreen[setupTextFieldLen], setupTextWindowed[setupTextFieldLen], setupTextPrefix[setupTextFieldLen], setupTextEnterToStart[setupTextFieldLen], setupImageName[setupTextFieldLen] = "SetupImage.png", setupMenuBarName[setupTextFieldLen] = "SetupMenuBar.png"; // Engine struct static DMEngineData engine; static const DMOptArg optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); static void argShowHelp() { dmPrintBanner(stdout, dmProgName, "[options]"); dmArgsPrintHelp(stdout, optList, optListN, 0); } static BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { (void) optArg; (void) currArg; switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: dmVerbosity++; break; default: dmErrorDBGMsg("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } static inline void dmFillRect(SDL_Surface *screen, const int x0, const int y0, const int x1, const int y1, const Uint32 col) { SDL_Rect rc; rc.x = x0; rc.y = y0; rc.w = x1 - x0 + 1; rc.h = y1 - y0 + 1; SDL_FillRect(screen, &rc, col); } static int engineShowProgress(int loaded, int total) { int dx = 60, dh = 20, dw = engine.screen->w - (2 * dx), dy = (engine.screen->h - dh) / 2; // Draw the progress bar dmClearSurface(engine.screen, dmMapRGBA(engine.screen, 0,0,0,0)); dmFillRect(engine.screen, dx, dy, dx+dw, dy+dh, dmMapRGB(engine.screen, 255,255,255)); dmFillRect(engine.screen, dx+1, dy+1, dx+dw-1, dy+dh-1, dmMapRGB(engine.screen, 0,0,0)); if (total > 0) { dmFillRect(engine.screen, dx+3, dy+3, dx + 3 + ((dw - 3) * loaded) / total, dy + dh - 3, dmMapRGB(engine.screen, 200,200,200)); } // Flip screen SDL_Surface dst; SDL_LockTexture(engine.texture, NULL, &dst.pixels, &dst.pitch); for (int yc = 0; yc < engine.screen->h; yc++) { memcpy(dst.pixels + dst.pitch * yc, engine.screen->pixels + engine.screen->pitch * yc, dst.pitch); } SDL_UnlockTexture(engine.texture); SDL_SetRenderDrawColor(engine.renderer, 0, 0, 0, 255); SDL_RenderClear(engine.renderer); SDL_RenderCopy(engine.renderer, engine.texture, NULL, NULL); SDL_RenderPresent(engine.renderer); return DMERR_OK; } static int engineLoadResources() { int err, loaded = 0, total = 0; BOOL first = TRUE; do { // Show a nice progress bar while loading if ((err = engineShowProgress(loaded, total)) != DMERR_OK) return err; err = dmResourcesPreload(engine.resources, first, &loaded, &total); first = FALSE; } while (err == DMERR_PROGRESS); return err; } static BOOL engineGenInitializeVideo(int width, int height, int depth, Uint32 flags) { dmPrint(1, "Initializing SDL video %d x %d x %dbpp, flags=0x%08x\n", width, height, depth, flags); SDL_SetWindowTitle(engine.window, dmProgDesc); return TRUE; } static BOOL engineInitializeVideo() { return engineGenInitializeVideo(engine.optVidWidth, engine.optVidHeight, engine.optVidDepth, engine.optVFlags); } typedef struct { int w, h, aspect; } DMModeEntry; DMModeEntry *engineModeList = NULL; int nengineModeList = 0, aengineModeList = 0; static int engineModeSort(const void *pa, const void *pb) { DMModeEntry *va = (DMModeEntry *) pa, *vb = (DMModeEntry *) pb; return (va->w - vb->w); } int engineAddModeToList(int w, int h) { DMModeEntry *mode; int i, aspect = engineGetVideoAspect(w, h); dmPrint(2, " - Proposed %d x %d\n", w, h); if (aspect <= 0) return DMERR_INVALID_ARGS; // Check if the mode is already in our list for (i = 0; i < nengineModeList; i++) { mode = &engineModeList[i]; if (mode->w == w && mode->h == h) return DMERR_OK; } // Check if the mode fits our criteria switch (engine.optVidSetup) { case DM_VSETUP_ASPECT: if (aspect != engine.optVidAspect) return DMERR_OK; break; case DM_VSETUP_ANY: break; } // Reallocate array if needed if (nengineModeList + 1 >= aengineModeList) { aengineModeList += 16; engineModeList = dmRealloc(engineModeList, sizeof(DMModeEntry) * aengineModeList); if (engineModeList == NULL) return DMERR_MALLOC; } // Store mode = &engineModeList[nengineModeList]; mode->w = w; mode->h = h; mode->aspect = engineGetVideoAspect(w, h); dmPrint(2, " - %d x %d, %d\n", w, h, mode->aspect); nengineModeList++; return DMERR_OK; } int engineParseSetupConfig(const char *filename) { DMResource *file = NULL; int res; char buf[256]; if ((res = dmf_open(engine.resources, filename, &file)) != DMERR_OK) { dmErrorMsg("Failed to open engine setup configuration '%s', %d: %s.\n", filename, res, dmErrorStr(res)); return res; } while (dmfgets(buf, sizeof(buf), file) != NULL) { ssize_t pos; // Trim line ending for (pos = strlen(buf) - 1; pos >= 0 && isspace(buf[pos]); pos--) buf[pos] = 0; // Find start of the line for (pos = 0; isspace(buf[pos]); pos++); // Skip empty lines and comments if (buf[pos] == 0 || buf[pos] == '#') continue; // XXX TODO FIXME: Needs better parsing, with size checks etc. char *str = buf+pos; if (sscanf(str, "menuPos %f %f", &setupMenuPos.x, &setupMenuPos.y) != 2 && sscanf(str, "menuDim %f %f", &setupMenuDim.x, &setupMenuDim.y) != 2 && sscanf(str, "text1Pos %f %f", &setupText1Pos.x, &setupText1Pos.y) != 2 && sscanf(str, "text2Pos %f %f", &setupText2Pos.x, &setupText2Pos.y) != 2 && sscanf(str, "menuBarOffs %f %f", &setupMenuBarOffs.x, &setupMenuBarOffs.y) != 2 && sscanf(str, "menuBarDimAdj %f %f", &setupMenuBarDimAdj.x, &setupMenuBarDimAdj.y) != 2 && sscanf(str, "menuCenter %d", &setupMenuCenter) != 1 && sscanf(str, "textCondensed %d", &setupTextCondensed) != 1 && sscanf(str, "textFullscreen %s", setupTextFullscreen) != 1 && sscanf(str, "textWindowed %s", setupTextWindowed) != 1 && sscanf(str, "textPrefix %s", setupTextPrefix) != 1 && sscanf(str, "textEnterToStart %s", setupTextEnterToStart) != 1 && sscanf(str, "setupImageName %s", setupImageName) != 1 && sscanf(str, "setupMenuBarName %s", setupMenuBarName) != 1 ) { res = dmErrorDBG(DMERR_INVALID_DATA, "Syntax error in configuration:\n%s\n", buf); goto out; } } out: dmf_close(file); return res; } static inline DMFloat vsX(DMVector vec) { return (DMFloat) engine.screen->w * vec.x; } static inline DMFloat vsY(DMVector vec) { return (DMFloat) engine.screen->h * vec.y; } int engineVideoSetup() { DMBitmapFont *menuFont = NULL; DMResource *file = NULL; SDL_Surface *menuBgImage = NULL, *menuBarImage = NULL; int result, menuState = -1; BOOL menuFullScreen = TRUE; // Compute a list of valid modes if (!engineGenInitializeVideo(DM_VSETUP_WIDTH, DM_VSETUP_HEIGHT, engine.optVidDepth, engine.optVFlags)) goto out; SDL_Rect **modes = SDL_ListModes(engine.screen->format, engine.screen->flags | SDL_FULLSCREEN); if (modes == (SDL_Rect**) 0) { dmError(DMERR_INIT_FAIL, "No compatible video resolutions/depths available at all. Bailing out.\n"); goto out; } if (modes != (SDL_Rect**) -1) { int i; dmPrint(1, "Enumerating modes.\n"); for (i = 0; modes[i] != NULL; i++) engineAddModeToList(modes[i]->w, modes[i]->h); } if (nengineModeList == 0) { dmError(DMERR_INIT_FAIL, "Umm, no modes found.\n"); goto out; } qsort(engineModeList, nengineModeList, sizeof(engineModeList[0]), engineModeSort); // Open video temporarily if (!engineGenInitializeVideo(DM_VSETUP_WIDTH, DM_VSETUP_HEIGHT, 32, SDL_SWSURFACE | SDL_DOUBLEBUF)) goto out; // Get setup data setupMenuPos.x = 0.18750f; setupMenuPos.y = 0.41666f; setupMenuDim.x = 0.625f; setupMenuDim.y = 0.41666f; setupMenuBarOffs.x = 0; setupMenuBarOffs.y = -0.0001; setupMenuBarDimAdj.x = 0; setupMenuBarDimAdj.y = 0; setupText1Pos.x = 0.3f; setupText1Pos.y = 0.9f; setupText2Pos.x = 0.25f; setupText2Pos.y = 0.85f; strcpy(setupTextFullscreen , "FULLSCREEN"); strcpy(setupTextWindowed , " WINDOWED "); strcpy(setupTextEnterToStart , "ENTER TO START THE DEMO"); strcpy(setupTextPrefix , "USE LEFT/RIGHT ARROW TO TOGGLE : "); if (engineParseSetupConfig(setupDataName) != DMERR_OK) goto out; // Fetch and decompress setup image, try regular resources first if ((result = dmf_open(engine.resources, setupImageName, &file)) == DMERR_OK #ifdef DM_BUILT_IN_SETUP || (result = dmf_open_memio(NULL, setupImageName, setupImage, sizeof(setupImage), &file)) == DMERR_OK #endif ) { menuBgImage = dmLoadImage(file); dmf_close(file); } if ((result = dmf_open(engine.resources, setupMenuBarName, &file)) == DMERR_OK #ifdef DM_BUILT_IN_SETUP || (result = dmf_open_memio(NULL, setupMenuBarName, setupMenuBar, sizeof(setupMenuBar), &file)) == DMERR_OK #endif ) { menuBarImage = dmLoadImage(file); dmf_close(file); } if (menuBgImage == NULL || menuBarImage == NULL) { dmErrorDBGMsg( "Could not instantiate setup screen images, %d: %s\n", result, dmErrorStr(result)); goto out; } if (menuBgImage->w != DM_VSETUP_WIDTH || menuBgImage->h != DM_VSETUP_HEIGHT) { dmErrorDBGMsg( "Setup screen background image does not match " "required dimensions (%dx%d vs %dx%d)\n", menuBgImage->w, menuBgImage->h, DM_VSETUP_WIDTH, DM_VSETUP_HEIGHT); goto out; } // Load up the bitmap font if ((result = dmf_open(engine.resources, setupFontName, &file)) == DMERR_OK #ifdef DM_BUILT_IN_SETUP || (result = dmf_open_memio(NULL, setupFontName, setupFont, sizeof(setupFont), &file)) == DMERR_OK #endif ) { result = dmLoadBitmapFont(file, &menuFont); dmf_close(file); } if (result != DMERR_OK) { dmErrorDBGMsg( "Could not instantiate setup screen font, %d: %s\n", result, dmErrorStr(result)); goto out; } SDL_Surface *tmp = dmConvertScaledSurface(menuBgImage, engine.screen->format, engine.screen->flags, engine.screen->w, engine.screen->h); if (tmp == NULL) { dmErrorDBGMsg( "Could not convert setup screen background image.\n"); goto out; } SDL_FreeSurface(menuBgImage); menuBgImage = tmp; SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); // Enter the main loop of the menu char menuStr[256]; int menuOffset = 0, menuIndex = 0, menuEntryHeight = menuFont->height + 2, menuHeight = vsY(setupMenuDim) / menuEntryHeight; menuState = 0; engine.startTime = SDL_GetTicks(); while (!menuState) { while (SDL_PollEvent(&engine.event)) switch (engine.event.type) { case SDL_KEYDOWN: switch (engine.event.key.keysym.sym) { case SDLK_ESCAPE: menuState = -1; break; case SDLK_RETURN: menuState = 1; break; case SDLK_UP: if (menuIndex > 0) menuIndex--; else if (menuOffset > 0) menuOffset--; break; case SDLK_DOWN: if (menuIndex < menuHeight - 1 && menuOffset + menuIndex < nengineModeList - 1) menuIndex++; else if (menuOffset + menuIndex < nengineModeList - 1) menuOffset++; break; case SDLK_LEFT: menuFullScreen = FALSE; break; case SDLK_RIGHT: menuFullScreen = TRUE; break; case SDLK_SPACE: menuFullScreen = !menuFullScreen; break; default: break; } break; case SDL_QUIT: menuState = -1; break; } // Draw frame engine.frameTime = SDL_GetTicks(); // Render the menu dmDirectBlitSurface(menuBgImage, engine.screen); // XXX/TODO: Some hardcoded bits here ... float t = engineGetTimeDT(&engine); int index, entry; for (index = 0, entry = menuOffset; entry < nengineModeList && index < menuHeight; index++, entry++) { DMModeEntry *mode = &engineModeList[entry]; if (entry == menuOffset + menuIndex) { dmScaledBlitSurface32to32TransparentGA(menuBarImage, vsX(setupMenuPos) + vsX(setupMenuBarOffs), vsY(setupMenuPos) + vsY(setupMenuBarOffs) + (index * menuEntryHeight), vsX(setupMenuDim) + vsX(setupMenuBarDimAdj), menuEntryHeight + vsY(setupMenuBarDimAdj), engine.screen, 200 + sin(t * 10.0) * 50); } snprintf(menuStr, sizeof(menuStr), "%4d X %-4d - %d:%d", mode->w, mode->h, mode->aspect / 1000, mode->aspect % 1000); DMFloat posX = setupMenuCenter ? 2.0f + (vsX(setupMenuDim) - menuFont->width * strlen(menuStr)) / 2.0f : 2.0f; dmDrawBMTextConst( engine.screen, menuFont, setupTextCondensed, DMD_TRANSPARENT, vsX(setupMenuPos) + posX, vsY(setupMenuPos) + (index * menuEntryHeight), menuStr); } dmDrawBMTextConst( engine.screen, menuFont, setupTextCondensed, DMD_TRANSPARENT, vsX(setupText2Pos), vsY(setupText2Pos), setupTextEnterToStart); snprintf(menuStr, sizeof(menuStr), "%s%s", setupTextPrefix, menuFullScreen ? setupTextFullscreen : setupTextWindowed); dmDrawBMTextConst( engine.screen, menuFont, setupTextCondensed, DMD_TRANSPARENT, vsX(setupText1Pos), vsY(setupText1Pos), menuStr); // Flip screen SDL_Flip(engine.screen); SDL_Delay(25); } // Okay, we accepted the selection if (menuState == 1) { DMModeEntry *mode = &engineModeList[menuOffset + menuIndex]; engine.optVidNative = mode->w == engine.optVidWidth && mode->h == engine.optVidHeight; engine.optVidWidth = mode->w; engine.optVidHeight = mode->h; engine.optVidAspect = mode->aspect; if (menuFullScreen) engine.optVFlags |= SDL_FULLSCREEN; } out: SDL_FreeSurface(menuBgImage); SDL_FreeSurface(menuBarImage); dmFreeBitmapFont(menuFont); return menuState; } int main(int argc, char *argv[]) { int err; BOOL initSDL = FALSE; dmMemset(&engine, 0, sizeof(engine)); // Pre-initialization if ((err = demoPreInit(&engine)) != DMERR_OK) goto out; if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, NULL, OPTH_BAILOUT)) return DMERR_INIT_FAIL; dmPrint(0, "%s\n" "%s\n", dmProgDesc, dmProgAuthor); dmPrint(0, "Using libSDL" #if defined(DM_USE_PACKFS) && defined(DM_USE_ZLIB) ", zlib" #endif #ifdef DM_USE_TREMOR ", Tremor Vorbis codec" #endif " and modified stb_image.\n" "See README.txt for more information.\n"); // Initialize dmZlib if ((err = dmZLibInit()) != DMERR_OK) { dmErrorDBGMsg( "Failed to initialize dmzlib: %d, %s.\n", err, dmErrorStr(err)); goto out; } // Initialize resource subsystem dmPrint(1, "Initializing resources subsystem.\n"); if ((err = dmResourcesInit(&engine.resources, engine.optPackFilename, "data/", engine.optResFlags, engineClassifier)) != DMERR_OK) { dmErrorMsg( "Could not initialize resource manager, #%d: %s.\n", err, dmErrorStr(err)); goto out; } // Initialize SDL components dmPrint(1, "Initializing libSDL.\n"); if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) { dmError(DMERR_INIT_FAIL, "Could not initialize SDL: %s\n", SDL_GetError()); goto out; } initSDL = TRUE; // Set start time engine.startTimeAudio = -1; engine.startTime = -1; // Present video mode selector if (engine.optVidAspect <= 0) engine.optVidAspect = engineGetVideoAspect(engine.optVidWidth, engine.optVidHeight); if (engine.optVidSetup) { if ((err = engineVideoSetup()) <= 0) goto out; } // Initialize audio parts if (engine.optAfmt.freq == 0 && engine.optAfmt.channels == 0) { engine.optAfmt.freq = 44100; engine.optAfmt.format = AUDIO_S16SYS; engine.optAfmt.channels = 2; } if (engine.optAfmt.samples == 0) engine.optAfmt.samples = engine.optAfmt.freq / 50; switch (engine.optAudioSetup) { case DM_ASETUP_JSS: #ifdef DM_USE_JSS jssInit(); switch (engine.optAfmt.format) { case AUDIO_S16SYS: engine.jssFormat = JSS_AUDIO_S16; break; case AUDIO_U16SYS: engine.jssFormat = JSS_AUDIO_U16; break; case AUDIO_S8: engine.jssFormat = JSS_AUDIO_S8; break; case AUDIO_U8: engine.jssFormat = JSS_AUDIO_U8; break; } dmPrint(1, "Initializing miniJSS mixer with fmt=%d, chn=%d, freq=%d\n", engine.jssFormat, engine.optAfmt.channels, engine.optAfmt.freq); if ((engine.jssDev = jvmInit(engine.jssFormat, engine.optAfmt.channels, engine.optAfmt.freq, JMIX_AUTO)) == NULL) { dmErrorDBGMsg( "jvmInit() returned NULL, voi perkele.\n"); goto out; } if ((engine.jssPlr = jmpInit(engine.jssDev)) == NULL) { dmErrorDBGMsg( "jmpInit() returned NULL\n"); goto out; } #else dmErrorDBGMsg("miniJSS support not included.\n"); #endif break; case DM_ASETUP_TREMOR: #ifndef DM_USE_TREMOR dmErrorDBGMsg("Tremor support not included.\n"); #endif break; } // Initialize SDL audio dmPrint(1, "Trying to init SDL audio with: fmt=%d, chn=%d, freq=%d, samples=%d\n", engine.optAfmt.format, engine.optAfmt.channels, engine.optAfmt.freq, engine.optAfmt.samples); if ((err = engineInitAudioParts(&engine)) != DMERR_OK) { dmErrorMsg( "engineInitAudioParts() failed: #%d: %s\n", err, dmErrorStr(err)); goto out; } // Initialize SDL video if (engine.demoInitPreVideo != NULL && (err = engine.demoInitPreVideo(&engine)) != DMERR_OK) { dmErrorMsg( "demoInitPreVideo() failed, #%d: %s\n", err, dmErrorStr(err)); goto out; } if (!engineInitializeVideo()) goto out; if (engine.demoInitPostVideo != NULL && (err = engine.demoInitPostVideo(&engine)) != DMERR_OK) { dmErrorDBGMsg( "demoInitPostVideo() failed, #%d: %s\n", err, dmErrorStr(err)); goto out; } // Hide cursor SDL_ShowCursor(SDL_DISABLE); // Load resources dmPrint(1, "Loading resources, please wait...\n"); if ((err = engineLoadResources()) != DMERR_OK) { dmErrorMsg( "Error loading resources, #%d: %s.\n", err, dmErrorStr(err)); goto out; } // Final initializations dmPrint(1, "Initializing demo...\n"); if ((err = engine.demoInit(&engine)) != DMERR_OK) { dmErrorMsg( "Failure in demoInit(), #%d: %s\n", err, dmErrorStr(err)); goto out; } // Initialize effects if ((err = engineInitializeEffects(&engine)) != DMERR_OK) { dmErrorMsg( "Effects initialization failed, #%d: %s\n", err, dmErrorStr(err)); goto out; } // Use a timeline, if set #ifdef DM_USE_TIMELINE if (engine.timeline != NULL) { if ((err = dmLoadTimeline(engine.timeline, &engine.tl)) != DMERR_OK) { dmErrorDBGMsg( "Error loading timeline, #%d: %s\n", err, dmErrorStr(err)); goto out; } if ((err = dmPrepareTimeline(engine.tl, engine.ptl)) != DMERR_OK) { dmErrorDBGMsg( "Error creating prepared timeline, #%d: %s\n", err, dmErrorStr(err)); goto out; } } #endif dmPrint(1, "Starting up.\n"); engine.startTime = SDL_GetTicks(); SDL_LockAudio(); enginePauseAudio(&engine, 0); SDL_UnlockAudio(); while (!engine.exitFlag) { while (SDL_PollEvent(&engine.event)) switch (engine.event.type) { case SDL_KEYDOWN: switch (engine.event.key.keysym.sym) { case SDLK_ESCAPE: engine.exitFlag = TRUE; break; case SDLK_SPACE: engine.pauseFlag = !engine.pauseFlag; break; case SDLK_f: engine.optVFlags ^= SDL_FULLSCREEN; if (!engineInitializeVideo()) goto out; break; case SDLK_RETURN: if (engine.event.key.keysym.mod & KMOD_ALT) { engine.optVFlags ^= SDL_FULLSCREEN; if (!engineInitializeVideo()) goto out; } break; default: break; } break; case SDL_VIDEOEXPOSE: break; case SDL_QUIT: engine.exitFlag = TRUE; break; } // Draw frame engine.frameTime = SDL_GetTicks(); if (engine.pauseFlag != engine.paused) { engine.paused = engine.pauseFlag; engine.pauseTime = engineGetTick(&engine); } if (engine.paused) { engine.startTime = engine.frameTime - engine.pauseTime; } // Call main tick if (engine.demoRender != NULL) { if ((err = engine.demoRender(&engine)) != DMERR_OK) goto out; } #ifdef DM_USE_TIMELINE else { if ((err = dmExecuteTimeline(engine.ptl, engine.screen, engineGetTick(&engine))) != DMERR_OK) goto out; } #endif SDL_Delay(engine.paused ? 100 : 20); engine.frameCount++; } // Print benchmark results engine.endTime = SDL_GetTicks(); dmPrint(1, "%d frames in %d ms, fps = %1.3f\n", engine.frameCount, engine.endTime - engine.startTime, (float) (engine.frameCount * 1000.0f) / (float) (engine.endTime - engine.startTime)); dmPrint(1, "startTime=%d, startTimeAudio=%d, offsetTime=%d\n", engine.startTime, engine.startTimeAudio, engine.offsetTime); out: dmPrint(1, "Shutting down.\n"); SDL_ShowCursor(SDL_ENABLE); SDL_LockAudio(); enginePauseAudio(&engine, 1); #ifdef DM_USE_JSS if (engine.optAudioSetup == DM_ASETUP_JSS) { jmpClose(engine.jssPlr); jvmClose(engine.jssDev); jssClose(); } #endif if (engine.audioSimThread != NULL) { dmMutexLock(engine.audioStreamMutex); engine.audioSimDone = TRUE; dmMutexUnlock(engine.audioStreamMutex); SDL_WaitThread(engine.audioSimThread, NULL); } SDL_UnlockAudio(); if (engine.audioStreamMutex != NULL) dmDestroyMutex(engine.audioStreamMutex); #ifdef DM_USE_TIMELINE dmFreeTimeline(engine.tl); dmFreePreparedTimelineData(engine.ptl); #endif engineShutdownEffects(&engine); dmResourcesClose(engine.resources); if (engine.demoShutdown != NULL) engine.demoShutdown(&engine); if (initSDL) SDL_Quit(); if (engine.demoQuit != NULL) engine.demoQuit(&engine); dmZLibClose(); return 0; }