diff --git a/files.cmake b/files.cmake index 8504aecb95..2339f5cb02 100644 --- a/files.cmake +++ b/files.cmake @@ -1334,6 +1334,7 @@ set(DUSK_FILES include/dusk/endian_gx.hpp src/dusk/asserts.cpp src/dusk/logging.cpp + src/dusk/layout.cpp src/dusk/stubs.cpp src/dusk/endian.cpp src/dusk/extras.c diff --git a/include/d/actor/d_a_movie_player.h b/include/d/actor/d_a_movie_player.h index e064d558dd..4e6a44bd54 100644 --- a/include/d/actor/d_a_movie_player.h +++ b/include/d/actor/d_a_movie_player.h @@ -1,7 +1,9 @@ #ifndef D_A_MOVIE_PLAYER_H #define D_A_MOVIE_PLAYER_H +#if !TARGET_PC #include +#endif #include "f_op/f_op_actor.h" #include "d/d_drawlist.h" @@ -11,6 +13,85 @@ struct daMP_THPReadBuffer { BOOL isValid; }; +#if TARGET_PC +// Copying here because thp.h is probably erroneous in the dolphin lib, +// and it's kind of a problem being there (Aurora owns the headers). +// TODO: Move this stuff in decomp? +typedef struct THPAudioRecordHeader { + BE(u32) offsetNextChannel; + BE(u32) sampleSize; + BE(s16) lCoef[8][2]; + BE(s16) rCoef[8][2]; + BE(s16) lYn1; + BE(s16) lYn2; + BE(s16) rYn1; + BE(s16) rYn2; +} THPAudioRecordHeader; + +typedef struct THPAudioDecodeInfo { + u8* encodeData; + u32 offsetNibbles; + u8 predictor; + u8 scale; + s16 yn1; + s16 yn2; +} THPAudioDecodeInfo; + +typedef struct THPTextureSet { + u8* ytexture; + u8* utexture; + u8* vtexture; + s32 frameNumber; +} THPTextureSet; + +typedef struct THPAudioBuffer { + s16* buffer; + s16* curPtr; + u32 validSample; +} THPAudioBuffer; + +typedef struct THPVideoInfo { + BE(u32) xSize; + BE(u32) ySize; + BE(u32) videoType; +} THPVideoInfo; + +typedef struct THPAudioInfo { + BE(u32) sndChannels; + BE(u32) sndFrequency; + BE(u32) sndNumSamples; + BE(u32) sndNumTracks; +} THPAudioInfo; + +typedef struct THPFrameCompInfo { + BE(u32) numComponents; + u8 frameComp[16]; +} THPFrameCompInfo; + +typedef struct THPHeader { + /* 0x00 */ char magic[4]; + /* 0x04 */ BE(u32) version; + /* 0x08 */ BE(u32) bufsize; + /* 0x0C */ BE(u32) audioMaxSamples; + /* 0x10 */ BE(f32) frameRate; + /* 0x14 */ BE(u32) numFrames; + /* 0x18 */ BE(u32) firstFrameSize; + /* 0x1C */ BE(u32) movieDataSize; + /* 0x20 */ BE(u32) compInfoDataOffsets; + /* 0x24 */ BE(u32) offsetDataOffsets; + /* 0x28 */ BE(u32) movieDataOffsets; + /* 0x2C */ BE(u32) finalFrameDataOffsets; +} THPHeader; + +u32 THPAudioDecode(s16* audioBuffer, u8* audioFrame, s32 flag); +s32 __THPAudioGetNewSample(THPAudioDecodeInfo* info); +void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr); + +#define THP_AUDIO_BUFFER_COUNT 3 +#define THP_READ_BUFFER_COUNT 10 +#define THP_TEXTURE_SET_COUNT 3 +#endif + struct daMP_THPPlayer { /* 0x000 */ DVDFileInfo fileInfo; /* 0x03C */ THPHeader header; @@ -34,7 +115,11 @@ struct daMP_THPPlayer { /* 0x0C8 */ s64 retaceCount; /* 0x0D0 */ s32 prevCount; /* 0x0D4 */ s32 curCount; +#if TARGET_PC + /* 0x0D8 */ std::atomic videoDecodeCount; +#else /* 0x0D8 */ s32 videoDecodeCount; +#endif /* 0x0DC */ f32 curVolume; /* 0x0E0 */ f32 targetVolume; /* 0x0E4 */ f32 deltaVolume; diff --git a/include/dusk/layout.hpp b/include/dusk/layout.hpp new file mode 100644 index 0000000000..78316d2e09 --- /dev/null +++ b/include/dusk/layout.hpp @@ -0,0 +1,37 @@ +#ifndef DUSK_LAYOUT_H +#define DUSK_LAYOUT_H + +#include "dolphin/types.h" + +namespace dusk { + +/** + * Helper struct for laying things out on the screen. Represents a rectangle via two corner + * positions. + */ +struct LayoutRect { + f32 PosX; + f32 PosY; + f32 PosX2; + f32 PosY2; + + [[nodiscard]] constexpr f32 Width() const { + return PosX2 - PosX; + } + + [[nodiscard]] constexpr f32 Height() const { + return PosY2 - PosY; + } + + /** + * Calculates the position to render one rectangle inside another, centered and maintaining aspect ratio. + */ + [[nodiscard]] static LayoutRect FitRectInRect( + f32 widthOuter, + f32 heightOuter, + f32 widthInner, + f32 heightInner); +}; +} + +#endif // DUSK_LAYOUT_H diff --git a/src/d/actor/d_a_movie_player.cpp b/src/d/actor/d_a_movie_player.cpp index eca4bdb9e1..f372c0216b 100644 --- a/src/d/actor/d_a_movie_player.cpp +++ b/src/d/actor/d_a_movie_player.cpp @@ -27,9 +27,10 @@ #include "f_op/f_op_overlap_mng.h" #include "dusk/gx_helper.h" -#include "dusk/memory.h" #include "dusk/os.h" +#include "dusk/layout.hpp" +#include "JSystem/JAudio2/JASCriticalSection.h" #include "turbojpeg.h" inline s32 daMP_NEXT_READ_SIZE(daMP_THPReadBuffer* readBuf) { @@ -2573,9 +2574,12 @@ static void __THPHuffDecodeDCTCompV(__REGISTER THPFileInfo* info, THPCoeff* bloc } #else // !TARGET_PC -static std::vector FixJpeg(const std::span data) { - std::vector fixedData; - fixedData.reserve(data.size()); +static std::vector FixedJpegData; +static tjhandle JpegDecompressHandle; + +static const std::vector& FixJpeg(const std::span data) { + FixedJpegData.resize(0); + FixedJpegData.reserve(data.size()); size_t startOfScanLocation = 0; for (; startOfScanLocation < data.size() - 1; startOfScanLocation++) { @@ -2602,48 +2606,32 @@ static std::vector FixJpeg(const std::span data) { // Copy data before SOS for (size_t i = 0; i < startOfScanLocation; i++) { - fixedData.push_back(data[i]); + FixedJpegData.push_back(data[i]); } // Copy data inside SOS, fixing up lacking of "byte shuffling" for (size_t i = startOfScanLocation; i < endOfImage; i++) { u8 value = data[i]; - fixedData.push_back(value); + FixedJpegData.push_back(value); if (value == 0xFF) { - fixedData.push_back(0x00); + FixedJpegData.push_back(0x00); } } // Copy data after SOS. for (size_t i = endOfImage; i < data.size(); i++) { - fixedData.push_back(data[i]); + FixedJpegData.push_back(data[i]); } - return fixedData; + return FixedJpegData; } -struct TjHandle { - tjhandle mHandle; - - explicit TjHandle(const tjhandle handle) : mHandle(handle) {} - ~TjHandle() { - tj3Destroy(mHandle); - } - - operator tjhandle() const { - return mHandle; - } -}; - extern daMP_THPPlayer daMP_ActivePlayer; -static s32 THPVideoDecode(void* file, size_t fileSize, void* tileY, void* tileU, void* tileV, void* work) { - const TjHandle handle(tj3Init(TJINIT_DECOMPRESS)); - if (handle == nullptr) { - OSReport_Error("Failed to create turbojpeg handle: %s", tj3GetErrorStr(nullptr)); - return 1; - } +static s32 THPVideoDecode(void* file, size_t fileSize, void* tileY, void* tileU, void* tileV, void*) { + assert(JpegDecompressHandle); + const auto handle = JpegDecompressHandle; const auto fixedData = FixJpeg(std::span(static_cast(file), fileSize)); auto ret = tj3DecompressHeader(handle, fixedData.data(), fixedData.size()); @@ -3220,8 +3208,13 @@ static void daMP_THPGXYuv2RgbSetup(const GXRenderModeObj* rmode) { Mtx44 m; Mtx e_m; +#if TARGET_PC + w = JUTVideo::getManager()->getFbWidth(); + h = JUTVideo::getManager()->getEfbHeight(); +#else w = rmode->fbWidth; h = rmode->efbHeight; +#endif var_f31 = 0.0f; #if WIDESCREEN_SUPPORT @@ -3651,6 +3644,13 @@ static BOOL daMP_THPPlayerOpen(char const* filename, BOOL onMemory) { } static BOOL daMP_THPPlayerClose() { +#if TARGET_PC + tj3Destroy(JpegDecompressHandle); + JpegDecompressHandle = nullptr; + + FixedJpegData.clear(); +#endif + if (daMP_ActivePlayer.open && daMP_ActivePlayer.state == 0) { daMP_ActivePlayer.open = 0; DVDClose(&daMP_ActivePlayer.fileInfo); @@ -3784,6 +3784,12 @@ static BOOL daMP_ProperTimingForGettingNextFrame() { } } else { s32 frameRate = daMP_ActivePlayer.header.frameRate * 100.0f; +#if TARGET_PC + // DUSK HACK: We only fire retrace callbacks *half* as often as the game expects, + // because we only run them once per frame, and normally there should be two scans + // per game frame. + frameRate *= 2; +#endif if (VIGetTvFormat() == VI_PAL) { daMP_ActivePlayer.curCount = daMP_ActivePlayer.retaceCount * frameRate / 5000; } else { @@ -4112,6 +4118,9 @@ static BOOL daMP_THPPlayerSetVolume(s32 vol, s32 duration) { if (duration < 0) duration = 0; +#if TARGET_PC + JASCriticalSection section; +#endif interrupt = OSDisableInterrupts(); daMP_ActivePlayer.targetVolume = vol; @@ -4155,11 +4164,14 @@ static BOOL daMP_ActivePlayer_Init(char const* moviePath) { daMP_THPPlayerGetVideoInfo(&daMP_videoInfo); daMP_THPPlayerGetAudioInfo(&daMP_audioInfo); +#if !TARGET_PC + // Window can be resized during playback, update this during draw. u16 width = JUTVideo::getManager()->getRenderMode()->fbWidth; u16 height = JUTVideo::getManager()->getRenderMode()->efbHeight; daMP_DrawPosX = (width - daMP_videoInfo.xSize) >> 1; daMP_DrawPosY = (height - daMP_videoInfo.ySize) >> 1; +#endif // "The memory needed for this THP movie is %d bytes\n" OS_REPORT("このTHPムービーが必要なメモリは%dバイトです\n", daMP_THPPlayerCalcNeedMemory()); @@ -4176,6 +4188,15 @@ static BOOL daMP_ActivePlayer_Init(char const* moviePath) { daMP_THPPlayerSetBuffer((u8*)daMP_buffer); +#if TARGET_PC + assert(JpegDecompressHandle == nullptr); + JpegDecompressHandle = tj3Init(TJINIT_DECOMPRESS); + if (JpegDecompressHandle == nullptr) { + OSReport_Error("Failed to create turbojpeg handle: %s", tj3GetErrorStr(nullptr)); + return 0; + } +#endif + if (!daMP_THPPlayerPrepare(0, 0, daMP_audioInfo.sndNumTracks != 1 ? OSGetTick() % daMP_audioInfo.sndNumTracks : 0)) { OSReport("Fail to prepare\n"); #if DEBUG @@ -4211,14 +4232,55 @@ static void daMP_ActivePlayer_Main() { } } +#if TARGET_PC && 0 +#include "imgui.h" +#endif + static void daMP_ActivePlayer_Draw() { - int frame = daMP_THPPlayerDrawCurrentFrame(JUTVideo::getManager()->getRenderMode(), daMP_DrawPosX, daMP_DrawPosY, daMP_videoInfo.xSize, daMP_videoInfo.ySize); +#if TARGET_PC + u16 width = JUTVideo::getManager()->getFbWidth(); + u16 height = JUTVideo::getManager()->getEfbHeight(); + + const auto rect = dusk::LayoutRect::FitRectInRect( + width, + height, + static_cast(daMP_videoInfo.xSize), + static_cast(daMP_videoInfo.ySize)); + + daMP_DrawPosX = static_cast(rect.PosX); + daMP_DrawPosY = static_cast(rect.PosY); +#endif + + int frame = daMP_THPPlayerDrawCurrentFrame( + JUTVideo::getManager()->getRenderMode(), + daMP_DrawPosX, daMP_DrawPosY, +#if TARGET_PC + static_cast(rect.Width()), + static_cast(rect.Height())); +#else + daMP_videoInfo.xSize, + daMP_videoInfo.ySize); +#endif daMP_THPPlayerDrawDone(); if (!fopOvlpM_IsPeek() && frame > 0 && (cAPICPad_ANY_BUTTON(0) || !daMP_c::daMP_c_Get_MovieRestFrame())) { dComIfGp_event_reset(); daMP_c::daMP_c_Set_PercentMovieVolume(0.0f); } + +#if TARGET_PC && 0 + if (ImGui::Begin("Movie player")) { + ImGui::Text("daMP_ReadedBufferQueue: %d", daMP_ReadedBufferQueue.usedCount); + ImGui::Text("daMP_ReadedBufferQueue2: %d", daMP_ReadedBufferQueue2.usedCount); + ImGui::Text("daMP_FreeReadBufferQueue: %d", daMP_FreeReadBufferQueue.usedCount); + ImGui::Text("daMP_DecodedTextureSetQueue: %d", daMP_DecodedTextureSetQueue.usedCount); + ImGui::Text("daMP_FreeTextureSetQueue: %d", daMP_FreeTextureSetQueue.usedCount); + ImGui::Text("daMP_DecodedAudioBufferQueue: %d", daMP_DecodedAudioBufferQueue.usedCount); + ImGui::Text("daMP_FreeAudioBufferQueue: %d", daMP_FreeAudioBufferQueue.usedCount); + } + + ImGui::End(); +#endif } static BOOL daMP_Fail_alloc; diff --git a/src/dusk/audio/DuskAudioSystem.cpp b/src/dusk/audio/DuskAudioSystem.cpp index 522f4c9d60..71b1944fd2 100644 --- a/src/dusk/audio/DuskAudioSystem.cpp +++ b/src/dusk/audio/DuskAudioSystem.cpp @@ -111,11 +111,6 @@ int RenderNewAudioFrame() { outRaw.flush(); #endif - if (JASDriver::extMixCallback != nullptr) { - // TODO: actually mix this data in. - JASDriver::extMixCallback(countSubframes * DSP_SUBFRAME_SIZE); - } - return static_cast(countSubframes) * DSP_SUBFRAME_SIZE; } @@ -138,6 +133,19 @@ void RenderAudioSubframe() { InterleaveOutputData(OutBuffer, OutInterleaveBuffer); + if (JASDriver::extMixCallback != nullptr && JASDriver::sMixMode == MIX_MODE_INTERLEAVE) { + static_assert(OutputSubframe::NUM_CHANNELS == 2); // This code only works with Stereo so far. + // NOTE: In the real game, this gets called on the entire audio frame, rather than the subframe. + // That's probably more efficient, but I didn't wanna change the code to calculate the + // entire audio buffers at once. + const auto mixData = JASDriver::extMixCallback(DSP_SUBFRAME_SIZE); + if (mixData) { + for (int i = 0; i < OutInterleaveBuffer.size(); i++) { + OutInterleaveBuffer[i] += static_cast(mixData[i]) / static_cast(0x7FFF); + } + } + } + #if defined(DUSK_DUMP_AUDIO) outRaw.write((const char*)OutInterleaveBuffer.data(), sizeof(OutInterleaveBuffer)); #endif diff --git a/src/dusk/layout.cpp b/src/dusk/layout.cpp new file mode 100644 index 0000000000..11185debd6 --- /dev/null +++ b/src/dusk/layout.cpp @@ -0,0 +1,25 @@ +#include "dusk/layout.hpp" + +using namespace dusk; + +LayoutRect LayoutRect::FitRectInRect( + const f32 widthOuter, + const f32 heightOuter, + const f32 widthInner, + const f32 heightInner) { + + // Try as if constrained vertically first. + auto width = widthInner * (heightOuter / heightInner); + auto height = heightOuter; + if (width > widthOuter) { + // Otherwise, constrained horizontally. + width = widthOuter; + height = heightOuter * (widthOuter / widthInner); + } + + // Center it + const auto posX = (widthOuter - width) / 2; + const auto posY = (heightOuter - height) / 2; + + return {posX, posY, posX + width, posY + height}; +}