From 6562a38a99e94d18b51897ffbf95a612eb8c095e Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 17 Mar 2022 00:59:57 +0000 Subject: [PATCH] monster commit Resolves issues: #65 #64 #63 #62 #61 #60 #59 #58 #55 #54 #52 #46 #45 --- .gitignore | 1 + KNOWN_GAME_BUGS.md | 30 +++ README.md | 8 + ci/win32-cross/cross_compile_dosbox.sh | 5 + ci/win32-cross/make_dosbox_zip_package.sh | 1 + dosbox-0.74-3/include/reelmagic.h | 21 +- dosbox-0.74-3/src/dosbox.cpp | 2 + .../src/hardware/reelmagic_driver.cpp | 192 +++++++++++++----- .../src/hardware/reelmagic_pl_mpeg.h | 132 +++++++----- .../src/hardware/reelmagic_player.cpp | 174 +++++++++++++--- .../src/hardware/reelmagic_videomixer.cpp | 161 ++++++--------- example.dosbox.conf | 16 ++ 12 files changed, 502 insertions(+), 241 deletions(-) create mode 100644 KNOWN_GAME_BUGS.md diff --git a/.gitignore b/.gitignore index 322b06c..eb57ef6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ dosbox-*/**/.deps dosbox-*/stamp-h1 dosbox-*/src/dosbox dosbox-*/src/dosbox.exe +dosbox-*/src/dosbox_heavydebug.exe diff --git a/KNOWN_GAME_BUGS.md b/KNOWN_GAME_BUGS.md new file mode 100644 index 0000000..9deb2d1 --- /dev/null +++ b/KNOWN_GAME_BUGS.md @@ -0,0 +1,30 @@ + +This file captures the known bugs that already exist in the games that are not introduced by this emulator. + + + +# Return to Zork + +* This game has no subtitles. (https://www.vogons.org/viewtopic.php?f=32&t=86635) +* The lack of the flashing effect when taking a photo is accurate. The game behaves this way on hardware, as well. (https://www.vogons.org/viewtopic.php?p=1052487#p1052487) +* The awful borders around the crank and box overlays in the hardware store are also accurate. (https://www.vogons.org/viewtopic.php?p=1052487#p1052487) +* When the Guardian disappears, the background appears far before the overlays fade in. This is also accurate. (https://www.vogons.org/viewtopic.php?p=1052487#p1052487) + + +# Lord of the Rings + + +* Hitting space+esc locks things up real good. +* Loading the demo on the title screen gives a "FMPOpen Error Code: 0" error message (https://www.vogons.org/viewtopic.php?p=1049075#p1049075) + + + +# Man Enough + +* No subtitles in first scene (see: https://www.vogons.org/viewtopic.php?p=1048737#p1048737) + + + + + + diff --git a/README.md b/README.md index 77a9b20..d6906ab 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,12 @@ order to get things properly working in DOSBox, I had to make a few changes to the MPEG decoder. More information on this can be found in the `NOTES_MPEG.md` file. +## Known Game Bugs + +Game bugs that are known to exist in both this emulator AND that also happen +wich a real hardware setup are documented in the `KNOWN_GAME_BUGS.md` file. +This helps with not spending too much time chasing phantom issues. + # Changes to DOSBox @@ -61,6 +67,8 @@ The parameters are: * `enabled` -- Enables/disables the ReelMagic emulator. By default this is `true` * `alwaysresident` -- This forces `FMPDRV.EXE` to always be loaded. By default this is `false` * `vgadup5hack` -- Duplicate every 5th VGA line to help give output a 4:3 ratio. By default this is `false` +* `magicfhack` -- Use for MPEG video debugging purposes only. See `reelmagic_player.cpp` for what exactly this does to the MPEG decoder. +* `a204debug` -- Controls FMPDRV.EXE function Ah subfunction 204h debug logging. Only applicable in "heavy debugging" build. For example: diff --git a/ci/win32-cross/cross_compile_dosbox.sh b/ci/win32-cross/cross_compile_dosbox.sh index f34d5c3..0766562 100755 --- a/ci/win32-cross/cross_compile_dosbox.sh +++ b/ci/win32-cross/cross_compile_dosbox.sh @@ -10,6 +10,11 @@ export PATH="$INSTALL_PREFIX/bin:$PATH" cd ../../dosbox-0.74-3 ./autogen.sh +./configure --host="$TOOLCHAIN" --prefix="$INSTALL_PREFIX" --with-sdl-prefix="$INSTALL_PREFIX" \ + --enable-debug=heavy LDFLAGS="-static-libgcc -static-libstdc++ -s" LIBS="-lvorbisfile -lvorbis -logg" +make -j +mv src/dosbox.exe src/dosbox_heavydebug.exe + ./configure --host="$TOOLCHAIN" --prefix="$INSTALL_PREFIX" --with-sdl-prefix="$INSTALL_PREFIX" \ --enable-core-inline LDFLAGS="-static-libgcc -static-libstdc++ -s" LIBS="-lvorbisfile -lvorbis -logg" make -j diff --git a/ci/win32-cross/make_dosbox_zip_package.sh b/ci/win32-cross/make_dosbox_zip_package.sh index 7046674..9358ce0 100755 --- a/ci/win32-cross/make_dosbox_zip_package.sh +++ b/ci/win32-cross/make_dosbox_zip_package.sh @@ -10,6 +10,7 @@ rm -Rf "$PACKAGE_DIR" "$PACKAGE_DIR.zip" mkdir -p "$PACKAGE_DIR" cp ../../example.dosbox.conf "$PACKAGE_DIR/dosbox.conf" cp ../../dosbox-0.74-3/src/dosbox.exe "$PACKAGE_DIR/" +cp ../../dosbox-0.74-3/src/dosbox_heavydebug.exe "$PACKAGE_DIR/" cp "$INSTALL_PREFIX/bin/"{SDL.dll,SDL_net.dll} "$PACKAGE_DIR/" mkdir -p "$PACKAGE_DIR/diagtools" diff --git a/dosbox-0.74-3/include/reelmagic.h b/dosbox-0.74-3/include/reelmagic.h index ba83c5a..3dcd577 100644 --- a/dosbox-0.74-3/include/reelmagic.h +++ b/dosbox-0.74-3/include/reelmagic.h @@ -36,7 +36,7 @@ struct ReelMagic_VideoMixerUnderlayProvider { virtual Bit16u GetPictureWidth() const = 0; virtual Bit16u GetPictureHeight() const = 0; - virtual Bit16u GetZOrder() const = 0; + virtual bool GetUnderVga() const = 0; }; void ReelMagic_RENDER_SetPal(Bit8u entry,Bit8u red,Bit8u green,Bit8u blue); @@ -50,7 +50,6 @@ extern ReelMagic_ScalerLineHandler_t ReelMagic_RENDER_DrawLine; void ReelMagic_SetVideoMixerEnabled(const bool enabled); void ReelMagic_PushVideoMixerUnderlayProvider(ReelMagic_VideoMixerUnderlayProvider& provider); void ReelMagic_PopVideoMixerUnderlayProvider(ReelMagic_VideoMixerUnderlayProvider& provider); -void ReelMagic_VideoMixerUnderlayProviderZOrderUpdate(void); void ReelMagic_InitVideoMixer(Section* /*sec*/); @@ -59,6 +58,9 @@ void ReelMagic_InitVideoMixer(Section* /*sec*/); // // player stuff // + +#define REELMAGIC_MAX_HANDLES (16) +typedef Bit8u ReelMagic_MediaPlayer_Handle; struct ReelMagic_MediaPlayerFile { virtual ~ReelMagic_MediaPlayerFile() {} virtual const char *GetFileName() const = 0; @@ -68,26 +70,31 @@ struct ReelMagic_MediaPlayerFile { }; struct ReelMagic_MediaPlayer { virtual ~ReelMagic_MediaPlayer() {} + virtual ReelMagic_MediaPlayer_Handle GetBaseHandle() const = 0; + virtual ReelMagic_MediaPlayer_Handle GetDemuxHandle() const = 0; + virtual ReelMagic_MediaPlayer_Handle GetVideoHandle() const = 0; + virtual ReelMagic_MediaPlayer_Handle GetAudioHandle() const = 0; + virtual void SetDisplayPosition(const Bit16u x, const Bit16u y) = 0; virtual void SetDisplaySize(const Bit16u width, const Bit16u height) = 0; - virtual void SetZOrder(const Bit16u value) = 0; + virtual void SetUnderVga(const bool value) = 0; virtual void SetLooping(const bool value) = 0; - virtual bool IsFileValid() const = 0; + virtual bool HasSystem() const = 0; + virtual bool HasVideo() const = 0; + virtual bool HasAudio() const = 0; virtual bool IsLooping() const = 0; virtual bool IsPlaying() const = 0; virtual Bit16u GetPictureWidth() const = 0; virtual Bit16u GetPictureHeight() const = 0; - virtual Bit16u GetZOrder() const = 0; + virtual bool GetUnderVga() const = 0; virtual void Play() = 0; virtual void Pause() = 0; virtual void Stop() = 0; }; -typedef Bit8u ReelMagic_MediaPlayer_Handle; - //note: once a player file object is handed to new/delete player, regardless of success, it will be cleaned up ReelMagic_MediaPlayer_Handle ReelMagic_NewPlayer(struct ReelMagic_MediaPlayerFile * const playerFile); void ReelMagic_DeletePlayer(const ReelMagic_MediaPlayer_Handle handle); diff --git a/dosbox-0.74-3/src/dosbox.cpp b/dosbox-0.74-3/src/dosbox.cpp index 0c6f6fa..13ce177 100644 --- a/dosbox-0.74-3/src/dosbox.cpp +++ b/dosbox-0.74-3/src/dosbox.cpp @@ -663,6 +663,8 @@ void DOSBOX_Init(void) { Pbool->Set_help("Enable the VGA DUP5 Hack. Duplicate's every VGA 5th line."); Pint = secprop->Add_int("magicfhack",Property::Changeable::OnlyAtStart,0); Pint->Set_help("MPEG debugging only! Consult the reelmagic_player.cpp source code and NOTES_MPEG.md"); + Pbool = secprop->Add_bool("a204debug",Property::Changeable::OnlyAtStart,true); + Pbool->Set_help("Turns on/off FMPDRV.EXE function Ah subfunction 204h debug logging. Only works in heavy debugging mode. Consult the reelmagic_driver.cpp source code and RMDOS_API.md"); secprop=control->AddSection_prop("joystick",&BIOS_Init,false);//done secprop->AddInitFunction(&INT10_Init); diff --git a/dosbox-0.74-3/src/hardware/reelmagic_driver.cpp b/dosbox-0.74-3/src/hardware/reelmagic_driver.cpp index 0f25e17..ffe45da 100644 --- a/dosbox-0.74-3/src/hardware/reelmagic_driver.cpp +++ b/dosbox-0.74-3/src/hardware/reelmagic_driver.cpp @@ -47,9 +47,9 @@ -//note: return to zork wants 1.11 or higher -static const Bit8u REELMAGIC_DRIVER_VERSION_MAJOR = 1; -static const Bit8u REELMAGIC_DRIVER_VERSION_MINOR = 11; +//note: Reported ReelMagic driver version 2.21 seems to be the most common... +static const Bit8u REELMAGIC_DRIVER_VERSION_MAJOR = 2; +static const Bit8u REELMAGIC_DRIVER_VERSION_MINOR = 21; static const Bit16u REELMAGIC_BASE_IO_PORT = 0x9800; //note: the real deal usually sits at 260h... practically unused for now; XXX should this be configurable!? static const Bit8u REELMAGIC_IRQ = 11; //practically unused for now; XXX should this be configurable!? static const char REELMAGIC_FMPDRV_EXE_LOCATION[] = "Z:\\"; //the trailing \ is super important! @@ -61,24 +61,46 @@ static bool _unloadAllowed = true; //enable full API logging only when heavy debugging is on... #if C_HEAVY_DEBUG -#define APILOG LOG + static bool _a204debug = true; + #define APILOG LOG + #define APILOG_DCFILT(COMMAND, SUBFUNC, ...) \ + if (_a204debug || (command != 0xA) || (subfunc != 0x204)) \ + APILOG(LOG_REELMAGIC, LOG_NORMAL)(__VA_ARGS__) #else -static inline void APILOG_DONOTHING_FUNC(...) {} -#define APILOG(A,B) APILOG_DONOTHING_FUNC + static inline void APILOG_DONOTHING_FUNC(...) {} + #define APILOG(A,B) APILOG_DONOTHING_FUNC + static inline void APILOG_DCFILT(...) {} #endif -// user callback function stuff... +// driver -> user callback function stuff... +namespace { +struct UserCallbackCall { + Bit16u command; + Bit16u handle; + Bit16u param1; + Bit16u param2; + bool invokeNext; //set to true if the next queued callback shall be auto-invoked when this one returns + //typedef (void* postcbcb_t) (void*); + //postcbcb_t postCallbackCallback; //set to non-null to invoke function after callback returns + //void * postCallbackCallbackUserPtr; + UserCallbackCall() : command(0), handle(0), param1(0), param2(0), invokeNext(false) {} + UserCallbackCall(const Bit16u command_, const Bit16u handle_ = 0, const Bit16u param1_ = 0, const Bit16u param2_ = 0, const bool invokeNext_ = false) : + command(command_), handle(handle_), param1(param1_), param2(param2_), invokeNext(invokeNext_) {} +}; struct UserCallbackPreservedState { struct Segments segs; struct CPU_Regs regs; UserCallbackPreservedState() : segs(Segs), regs(cpu_regs) {} }; +}; +static std::stack _userCallbackStack; static std::stack _preservedUserCallbackStates; static RealPt _userCallbackReturnIp = 0; //place to point the return address to after the user callback returns back to us static RealPt _userCallbackReturnDetectIp = 0; //used to detect if we are returning from the user registered FMPDRV.EXE callback static RealPt _userCallbackFarPtr = 0; // 0 = no callback registered +static Bitu _userCallbackType = 0; //or rather calling convention namespace {struct RMException : ::std::exception { //XXX currently duplicating this in realmagic_*.cpp files to avoid header pollution... TDB if this is a good idea... @@ -274,42 +296,119 @@ static void FMPDRV_EXE_UninstallINTHandler(void) { } + +// +// functions to serialize the player state into the required API format +// +static Bit16u GetFileStateValue(const ReelMagic_MediaPlayer& player) { + Bit16u value = 0; + if (player.HasVideo()) value |= 2; + if (player.HasAudio()) value |= 1; + return value; +} + +static Bit16u GetPlayStateValue(const ReelMagic_MediaPlayer& player) { + //XXX status code 1 = paused + //XXX status code 2 = stopped (e.g. never started with function 3) + Bit16u value = player.IsPlaying() ? 0x4 : 0x1; + if ((_userCallbackType == 0x2000) && player.IsPlaying()) value |= 0x10; //XXX hack for RTZ!!! + return value; +} + // // This is to invoke the user program driver callback if registered... // -static void InvokeUserCallbackOnResume(const Bit16u command, const Bit8u media_handle, const Bit8u unknown1, const Bit32u unknown2) { - if (_userCallbackFarPtr == 0) return; //no callback registered... +static void EnqueueTopUserCallbackOnCPUResume() { + if (_userCallbackStack.empty()) E_Exit("FMPDRV.EXE Asking to enqueue a callback with nothing on the top of the callback stack!"); + if (_userCallbackFarPtr == 0) E_Exit("FMPDRV.EXE Asking to enqueue a callback with no user callback pointer set!"); + UserCallbackCall& ucc = _userCallbackStack.top(); //snapshot the current state... _preservedUserCallbackStates.push(UserCallbackPreservedState()); //prepare the function call... - mem_writed(PhysMake(SegValue(ss), reg_sp-=4), unknown2); - mem_writeb(PhysMake(SegValue(ss), reg_sp-=1), unknown1); - mem_writeb(PhysMake(SegValue(ss), reg_sp-=1), media_handle); - mem_writew(PhysMake(SegValue(ss), reg_sp-=2), command); + switch (_userCallbackType) { //AFAIK, _userCallbackType dictates the calling convention... this is the value that was passed in when registering the callback function to us + case 0x2000: //RTZ-style; shit is passed on the stack... + reg_ax = reg_bx = reg_cx = reg_dx = 0; //clear the GP regs for good measure... + mem_writew(PhysMake(SegValue(ss), reg_sp-=2), ucc.param2); + mem_writew(PhysMake(SegValue(ss), reg_sp-=2), ucc.param1); + mem_writew(PhysMake(SegValue(ss), reg_sp-=2), ucc.handle); + mem_writew(PhysMake(SegValue(ss), reg_sp-=2), ucc.command); + break; + default: + LOG(LOG_REELMAGIC, LOG_WARN)("Unknown user callback type %04Xh. Defaulting to 0000. This is probably gonna screw something up!", (unsigned)_userCallbackType); + //fall through + case 0x0000: //The Horde style... shit is passed in registers... + reg_bx = ((ucc.command << 8) & 0xFF00) | (ucc.handle & 0xFF); + reg_ax = ucc.param1; + reg_dx = ucc.param2; + reg_cx = 0; //??? + break; + } + + //push the far-call return address... mem_writew(PhysMake(SegValue(ss), reg_sp-=2), RealSeg(_userCallbackReturnIp)); //return address to invoke CleanupFromUserCallback() mem_writew(PhysMake(SegValue(ss), reg_sp-=2), RealOff(_userCallbackReturnIp)); //return address to invoke CleanupFromUserCallback() - //clear some regs for good measure... - reg_ax = 0; reg_bx = 0; reg_cx = 0; reg_dx = 0; - //then we blast off into the wild blue... SegSet16(cs, RealSeg(_userCallbackFarPtr)); reg_ip = RealOff(_userCallbackFarPtr); - APILOG(LOG_REELMAGIC, LOG_NORMAL)("Post-Invoking driver_callback(%04Xh, %02Xh, %01Xh, %04Xh)", (unsigned)command, (unsigned)media_handle, (unsigned)unknown1, (unsigned)unknown2); + APILOG(LOG_REELMAGIC, LOG_NORMAL)("Post-Invoking registered user-callback on CPU resume. cmd=%04Xh handle=%04Xh p1=%04Xh p2=%04Xh", (unsigned)ucc.command, (unsigned)ucc.handle, (unsigned)ucc.param1, (unsigned)ucc.param2); +} + +static void InvokePlayerStateChangeCallbackOnCPUResumeIfRegistered(const bool isPausing, ReelMagic_MediaPlayer& player) { + if (_userCallbackFarPtr == 0) return; //no callback registered... + + const unsigned cbstackStartSize = _userCallbackStack.size(); + + if ((_userCallbackType == 0x2000) && (!isPausing)) { + //hack to make RTZ work for now... + _userCallbackStack.push(UserCallbackCall(5, player.GetBaseHandle(), 0, 0, _userCallbackStack.size() != cbstackStartSize)); + } + + if (isPausing) { + //we are being invoked from a pause command + if (player.GetDemuxHandle()) //is this the correct "last" handle !? + _userCallbackStack.push(UserCallbackCall(7, player.GetDemuxHandle(), GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize)); + if (player.GetVideoHandle()) //is this the correct "middle" handle !? + _userCallbackStack.push(UserCallbackCall(7, player.GetVideoHandle(), GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize)); + if (player.GetAudioHandle()) //on the real deal, highest handle always calls back first! I'm assuming its audio! + _userCallbackStack.push(UserCallbackCall(7, player.GetAudioHandle(), GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize)); + } + else { + //we are being invoked from a close command + if (player.IsPlaying() && player.GetDemuxHandle()) //is this the correct "last" handle !? + _userCallbackStack.push(UserCallbackCall(7, player.GetDemuxHandle(), GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize)); //4 = state of player; since we only get called on pause, this will always be 4 + if (player.GetAudioHandle()) + _userCallbackStack.push(UserCallbackCall(7, player.GetAudioHandle(), GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize)); //4 = state of player; since we only get called on pause, this will always be 4 + if (player.GetVideoHandle()) + _userCallbackStack.push(UserCallbackCall(7, player.GetVideoHandle(), GetPlayStateValue(player), 0, _userCallbackStack.size() != cbstackStartSize)); //4 = state of player; since we only get called on pause, this will always be 4 + } + + if (_userCallbackStack.size() != cbstackStartSize) + EnqueueTopUserCallbackOnCPUResume(); } static void CleanupFromUserCallback(void) { if (_preservedUserCallbackStates.empty()) E_Exit("FMPDRV.EXE Asking to cleanup with nothing on preservation stack"); + if (_userCallbackStack.empty()) E_Exit("FMPDRV.EXE Asking to cleanup with nothing on user callback stack"); APILOG(LOG_REELMAGIC, LOG_NORMAL)("Returning from driver_callback()"); //restore the previous state of things... + const UserCallbackCall& ucc = _userCallbackStack.top(); + const bool invokeNext = ucc.invokeNext; + _userCallbackStack.pop(); + const UserCallbackPreservedState& s = _preservedUserCallbackStates.top(); Segs = s.segs; cpu_regs = s.regs; _preservedUserCallbackStates.pop(); + + if (invokeNext) { + APILOG(LOG_REELMAGIC, LOG_NORMAL)("Invoking Next Chained Callback..."); + EnqueueTopUserCallbackOnCPUResume(); + } } @@ -330,12 +429,10 @@ static Bit32u FMPDRV_EXE_driver_call(const Bit8u command, const Bit8u media_hand // Close Media Handle // case 0x02: + player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle + InvokePlayerStateChangeCallbackOnCPUResumeIfRegistered(false, *player); ReelMagic_DeletePlayer(media_handle); LOG(LOG_REELMAGIC, LOG_NORMAL)("Closed media player handle=%u", media_handle); - //invoke the user callback if one is registered after this call returns... - //i think 5 means either handle closed or handle has stopped... - //TODO: it would make more sense to invoke this callback BEFORE we delete the player... - InvokeUserCallbackOnResume(5, media_handle, 0, 0); //XXX No idea if command 5 is the right one or not... return 0; // @@ -361,23 +458,19 @@ static Bit32u FMPDRV_EXE_driver_call(const Bit8u command, const Bit8u media_hand return 0; // - // Probably Stop Media Handle + // Pause Media Handle // case 0x04: - //this always seems to be called with a valid media handle from return to zork right before it closes - //a media handle, hence why I am assuming this is some kind of a stop command. for now, doing nothing - //as closing the media handle is all thats really needed to clean things up. - // - //this is also called by LOTR when the user hits ESC to skip a scene - //currently implementing as stop... player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle - player->Stop(); + if (!player->IsPlaying()) return 0; //nothing to do + InvokePlayerStateChangeCallbackOnCPUResumeIfRegistered(true, *player); + player->Pause(); return 0; //returning zero, nobody seems to actually check this anyways... // - // Set Parameter (I think) + // Set Parameter // - case 0x09: //likely set parameter + case 0x09: if (media_handle == 0) { //global? switch (subfunc) { @@ -389,7 +482,7 @@ static Bit32u FMPDRV_EXE_driver_call(const Bit8u command, const Bit8u media_hand case 0x040E: return 0; //unknown what all these currently do... callers seem to ignore the return value anyways... - case 0x0210: //unsure what this is, it might be something like a "set user pointer" + case 0x0210: //XXX THIS IS THE MAGICAL F_CODE LOADER!!! return 0; //for the user_callback() function... ignoring for now... } } @@ -398,15 +491,15 @@ static Bit32u FMPDRV_EXE_driver_call(const Bit8u command, const Bit8u media_hand player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle switch (subfunc) { case 0x040E: - LOG(LOG_REELMAGIC, LOG_WARN)("Setting Player #%u Z-Order To: %u", (unsigned)media_handle, (unsigned short)param1); - player->SetZOrder(param1); + LOG(LOG_REELMAGIC, LOG_NORMAL)("Setting Player #%u Surface Z-Order To: %s VGA", (unsigned)media_handle, (param1&4) ? "Under" : "Over"); + player->SetUnderVga((param1&4) != 0); return 0; case 0x1409: - LOG(LOG_REELMAGIC, LOG_WARN)("Setting Player #%u Display Size To: %ux%u", (unsigned)media_handle, (unsigned)param1, (unsigned)param2); + LOG(LOG_REELMAGIC, LOG_NORMAL)("Setting Player #%u Display Size To: %ux%u", (unsigned)media_handle, (unsigned)param1, (unsigned)param2); player->SetDisplaySize(param1, param2); return 0; case 0x2408: - LOG(LOG_REELMAGIC, LOG_WARN)("Setting Player #%u Display Position To: %ux%u", (unsigned)media_handle, (unsigned)param1, (unsigned)param2); + LOG(LOG_REELMAGIC, LOG_NORMAL)("Setting Player #%u Display Position To: %ux%u", (unsigned)media_handle, (unsigned)param1, (unsigned)param2); player->SetDisplayPosition(param1, param2); return 0; } @@ -416,9 +509,9 @@ static Bit32u FMPDRV_EXE_driver_call(const Bit8u command, const Bit8u media_hand return 0; //return zero on unknown parameter, although callers seem to ignore this... // - // Get Parameter (I think) + // Get Parameter or Status // - case 0x0A: //get media_handle status + case 0x0A: if (media_handle == 0) { //global? switch (subfunc) { @@ -430,15 +523,10 @@ static Bit32u FMPDRV_EXE_driver_call(const Bit8u command, const Bit8u media_hand //per player ... player = &ReelMagic_HandleToMediaPlayer(media_handle); // will throw on bad handle switch (subfunc) { - case 0x202: //is file valid? - //XXX note: both SPLAYER.EXE and MADERM.EXE check AX and DX when returning from this: - // something like: if (ax | dx) then cleanup and close the media handle... - return player->IsFileValid() ? 3 : 0; - - case 0x204: //this is some kind of play state query... - //SPLAYER.EXE hammers this until it either bit 0x01 or bit 0x02 (or both) it set - //RTZ hammers this while it returns 0x14 - return player->IsPlaying() ? 0x14 : 0x1; //assuming 1 means stopped + case 0x202: //get file state (bitmap of what streams are available...) + return GetFileStateValue(*player); + case 0x204: //get play state + return GetPlayStateValue(*player); case 0x208: //unsure -- see notes in "RMDOS_API.md" return 0; //for the love of God, do NOT return anything thats not zero here! case 0x403: @@ -456,6 +544,7 @@ static Bit32u FMPDRV_EXE_driver_call(const Bit8u command, const Bit8u media_hand case 0x0b: LOG(LOG_REELMAGIC, LOG_WARN)("Registering driver_callback() as [%04X:%04X]", (unsigned)param2, (unsigned)param1); _userCallbackFarPtr = RealMake(param2, param1); + _userCallbackType = subfunc; return 0; // @@ -467,12 +556,13 @@ static Bit32u FMPDRV_EXE_driver_call(const Bit8u command, const Bit8u media_hand return 0; // - // Reset (I think) + // Reset // case 0x0e: LOG(LOG_REELMAGIC, LOG_NORMAL)("Reset"); ReelMagic_DeleteAllPlayers(); _userCallbackFarPtr = 0; + _userCallbackType = 0; return 0; // @@ -507,7 +597,7 @@ static Bitu FMPDRV_EXE_INTHandler(void) { const Bit32u driver_call_rv = FMPDRV_EXE_driver_call(command, media_handle, subfunc, param1, param2); reg_ax = (Bit16u)(driver_call_rv & 0xFFFF); //low reg_dx = (Bit16u)(driver_call_rv >> 16); //high - APILOG(LOG_REELMAGIC, LOG_NORMAL)("driver_call(%02Xh,%02Xh,%Xh,%Xh,%Xh)=%Xh", (unsigned)command, (unsigned)media_handle, (unsigned)subfunc, (unsigned)param1, (unsigned)param2, (unsigned)driver_call_rv); + APILOG_DCFILT(command, subfunc, "driver_call(%02Xh,%02Xh,%Xh,%Xh,%Xh)=%Xh", (unsigned)command, (unsigned)media_handle, (unsigned)subfunc, (unsigned)param1, (unsigned)param2, (unsigned)driver_call_rv); } catch (std::exception& ex) { LOG(LOG_REELMAGIC, LOG_WARN)("Zeroing out INT return registers due to exception in driver_call(%02Xh,%02Xh,%Xh,%Xh,%Xh)", (unsigned)command, (unsigned)media_handle, (unsigned)subfunc, (unsigned)param1, (unsigned)param2); @@ -578,6 +668,7 @@ static void SetMixerVolume(const char * const channelName, const Bit16u val, con } static bool RMDEV_SYS_int2fHandler() { if ((reg_ax & 0xFF00) != 0x9800) return false; + APILOG(LOG_REELMAGIC, LOG_NORMAL)("RMDEV.SYS ax = 0x%04X bx = 0x%04X cx = 0x%04X dx = 0x%04X", (unsigned)reg_ax, (unsigned)reg_bx, (unsigned)reg_cx, (unsigned)reg_dx); switch (reg_ax) { case 0x9800: switch (reg_bx) { @@ -736,4 +827,7 @@ void ReelMagic_Init(Section* sec) { _unloadAllowed = false; FMPDRV_EXE_InstallINTHandler(); } + #if C_HEAVY_DEBUG + _a204debug = section->Get_bool("a204debug"); + #endif } diff --git a/dosbox-0.74-3/src/hardware/reelmagic_pl_mpeg.h b/dosbox-0.74-3/src/hardware/reelmagic_pl_mpeg.h index 756daf3..68928ed 100644 --- a/dosbox-0.74-3/src/hardware/reelmagic_pl_mpeg.h +++ b/dosbox-0.74-3/src/hardware/reelmagic_pl_mpeg.h @@ -233,6 +233,10 @@ typedef struct { typedef void(*plm_video_decode_callback) (plm_t *self, plm_frame_t *frame, void *user); +// Callback function for the video decoder once a picture header has been decoded + +typedef void(*plm_video_decode_picture_header_callback) + (plm_video_t *self, void *user); // Decoded Audio Samples // Samples are stored as normalized (-1, 1) float either interleaved, or if @@ -270,7 +274,6 @@ typedef void(*plm_buffer_load_callback)(plm_buffer_t *self, void *user); typedef void(*plm_buffer_seek_callback)(plm_buffer_t *self, void *user, size_t pos); - // ----------------------------------------------------------------------------- // plm_* public API // High-Level API for loading/demuxing/decoding MPEG-PS data @@ -489,7 +492,6 @@ plm_buffer_t *plm_buffer_create_with_file(FILE *fh, int close_when_done); plm_buffer_t *plm_buffer_create_with_virtual_file(plm_buffer_load_callback load_fp, plm_buffer_seek_callback seek_fp, void *user, size_t file_size); - // Create a buffer instance with a pointer to memory as source. This assumes // the whole file is in memory. The bytes are not copied. Pass 1 to // free_when_done to let plmpeg call free() on the pointer when plm_destroy() @@ -715,6 +717,11 @@ int plm_video_has_ended(plm_video_t *self); plm_frame_t *plm_video_decode(plm_video_t *self); +// Set a callback that is called whenever a picture header is decoded + +void plm_video_set_decode_picture_header_callback(plm_video_t *self, plm_video_decode_picture_header_callback fp, void *user); + + // Convert the YCrCb data of a frame into interleaved R G B data. The stride // specifies the width in bytes of the destination buffer. I.e. the number of // bytes from one line to the next. The stride must be at least @@ -1312,7 +1319,7 @@ int plm_seek(plm_t *self, double time, int seek_exact) { // plm_buffer implementation enum plm_buffer_mode { - PLM_BUFFER_MODE_FILE, //if seek_callback != NULL then mode is virtual file... + PLM_BUFFER_MODE_FILE, //if seek_callback != NULL then mode is virtual file... PLM_BUFFER_MODE_FIXED_MEM, PLM_BUFFER_MODE_RING, PLM_BUFFER_MODE_APPEND @@ -1392,7 +1399,7 @@ plm_buffer_t *plm_buffer_create_with_virtual_file(plm_buffer_load_callback load_ self->discard_read_bytes = TRUE; self->total_size = file_size; plm_buffer_set_load_callback(self, load_fp, user); - self->seek_callback = seek_fp; //setting this to non-NULL makes it a virtual file + self->seek_callback = seek_fp; //setting this to non-NULL makes it a virtual file return self; } @@ -1498,12 +1505,12 @@ void plm_buffer_seek(plm_buffer_t *self, size_t pos) { if (self->mode == PLM_BUFFER_MODE_FILE) { if (self->seek_callback) - (*self->seek_callback)(self, self->load_callback_user_data, pos); + (*self->seek_callback)(self, self->load_callback_user_data, pos); else - fseek(self->fh, pos, SEEK_SET); + fseek(self->fh, pos, SEEK_SET); self->bit_index = 0; self->length = 0; - self->file_pos = pos; + self->file_pos = pos; } else if (self->mode == PLM_BUFFER_MODE_RING) { if (pos != 0) { @@ -1548,7 +1555,7 @@ void plm_buffer_load_file_callback(plm_buffer_t *self, void *user) { size_t bytes_available = self->capacity - self->length; size_t bytes_read = fread(self->bytes + self->length, 1, bytes_available, self->fh); self->length += bytes_read; - self->file_pos += bytes_read; + self->file_pos += bytes_read; if (bytes_read == 0) { self->has_ended = TRUE; @@ -2586,15 +2593,16 @@ typedef struct plm_video_t { int start_code; int gop_index; int gop_frames_decoded; + int picture_temporal_reference; int picture_type; + int picture_extension_count; + int picture_user_data_count; - int motion_forward_f_code_override; - int motion_backward_f_code_override; plm_video_motion_t motion_forward; plm_video_motion_t motion_backward; int has_sequence_header; - int seqh_picture_rate; + int seqh_picture_rate; int quantizer_scale; int slice_begin; @@ -2624,6 +2632,9 @@ typedef struct plm_video_t { int has_reference_frame; int assume_no_b_frames; + + plm_video_decode_picture_header_callback decode_picture_header_callback; + void *decode_picture_header_callback_user_data; } plm_video_t; static inline uint8_t plm_clamp(int n) { @@ -2658,7 +2669,7 @@ plm_video_t * plm_video_create_with_buffer(plm_buffer_t *buffer, int destroy_whe self->destroy_buffer_when_done = destroy_when_done; // Attempt to decode the sequence header - self->gop_index = -1; + self->gop_index = -1; self->start_code = plm_buffer_find_start_code(self->buffer, PLM_START_SEQUENCE); if (self->start_code != -1) { plm_video_decode_sequence_header(self); @@ -2731,30 +2742,31 @@ plm_frame_t *plm_video_decode(plm_video_t *self) { plm_frame_t *frame = NULL; do { while (self->start_code != PLM_START_PICTURE) { - if (self->start_code == PLM_START_GOP) { - ++self->gop_index; - self->gop_frames_decoded = 0; - plm_buffer_read(self->buffer, 32); //TODO: do something with the GOP header? - } - self->start_code = plm_buffer_next_start_code(self->buffer); - if (self->start_code == -1) { - // If we reached the end of the file and the previously decoded - // frame was a reference frame, we still have to return it. - if ( - self->has_reference_frame && - !self->assume_no_b_frames && - plm_buffer_has_ended(self->buffer) && ( - self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA || - self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE - ) - ) { - self->has_reference_frame = FALSE; - frame = &self->frame_backward; - break; + if (self->start_code == PLM_START_GOP) { + if (!plm_buffer_has(self->buffer, 32)) return NULL; + plm_buffer_skip(self->buffer, 32); //TODO: do something with the GOP header? + ++self->gop_index; + self->gop_frames_decoded = 0; } + self->start_code = plm_buffer_next_start_code(self->buffer); + if (self->start_code == -1) { + // If we reached the end of the file and the previously decoded + // frame was a reference frame, we still have to return it. + if ( + self->has_reference_frame && + !self->assume_no_b_frames && + plm_buffer_has_ended(self->buffer) && ( + self->picture_type == PLM_VIDEO_PICTURE_TYPE_INTRA || + self->picture_type == PLM_VIDEO_PICTURE_TYPE_PREDICTIVE + ) + ) { + self->has_reference_frame = FALSE; + frame = &self->frame_backward; + break; + } - return NULL; - } + return NULL; + } } // Make sure we have a full picture in the buffer before attempting to @@ -2781,30 +2793,34 @@ plm_frame_t *plm_video_decode(plm_video_t *self) { frame = &self->frame_forward; } else { - if (self->frames_decoded == 0) frame = &self->frame_backward; //to capture first i picture + if (self->frames_decoded == 0) frame = &self->frame_backward; //to capture first i picture self->has_reference_frame = TRUE; } } while (!frame); frame->time = self->time; self->frames_decoded++; - self->gop_frames_decoded++; + self->gop_frames_decoded++; self->time = (double)self->frames_decoded / self->framerate; - //note: due to the way we tell the buffer to ignore "discard_read_bytes" flag - // during the plm_buffer_has_start_code() to capture the entire picture, - // this causes a memory leak in the ES buffer because the check of the - // "discard_read_bytes" variable in "plm_buffer_write()" for a video ES - // never gets checked... manually checking and disposing this here... - if (self->buffer->discard_read_bytes) { - plm_buffer_discard_read_bytes(self->buffer); - if (self->buffer->mode == PLM_BUFFER_MODE_RING) self->buffer->total_size = 0; - } - + //note: due to the way we tell the buffer to ignore "discard_read_bytes" flag + // during the plm_buffer_has_start_code() to capture the entire picture, + // this causes a memory leak in the ES buffer because the check of the + // "discard_read_bytes" variable in "plm_buffer_write()" for a video ES + // never gets checked... manually checking and disposing this here... + if (self->buffer->discard_read_bytes) { + plm_buffer_discard_read_bytes(self->buffer); + if (self->buffer->mode == PLM_BUFFER_MODE_RING) self->buffer->total_size = 0; + } return frame; } +void plm_video_set_decode_picture_header_callback(plm_video_t *self, plm_video_decode_picture_header_callback fp, void *user) { + self->decode_picture_header_callback = fp; + self->decode_picture_header_callback_user_data = user; +} + int plm_video_has_header(plm_video_t *self) { if (self->has_sequence_header) { return TRUE; @@ -2913,9 +2929,11 @@ void plm_video_init_frame(plm_video_t *self, plm_frame_t *frame, uint8_t *base) } void plm_video_decode_picture(plm_video_t *self) { - plm_buffer_skip(self->buffer, 10); // skip temporalReference + self->picture_temporal_reference = plm_buffer_read(self->buffer, 10); self->picture_type = plm_buffer_read(self->buffer, 3); plm_buffer_skip(self->buffer, 16); // skip vbv_delay + self->picture_extension_count = 0; + self->picture_user_data_count = 0; // D frames or unknown coding type if (self->picture_type <= 0 || self->picture_type > PLM_VIDEO_PICTURE_TYPE_B) { @@ -2929,10 +2947,7 @@ void plm_video_decode_picture(plm_video_t *self) { ) { self->motion_forward.full_px = plm_buffer_read(self->buffer, 1); int f_code = plm_buffer_read(self->buffer, 3); - if (self->motion_forward_f_code_override) { - f_code = self->motion_forward_f_code_override; - } - else if (f_code == 0) { + if (f_code == 0) { // Ignore picture with zero f_code return; } @@ -2943,10 +2958,7 @@ void plm_video_decode_picture(plm_video_t *self) { if (self->picture_type == PLM_VIDEO_PICTURE_TYPE_B) { self->motion_backward.full_px = plm_buffer_read(self->buffer, 1); int f_code = plm_buffer_read(self->buffer, 3); - if (self->motion_backward_f_code_override) { - f_code = self->motion_backward_f_code_override; - } - else if (f_code == 0) { + if (f_code == 0) { // Ignore picture with zero f_code return; } @@ -2965,11 +2977,21 @@ void plm_video_decode_picture(plm_video_t *self) { // Find first slice start code; skip extension and user data do { self->start_code = plm_buffer_next_start_code(self->buffer); + switch(self->start_code) { + case PLM_START_EXTENSION: ++self->picture_extension_count; break; + case PLM_START_USER_DATA: ++self->picture_user_data_count; break; + } } while ( self->start_code == PLM_START_EXTENSION || self->start_code == PLM_START_USER_DATA ); + // Call the decode picture header callback if it needs to make + // any modifications before slice decoding... + if (self->decode_picture_header_callback) { + (*self->decode_picture_header_callback)(self, self->decode_picture_header_callback_user_data); + } + // Decode all slices while (PLM_START_IS_SLICE(self->start_code)) { plm_video_decode_slice(self, self->start_code & 0x000000FF); diff --git a/dosbox-0.74-3/src/hardware/reelmagic_player.cpp b/dosbox-0.74-3/src/hardware/reelmagic_player.cpp index 238f813..b3ee8c6 100644 --- a/dosbox-0.74-3/src/hardware/reelmagic_player.cpp +++ b/dosbox-0.74-3/src/hardware/reelmagic_player.cpp @@ -144,9 +144,12 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl // creation parameters... ReelMagic_MediaPlayerFile * const _file; const ReelMagic_MediaPlayer_Handle _handle; + ReelMagic_MediaPlayer_Handle _demuxHandle; + ReelMagic_MediaPlayer_Handle _videoHandle; + ReelMagic_MediaPlayer_Handle _audioHandle; // running / adjustable variables... - Bit16u _zorder; + bool _underVga; bool _loop; bool _playing; @@ -162,6 +165,7 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl Bit16u _width; Bit16u _height; double _framerate; + Bit8u _magicalRSizeOverride; AudioSampleFIFO _audioFifo; @@ -195,6 +199,16 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl } } + static void plmDecodeMagicalPictureHeaderCallback(plm_video_t *self, void *user) { + switch (self->picture_type) { + case PLM_VIDEO_PICTURE_TYPE_B: + self->motion_backward.r_size = ((ReelMagic_MediaPlayerImplementation*)user)->_magicalRSizeOverride; + //fallthrough + case PLM_VIDEO_PICTURE_TYPE_PREDICTIVE: + self->motion_forward.r_size = ((ReelMagic_MediaPlayerImplementation*)user)->_magicalRSizeOverride; + } + } + void advanceNextFrame() { _nextFrame = plm_decode_video(_plm); if (_nextFrame == NULL) { @@ -276,14 +290,14 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl LOG(LOG_REELMAGIC, LOG_NORMAL)("Detected a magical picture_rate code of 0x%X.", (unsigned)_plm->video_decoder->seqh_picture_rate); const unsigned magical_f_code = _magicalFcodeOverride ? _magicalFcodeOverride: FindMagicalFCode(); if (magical_f_code) { - _plm->video_decoder->motion_forward_f_code_override = magical_f_code; - _plm->video_decoder->motion_backward_f_code_override = magical_f_code; - LOG(LOG_REELMAGIC, LOG_NORMAL)("Applying static %u:%u f_code override", (unsigned)_plm->video_decoder->motion_forward_f_code_override, (unsigned)_plm->video_decoder->motion_backward_f_code_override); + _magicalRSizeOverride = magical_f_code - 1; + plm_video_set_decode_picture_header_callback(_plm->video_decoder, &plmDecodeMagicalPictureHeaderCallback, this); + LOG(LOG_REELMAGIC, LOG_NORMAL)("Applying static %u:%u f_code override", magical_f_code, magical_f_code); } else { LOG(LOG_REELMAGIC, LOG_WARN)("No magical f_code found. Playback will likely be screwed up!"); } - _plm->video_decoder->framerate = PLM_VIDEO_PICTURE_RATE[0x7 & _plm->video_decoder->seqh_picture_rate]; //im pretty sure this is correct + _plm->video_decoder->framerate = PLM_VIDEO_PICTURE_RATE[0x7 & _plm->video_decoder->seqh_picture_rate]; } if (_plm->video_decoder->framerate == 0.000) { LOG(LOG_REELMAGIC, LOG_ERROR)("Detected a bad framerate. Hardcoding to 30. This video will likely not work at all."); @@ -316,12 +330,16 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl ReelMagic_MediaPlayerImplementation(ReelMagic_MediaPlayerFile * const file, const ReelMagic_MediaPlayer_Handle handle) : _file(file), _handle(handle), - _zorder(0), //default is highest priority (nearest) Z-Order... TBD if this is the right default... + _demuxHandle(0), + _videoHandle(0), + _audioHandle(0), + _underVga(false), _loop(IsLoopFilename(_file->GetFileName())), _playing(false), _vgaFps(0.0f), _plm(NULL), - _nextFrame(NULL) { + _nextFrame(NULL), + _magicalRSizeOverride(0) { bool detetectedFileTypeVesOnly = false; @@ -337,6 +355,10 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl //failed to detect an MPEG-1 PS (muxed) stream... try MPEG-ES assuming video-only... detetectedFileTypeVesOnly = true; SetupVESOnlyDecode(); + _videoHandle = _handle; + } + else { + _demuxHandle = _handle; } //disable audio buffer load callback so pl_mpeg dont try to "auto fetch" audio samples @@ -372,6 +394,34 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl delete _file; } + + // + // function for accessing this class just in this file... + // + Bitu GetHandlesNeeded() const { + Bitu rv = 0; + if (HasSystem()) ++rv; + if (HasVideo()) ++rv; + if (HasAudio()) ++rv; + if (rv == 0) rv=1; + return rv; + } + void DeclareAuxHandle(const ReelMagic_MediaPlayer_Handle auxHandle) { + //this is so damn hacky :-( + if (_videoHandle == 0) { + _videoHandle = auxHandle; + return; + } + if (_audioHandle == 0) { + _audioHandle = auxHandle; + return; + } + LOG(LOG_REELMAGIC, LOG_WARN)("Declaring too many handles!"); + } + + + + // // ReelMagic_VideoMixerUnderlayProvider implementation here... // @@ -406,30 +456,44 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl //Bit16u GetPictureWidth() const {return _width;} -- is implemented below as it exists in both base "interfaces" //Bit16u GetPictureHeight() const {return _height;} -- is implemented below as it exists in both base "interfaces" - //Bit16u GetZOrder() const -- is implemented below as it exists in both base "interfaces" + //bool GetUnderVga() const -- is implemented below as it exists in both base "interfaces" // // ReelMagic_MediaPlayer implementation here... // + ReelMagic_MediaPlayer_Handle GetBaseHandle() const { return _handle; } + ReelMagic_MediaPlayer_Handle GetDemuxHandle() const { return _demuxHandle; } + ReelMagic_MediaPlayer_Handle GetVideoHandle() const { return _videoHandle; } + ReelMagic_MediaPlayer_Handle GetAudioHandle() const { return _audioHandle; } + void SetDisplayPosition(const Bit16u x, const Bit16u y) { LOG(LOG_REELMAGIC, LOG_ERROR)("Set Player Display Position Not Implemented!"); } void SetDisplaySize(const Bit16u width, const Bit16u height) { LOG(LOG_REELMAGIC, LOG_ERROR)("Set Player Display Size Not Implemented!"); } - void SetZOrder(const Bit16u value) { - if (_zorder == value) return; - _zorder = value; - if (_playing) ReelMagic_VideoMixerUnderlayProviderZOrderUpdate(); + void SetUnderVga(const bool value) { + if (_underVga == value) return; + _underVga = value; + if (_playing) ReelMagic_PushVideoMixerUnderlayProvider(*this); } void SetLooping(const bool value) { _loop = value; if (_plm != NULL) plm_set_loop(_plm, _loop ? TRUE : FALSE); } - bool IsFileValid() const { - return _plm != NULL; + bool HasSystem() const { + if (_plm == NULL) return false; + return _plm->demux->buffer != _plm->video_decoder->buffer; + } + bool HasVideo() const { + if (_plm == NULL) return false; + return plm_get_video_enabled(_plm) != FALSE; + } + bool HasAudio() const { + if (_plm == NULL) return false; + return plm_get_audio_enabled(_plm) != FALSE; } bool IsLooping() const { return _loop; @@ -440,10 +504,10 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl Bit16u GetPictureWidth() const {return _width;} Bit16u GetPictureHeight() const {return _height;} - Bit16u GetZOrder() const {return _zorder;} + bool GetUnderVga() const {return _underVga;} void Play() { - if (!IsFileValid()) return; + if (_plm == NULL) return; _playing = true; ReelMagic_PushVideoMixerUnderlayProvider(*this); ActivatePlayerAudioFifo(_audioFifo); @@ -461,46 +525,94 @@ namespace { class ReelMagic_MediaPlayerImplementation : public ReelMagic_MediaPl // -//stuff to manage player instances and handles to them... -//note: handles are _players[] index + 1 as "FMPDRV.EXE" uses 0 as invalid handle +//stuff to manage ReelMagic media/decoder/player handles... +//note: handles are _rmhandles[] index + 1 as "FMPDRV.EXE" uses 0 as invalid handle // -static const Bit8u MAX_RM_PLAYERS = 10; -static ReelMagic_MediaPlayer *_players[MAX_RM_PLAYERS] = {NULL}; +static ReelMagic_MediaPlayerImplementation *_rmhandles[REELMAGIC_MAX_HANDLES] = {NULL}; +static Bitu ComputeFreePlayerHandleCount() { + Bitu rv = 0; + for (Bitu i = 0; i < REELMAGIC_MAX_HANDLES; ++i) { + if (_rmhandles[i] == NULL) ++rv; + } + return rv; +} ReelMagic_MediaPlayer_Handle ReelMagic_NewPlayer(struct ReelMagic_MediaPlayerFile * const playerFile) { + //so why all this mickey-mouse for simply allocating a handle? + //the real setup allocates one handle per decoder resource + //for example, if an MPEG file is opened that only contains a video ES, + //then only one handle is allocated + //however, if an MPEG PS file is openened that contains both A/V ES streams, + //then three handles are allocated. One for system, one for audio, one for video + // + //to ensure maximum compatibility, we must also emulate this behavior + + const Bitu freeHandles = ComputeFreePlayerHandleCount(); + Bit8u handleIndex; + try { - for (Bit8u i = 0; i < MAX_RM_PLAYERS; ++i) { - if (_players[i] == NULL) { - _players[i] = new ReelMagic_MediaPlayerImplementation(playerFile, i+1); - return i+1; + if (freeHandles < 1) throw RMException("Out of handles!"); + for (handleIndex = 0; handleIndex < REELMAGIC_MAX_HANDLES; ++handleIndex) { + if (_rmhandles[handleIndex] == NULL) { + _rmhandles[handleIndex] = new ReelMagic_MediaPlayerImplementation(playerFile, handleIndex+1); + break; } } - throw RMException("Out of player handles!"); } catch (...) { delete playerFile; throw; } + + Bitu handlesNeeded = _rmhandles[handleIndex]->GetHandlesNeeded(); + if (freeHandles < handlesNeeded) { + delete _rmhandles[handleIndex]; + _rmhandles[handleIndex] = NULL; + throw RMException("Out of handles!"); + } + + Bitu additionalHandleIndex = handleIndex; + while (--handlesNeeded) { + while (_rmhandles[++additionalHandleIndex] != NULL); + _rmhandles[handleIndex]->DeclareAuxHandle(additionalHandleIndex+1); + _rmhandles[additionalHandleIndex] = _rmhandles[handleIndex]; + LOG(LOG_REELMAGIC, LOG_NORMAL)("Consuming additional handle #%u for base handle #%u", (unsigned)(additionalHandleIndex+1), (unsigned)(handleIndex+1)); + } + + return handleIndex + 1; //all ReelMagic media handles are non-zero } void ReelMagic_DeletePlayer(const ReelMagic_MediaPlayer_Handle handle) { ReelMagic_MediaPlayer *player = &ReelMagic_HandleToMediaPlayer(handle); delete player; - _players[handle-1] = NULL; + for (Bitu i = 0; i < REELMAGIC_MAX_HANDLES; ++i) { + if (_rmhandles[i] == player) { + _rmhandles[i] = NULL; + LOG(LOG_REELMAGIC, LOG_NORMAL)("Freeing handle #%u", (unsigned)(i+1)); + } + } } ReelMagic_MediaPlayer& ReelMagic_HandleToMediaPlayer(const ReelMagic_MediaPlayer_Handle handle) { - if ((handle == 0) || (handle > MAX_RM_PLAYERS)) throw RMException("Invalid player handle #%u", (unsigned)handle); - ReelMagic_MediaPlayer * const player = _players[handle-1]; + if ((handle == 0) || (handle > REELMAGIC_MAX_HANDLES)) throw RMException("Invalid handle #%u", (unsigned)handle); + ReelMagic_MediaPlayer * const player = _rmhandles[handle-1]; if (player == NULL) throw RMException("No active player at handle #%u", (unsigned)handle); return *player; } void ReelMagic_DeleteAllPlayers() { - for (Bit8u i = 0; i < MAX_RM_PLAYERS; ++i) { - delete _players[i]; - _players[i] = NULL; + for (Bit8u i = 0; i < REELMAGIC_MAX_HANDLES; ++i) { + if (_rmhandles[i] == NULL) continue; + delete _rmhandles[i]; + for (Bit8u j = i+1; j < REELMAGIC_MAX_HANDLES; ++j) { + if (_rmhandles[j] == _rmhandles[i]) { + _rmhandles[j] = NULL; + LOG(LOG_REELMAGIC, LOG_NORMAL)("Freeing handle #%u", (unsigned)(j+1)); + } + } + _rmhandles[i] = NULL; + LOG(LOG_REELMAGIC, LOG_NORMAL)("Freeing handle #%u", (unsigned)(i+1)); } } diff --git a/dosbox-0.74-3/src/hardware/reelmagic_videomixer.cpp b/dosbox-0.74-3/src/hardware/reelmagic_videomixer.cpp index e1c3d41..8ac7c32 100644 --- a/dosbox-0.74-3/src/hardware/reelmagic_videomixer.cpp +++ b/dosbox-0.74-3/src/hardware/reelmagic_videomixer.cpp @@ -51,26 +51,47 @@ namespace { //explicit definition of the different pixel types... //being a bit redundant here as an attempt to keep this //already complicated logic somewhat readable +namespace { struct RenderOutputPixel { Bit8u blue; Bit8u green; Bit8u red; Bit8u alpha; }; + struct VGA16bppPixel { /* ??? */ + template inline void CopyRGBTo(T& out) { } + inline bool IsTransparent() const { return false; } }; + struct VGA32bppPixel { Bit8u blue; Bit8u green; Bit8u red; Bit8u alpha; + template inline void CopyRGBTo(T& out) const {out.red=red; out.green=green; out.blue=blue;} +}; +struct VGAUnder32bppPixel : VGA32bppPixel { inline bool IsTransparent() const { return true; } }; +struct VGAOver32bppPixel : VGA32bppPixel { inline bool IsTransparent() const { return (red|green|blue) == 0; } }; + +struct VGAPalettePixel { + static VGA32bppPixel _vgaPalette[256]; + Bit8u index; + template inline void CopyRGBTo(T& out) const { _vgaPalette[index].CopyRGBTo(out); } + inline bool IsTransparent() const { return index == 0; } }; +struct VGAUnderPalettePixel : VGAPalettePixel { inline bool IsTransparent() const { return true; } }; +struct VGAOverPalettePixel : VGAPalettePixel { inline bool IsTransparent() const { return index == 0; } }; + struct PlayerPicturePixel { Bit8u red; Bit8u green; Bit8u blue; + template inline void CopyRGBTo(T& out) const {out.red=red; out.green=green; out.blue=blue;} + inline bool IsTransparent() const {return false;} }; +} // @@ -83,7 +104,7 @@ static bool _mpegDictatesOutputSize = false; //false if VG static bool _vgaDup5Enabled = false; //state captured from VGA -static VGA32bppPixel _vgaPalette[256]; +VGA32bppPixel VGAPalettePixel::_vgaPalette[256]; static Bitu _vgaWidth = 0; static Bitu _vgaHeight = 0; static Bitu _vgaBitsPerPixel = 0; // != 0 on this variable means we have collected the first call @@ -122,51 +143,19 @@ static Bitu _renderHeight // workaround to make this look nice and clean for the mean time.... // also, as i'm carrying around an alpha channel, i should probably put that to good use... // -static inline void MixPixel(RenderOutputPixel& out, const VGA32bppPixel& inputVgaPixel, const PlayerPicturePixel& inputMpegPixel) { - if ((inputVgaPixel.red | inputVgaPixel.green | inputVgaPixel.blue) == 0) { - //VGA pixel is "pure black"... use this as the transparent color for underlaying the video on to - out.red = inputMpegPixel.red; - out.green = inputMpegPixel.green; - out.blue = inputMpegPixel.blue; - } - else { - //VGA pixel is not "pure black"... output VGA only here... - out.red = inputVgaPixel.red; - out.green = inputVgaPixel.green; - out.blue = inputVgaPixel.blue; - } - out.alpha = 0; -} -static inline void MixPixel(RenderOutputPixel& out, const Bit8u inputVgaPaletteIndex, const PlayerPicturePixel& inputMpegPixel) { - if (inputVgaPaletteIndex == 0) { - //VGA pixel is index 0... use this as the "transparent color" for underlaying the video on to - out.red = inputMpegPixel.red; - out.green = inputMpegPixel.green; - out.blue = inputMpegPixel.blue; - } - else { - //VGA pixel is not "transparent"... output VGA only here... - const VGA32bppPixel& inputVgaPixel = _vgaPalette[inputVgaPaletteIndex]; - out.red = inputVgaPixel.red; - out.green = inputVgaPixel.green; - out.blue = inputVgaPixel.blue; - } +template +static inline void MixPixel(RenderOutputPixel& out, const VGAPixelT& vga, const MPEGPixelT& mpeg) { + if (vga.IsTransparent()) + mpeg.CopyRGBTo(out); + else + vga.CopyRGBTo(out); out.alpha = 0; } -static inline void MixPixel(RenderOutputPixel& out, const VGA32bppPixel& inputVgaPixel) { - out.red = inputVgaPixel.red; - out.green = inputVgaPixel.green; - out.blue = inputVgaPixel.blue; - out.alpha = 0; -} - -static inline void MixPixel(RenderOutputPixel& out, const Bit8u inputVgaPaletteIndex) { - const VGA32bppPixel& inputVgaPixel = _vgaPalette[inputVgaPaletteIndex]; - out.red = inputVgaPixel.red; - out.green = inputVgaPixel.green; - out.blue = inputVgaPixel.blue; - out.alpha = 0; +template +static inline void MixPixel(RenderOutputPixel& out, const VGAPixelT& vga) { + vga.CopyRGBTo(out); + out.alpha = 0; } static void ClearMpegUnderlayBuffer(const PlayerPicturePixel& p) { @@ -211,15 +200,22 @@ static void RMR_DrawLine_MixerError(const void *src) { } //its not real enterprise C++ until yer mixing macros and templates... -#define CREATE_RMR_VGA_TYPED_FUNCTIONS(DRAWLINE_FUNC_NAME) \ - static void DRAWLINE_FUNC_NAME##_VGA8(const void *src) {DRAWLINE_FUNC_NAME((const Bit8u *)src);} \ - static void DRAWLINE_FUNC_NAME##_VGA32(const void *src) {DRAWLINE_FUNC_NAME((const VGA32bppPixel*)src);} - -#define ASSIGN_RMR_DRAWLINE_FUNCTION(DRAWLINE_FUNC_NAME, VGA_BPP) { \ - switch(VGA_BPP) { \ - case 8: ReelMagic_RENDER_DrawLine = &DRAWLINE_FUNC_NAME##_VGA8; break; \ - case 32: ReelMagic_RENDER_DrawLine = &DRAWLINE_FUNC_NAME##_VGA32; break; \ - default: ReelMagic_RENDER_DrawLine = &RMR_DrawLine_MixerError; break; \ +#define CREATE_RMR_VGA_TYPED_FUNCTIONS(DRAWLINE_FUNC_NAME) \ + static void DRAWLINE_FUNC_NAME##_VGAO8(const void *src) {DRAWLINE_FUNC_NAME((const VGAOverPalettePixel *)src);} \ + static void DRAWLINE_FUNC_NAME##_VGAO32(const void *src) {DRAWLINE_FUNC_NAME((const VGAOver32bppPixel*)src);} \ + static void DRAWLINE_FUNC_NAME##_VGAU8(const void *src) {DRAWLINE_FUNC_NAME((const VGAUnderPalettePixel *)src);} \ + static void DRAWLINE_FUNC_NAME##_VGAU32(const void *src) {DRAWLINE_FUNC_NAME((const VGAUnder32bppPixel*)src);} \ + +#define ASSIGN_RMR_DRAWLINE_FUNCTION(DRAWLINE_FUNC_NAME, VGA_BPP, VGA_OVER) { \ + if (VGA_OVER) switch(VGA_BPP) { \ + case 8: ReelMagic_RENDER_DrawLine = &DRAWLINE_FUNC_NAME##_VGAO8; break; \ + case 32: ReelMagic_RENDER_DrawLine = &DRAWLINE_FUNC_NAME##_VGAO32; break; \ + default: ReelMagic_RENDER_DrawLine = &RMR_DrawLine_MixerError; break; \ + } \ + else switch(VGA_BPP) { \ + case 8: ReelMagic_RENDER_DrawLine = &DRAWLINE_FUNC_NAME##_VGAU8; break; \ + case 32: ReelMagic_RENDER_DrawLine = &DRAWLINE_FUNC_NAME##_VGAU32; break; \ + default: ReelMagic_RENDER_DrawLine = &RMR_DrawLine_MixerError; break; \ } \ } @@ -423,10 +419,10 @@ static void SetupVideoMixer(const bool updateRenderMode) { // call when starting/stopping a video to give the user that smooth hardware decoder feel :-) if (!mpeg) { if (_vgaDup5Enabled) { - ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VGAOnlyDup5Vertical, _vgaBitsPerPixel); + ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VGAOnlyDup5Vertical, _vgaBitsPerPixel, true); } else { - ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VGAOnly, _vgaBitsPerPixel); + ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VGAOnly, _vgaBitsPerPixel, true); } LOG(LOG_REELMAGIC, LOG_NORMAL)("Video Mixer Mode VGA Only (vga=%ux%u mpeg=off render=%ux%u)", (unsigned)_vgaWidth, (unsigned)_vgaHeight, (unsigned)_renderWidth, (unsigned)_renderHeight); return; @@ -434,6 +430,7 @@ static void SetupVideoMixer(const bool updateRenderMode) { //choose a RENDER draw function... + const bool vgaOver = mpeg->GetUnderVga(); const char * modeStr = "UNKNOWN"; if (_mpegDictatesOutputSize) { E_Exit("MPEG output size not yet implemented!"); @@ -443,33 +440,33 @@ static void SetupVideoMixer(const bool updateRenderMode) { if ((_renderWidth != _mpegUnderlayWidth) || (_renderHeight != _mpegUnderlayHeight)) { modeStr = "Generic Unoptimized MPEG Resize to DUP5 VGA Pictures"; Initialize_RMR_DrawLine_VSO_GeneralResizeMPEGToVGA_Dimensions(); - ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_GeneralResizeMPEGToVGADup5, _vgaBitsPerPixel); + ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_GeneralResizeMPEGToVGADup5, _vgaBitsPerPixel, vgaOver); } else { modeStr = "Matching Sized MPEG to DUP5 VGA Pictures"; - ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VGADup5VerticalMPEGSameSize, _vgaBitsPerPixel); + ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VGADup5VerticalMPEGSameSize, _vgaBitsPerPixel, vgaOver); } } else if ((_vgaWidth == _mpegUnderlayWidth) && (_vgaHeight == _mpegUnderlayHeight)) { modeStr = "Matching Sized MPEG to VGA Pictures"; - ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VGAMPEGSameSize, _vgaBitsPerPixel); + ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VGAMPEGSameSize, _vgaBitsPerPixel, vgaOver); } else if ((_vgaWidth == (_mpegUnderlayWidth*2)) && (_vgaHeight == ((_mpegUnderlayHeight*2)))) { modeStr = "Double Sized MPEG to VGA Pictures"; - ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_MPEGDoubleVGASize, _vgaBitsPerPixel); + ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_MPEGDoubleVGASize, _vgaBitsPerPixel, vgaOver); } else if ((_vgaWidth == _mpegUnderlayWidth) && ((_mpegUnderlayHeight / (_mpegUnderlayHeight - _vgaHeight)) == 6)) { modeStr = "Matching Sized MPEG to VGA Pictures, skipping every 6th MPEG line"; - ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_VGAMPEGSameWidthSkip6Vertical, _vgaBitsPerPixel); + ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_VGAMPEGSameWidthSkip6Vertical, _vgaBitsPerPixel, vgaOver); } else if ((_vgaWidth == (_mpegUnderlayWidth*2)) && (((_mpegUnderlayHeight*2) / ((_mpegUnderlayHeight*2) - _vgaHeight)) == 6)) { modeStr = "Double Sized MPEG to VGA Pictures, skipping every 6th MPEG line"; - ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_VGAMPEGDoubleSameWidthSkip6Vertical, _vgaBitsPerPixel); + ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_VGAMPEGDoubleSameWidthSkip6Vertical, _vgaBitsPerPixel, vgaOver); } else { modeStr = "Generic Unoptimized MPEG Resize"; Initialize_RMR_DrawLine_VSO_GeneralResizeMPEGToVGA_Dimensions(); - ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_GeneralResizeMPEGToVGA, _vgaBitsPerPixel); + ASSIGN_RMR_DRAWLINE_FUNCTION(RMR_DrawLine_VSO_GeneralResizeMPEGToVGA, _vgaBitsPerPixel, vgaOver); } } @@ -487,7 +484,7 @@ static void SetupVideoMixer(const bool updateRenderMode) { // The RENDER_*() interceptors begin here... // void ReelMagic_RENDER_SetPal(Bit8u entry,Bit8u red,Bit8u green,Bit8u blue) { - VGA32bppPixel& p = _vgaPalette[entry]; + VGA32bppPixel& p = VGAPalettePixel::_vgaPalette[entry]; p.red = red; p.green = green; p.blue = blue; @@ -532,27 +529,14 @@ void ReelMagic_PushVideoMixerUnderlayProvider(ReelMagic_VideoMixerUnderlayProvid return; } - ReelMagic_VideoMixerUnderlayProvider * const previousFront = _underlayProviders.empty() ? NULL : _underlayProviders.front(); + //clear the underlay buffer on first provider push + if (_underlayProviders.empty()) + ClearMpegUnderlayBuffer(); //put the new provider at the top of the provider stack... _underlayProviders.remove(&provider); //defensive _underlayProviders.push_front(&provider); - //if the previous top provider had a higher priority z-order, - //bring the previous one back to the top... - if (previousFront != NULL) { - if (previousFront->GetZOrder() < provider.GetZOrder()) { - //previous provider z-order was nearer! the previous one needs remain on top... - _underlayProviders.remove(previousFront); - _underlayProviders.push_front(previousFront); - return; - } - } - else { - //clear the underlay buffer on first provider push - ClearMpegUnderlayBuffer(); - } - //update the video rendering mode if necessary... SetupVideoMixer(_mpegDictatesOutputSize); } @@ -562,27 +546,6 @@ void ReelMagic_PopVideoMixerUnderlayProvider(ReelMagic_VideoMixerUnderlayProvide SetupVideoMixer(_mpegDictatesOutputSize); } -void ReelMagic_VideoMixerUnderlayProviderZOrderUpdate(void) { - //find if someone has a nearer Z-Order than the current - //front() underlay provider... if so, bring the nearest - //discovery to the front - if (_underlayProviders.empty()) return; //nothing to do - - ReelMagic_VideoMixerUnderlayProvider *_nearest = _underlayProviders.front(); - - //scan the list from front to back so that way players with a matching Z-Order - //are prioritied on a FILO basis (newer pushed player gets the priorty) - for (std::list::const_iterator i = _underlayProviders.begin(); - i != _underlayProviders.end(); ++i) { - if ((*i)->GetZOrder() < _nearest->GetZOrder()) - _nearest = (*i); //found a player with a nearer Z-Order! - } - if (_nearest != _underlayProviders.front()) { - //a nearer player was found... put it in front... - ReelMagic_PushVideoMixerUnderlayProvider(*_nearest); - } -} - void ReelMagic_InitVideoMixer(Section* sec) { Section_prop * section=static_cast(sec); // diff --git a/example.dosbox.conf b/example.dosbox.conf index b0171bd..bd2ccba 100755 --- a/example.dosbox.conf +++ b/example.dosbox.conf @@ -244,10 +244,26 @@ keyboardlayout=auto ipx=false +# Logging / debugging stuff... +# This section ONLY works on the heavy debugging build and will +# do nothing if set when running the normal build... +# They are simply an example to provide good debugging verbosity for +# ReelMagic stuff. Adjust to your debugging needs though +[log] +logfile=dosbox.log +pic=false +pit=false +sblaster=false +files=true +reelmagic=true +int10=true + + [reelmagic] enabled=true alwaysresident=true #vgadup5hack=true +#a204debug=false [autoexec]