Mercurial > hg > dmlib
view tools/ppl.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 | e3f0eaf23f4f |
children | ba696835f66d |
line wrap: on
line source
/* * Cyrbe Pasci Player - A simple SDL-based UI for XM module playing * Programmed and designed by Matti 'ccr' Hamalainen * (C) Copyright 2012-2018 Tecnic Software productions (TNSP) * * Please read file 'COPYING' for information on license and distribution. */ #include "dmlib.h" #include <SDL.h> #include "libgutil.h" #include "jss.h" #include "jssmod.h" #include "jssmix.h" #include "jssplr.h" #include "dmargs.h" #include "dmimage.h" #include "dmtext.h" #include "setupfont.h" struct { BOOL exitFlag; SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; SDL_Surface *screen; SDL_Event event; int optScrWidth, optScrHeight, optVFlags, optScrDepth; int actChannel; BOOL pauseFlag; JSSModule *mod; JSSMixer *dev; JSSPlayer *plr; SDL_AudioSpec afmt; } eng; struct { Uint32 boxBg, inboxBg, box1, box2, viewDiv, activeRow, activeChannel, scope, muted, black, red; } col; DMBitmapFont *font = NULL; char *optFilename = NULL; int optOutFormat = JSS_AUDIO_S16, optOutChannels = 2, optOutFreq = 48000, optMuteOChannels = -1, optStartOrder = 0; BOOL optUsePlayTime = FALSE; size_t optPlayTime; static const DMOptArg optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, { 2, 0, "fs", "Fullscreen", OPT_NONE }, { 3, 'w', "window", "Initial window size/resolution -w 640x480", OPT_ARGREQ }, { 4, '1', "16bit", "16-bit output", OPT_NONE }, { 5, '8', "8bit", "8-bit output", OPT_NONE }, { 6, 'm', "mono", "Mono output", OPT_NONE }, { 7, 's', "stereo", "Stereo output", OPT_NONE }, { 8, 'f', "freq", "Output frequency", OPT_ARGREQ }, { 9, 'M', "mute", "Mute other channels than #", OPT_ARGREQ }, { 10, 'o', "order", "Start from order #", OPT_ARGREQ }, { 11, 't', "time", "Play for # seconds", OPT_ARGREQ }, }; const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp() { dmPrintBanner(stdout, dmProgName, "[options] <module>"); dmArgsPrintHelp(stdout, optList, optListN, 0); } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: dmVerbosity++; break; case 2: eng.optVFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP; break; case 3: { int w, h; if (sscanf(optArg, "%dx%d", &w, &h) == 2) { if (w < 320 || h < 200 || w > 3200 || h > 3200) { dmErrorMsg("Invalid width or height: %d x %d\n", w, h); return FALSE; } eng.optScrWidth = w; eng.optScrHeight = h; } else { dmErrorMsg("Invalid size argument '%s'.\n", optArg); return FALSE; } } break; case 4: optOutFormat = JSS_AUDIO_S16; break; case 5: optOutFormat = JSS_AUDIO_U8; break; case 6: optOutChannels = JSS_AUDIO_MONO; break; case 7: optOutChannels = JSS_AUDIO_STEREO; break; case 8: optOutFreq = atoi(optArg); break; case 9: optMuteOChannels = atoi(optArg); break; case 10: optStartOrder = atoi(optArg); break; case 11: optPlayTime = atoi(optArg); optUsePlayTime = TRUE; break; default: dmErrorMsg("Unimplemented option argument '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (!optFilename) optFilename = currArg; else { dmErrorMsg("Too many filename arguments '%s'\n", currArg); return FALSE; } return TRUE; } static inline Uint32 dmCol(const float r, const float g, const float b) { return dmMapRGB(eng.screen, 255.0f * r, 255.0f * g, 255.0f * b); } BOOL dmInitializeVideo() { SDL_DestroyTexture(eng.texture); SDL_FreeSurface(eng.screen); if ((eng.texture = SDL_CreateTexture(eng.renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, eng.optScrWidth, eng.optScrHeight)) == NULL) { dmErrorMsg("Could not create SDL texture.\n"); return FALSE; } if ((eng.screen = SDL_CreateRGBSurfaceWithFormat(0, eng.optScrWidth, eng.optScrHeight, 32, SDL_PIXELFORMAT_RGBA32)) == NULL) { dmErrorMsg("Could not create SDL surface.\n"); return FALSE; } col.black = dmCol(0 , 0 , 0); col.inboxBg = dmCol(0.6, 0.5, 0.2); col.boxBg = dmCol(0.7, 0.6, 0.3); col.box1 = dmCol(1.0, 0.9, 0.6); col.box2 = dmCol(0.3, 0.3, 0.15); col.viewDiv = dmCol(0 , 0 , 0); col.activeRow = dmCol(0.5, 0.4, 0.1); col.activeChannel = dmCol(0.6, 0.8, 0.2); col.muted = dmCol(0.3, 0.1, 0.1); col.scope = dmCol(0 , 0.8, 0); col.red = dmCol(1 , 0 , 0); return TRUE; } // // XXX TODO: To display actual continuous sample data for channel, // we would need to have separate "FIFO" buffers for each, updating // them with new data from incoming channel data. // void dmDisplayChn(SDL_Surface *screen, int x0, int y0, int x1, int y1, int nchannel, JSSChannel *chn) { int yh = y1 - y0 - 2; if (yh < 10 || chn == NULL) return; int xc, ym = y0 + (y1 - y0) / 2, vol = FP_GETH32(chn->chVolume); DMFixedPoint offs = chn->chPos; int len = FP_GETH32(chn->chSize); Uint32 pitch = screen->pitch / sizeof(Uint32); Uint32 *pix = screen->pixels; Sint16 *data = chn->chData; dmFillBox3D(screen, x0, y0, x1, y1, (chn->chMute ? col.muted : col.black), nchannel == eng.actChannel ? col.red : col.box2, nchannel == eng.actChannel ? col.red : col.box1); if (chn->chData == NULL || !chn->chPlaying) return; if (chn->chDirection) { for (xc = x0 + 1; xc < x1 - 1; xc++) { if (FP_GETH32(offs) >= len) break; Sint16 val = ym + (data[FP_GETH32(offs)] * yh * vol) / (65535 * 255); pix[xc + val * pitch] = col.scope; FP_ADD(offs, chn->chDeltaO); } } else { for (xc = x0 + 1; xc < x1 - 1; xc++) { if (FP_GETH32(offs) < 0) break; Sint16 val = ym + (data[FP_GETH32(offs)] * yh * vol) / (65535 * 255); pix[xc + val * pitch] = col.scope; FP_SUB(offs, chn->chDeltaO); } } } void dmDisplayChannels(SDL_Surface *screen, int x0, int y0, int x1, int y1, JSSMixer *dev) { int nchannel, qx, qy, qwidth = x1 - x0, qheight = y1 - y0, nwidth = jsetNChannels, nheight = 1; if (qheight < 40) return; while (qwidth / nwidth <= 60 && qheight / nheight >= 40) { nheight++; nwidth /= nheight; } // fprintf(stderr, "%d x %d\n", nwidth, nheight); if (qheight / nheight <= 40) { nwidth = qwidth / 60; nheight = qheight / 40; } qwidth /= nwidth; qheight /= nheight; for (nchannel = qy = 0; qy < nheight && nchannel < jsetNChannels; qy++) { for (qx = 0; qx < nwidth && nchannel < jsetNChannels; qx++) { int xc = x0 + qx * qwidth, yc = y0 + qy * qheight; dmDisplayChn(screen, xc + 1, yc + 1, xc + qwidth - 1, yc + qheight - 1, nchannel, &dev->channels[nchannel]); nchannel++; } } } static const char patNoteTable[12][3] = { "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" }; #define jmpNMODEffectTable (36) static const char jmpMODEffectTable[jmpNMODEffectTable] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const char jmpHexTab[16] = "0123456789ABCDEF"; static inline char dmHexVal(int v) { return jmpHexTab[v & 15]; } void dmPrintNote(SDL_Surface *screen, int xc, int yc, JSSNote *n) { char text[32]; char *ptr = text; switch (n->note) { case jsetNotSet: strcpy(ptr, "..._"); break; case jsetNoteOff: strcpy(ptr, "===_"); break; default: sprintf(ptr, "%s%i_", patNoteTable[n->note % 12], n->note / 12); break; } ptr += 4; if (n->instrument != jsetNotSet) { int v = n->instrument + 1; *ptr++ = dmHexVal(v >> 4); *ptr++ = dmHexVal(v); } else { *ptr++ = '.'; *ptr++ = '.'; } *ptr++ = '_'; if (n->volume == jsetNotSet) { *ptr++ = '.'; *ptr++ = '.'; } else if (n->volume >= 0x00 && n->volume <= 0x40) { *ptr++ = dmHexVal(n->volume >> 4); *ptr++ = dmHexVal(n->volume); } else { char c; switch (n->volume & 0xf0) { case 0x50: c = '-'; break; case 0x60: c = '+'; break; case 0x70: c = '/'; break; case 0x80: c = '\\'; break; case 0x90: c = 'S'; break; case 0xa0: c = 'V'; break; case 0xb0: c = 'P'; break; case 0xc0: c = '<'; break; case 0xd0: c = '>'; break; case 0xe0: c = 'M'; break; default: c = '?'; break; } *ptr++ = c; *ptr++ = dmHexVal(n->volume); } *ptr++ = '_'; if (n->effect >= 0 && n->effect < jmpNMODEffectTable) *ptr++ = jmpMODEffectTable[n->effect]; else *ptr++ = (n->effect == jsetNotSet ? '.' : '?'); if (n->param != jsetNotSet) { *ptr++ = dmHexVal(n->param >> 4); *ptr++ = dmHexVal(n->param); } else { *ptr++ = '.'; *ptr++ = '.'; } *ptr = 0; dmDrawBMTextConstQ(screen, font, DMD_TRANSPARENT, xc, yc, text); } void dmDisplayPattern(SDL_Surface *screen, int x0, int y0, int x1, int y1, JSSPattern *pat, int row) { int cwidth = (font->width * 10 + 3 * 4 + 5), lwidth = 6 + font->width * 3, qy0 = y0 + font->height + 2, qy1 = y1 - font->height - 2, qwidth = ((x1 - x0 - lwidth) / cwidth), qheight = ((qy1 - qy0 - 4) / (font->height + 1)), nrow, nchannel, yc, choffs, midrow = qheight / 2; if (pat == NULL) return; if (qwidth > pat->nchannels) qwidth = pat->nchannels; if (eng.actChannel < qwidth / 2) choffs = 0; else if (eng.actChannel >= pat->nchannels - qwidth/2) choffs = pat->nchannels - qwidth; else choffs = eng.actChannel - qwidth/2; dmDrawBox3D(screen, x0 + lwidth, qy0, x1, qy1, col.box2, col.box1); for (nchannel = 0; nchannel < qwidth; nchannel++) { int bx0 = x0 + lwidth + 1 + nchannel * cwidth, bx1 = bx0 + cwidth; dmFillRect(screen, bx0+1, qy0+1, bx1-1, qy1-1, (eng.actChannel == nchannel + choffs) ? col.activeChannel : col.inboxBg); } yc = qy0 + 2 + (font->height + 1) * midrow; dmFillRect(screen, x0 + lwidth + 1, yc - 1, x1 - 1, yc + font->height, col.activeRow); for (nchannel = 0; nchannel < qwidth; nchannel++) { int bx0 = x0 + lwidth + 1 + nchannel * cwidth, bx1 = bx0 + cwidth; dmDrawVLine(screen, qy0 + 1, qy1 - 1, bx1, col.viewDiv); if (jvmGetMute(eng.dev, nchannel + choffs)) { dmDrawBMTextConstQ(screen, font, DMD_TRANSPARENT, bx0 + (cwidth - font->width * 5) / 2, qy1 + 3, "MUTED"); } dmDrawBMTextQ(screen, font, DMD_TRANSPARENT, bx0 + (cwidth - font->width * 3) / 2, y0 + 1, "%3d", nchannel + choffs); } for (nrow = 0; nrow < qheight; nrow++) { int crow = nrow - midrow + row; yc = qy0 + 2 + (font->height + 1) * nrow; if (crow >= 0 && crow < pat->nrows) { dmDrawBMTextQ(screen, font, DMD_TRANSPARENT, x0, yc, "%03d", crow); for (nchannel = 0; nchannel < qwidth; nchannel++) { if (choffs + nchannel >= pat->nchannels) break; dmPrintNote(screen, x0 + lwidth + 4 + nchannel * cwidth, yc, pat->data + (pat->nchannels * crow) + choffs + nchannel); } } } } void audioCallback(void *userdata, Uint8 *stream, int len) { JSSMixer *d = (JSSMixer *) userdata; if (d != NULL) { jvmRenderAudio(d, stream, len / jvmGetSampleSize(d)); } } void dmMuteChannels(BOOL mute) { int i; for (i = 0; i < eng.mod->nchannels; i++) jvmMute(eng.dev, i, mute); } int main(int argc, char *argv[]) { BOOL initSDL = FALSE, audioInit = FALSE; DMResource *file = NULL; int result = -1; BOOL muteState = FALSE; dmMemset(&eng, 0, sizeof(eng)); eng.optScrWidth = 640; eng.optScrHeight = 480; eng.optScrDepth = 32; dmInitProg("CBP", "Cyrbe Basci Player", "0.2", NULL, NULL); // Parse arguments if (!dmArgsProcess(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_BAILOUT)) exit(1); // Open the files if (optFilename == NULL) { dmErrorMsg("No filename specified.\n"); return 1; } if ((result = dmf_open_stdio(optFilename, "rb", &file)) != DMERR_OK) { dmErrorMsg("Error opening file '%s', %d: (%s)\n", optFilename, result, dmErrorStr(result)); return 1; } // Initialize miniJSS jssInit(); // Read module file dmMsg(1, "Reading file: %s\n", optFilename); #ifdef JSS_SUP_XM result = jssLoadXM(file, &eng.mod, TRUE); #endif #ifdef JSS_SUP_JSSMOD dmfreset(file); if (result != DMERR_OK) { dmMsg(1, "* Trying JSSMOD ...\n"); result = jssLoadJSSMOD(file, &eng.mod, TRUE); dmfreset(file); if (result == DMERR_OK) result = jssLoadJSSMOD(file, &eng.mod, FALSE); } else { dmMsg(2, "* Trying XM...\n"); result = jssLoadXM(file, &eng.mod, FALSE); } #endif dmf_close(file); if (result != DMERR_OK) { dmErrorMsg("Error loading module file, %d: %s\n", result, dmErrorStr(result)); goto error_exit; } // Try to convert it if ((result = jssConvertModuleForPlaying(eng.mod)) != DMERR_OK) { dmErrorMsg("Could not convert module for playing, %d: %s\n", result, dmErrorStr(result)); goto error_exit; } // Get font result = dmf_open_memio(NULL, "pplfont.fnt", engineSetupFont, sizeof(engineSetupFont), &file); if (result != DMERR_OK) { dmErrorMsg("Error opening font file 'pplfont.fnt', #%d: %s\n", result, dmErrorStr(result)); goto error_exit; } result = dmLoadBitmapFont(file, &font); dmf_close(file); if (result != DMERR_OK) { dmErrorMsg("Could not load font from file, %d: %s\n", result, dmErrorStr(result)); goto error_exit; } SDL_Color pal[DMFONT_NPALETTE]; for (int n = 0; n < DMFONT_NPALETTE; n++) { pal[n].r = pal[n].g = pal[n].b = 0; pal[n].a = n > 0 ? 255 : 0; } dmSetBitmapFontPalette(font, pal, 0, DMFONT_NPALETTE); // Initialize SDL components if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) { dmErrorMsg("Could not initialize SDL: %s\n", SDL_GetError()); goto error_exit; } initSDL = TRUE; // Initialize mixing device dmMsg(2, "Initializing miniJSS mixer with: %d, %d, %d\n", optOutFormat, optOutChannels, optOutFreq); eng.dev = jvmInit(optOutFormat, optOutChannels, optOutFreq, JMIX_AUTO); if (eng.dev == NULL) { dmErrorMsg("jvmInit() returned NULL\n"); goto error_exit; } switch (optOutFormat) { case JSS_AUDIO_S16: eng.afmt.format = AUDIO_S16SYS; break; case JSS_AUDIO_U16: eng.afmt.format = AUDIO_U16SYS; break; case JSS_AUDIO_S8: eng.afmt.format = AUDIO_S8; break; case JSS_AUDIO_U8: eng.afmt.format = AUDIO_U8; break; default: dmErrorMsg("Unsupported audio format %d (could not set matching SDL format)\n", optOutFormat); goto error_exit; } eng.afmt.freq = optOutFreq; eng.afmt.channels = optOutChannels; eng.afmt.samples = optOutFreq / 16; eng.afmt.callback = audioCallback; eng.afmt.userdata = (void *) eng.dev; // Open the audio device if (SDL_OpenAudio(&eng.afmt, NULL) < 0) { dmErrorMsg("Couldn't open SDL audio: %s\n", SDL_GetError()); goto error_exit; } audioInit = TRUE; // Initialize player if ((eng.plr = jmpInit(eng.dev)) == NULL) { dmErrorMsg("jmpInit() returned NULL\n"); goto error_exit; } jvmSetCallback(eng.dev, jmpExec, eng.plr); jmpSetModule(eng.plr, eng.mod); jmpPlayOrder(eng.plr, optStartOrder); jvmSetGlobalVol(eng.dev, 200); if (optMuteOChannels >= 0 && optMuteOChannels < eng.mod->nchannels) { dmMuteChannels(TRUE); jvmMute(eng.dev, optMuteOChannels, FALSE); eng.actChannel = optMuteOChannels; muteState = TRUE; } // Open window if ((eng.window = SDL_CreateWindow(dmProgName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, eng.optScrWidth, eng.optScrHeight, eng.optVFlags | SDL_WINDOW_RESIZABLE //| SDL_WINDOW_HIDDEN )) == NULL) { dmErrorMsg("Can't create an SDL window: %s\n", SDL_GetError()); goto error_exit; } SDL_SetWindowTitle(eng.window, dmProgDesc); if ((eng.renderer = SDL_CreateRenderer(eng.window, -1, SDL_RENDERER_PRESENTVSYNC)) == NULL) { dmErrorMsg("Can't create an SDL renderer: %s\n", SDL_GetError()); goto error_exit; } if (!dmInitializeVideo()) goto error_exit; // SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best"); // SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); // okay, main loop here ... "play" module and print out info SDL_LockAudio(); SDL_PauseAudio(0); SDL_UnlockAudio(); int currTick, prevTick = 0, prevRow = -1; while (!eng.exitFlag) { currTick = SDL_GetTicks(); BOOL needUpdate = (currTick - prevTick > 500), needRender = FALSE; while (SDL_PollEvent(&eng.event)) switch (eng.event.type) { case SDL_KEYDOWN: switch (eng.event.key.keysym.sym) { case SDLK_ESCAPE: case SDLK_q: eng.exitFlag = TRUE; break; case SDLK_SPACE: eng.pauseFlag = !eng.pauseFlag; SDL_PauseAudio(eng.pauseFlag); break; case SDLK_LEFT: if (eng.actChannel > 0) { eng.actChannel--; needUpdate = TRUE; } break; case SDLK_RIGHT: if (eng.actChannel < eng.mod->nchannels - 1) { eng.actChannel++; needUpdate = TRUE; } break; case SDLK_m: if (eng.event.key.keysym.mod & KMOD_SHIFT) { muteState = !muteState; dmMuteChannels(muteState); } else if (eng.event.key.keysym.mod & KMOD_CTRL) { dmMuteChannels(FALSE); } else { jvmMute(eng.dev, eng.actChannel, !jvmGetMute(eng.dev, eng.actChannel)); } needUpdate = TRUE; break; case SDLK_PAGEUP: JSS_LOCK(eng.dev); JSS_LOCK(eng.plr); jmpChangeOrder(eng.plr, dmClamp(eng.plr->order - 1, 0, eng.mod->norders)); JSS_UNLOCK(eng.plr); JSS_UNLOCK(eng.dev); needUpdate = TRUE; break; case SDLK_PAGEDOWN: JSS_LOCK(eng.dev); JSS_LOCK(eng.plr); jmpChangeOrder(eng.plr, dmClamp(eng.plr->order + 1, 0, eng.mod->norders)); JSS_UNLOCK(eng.plr); JSS_UNLOCK(eng.dev); needUpdate = TRUE; break; case SDLK_f: eng.optVFlags ^= SDL_WINDOW_FULLSCREEN_DESKTOP; if (SDL_SetWindowFullscreen(eng.window, eng.optVFlags) != 0) goto error_exit; needUpdate = TRUE; break; default: break; } break; case SDL_WINDOWEVENT: switch (eng.event.window.event) { case SDL_WINDOWEVENT_EXPOSED: needUpdate = TRUE; break; case SDL_WINDOWEVENT_RESIZED: eng.optScrWidth = eng.event.window.data1; eng.optScrHeight = eng.event.window.data2; if (!dmInitializeVideo()) goto error_exit; needUpdate = TRUE; break; } break; case SDL_QUIT: eng.exitFlag = TRUE; break; } #if 1 JSS_LOCK(eng.plr); JSSPattern *currPattern = eng.plr->pattern; int currRow = eng.plr->row; if (!eng.plr->isPlaying) eng.exitFlag = TRUE; JSS_UNLOCK(eng.plr); if (currRow != prevRow || needUpdate) { prevRow = currRow; needUpdate = TRUE; } // Draw frame if (needUpdate) { dmClearSurface(eng.screen, col.boxBg); dmDrawBMTextQ(eng.screen, font, DMD_TRANSPARENT, 5, 5, "%s v%s by ccr/TNSP - (c) Copyright 2012-2018 TNSP", dmProgDesc, dmProgVersion); dmDrawBMTextQ(eng.screen, font, DMD_TRANSPARENT, 5, 5 + (font->height + 2), "Song: '%s'", eng.mod->moduleName); dmDisplayPattern(eng.screen, 5, 5 + (font->height + 2) * 3 + 4, eng.screen->w - 6, eng.screen->h * 0.8, currPattern, currRow); JSS_LOCK(eng.plr); dmDrawBMTextQ(eng.screen, font, DMD_TRANSPARENT, 5, 5 + (font->height + 2) * 2, "Tempo: %3d | Speed: %3d | Row: %3d/%-3d | Order: %3d/%-3d | Pattern: %3d/%-3d", eng.plr->tempo, eng.plr->speed, eng.plr->row, (eng.plr->pattern != NULL) ? eng.plr->pattern->nrows : 0, eng.plr->order + 1, eng.mod->norders, eng.plr->npattern, eng.mod->npatterns); JSS_UNLOCK(eng.plr); needRender = TRUE; } if (needUpdate || currTick - prevTick >= (eng.pauseFlag ? 100 : 20)) { JSS_LOCK(eng.dev); dmDisplayChannels(eng.screen, 5, eng.screen->h * 0.8 + 5, eng.screen->w - 5, eng.screen->h - 5, eng.dev); JSS_UNLOCK(eng.dev); needRender = TRUE; } if (needUpdate) prevTick = currTick; #endif // Flip screen if (needRender) { SDL_Surface dst; SDL_LockTexture(eng.texture, NULL, &dst.pixels, &dst.pitch); if (dst.pitch != eng.screen->pitch) eng.exitFlag = TRUE; else memcpy(dst.pixels, eng.screen->pixels, eng.screen->h * dst.pitch); SDL_UnlockTexture(eng.texture); //SDL_RenderClear(eng.renderer); SDL_RenderCopy(eng.renderer, eng.texture, NULL, NULL); SDL_RenderPresent(eng.renderer); } SDL_Delay(eng.pauseFlag ? 100 : 30); } error_exit: if (eng.texture != NULL) SDL_DestroyTexture(eng.texture); if (eng.renderer != NULL) SDL_DestroyRenderer(eng.renderer); if (eng.window != NULL) SDL_DestroyWindow(eng.window); if (eng.screen != NULL) SDL_FreeSurface(eng.screen); dmMsg(0, "Audio shutdown.\n"); if (audioInit) { SDL_LockAudio(); SDL_PauseAudio(1); SDL_UnlockAudio(); SDL_CloseAudio(); } jmpClose(eng.plr); jvmClose(eng.dev); jssFreeModule(eng.mod); dmFreeBitmapFont(font); if (initSDL) SDL_Quit(); jssClose(); return 0; }