Mercurial > hg > dmlib
view src/dmsimple.c @ 2521:b19535da15e9
Add new field 'size_t extra' to DMC64ImageFormat for certain image format data
for which size field was previously abused for.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Fri, 15 May 2020 03:06:48 +0300 |
parents | 69a5af2eb1ea |
children | 9807ae37ad69 |
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; memset(&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; }