From 74e5cc763ceb2e96d4a68f00e664fa52cbb3c229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sat, 3 May 2025 18:27:44 -0400 Subject: [PATCH] Implement `Graphics.play_movie` in libretro builds --- binding-sandbox/graphics-binding.cpp | 15 +- binding-sandbox/sandbox.cpp | 27 ++- binding-sandbox/sandbox.h | 8 + meson.build | 12 ++ src/audio/audio.cpp | 7 + src/display/graphics.cpp | 285 +++++++++++++++++++-------- src/display/graphics.h | 5 +- src/meson.build | 2 +- src/mkxp-polyfill.cpp | 23 +++ src/mkxp-polyfill.h | 3 + 10 files changed, 301 insertions(+), 86 deletions(-) diff --git a/binding-sandbox/graphics-binding.cpp b/binding-sandbox/graphics-binding.cpp index 7dea0d2e..93c473d0 100644 --- a/binding-sandbox/graphics-binding.cpp +++ b/binding-sandbox/graphics-binding.cpp @@ -328,6 +328,7 @@ static VALUE play_movie(int32_t argc, wasm_ptr_t argv, VALUE self) { struct coro : boost::asio::coroutine { wasm_ptr_t str; int32_t volume; + bool skippable; VALUE operator()(int32_t argc, wasm_ptr_t argv, VALUE self) { BOOST_ASIO_CORO_REENTER (this) { @@ -338,13 +339,21 @@ static VALUE play_movie(int32_t argc, wasm_ptr_t argv, VALUE self) { } else { volume = 100; } - bool skippable = argc >= 3 ? SANDBOX_VALUE_TO_BOOL(((VALUE *)(**sb() + argv))[2]) : false; - // TODO: use SANDBOX_YIELD as required - GFX_GUARD_EXC(shState->graphics().playMovie((const char *)(**sb() + str), volume, skippable)); + skippable = argc >= 3 ? SANDBOX_VALUE_TO_BOOL(((VALUE *)(**sb() + argv))[2]) : false; + + GFX_GUARD_EXC(sb().set_movie(shState->graphics().playMovie((const char *)(**sb() + str), volume, skippable));); + while (sb().get_movie_from_main_thread() != nullptr) { + SANDBOX_YIELD; + GFX_GUARD_EXC(sb().set_movie(shState->graphics().playMovie(sb().get_movie_from_main_thread()));); + } } return SANDBOX_NIL; } + + ~coro() { + GFX_GUARD_EXC(sb().set_movie(nullptr);); + } }; return sb()->bind()()(argc, argv, self); diff --git a/binding-sandbox/sandbox.cpp b/binding-sandbox/sandbox.cpp index 204db9b0..271f6d87 100644 --- a/binding-sandbox/sandbox.cpp +++ b/binding-sandbox/sandbox.cpp @@ -57,7 +57,7 @@ void sandbox::sandbox_free(usize ptr) { w2c_ruby_mkxp_sandbox_free(RB, ptr); } -sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), yielding(false), transitioning(false) { +sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings(ruby), movie(nullptr), yielding(false), transitioning(false) { // Initialize the sandbox wasm2c_ruby_instantiate(RB, wasi.get()); w2c_ruby_mkxp_sandbox_init( @@ -133,9 +133,34 @@ sandbox::sandbox() : ruby(new struct w2c_ruby), wasi(new wasi_t(ruby)), bindings } sandbox::~sandbox() { + set_movie(nullptr); if (yielding) { w2c_ruby_asyncify_stop_unwind(ruby.get()); } bindings.reset(); // Destroy the bindings before destroying the runtime since the bindings destructor requires the runtime to be alive wasm2c_ruby_free(RB); } + +Movie *sandbox::get_movie_from_main_thread() { + return movie.load(std::memory_order_relaxed); // No need for synchronization because we always set the movie from the main thread +} + +Movie *sandbox::get_movie_from_audio_thread() { + return movie.load(std::memory_order_seq_cst); +} + +void sandbox::set_movie(Movie *new_movie) { + Movie *old_movie = get_movie_from_main_thread(); + if (old_movie == new_movie) { + return; + } + if (old_movie != nullptr) { + movie.store(nullptr, std::memory_order_seq_cst); + AudioMutexGuard guard(movie_mutex); + Graphics::stopMovie(old_movie); + } + if (new_movie != nullptr) { + movie.store(new_movie, std::memory_order_seq_cst); + } +} + diff --git a/binding-sandbox/sandbox.h b/binding-sandbox/sandbox.h index 112f4e4b..b51f2d04 100644 --- a/binding-sandbox/sandbox.h +++ b/binding-sandbox/sandbox.h @@ -22,10 +22,13 @@ #ifndef MKXPZ_SANDBOX_H #define MKXPZ_SANDBOX_H +#include #include #include #include #include "types.h" +#include "audio.h" +#include "graphics.h" #define SANDBOX_AWAIT(coroutine, ...) \ do { \ @@ -78,16 +81,21 @@ namespace mkxp_sandbox { std::shared_ptr ruby; std::unique_ptr wasi; boost::optional bindings; + std::atomic movie; bool yielding; usize sandbox_malloc(usize size); void sandbox_free(usize ptr); public: + AudioMutex movie_mutex; bool transitioning; inline struct mkxp_sandbox::bindings &operator*() noexcept { return *bindings; } inline struct mkxp_sandbox::bindings *operator->() noexcept { return &*bindings; } sandbox(); ~sandbox(); + Movie *get_movie_from_main_thread(); + Movie *get_movie_from_audio_thread(); + void set_movie(Movie *new_movie); // Internal utility method of the `SANDBOX_YIELD` macro. inline void _begin_yield() { diff --git a/meson.build b/meson.build index 9d1b2f77..4c4d3791 100644 --- a/meson.build +++ b/meson.build @@ -192,6 +192,18 @@ if not compilers['cpp'].has_header_symbol('thread', 'std::this_thread::yield') global_args += '-DMKXPZ_NO_STD_THIS_THREAD_YIELD' endif +if not compilers['cpp'].has_header_symbol('thread', 'std::this_thread::sleep_for') + global_args += '-DMKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR' + + if not compilers['cpp'].has_header_symbol('unistd.h', 'usleep') + global_args += '-DMKXPZ_NO_USLEEP' + + if not compilers['cpp'].has_header_symbol('time.h', 'nanosleep') + global_args += '-DMKXPZ_NO_NANOSLEEP' + endif + endif +endif + if not compilers['cpp'].has_header('pthread.h') or not compilers['cpp'].links(''' #include int main(void) { diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 5ba25b01..6bf0127a 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -389,6 +389,13 @@ Audio::Audio(RGSSThreadData &rtData) #ifdef MKXPZ_RETRO void Audio::render() { + if (mkxp_retro::sandbox->get_movie_from_audio_thread() != nullptr) { + AudioMutexGuard guard(mkxp_retro::sandbox->movie_mutex); + /* We need to call `get_movie_from_audio_thread()` a second time to avoid race + * conditions where the movie gets destroyed after the first time we checked + * that the movie isn't null but before we lock the mutex */ + Graphics::streamMovieAudioProc(mkxp_retro::sandbox->get_movie_from_audio_thread()); + } p->meWatchProc(); for (int i = 0; i < (int)p->bgmTracks.size(); i++) { p->bgmTracks[i]->render(); diff --git a/src/display/graphics.cpp b/src/display/graphics.cpp index 51acad40..8fe266aa 100644 --- a/src/display/graphics.cpp +++ b/src/display/graphics.cpp @@ -41,14 +41,15 @@ #include "shader.h" #include "sharedstate.h" #include "texpool.h" -#ifndef MKXPZ_RETRO -# include "theoraplay/theoraplay.h" -#endif // MKXPZ_RETRO +#include "theoraplay/theoraplay.h" #include "util.h" #include "input.h" #include "sprite.h" -#ifndef MKXPZ_RETRO +#ifdef MKXPZ_RETRO +# include +# include "mkxp-polyfill.h" +#else # include # include # include @@ -80,7 +81,6 @@ #define MOVIE_AUDIO_BUFFER_SIZE 2048 #define AUDIO_BUFFER_LEN_MS 2000 -#ifndef MKXPZ_RETRO typedef struct AudioQueue { const THEORAPLAY_AudioPacket *audio; @@ -91,19 +91,45 @@ typedef struct AudioQueue static long readMovie(THEORAPLAY_Io *io, void *buf, long buflen) { +#ifdef MKXPZ_RETRO + std::shared_ptr *f = (std::shared_ptr *) io->userdata; + return (long) PHYSFS_readBytes((*f)->get(), buf, buflen); +#else SDL_RWops *f = (SDL_RWops *) io->userdata; return (long) SDL_RWread(f, buf, 1, buflen); +#endif // MKXPZ_RETRO } // IoFopenRead static void closeMovie(THEORAPLAY_Io *io) { +#ifndef MKXPZ_RETRO SDL_RWops *f = (SDL_RWops *) io->userdata; SDL_RWclose(f); +#endif // MKXPZ_RETRO free(io); } // IoFopenClose +static void movieSleep(uint32_t milliseconds) +{ +#ifdef MKXPZ_RETRO + mkxp_sleep_ms(milliseconds); +#else + SDL_Delay(milliseconds); +#endif // MKXPZ_RETRO +} + + +#ifdef MKXPZ_RETRO +template +static void movieThreadFun(C *obj) +{ + (obj->*func)(); +} +#endif // MKXPZ_RETRO + + struct Movie { THEORAPLAY_Decoder *decoder; @@ -113,20 +139,49 @@ struct Movie bool hasAudio; bool skippable; Bitmap *videoBitmap; +#ifdef MKXPZ_RETRO + std::shared_ptr srcOps; +#else SDL_RWops srcOps; SDL_Thread *audioThread; +#endif // MKXPZ_RETRO AtomicFlag audioThreadTermReq; volatile AudioQueue *audioQueueHead; volatile AudioQueue *audioQueueTail; ALuint audioSource; ALuint alBuffers[STREAM_BUFS]; ALshort audioBuffer[MOVIE_AUDIO_BUFFER_SIZE]; - SDL_mutex *audioMutex; + AudioMutex audioMutex; + Sprite *movieSprite; + Sprite *letterboxSprite; + Bitmap *letterbox; + uint64_t frameMs; + uint64_t baseTicks; + float volume; + bool openedAudio; + + // Variables used by streamMovieAudioProc + ALint streamMovieAudioState; + ALint procBufs; + volatile AudioQueue *audioPacketAndOffset; + int channels; + int sampleRate; + float *sourceSamples; + ALuint samplesToProcess; + ALshort *sampleBuffer; + ALuint remainingSamples; - Movie(bool skippable_) - : decoder(0), audio(0), video(0), skippable(skippable_), videoBitmap(0), audioThread(0) + Movie(float volume, bool skippable_) + : decoder(0), audio(0), video(0), skippable(skippable_), videoBitmap(0), +#ifndef MKXPZ_RETRO + audioThread(0), +#endif // MKXPZ_RETRO + movieSprite(nullptr), letterboxSprite(nullptr), letterbox(nullptr), frameMs(0), baseTicks(-1), volume(volume), openedAudio(false), + streamMovieAudioState(0), procBufs(STREAM_BUFS) { + audioThreadTermReq.set(); } + bool preparePlayback() { @@ -134,7 +189,9 @@ struct Movie // https://ffmpeg.org/doxygen/0.11/group__lavc__misc__pixfmt.html THEORAPLAY_Io *io = (THEORAPLAY_Io *) malloc(sizeof (THEORAPLAY_Io)); if(!io) { +#ifndef MKXPZ_RETRO SDL_RWclose(&srcOps); +#endif // MKXPZ_RETRO return false; } @@ -143,13 +200,15 @@ struct Movie io->userdata = &srcOps; decoder = THEORAPLAY_startDecode(io, DEF_MAX_VIDEO_FRAMES, THEORAPLAY_VIDFMT_RGBA); if (!decoder) { +#ifndef MKXPZ_RETRO SDL_RWclose(&srcOps); +#endif // MKXPZ_RETRO return false; } // Wait until the decoder has parsed out some basic truths from the file. while (!THEORAPLAY_isInitialized(decoder)) { - SDL_Delay(VIDEO_DELAY); + movieSleep(VIDEO_DELAY); } // Once we're initialized, we can tell if this file has audio and/or video. @@ -162,7 +221,7 @@ struct Movie if ((THEORAPLAY_availableVideo(decoder) >= DEF_MAX_VIDEO_FRAMES)) { break; // we'll never progress, there's no audio yet but we've prebuffered as much as we plan to. } - SDL_Delay(VIDEO_DELAY); + movieSleep(VIDEO_DELAY); } } @@ -174,14 +233,14 @@ struct Movie // Wait until we have video while ((video = THEORAPLAY_getVideo(decoder)) == NULL) { - SDL_Delay(VIDEO_DELAY); + movieSleep(VIDEO_DELAY); } // Wait until we have audio, if applicable audio = NULL; if (hasAudio) { while ((audio = THEORAPLAY_getAudio(decoder)) == NULL && THEORAPLAY_availableVideo(decoder) < DEF_MAX_VIDEO_FRAMES) { - SDL_Delay(VIDEO_DELAY); + movieSleep(VIDEO_DELAY); } } // Create this Bitmap without a hires replacement, because we don't @@ -210,17 +269,16 @@ struct Movie item->offset = 0; item->next = NULL; - SDL_LockMutex(audioMutex); + AudioMutexGuard guard(audioMutex); if (audioQueueTail) { audioQueueTail->next = item; } else { audioQueueHead = item; } audioQueueTail = item; - SDL_UnlockMutex(audioMutex); } - void bufferMovieAudio(THEORAPLAY_Decoder *decoder, const Uint32 now) { + void bufferMovieAudio(THEORAPLAY_Decoder *decoder, const uint32_t now) { const THEORAPLAY_AudioPacket *audio; while ((audio = THEORAPLAY_getAudio(decoder)) != NULL) { queueAudioPacket(audio); @@ -230,25 +288,18 @@ struct Movie } } - void streamMovieAudio(){ - ALint state = 0; - ALint procBufs = STREAM_BUFS; - volatile AudioQueue *audioPacketAndOffset; - int channels; - int sampleRate; - float *sourceSamples; - ALuint samplesToProcess; - ALshort *sampleBuffer; - ALuint remainingSamples; + bool streamMovieAudioProc() { + // Quit if audio thread terminate request has been made + if (audioThreadTermReq) return false; - while(true) { - while(procBufs--) { - // Quit if audio thread terminate request has been made - if (audioThreadTermReq) return; + if(procBufs > 0) { + --procBufs; - remainingSamples = MOVIE_AUDIO_BUFFER_SIZE; - sampleBuffer = audioBuffer; - SDL_LockMutex(audioMutex); + remainingSamples = MOVIE_AUDIO_BUFFER_SIZE; + sampleBuffer = audioBuffer; + + { + AudioMutexGuard guard(audioMutex); while(audioQueueHead && (remainingSamples > 0)) { audioPacketAndOffset = audioQueueHead; @@ -283,27 +334,32 @@ struct Movie } if(!audioQueueHead) audioQueueTail = NULL; - - SDL_UnlockMutex(audioMutex); - - alBufferData(alBuffers[procBufs], channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, audioBuffer, - (MOVIE_AUDIO_BUFFER_SIZE - remainingSamples) * sizeof(ALshort), sampleRate); - alSourceQueueBuffers(audioSource, 1, &alBuffers[procBufs]); - alGetSourcei(audioSource, AL_SOURCE_STATE, &state); - if(state != AL_PLAYING) alSourcePlay(audioSource); } - // Periodically check the buffers until one is available - while(true) { - // Quit if audio thread terminate request has been made - if (audioThreadTermReq) return; - - alGetSourcei(audioSource, AL_BUFFERS_PROCESSED, &procBufs); - if(procBufs > 0) break; - SDL_Delay(AUDIO_SLEEP); - } - alSourceUnqueueBuffers(audioSource, procBufs, alBuffers); + alBufferData(alBuffers[procBufs], channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, audioBuffer, + (MOVIE_AUDIO_BUFFER_SIZE - remainingSamples) * sizeof(ALshort), sampleRate); + alSourceQueueBuffers(audioSource, 1, &alBuffers[procBufs]); + alGetSourcei(audioSource, AL_SOURCE_STATE, &streamMovieAudioState); + if(streamMovieAudioState != AL_PLAYING) alSourcePlay(audioSource); } + + // Periodically check the buffers until one is available + else { + alGetSourcei(audioSource, AL_BUFFERS_PROCESSED, &procBufs); + if(procBufs > 0) { + alSourceUnqueueBuffers(audioSource, procBufs, alBuffers); + } else { +#ifndef MKXPZ_RETRO + movieSleep(AUDIO_SLEEP); +#endif // MKXPZ_RETRO + } + } + + return true; + } + + void streamMovieAudio() { + while(streamMovieAudioProc()); } bool startAudio(float volume) @@ -313,31 +369,45 @@ struct Movie alSourcef(audioSource, AL_GAIN, volume); audioThreadTermReq.clear(); - audioMutex = SDL_CreateMutex(); queueAudioPacket(audio); audio = NULL; bufferMovieAudio(decoder, 0); +#ifndef MKXPZ_RETRO audioThread = createSDLThread (this, "movieaudio"); +#endif // MKXPZ_RETRO return true; } - void play(float volume) + bool play() { - Uint32 frameMs = 0; - Uint32 baseTicks = SDL_GetTicks(); - bool openedAudio = false; + if (baseTicks == (uint64_t)-1) { +#ifdef MKXPZ_RETRO + baseTicks = mkxp_retro::get_ticks_ms(); +#else + baseTicks = SDL_GetTicks(); +#endif // MKXPZ_RETRO + } while (THEORAPLAY_isDecoding(decoder)) { // Check for reset/shutdown input if(shState->graphics().updateMovieInput(this)) break; // Check for attempted skip if (skippable) { +#ifdef MKXPZ_RETRO // TODO: move into shState + mkxp_retro::input->update(); + if (mkxp_retro::input->isTriggered(Input::C) || mkxp_retro::input->isTriggered(Input::B)) break; +#else shState->input().update(); if (shState->input().isTriggered(Input::C) || shState->input().isTriggered(Input::B)) break; +#endif // MKXPZ_RETRO } - const Uint32 now = SDL_GetTicks() - baseTicks; +#ifdef MKXPZ_RETRO + const uint64_t now = mkxp_retro::get_ticks_ms() - baseTicks; +#else + const uint64_t now = SDL_GetTicks() - baseTicks; +#endif // MKXPZ_RETRO if (!video) { video = THEORAPLAY_getVideo(decoder); @@ -359,7 +429,7 @@ struct Movie } if (video && (video->playms <= now)) { - frameMs = (video->fps == 0.0) ? 0 : ((Uint32) (1000.0 / video->fps)); + frameMs = (video->fps == 0.0) ? 0 : ((uint64_t) (1000.0 / video->fps)); if ( frameMs && ((now - video->playms) >= frameMs) ) { // Skip frames to catch up @@ -389,18 +459,29 @@ struct Movie video = NULL; } else { +#ifndef MKXPZ_RETRO // Next video frame not yet ready, let the CPU breathe - SDL_Delay(VIDEO_DELAY); + movieSleep(VIDEO_DELAY); +#endif // MKXPZ_RETRO } if (openedAudio) { bufferMovieAudio(decoder, now); } + +#ifdef MKXPZ_RETRO + return true; +#endif // MKXPZ_RETRO } + + return false; } ~Movie() { + if (letterbox) delete letterbox; + if (letterboxSprite) delete letterboxSprite; + if (movieSprite) delete movieSprite; if (hasAudio) { if (audioQueueTail) { THEORAPLAY_freeAudio(audioQueueTail->audio); @@ -411,12 +492,13 @@ struct Movie THEORAPLAY_freeAudio(audioQueueHead->audio); } audioQueueHead = NULL; - SDL_DestroyMutex(audioMutex); audioThreadTermReq.set(); +#ifndef MKXPZ_RETRO if(audioThread) { SDL_WaitThread(audioThread, 0); audioThread = 0; } +#endif // MKXPZ_RETRO alSourceStop(audioSource); alDeleteSources(1, &audioSource); alDeleteBuffers(STREAM_BUFS, alBuffers); @@ -430,19 +512,30 @@ struct Movie struct MovieOpenHandler : FileSystem::OpenHandler { +#ifdef MKXPZ_RETRO + std::shared_ptr *srcOps; +#else SDL_RWops *srcOps; +#endif // MKXPZ_RETRO +#ifdef MKXPZ_RETRO + MovieOpenHandler(std::shared_ptr &srcOps) +#else MovieOpenHandler(SDL_RWops &srcOps) +#endif // MKXPZ_RETRO : srcOps(&srcOps) {} +#ifdef MKXPZ_RETRO + bool tryRead(std::shared_ptr ops, const char *ext) +#else bool tryRead(SDL_RWops &ops, const char *ext) +#endif // MKXPZ_RETRO { *srcOps = ops; return true; } }; -#endif // MKXPZ_RETRO struct PingPong { TEXFBO rt[2]; @@ -1634,48 +1727,80 @@ void Graphics::resizeWindow(int width, int height, bool center) { } bool Graphics::updateMovieInput(Movie *movie) { -#ifdef MKXPZ_RETRO - return false; // TODO -#else return p->threadData->rqTerm || p->threadData->rqReset; -#endif // MKXPZ_RETRO } -void Graphics::playMovie(const char *filename, int volume_, bool skippable) { -#ifndef MKXPZ_RETRO +Movie *Graphics::playMovie(const char *filename, int volume_, bool skippable) { if (shState->config().enableHires) { Debug() << "BUG: High-res Graphics playMovie not implemented"; } - Movie *movie = new Movie(skippable); + Movie *movie = new Movie(volume_, skippable); MovieOpenHandler handler(movie->srcOps); +#ifdef MKXPZ_RETRO // TODO: move into shState + { + std::string path("/mkxp-retro-game/"); + path.append(filename); + mkxp_retro::fs->openRead(handler, path.c_str()); + } +#else shState->fileSystem().openRead(handler, filename); +#endif // MKXPZ_RETRO float volume = volume_ * 0.01f; if (movie->preparePlayback()) { - Sprite movieSprite; + movie->movieSprite = new Sprite; // Currently this stretches to fit the screen. VX Ace behavior is to center it and let the edges run off - movieSprite.setBitmap(movie->videoBitmap); + movie->movieSprite->setBitmap(movie->videoBitmap); double ratio = std::min((double)width() / movie->video->width, (double)height() / movie->video->height); - movieSprite.setZoomX(ratio); - movieSprite.setZoomY(ratio); - movieSprite.setX((width() / 2) - (movie->video->width * ratio / 2)); - movieSprite.setY((height() / 2) - (movie->video->height * ratio / 2)); + movie->movieSprite->setZoomX(ratio); + movie->movieSprite->setZoomY(ratio); + movie->movieSprite->setX((width() / 2) - (movie->video->width * ratio / 2)); + movie->movieSprite->setY((height() / 2) - (movie->video->height * ratio / 2)); - Sprite letterboxSprite; - Bitmap letterbox(width(), height()); - letterbox.fillRect(0, 0, width(), height(), Vec4(0,0,0,255)); - letterboxSprite.setBitmap(&letterbox); + movie->letterboxSprite = new Sprite; + movie->letterbox = new Bitmap(width(), height()); + movie->letterbox->fillRect(0, 0, width(), height(), Vec4(0,0,0,255)); + movie->letterboxSprite->setBitmap(movie->letterbox); - letterboxSprite.setZ(4999); - movieSprite.setZ(5001); + movie->letterboxSprite->setZ(4999); + movie->movieSprite->setZ(5001); - movie->play(volume); + if (movie->play()) { + return movie; + } } +#ifndef MKXPZ_RETRO delete movie; #endif // MKXPZ_RETRO + return nullptr; +} + +Movie *Graphics::playMovie(Movie *movie) { + if (movie == nullptr) { + return nullptr; + } + + if (movie->play()) { + return movie; + } + +#ifndef MKXPZ_RETRO + delete movie; +#endif // MKXPZ_RETRO + return nullptr; +} + +void Graphics::stopMovie(Movie *movie) { + if (movie != nullptr) { + delete movie; + } +} + +bool Graphics::streamMovieAudioProc(Movie *movie) { + return movie->streamMovieAudioProc(); } void Graphics::screenshot(const char *filename) { diff --git a/src/display/graphics.h b/src/display/graphics.h index bb2ab6b0..5f4d7806 100644 --- a/src/display/graphics.h +++ b/src/display/graphics.h @@ -73,7 +73,10 @@ public: void resizeWindow(int width, int height, bool center=false); void drawMovieFrame(const THEORAPLAY_VideoFrame* video, Bitmap *videoBitmap); bool updateMovieInput(Movie *movie); - void playMovie(const char *filename, int volume, bool skippable); + Movie *playMovie(const char *filename, int volume, bool skippable); + Movie *playMovie(Movie *movie); + static void stopMovie(Movie *movie); + static bool streamMovieAudioProc(Movie *movie); void screenshot(const char *filename); void reset(); diff --git a/src/meson.build b/src/meson.build index 2a466074..00140d07 100755 --- a/src/meson.build +++ b/src/meson.build @@ -406,7 +406,7 @@ main_source = files( is_libretro ? 'audio/sndfilesource.cpp' : 'audio/sdlsoundsource.cpp', 'audio/soundemitter.cpp', 'audio/vorbissource.cpp', - is_libretro ? [] : 'theoraplay/theoraplay.c', + 'theoraplay/theoraplay.c', 'crypto/rgssad.cpp', diff --git a/src/mkxp-polyfill.cpp b/src/mkxp-polyfill.cpp index d0270ffa..2a7a12f0 100644 --- a/src/mkxp-polyfill.cpp +++ b/src/mkxp-polyfill.cpp @@ -27,6 +27,15 @@ # include #endif +#ifndef MKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR +# include +#elif !defined(MKXPZ_NO_USLEEP) +# include +#elif !defined(MKXPZ_NO_NANOSLEEP) +# include +#endif + + #ifdef MKXPZ_NO_SPRINTF extern "C" int sprintf(char *buffer, const char *format, ...) { va_list vlist; @@ -311,3 +320,17 @@ extern "C" int mkxp_sem_wait(mkxp_sem_t *sem) { return 0; #endif } + +extern "C" void mkxp_sleep_ms(uint32_t milliseconds) { +#ifndef MKXPZ_NO_STD_THIS_THREAD_SLEEP_FOR + std::this_thread::sleep_for(std::chrono::duration(milliseconds)); +#elif !defined(MKXPZ_NO_USLEEP) + usleep((useconds_t)1000 * (useconds_t)milliseconds); +#elif !defined(MKXPZ_NO_NANOSLEEP) + struct timespec t; + t.tv_sec = milliseconds / 1000; + t.tv_nsec = milliseconds % 1000; + t.tv_nsec *= 1000000; + nanosleep(&t, nullptr); +#endif +} diff --git a/src/mkxp-polyfill.h b/src/mkxp-polyfill.h index 04ee2960..85e2da7d 100644 --- a/src/mkxp-polyfill.h +++ b/src/mkxp-polyfill.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #ifndef MKXPZ_NO_PTHREAD_H @@ -131,6 +132,8 @@ int mkxp_sem_post(mkxp_sem_t *sem); int mkxp_sem_wait(mkxp_sem_t *sem); +void mkxp_sleep_ms(uint32_t milliseconds); + #ifdef __cplusplus }