diff --git a/SourceS/sdl2_to_1_2_backports.h b/SourceS/sdl2_to_1_2_backports.h index 17785594b..435e80880 100644 --- a/SourceS/sdl2_to_1_2_backports.h +++ b/SourceS/sdl2_to_1_2_backports.h @@ -16,6 +16,7 @@ #define SDL_zero(x) SDL_memset(&(x), 0, sizeof((x))) #define SDL_InvalidParamError(param) SDL_SetError("Parameter '%s' is invalid", (param)) #define SDL_Log puts +#define SDL_floor floor //== Events handling @@ -100,7 +101,8 @@ inline void SDL_GetWindowPosition(SDL_Window *window, int *x, int *y) printf("SDL_GetWindowPosition %d %d", *x, *y); } -inline void SDL_SetWindowPosition(SDL_Window *window, int x, int y) { +inline void SDL_SetWindowPosition(SDL_Window *window, int x, int y) +{ DUMMY(); } @@ -132,12 +134,11 @@ inline void SDL_DestroyWindow(SDL_Window *window) } inline void -SDL_WarpMouseInWindow(SDL_Window * window, int x, int y) +SDL_WarpMouseInWindow(SDL_Window *window, int x, int y) { SDL_WarpMouse(x, y); } - //= Renderer stubs #define SDL_Renderer void @@ -244,6 +245,22 @@ inline void SDLBackport_PixelformatToMask(int pixelformat, Uint32 *flags, Uint32 } } +/** + * A limited implementation of `a.format` == `b.format` from SDL2. + */ +inline bool SDLBackport_PixelFormatFormatEq(const SDL_PixelFormat *a, const SDL_PixelFormat *b) +{ + return a->BitsPerPixel == b->BitsPerPixel && (a->palette != nullptr) == (b->palette != nullptr); +} + +/** + * Similar to `SDL_ISPIXELFORMAT_INDEXED` from SDL2. + */ +inline bool SDLBackport_IsPixelFormatIndexed(const SDL_PixelFormat *pf) +{ + return pf->BitsPerPixel == 8 && pf->palette != nullptr; +} + //= Surface creation inline SDL_Surface * @@ -264,6 +281,334 @@ SDL_CreateRGBSurfaceWithFormatFrom(void *pixels, Uint32 flags, int width, int he return SDL_CreateRGBSurfaceFrom(pixels, flags, width, height, depth, rmask, gmask, bmask, amask); } +//= BlitScaled backport from SDL 2.0.9. + +#define SDL_BlitScaled SDL_UpperBlitScaled + +#define DEFINE_COPY_ROW(name, type) \ + static void name(type *src, int src_w, type *dst, int dst_w) \ + { \ + int i; \ + int pos, inc; \ + type pixel = 0; \ + \ + pos = 0x10000; \ + inc = (src_w << 16) / dst_w; \ + for (i = dst_w; i > 0; --i) { \ + while (pos >= 0x10000L) { \ + pixel = *src++; \ + pos -= 0x10000L; \ + } \ + *dst++ = pixel; \ + pos += inc; \ + } \ + } +DEFINE_COPY_ROW(copy_row1, Uint8) +DEFINE_COPY_ROW(copy_row2, Uint16) +DEFINE_COPY_ROW(copy_row4, Uint32) + +static void +copy_row3(Uint8 *src, int src_w, Uint8 *dst, int dst_w) +{ + int i; + int pos, inc; + Uint8 pixel[3] = { 0, 0, 0 }; + + pos = 0x10000; + inc = (src_w << 16) / dst_w; + for (i = dst_w; i > 0; --i) { + while (pos >= 0x10000L) { + pixel[0] = *src++; + pixel[1] = *src++; + pixel[2] = *src++; + pos -= 0x10000L; + } + *dst++ = pixel[0]; + *dst++ = pixel[1]; + *dst++ = pixel[2]; + pos += inc; + } +} + +// NOTE: Not thread-safe +inline int +SDL_SoftStretch(SDL_Surface *src, const SDL_Rect *srcrect, + SDL_Surface *dst, const SDL_Rect *dstrect) +{ + // All the ASM support has been removed, as the platforms that the ASM + // implementation exists for support SDL2 anyway. + int src_locked; + int dst_locked; + int pos, inc; + int dst_maxrow; + int src_row, dst_row; + Uint8 *srcp = NULL; + Uint8 *dstp; + SDL_Rect full_src; + SDL_Rect full_dst; + const int bpp = dst->format->BytesPerPixel; + + if (!SDLBackport_PixelFormatFormatEq(src->format, dst->format)) { + SDL_SetError("Only works with same format surfaces"); + return -1; + } + + /* Verify the blit rectangles */ + if (srcrect) { + if ((srcrect->x < 0) || (srcrect->y < 0) || ((srcrect->x + srcrect->w) > src->w) || ((srcrect->y + srcrect->h) > src->h)) { + SDL_SetError("Invalid source blit rectangle"); + return -1; + } + } else { + full_src.x = 0; + full_src.y = 0; + full_src.w = src->w; + full_src.h = src->h; + srcrect = &full_src; + } + if (dstrect) { + if ((dstrect->x < 0) || (dstrect->y < 0) || ((dstrect->x + dstrect->w) > dst->w) || ((dstrect->y + dstrect->h) > dst->h)) { + SDL_SetError("Invalid destination blit rectangle"); + return -1; + } + } else { + full_dst.x = 0; + full_dst.y = 0; + full_dst.w = dst->w; + full_dst.h = dst->h; + dstrect = &full_dst; + } + + /* Lock the destination if it's in hardware */ + dst_locked = 0; + if (SDL_MUSTLOCK(dst)) { + if (SDL_LockSurface(dst) < 0) { + SDL_SetError("Unable to lock destination surface"); + return -1; + } + dst_locked = 1; + } + /* Lock the source if it's in hardware */ + src_locked = 0; + if (SDL_MUSTLOCK(src)) { + if (SDL_LockSurface(src) < 0) { + if (dst_locked) { + SDL_UnlockSurface(dst); + } + SDL_SetError("Unable to lock source surface"); + return -1; + } + src_locked = 1; + } + + /* Set up the data... */ + pos = 0x10000; + inc = (srcrect->h << 16) / dstrect->h; + src_row = srcrect->y; + dst_row = dstrect->y; + + /* Perform the stretch blit */ + for (dst_maxrow = dst_row + dstrect->h; dst_row < dst_maxrow; ++dst_row) { + dstp = (Uint8 *)dst->pixels + (dst_row * dst->pitch) + + (dstrect->x * bpp); + while (pos >= 0x10000L) { + srcp = (Uint8 *)src->pixels + (src_row * src->pitch) + + (srcrect->x * bpp); + ++src_row; + pos -= 0x10000L; + } + switch (bpp) { + case 1: + copy_row1(srcp, srcrect->w, dstp, dstrect->w); + break; + case 2: + copy_row2((Uint16 *)srcp, srcrect->w, + (Uint16 *)dstp, dstrect->w); + break; + case 3: + copy_row3(srcp, srcrect->w, dstp, dstrect->w); + break; + case 4: + copy_row4((Uint32 *)srcp, srcrect->w, + (Uint32 *)dstp, dstrect->w); + break; + } + pos += inc; + } + + /* We need to unlock the surfaces if they're locked */ + if (dst_locked) { + SDL_UnlockSurface(dst); + } + if (src_locked) { + SDL_UnlockSurface(src); + } + return (0); +} + +inline int +SDL_LowerBlitScaled(SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect) +{ + if (SDLBackport_PixelFormatFormatEq(src->format, dst->format) && !SDLBackport_IsPixelFormatIndexed(src->format)) { + return SDL_SoftStretch(src, srcrect, dst, dstrect); + } else { + return SDL_LowerBlit(src, srcrect, dst, dstrect); + } +} + +// NOTE: The second argument is const in SDL2 but not here. +inline int +SDL_UpperBlitScaled(SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect) +{ + double src_x0, src_y0, src_x1, src_y1; + double dst_x0, dst_y0, dst_x1, dst_y1; + SDL_Rect final_src, final_dst; + double scaling_w, scaling_h; + int src_w, src_h; + int dst_w, dst_h; + + /* Make sure the surfaces aren't locked */ + if (!src || !dst) { + SDL_SetError("SDL_UpperBlitScaled: passed a NULL surface"); + return -1; + } + if (src->locked || dst->locked) { + SDL_SetError("Surfaces must not be locked during blit"); + return -1; + } + + if (NULL == srcrect) { + src_w = src->w; + src_h = src->h; + } else { + src_w = srcrect->w; + src_h = srcrect->h; + } + + if (NULL == dstrect) { + dst_w = dst->w; + dst_h = dst->h; + } else { + dst_w = dstrect->w; + dst_h = dstrect->h; + } + + if (dst_w == src_w && dst_h == src_h) { + /* No scaling, defer to regular blit */ + return SDL_BlitSurface(src, srcrect, dst, dstrect); + } + + scaling_w = (double)dst_w / src_w; + scaling_h = (double)dst_h / src_h; + + if (NULL == dstrect) { + dst_x0 = 0; + dst_y0 = 0; + dst_x1 = dst_w - 1; + dst_y1 = dst_h - 1; + } else { + dst_x0 = dstrect->x; + dst_y0 = dstrect->y; + dst_x1 = dst_x0 + dst_w - 1; + dst_y1 = dst_y0 + dst_h - 1; + } + + if (NULL == srcrect) { + src_x0 = 0; + src_y0 = 0; + src_x1 = src_w - 1; + src_y1 = src_h - 1; + } else { + src_x0 = srcrect->x; + src_y0 = srcrect->y; + src_x1 = src_x0 + src_w - 1; + src_y1 = src_y0 + src_h - 1; + + /* Clip source rectangle to the source surface */ + + if (src_x0 < 0) { + dst_x0 -= src_x0 * scaling_w; + src_x0 = 0; + } + + if (src_x1 >= src->w) { + dst_x1 -= (src_x1 - src->w + 1) * scaling_w; + src_x1 = src->w - 1; + } + + if (src_y0 < 0) { + dst_y0 -= src_y0 * scaling_h; + src_y0 = 0; + } + + if (src_y1 >= src->h) { + dst_y1 -= (src_y1 - src->h + 1) * scaling_h; + src_y1 = src->h - 1; + } + } + + /* Clip destination rectangle to the clip rectangle */ + + /* Translate to clip space for easier calculations */ + dst_x0 -= dst->clip_rect.x; + dst_x1 -= dst->clip_rect.x; + dst_y0 -= dst->clip_rect.y; + dst_y1 -= dst->clip_rect.y; + + if (dst_x0 < 0) { + src_x0 -= dst_x0 / scaling_w; + dst_x0 = 0; + } + + if (dst_x1 >= dst->clip_rect.w) { + src_x1 -= (dst_x1 - dst->clip_rect.w + 1) / scaling_w; + dst_x1 = dst->clip_rect.w - 1; + } + + if (dst_y0 < 0) { + src_y0 -= dst_y0 / scaling_h; + dst_y0 = 0; + } + + if (dst_y1 >= dst->clip_rect.h) { + src_y1 -= (dst_y1 - dst->clip_rect.h + 1) / scaling_h; + dst_y1 = dst->clip_rect.h - 1; + } + + /* Translate back to surface coordinates */ + dst_x0 += dst->clip_rect.x; + dst_x1 += dst->clip_rect.x; + dst_y0 += dst->clip_rect.y; + dst_y1 += dst->clip_rect.y; + + final_src.x = (int)SDL_floor(src_x0 + 0.5); + final_src.y = (int)SDL_floor(src_y0 + 0.5); + final_src.w = (int)SDL_floor(src_x1 + 1 + 0.5) - (int)SDL_floor(src_x0 + 0.5); + final_src.h = (int)SDL_floor(src_y1 + 1 + 0.5) - (int)SDL_floor(src_y0 + 0.5); + + final_dst.x = (int)SDL_floor(dst_x0 + 0.5); + final_dst.y = (int)SDL_floor(dst_y0 + 0.5); + final_dst.w = (int)SDL_floor(dst_x1 - dst_x0 + 1.5); + final_dst.h = (int)SDL_floor(dst_y1 - dst_y0 + 1.5); + + if (final_dst.w < 0) + final_dst.w = 0; + if (final_dst.h < 0) + final_dst.h = 0; + + if (dstrect) + *dstrect = final_dst; + + if (final_dst.w == 0 || final_dst.h == 0 || final_src.w <= 0 || final_src.h <= 0) { + /* No-op. */ + return 0; + } + + return SDL_LowerBlitScaled(src, &final_src, dst, &final_dst); +} + //= Display handling typedef struct @@ -287,10 +632,13 @@ inline int SDL_GetCurrentDisplayMode(int displayIndex, SDL_DisplayMode *mode) switch (info->vfmt->BitsPerPixel) { case 8: mode->format = SDL_PIXELFORMAT_INDEX8; + break; case 32: mode->format = SDL_PIXELFORMAT_RGBA8888; + break; default: mode->format = 0; + break; } mode->w = info->current_w; diff --git a/SourceX/storm/storm.cpp b/SourceX/storm/storm.cpp index b36773001..bcafbef6b 100644 --- a/SourceX/storm/storm.cpp +++ b/SourceX/storm/storm.cpp @@ -1,4 +1,9 @@ #include "devilution.h" + +#ifdef USE_SDL1 +#include +#endif + #include "miniwin/ddraw.h" #include "stubs.h" #include @@ -426,8 +431,18 @@ SDL_Surface *SVidSurface; BYTE *SVidBuffer; unsigned long SVidWidth, SVidHeight; -#ifndef USE_SDL1 +#ifdef USE_SDL1 +static bool HaveAudio() +{ + return SDL_GetAudioStatus() != SDL_AUDIO_STOPPED; +} +#else SDL_AudioDeviceID deviceId; + +static bool HaveAudio() +{ + return deviceId != 0; +} #endif void SVidRestartMixer() @@ -439,6 +454,90 @@ void SVidRestartMixer() Mix_ReserveChannels(1); } +#ifdef USE_SDL1 +struct AudioQueueItem { + unsigned char *data; + unsigned long len; + const unsigned char *pos; +}; + +class AudioQueue { +public: + static void Callback(void *userdata, Uint8 *out, int out_len) + { + static_cast(userdata)->Dequeue(out, out_len); + } + + void Subscribe(SDL_AudioSpec *spec) + { + spec->userdata = this; + spec->callback = AudioQueue::Callback; + } + + void Enqueue(const unsigned char *data, unsigned long len) + { + SDL_LockAudio(); + EnqueueUnsafe(data, len); + SDL_UnlockAudio(); + } + + void Clear() + { + while (!queue_.empty()) + Pop(); + } + +private: + void EnqueueUnsafe(const unsigned char *data, unsigned long len) + { + AudioQueueItem item; + item.data = new unsigned char[len]; + memcpy(item.data, data, len * sizeof(item.data[0])); + item.len = len; + item.pos = item.data; + queue_.push(item); + } + + void Dequeue(Uint8 *out, int out_len) + { + AudioQueueItem *item; + while ((item = Next()) != NULL) { + if (out_len <= item->len) { + SDL_MixAudio(out, item->pos, out_len, SDL_MIX_MAXVOLUME); + item->pos += out_len; + item->len -= out_len; + return; + } + + SDL_MixAudio(out, item->pos, item->len, SDL_MIX_MAXVOLUME); + out += item->len; + out_len -= item->len; + Pop(); + } + memset(out, 0, sizeof(out[0]) * out_len); + } + + AudioQueueItem *Next() + { + while (!queue_.empty() && queue_.front().len == 0) + Pop(); + if (queue_.empty()) + return NULL; + return &queue_.front(); + } + + void Pop() + { + delete[] queue_.front().data; + queue_.pop(); + } + + std::queue queue_; +}; + +static AudioQueue *sVidAudioQueue = new AudioQueue(); +#endif + BOOL SVidPlayBegin(char *filename, int a2, int a3, int a4, int a5, int flags, HANDLE *video) { if (flags & 0x10000 || flags & 0x20000000) { @@ -479,12 +578,13 @@ BOOL SVidPlayBegin(char *filename, int a2, int a3, int a4, int a5, int flags, HA Mix_CloseAudio(); #ifdef USE_SDL1 - if (SDL_OpenAudio(&audioFormat, NULL) != 0) { - SDL_Log(SDL_GetError()); - SVidRestartMixer(); - return false; - } - SDL_PauseAudio(0); + sVidAudioQueue->Subscribe(&audioFormat); + if (SDL_OpenAudio(&audioFormat, NULL) != 0) { + SDL_Log(SDL_GetError()); + SVidRestartMixer(); + return false; + } + SDL_PauseAudio(0); #else deviceId = SDL_OpenAudioDevice(NULL, 0, &audioFormat, NULL, 0); if (deviceId == 0) { @@ -537,19 +637,14 @@ BOOL SVidPlayBegin(char *filename, int a2, int a3, int a4, int a5, int flags, HA } #ifdef USE_SDL1 if (SDL_SetPalette(SVidSurface, SDL_LOGPAL, SVidPalette->colors, 0, SVidPalette->ncolors) != 1) { - SDL_Log(SDL_GetError()); - if (SDL_GetAudioStatus() != SDL_AUDIO_STOPPED) - SVidRestartMixer(); - return false; - } #else if (SDL_SetSurfacePalette(SVidSurface, SVidPalette) <= -1) { +#endif SDL_Log(SDL_GetError()); - if (deviceId > 0) + if (HaveAudio()) SVidRestartMixer(); return false; } -#endif SVidFrameEnd = SDL_GetTicks() * 1000 + SVidFrameLength; @@ -596,20 +691,29 @@ BOOL SVidPlayContinue(void) SDL_Log(SDL_GetError()); return false; } + +#ifdef USE_SDL1 + if (SDL_SetPalette(SVidSurface, SDL_LOGPAL, SVidPalette->colors, 0, SVidPalette->ncolors) != 1) { + SDL_Log(SDL_GetError()); + return false; + } +#endif } if (SDL_GetTicks() * 1000 >= SVidFrameEnd) { return SVidLoadNextFrame(); // Skip video and audio if the system is to slow } + if (HaveAudio()) { #ifdef USE_SDL1 - // TODO: Support audio. + sVidAudioQueue->Enqueue(smk_get_audio(SVidSMK, 0), smk_get_audio_size(SVidSMK, 0)); #else - if (deviceId && SDL_QueueAudio(deviceId, smk_get_audio(SVidSMK, 0), smk_get_audio_size(SVidSMK, 0)) <= -1) { - SDL_Log(SDL_GetError()); - return false; - } + if (SDL_QueueAudio(deviceId, smk_get_audio(SVidSMK, 0), smk_get_audio_size(SVidSMK, 0)) <= -1) { + SDL_Log(SDL_GetError()); + return false; + } #endif + } if (SDL_GetTicks() * 1000 >= SVidFrameEnd) { return SVidLoadNextFrame(); // Skip video if the system is to slow @@ -637,20 +741,17 @@ BOOL SVidPlayContinue(void) SDL_Rect pal_surface_offset = { (SCREEN_WIDTH - scaledW) / 2, (SCREEN_HEIGHT - scaledH) / 2, scaledW, scaledH }; #ifdef USE_SDL1 - // TODO: Scale before blitting. SDL_Surface *tmp = SDL_ConvertSurface(SVidSurface, window->format, 0); - if (SDL_BlitSurface(tmp, NULL, surface, &pal_surface_offset) <= -1) { - SDL_Log(SDL_GetError()); - return false; - } + // NOTE: Consider resolution switching instead if video doesn't play + // fast enough. #else Uint32 format = SDL_GetWindowPixelFormat(window); SDL_Surface *tmp = SDL_ConvertSurfaceFormat(SVidSurface, format, 0); +#endif if (SDL_BlitScaled(tmp, NULL, surface, &pal_surface_offset) <= -1) { SDL_Log(SDL_GetError()); return false; } -#endif SDL_FreeSurface(tmp); } @@ -667,19 +768,17 @@ BOOL SVidPlayContinue(void) BOOL SVidPlayEnd(HANDLE video) { + if (HaveAudio()) { #ifdef USE_SDL1 - if (SDL_GetAudioStatus() != SDL_AUDIO_STOPPED) { SDL_CloseAudio(); - SVidRestartMixer(); - } + sVidAudioQueue->Clear(); #else - if (deviceId) { SDL_ClearQueuedAudio(deviceId); SDL_CloseAudioDevice(deviceId); deviceId = 0; +#endif SVidRestartMixer(); } -#endif if (SVidSMK) smk_close(SVidSMK); @@ -690,7 +789,10 @@ BOOL SVidPlayEnd(HANDLE video) } SDL_FreePalette(SVidPalette); + SVidPalette = NULL; + SDL_FreeSurface(SVidSurface); + SVidSurface = NULL; SFileCloseFile(video); video = NULL; @@ -786,5 +888,4 @@ BOOL SFileEnableDirectAccess(BOOL enable) directFileAccess = enable; return true; } - }