Make the movie player work somewhat.

THPs are *almost* just a bunch of JPEGs so the TL;DR is BE fixes and replacing the decoding with libjpeg-turbo.

Needs changes to thp.h which should be removed from Aurora, will do that later.

Also audio not implemented yet.
This commit is contained in:
PJB3005
2026-03-28 18:29:58 +01:00
parent 81d0312f2b
commit bb92f955c8
6 changed files with 203 additions and 16 deletions
+3 -1
View File
@@ -52,6 +52,7 @@ endif ()
include(FetchContent)
include(ExternalProject)
message(STATUS "dusk: Fetching cxxopts")
FetchContent_Declare(
@@ -62,6 +63,7 @@ FetchContent_Declare(
FetchContent_MakeAvailable(cxxopts)
include(cmake/libjpeg.cmake)
include(files.cmake)
# TODO: version handling for res includes
@@ -107,7 +109,7 @@ add_library(game SHARED ${DOLZEL_FILES} ${Z2AUDIOLIB_FILES} ${SSYSTEM_FILES} ${J
src/dusk/imgui/ImGuiStubLog.cpp
src/dusk/imgui/ImGuiAudio.cpp)
target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts)
target_link_libraries(game PRIVATE game_debug cxxopts::cxxopts libjpeg-turbo-lib)
target_compile_definitions(game PRIVATE TARGET_PC AVOID_UB=1 VERSION=0 NDEBUG=1 NDEBUG_DEFINED=1 DEBUG_DEFINED=0
DUSK_TP_VERSION="${DUSK_TP_VERSION}" DUSK_GAME_NAME="${DUSK_GAME_NAME}" DUSK_GAME_VERSION="${DUSK_GAME_VERSION}")
add_executable(dusk src/dusk/main.cpp)
+12
View File
@@ -0,0 +1,12 @@
ExternalProject_Add(
libjpeg-turbo
URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.1.90.tar.gz
URL_HASH SHA256=076ef1431f2803a91f07e0f92433d4dcf39bc9113226c4f46ba3d3d54f514c9d
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
)
ExternalProject_Get_Property(libjpeg-turbo INSTALL_DIR)
add_library(libjpeg-turbo-lib STATIC IMPORTED GLOBAL)
set_target_properties(libjpeg-turbo-lib PROPERTIES IMPORTED_LOCATION ${INSTALL_DIR}/lib/turbojpeg-static.lib)
target_include_directories(libjpeg-turbo-lib INTERFACE ${INSTALL_DIR}/include)
+3
View File
@@ -629,5 +629,8 @@ constexpr auto gameRegions = std::to_array({
MapEntry("Cutscene: Hyrule Castle Throne Room", "R_SP301", {
{0, {0, 20, 100}},
}),
MapEntry("Title screen movie map", "S_MV000", {
{0, {0, 1}},
}),
})
});
+4
View File
@@ -260,6 +260,10 @@ void JASDriver::finishDSPFrame() {
}
void JASDriver::registerMixCallback(MixCallback param_0, JASMixMode param_1) {
#if TARGET_PC
JASCriticalSection section;
#endif
extMixCallback = param_0;
sMixMode = param_1;
}
+176 -15
View File
@@ -14,21 +14,29 @@
#pragma optimization_level 4
#pragma optimize_for_size off
#include "JSystem/JKernel/JKRExpHeap.h"
#include <cstring>
#include <span>
#include "JSystem/JAudio2/JASAiCtrl.h"
#include "JSystem/JAudio2/JASDriverIF.h"
#include "d/actor/d_a_movie_player.h"
#include "JSystem/JKernel/JKRExpHeap.h"
#include "Z2AudioLib/Z2Instances.h"
#include "d/actor/d_a_movie_player.h"
#include <cassert>
#include "f_op/f_op_overlap_mng.h"
#include <cstring>
#include "dusk/gx_helper.h"
#include "dusk/memory.h"
#include "dusk/os.h"
#include "turbojpeg.h"
inline s32 daMP_NEXT_READ_SIZE(daMP_THPReadBuffer* readBuf) {
return *(s32*)readBuf->ptr;
return *(BE(s32)*)readBuf->ptr;
}
#ifdef __cplusplus
#if defined(__cplusplus) && !TARGET_PC
extern "C" {
#endif
@@ -196,6 +204,7 @@ static void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr) {
info->encodeData++;
}
#if !TARGET_PC
static u8 THPStatistics[1120] ATTRIBUTE_ALIGN(32);
static THPHuffmanTab* Ydchuff ATTRIBUTE_ALIGN(32);
@@ -2562,8 +2571,116 @@ static void __THPHuffDecodeDCTCompV(__REGISTER THPFileInfo* info, THPCoeff* bloc
}
}
}
#else // !TARGET_PC
static std::vector<u8> FixJpeg(const std::span<u8> data) {
std::vector<u8> fixedData;
fixedData.reserve(data.size());
size_t startOfScanLocation = 0;
for (; startOfScanLocation < data.size() - 1; startOfScanLocation++) {
if (data[startOfScanLocation] == 0xFF && data[startOfScanLocation + 1] == 0xDA) {
goto sosFound;
}
}
CRASH("Unable to find SOS marker!");
sosFound:
startOfScanLocation += 2; // TODO: Skip entire SOS header?
size_t endOfImage = data.size() - 1;
for (; endOfImage > startOfScanLocation; endOfImage--) {
if (data[endOfImage] == 0xFF && data[endOfImage + 1] == 0xD9) {
goto eoiFound;
}
}
CRASH("Unable to find EOI marker!");
eoiFound:
// Copy data before SOS
for (size_t i = 0; i < startOfScanLocation; i++) {
fixedData.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);
if (value == 0xFF) {
fixedData.push_back(0x00);
}
}
// Copy data after SOS.
for (size_t i = endOfImage; i < data.size(); i++) {
fixedData.push_back(data[i]);
}
return fixedData;
}
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;
}
const auto fixedData = FixJpeg(std::span(static_cast<u8*>(file), fileSize));
auto ret = tj3DecompressHeader(handle, fixedData.data(), fixedData.size());
if (ret == -1) {
OSReport_Error("Parsing JPEG header failed: %s", tj3GetErrorStr(handle));
return 1;
}
if (tj3Get(handle, TJPARAM_JPEGWIDTH) != daMP_ActivePlayer.videoInfo.xSize) {
OSReport_Error("Invalid width in video frame!");
return 1;
}
if (tj3Get(handle, TJPARAM_JPEGHEIGHT) != daMP_ActivePlayer.videoInfo.ySize) {
OSReport_Error("Invalid height in video frame!");
return 1;
}
ret = tj3Set(handle, TJPARAM_SUBSAMP, TJSAMP_420);
if (ret != 0) {
OSReport_Error("Failed to set subsampling mode: %s", tj3GetErrorStr(handle));
return 1;
}
u8* planes[3] = {static_cast<u8*>(tileY), static_cast<u8*>(tileU), static_cast<u8*>(tileV)};
ret = tj3DecompressToYUVPlanes8(handle, fixedData.data(), fixedData.size(), planes, nullptr);
if (ret != 0) {
OSReport_Error("Image decompression failed: %s", tj3GetErrorStr(handle));
return 1;
}
return 0;
}
#endif
static BOOL THPInit() {
#if !TARGET_PC
u8* base;
base = (u8*)(0xE000 << 16);
@@ -2585,10 +2702,11 @@ static BOOL THPInit() {
OSInitFastCast();
__THPInitFlag = TRUE;
#endif
return TRUE;
}
#ifdef __cplusplus
#if defined(__cplusplus) && !TARGET_PC
}
#endif
@@ -2654,6 +2772,10 @@ void daMP_ReadThreadCancel() {
}
void* daMP_Reader(void*) {
#if TARGET_PC
OSSetCurrentThreadName("movie player reader");
#endif
daMP_THPReadBuffer* buf;
s32 curFrame;
s32 status;
@@ -2751,10 +2873,10 @@ static BOOL daMP_First;
static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) {
THPTextureSet* textureSet;
s32 i;
u32* tileOffsets;
BE(u32)* tileOffsets;
u8* tile;
tileOffsets = (u32*)(readBuffer->ptr + 8);
tileOffsets = (BE(u32)*)(readBuffer->ptr + 8);
tile = &readBuffer->ptr[daMP_ActivePlayer.compInfo.numComponents * 4] + 8;
textureSet = (THPTextureSet*)daMP_PopFreeTextureSet();
@@ -2762,8 +2884,13 @@ static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) {
switch (daMP_ActivePlayer.compInfo.frameComp[i]) {
case 0: {
if ((daMP_ActivePlayer.videoError = THPVideoDecode(
tile, textureSet->ytexture, textureSet->utexture,
textureSet->vtexture, daMP_ActivePlayer.thpWork))) {
tile,
#if TARGET_PC
*tileOffsets,
#endif
textureSet->ytexture, textureSet->utexture,
textureSet->vtexture,
daMP_ActivePlayer.thpWork))) {
if (daMP_First) {
daMP_PrepareReady(FALSE);
daMP_First = FALSE;
@@ -2789,6 +2916,10 @@ static void daMP_VideoDecode(daMP_THPReadBuffer* readBuffer) {
}
static void* daMP_VideoDecoder(void* param_0) {
#if TARGET_PC
OSSetCurrentThreadName("video decoder");
#endif
daMP_THPReadBuffer* thpBuffer;
while (TRUE) {
@@ -2820,6 +2951,10 @@ static void* daMP_VideoDecoder(void* param_0) {
}
static void* daMP_VideoDecoderForOnMemory(void* param_0) {
#if TARGET_PC
OSSetCurrentThreadName("video decoder");
#endif
daMP_THPReadBuffer readBuffer;
s32 readSize;
s32 frame;
@@ -2944,10 +3079,10 @@ static void daMP_PushDecodedAudioBuffer(void* buffer) {
static void daMP_AudioDecode(daMP_THPReadBuffer* readBuffer) {
THPAudioBuffer* audioBuf;
s32 i;
u32* offsets;
BE(u32)* offsets;
u8* audioData;
offsets = (u32*)(readBuffer->ptr + 8);
offsets = (BE(u32)*)(readBuffer->ptr + 8);
audioData = &readBuffer->ptr[daMP_ActivePlayer.compInfo.numComponents * 4] + 8;
audioBuf = (THPAudioBuffer*)daMP_PopFreeAudioBuffer();
@@ -2969,6 +3104,10 @@ static void daMP_AudioDecode(daMP_THPReadBuffer* readBuffer) {
}
static void* daMP_AudioDecoder(void* param_0) {
#if TARGET_PC
OSSetCurrentThreadName("movie audio decoder");
#endif
daMP_THPReadBuffer* buf;
while (TRUE) {
@@ -2979,6 +3118,10 @@ static void* daMP_AudioDecoder(void* param_0) {
}
static void* daMP_AudioDecoderForOnMemory(void* param_0) {
#if TARGET_PC
OSSetCurrentThreadName("movie audio decoder");
#endif
s32 size;
s32 readSize;
daMP_THPReadBuffer readBuffer;
@@ -3168,19 +3311,26 @@ static void daMP_THPGXYuv2RgbDraw(u8* y_data, u8* u_data, u8* v_data, s16 x,
TGXTexObj tobj0;
TGXTexObj tobj1;
TGXTexObj tobj2;
#if TARGET_PC
#define FMT (GXTexFmt)GX_TF_R8_PC
#else
#define FMT GX_TF_I8
#endif
GXInitTexObj(&tobj0, y_data, textureWidth, textureHeight, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObj(&tobj0, y_data, textureWidth, textureHeight, FMT, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObjLOD(&tobj0, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1);
GXLoadTexObj(&tobj0, GX_TEXMAP0);
GXInitTexObj(&tobj1, u_data, textureWidth >> 1, textureHeight >> 1, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObj(&tobj1, u_data, textureWidth >> 1, textureHeight >> 1, FMT, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObjLOD(&tobj1, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1);
GXLoadTexObj(&tobj1, GX_TEXMAP1);
GXInitTexObj(&tobj2, v_data, textureWidth >> 1, textureHeight >> 1, GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObj(&tobj2, v_data, textureWidth >> 1, textureHeight >> 1, FMT, GX_CLAMP, GX_CLAMP, GX_FALSE);
GXInitTexObjLOD(&tobj2, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1);
GXLoadTexObj(&tobj2, GX_TEXMAP2);
#undef FMT
GXBegin(GX_QUADS, GX_VTXFMT7, 4);
GXPosition3s16(x, y, 0);
GXTexCoord2u16(0, 0);
@@ -3191,6 +3341,12 @@ static void daMP_THPGXYuv2RgbDraw(u8* y_data, u8* u_data, u8* v_data, s16 x,
GXPosition3s16(x, y + polygonHeight, 0);
GXTexCoord2u16(0, 1);
GXEnd();
#if TARGET_PC
GXDestroyTexObj(&tobj0);
GXDestroyTexObj(&tobj1);
GXDestroyTexObj(&tobj2);
#endif
}
static u16 daMP_VolumeTable[] = {
@@ -3546,6 +3702,11 @@ static BOOL daMP_THPPlayerSetBuffer(u8* buffer) {
ysize = ALIGN_NEXT(daMP_ActivePlayer.videoInfo.xSize * daMP_ActivePlayer.videoInfo.ySize, 32);
uvsize = ALIGN_NEXT(daMP_ActivePlayer.videoInfo.xSize * daMP_ActivePlayer.videoInfo.ySize / 4, 32);
#if TARGET_PC
assert(ysize >= tj3YUVPlaneSize(0, daMP_ActivePlayer.videoInfo.xSize, 0, daMP_ActivePlayer.videoInfo.ySize, TJSAMP_420));
assert(uvsize >= tj3YUVPlaneSize(1, daMP_ActivePlayer.videoInfo.xSize, 0, daMP_ActivePlayer.videoInfo.ySize, TJSAMP_420));
assert(uvsize >= tj3YUVPlaneSize(2, daMP_ActivePlayer.videoInfo.xSize, 0, daMP_ActivePlayer.videoInfo.ySize, TJSAMP_420));
#endif
for (i = 0; i < ARRAY_SIZE(daMP_ActivePlayer.textureSet); i++) {
daMP_ActivePlayer.textureSet[i].ytexture = ptr;
+5
View File
@@ -111,6 +111,11 @@ int RenderNewAudioFrame() {
outRaw.flush();
#endif
if (JASDriver::extMixCallback != nullptr) {
// TODO: actually mix this data in.
JASDriver::extMixCallback(countSubframes * DSP_SUBFRAME_SIZE);
}
return static_cast<u16>(countSubframes) * DSP_SUBFRAME_SIZE;
}