#include "ui/CSimpleMovieFrame.hpp" #include "ui/CSimpleMovieFrameScript.hpp" #include "util/SFile.hpp" #include "gx/Coordinate.hpp" #include "gx/Buffer.hpp" #include "gx/RenderState.hpp" #include "gx/Texture.hpp" #include "gx/Transform.hpp" #include "gx/Draw.hpp" #include #include #define XVDEC_CALL #if defined(WHOA_SYSTEM_WIN) #include #if defined(WHOA_ARCH_32) #undef XVDEC_CALL #define XVDEC_CALL __cdecl #elif defined(__GNUC__) || defined(__clang__) #undef XVDEC_CALL #define XVDEC_CALL __attribute__((cdecl)) #endif #define RTLD_LAZY 1 static inline void* dlopen(const char* filename, int flag) { (void)flag; return LoadLibraryA(filename); } static inline void* dlsym(void* handle, const char* symbol) { return GetProcAddress(reinterpret_cast(handle), symbol); } static inline int dlclose(void* handle) { return static_cast(FreeLibrary(reinterpret_cast(handle))); } #else // !defined(WHOA_SYSTEM_WIN) #include #endif extern "C" { typedef struct { void* output; void* input; unsigned int input_size; int update; int zero0; int zero1; } decoder_data_t; typedef int (XVDEC_CALL * INITIALIZEDIVXDECODER)(unsigned int index, unsigned int width, unsigned int height); typedef int (XVDEC_CALL * SETOUTPUTFORMAT)(unsigned int index, unsigned int one, unsigned int width, unsigned int height); typedef int (XVDEC_CALL * DIVXDECODE)(unsigned int index, void* data, unsigned int zero); typedef int (XVDEC_CALL * UNINITIALIZEDIVXDECODER)(unsigned int index); } static void* s_decoderLibrary = nullptr; static INITIALIZEDIVXDECODER InitializeDivxDecoder = nullptr; static SETOUTPUTFORMAT SetOutputFormat = nullptr; static DIVXDECODE DivxDecode = nullptr; static UNINITIALIZEDIVXDECODER UnInitializeDivxDecoder = nullptr; static const char* s_decoderNames[] = { "DivxDecoder.dll", "XvidDecoder.dll", "libXvidDecoder.dylib", "XvidDecoder.dylib", "libXvidDecoder.so", "XvidDecoder.so", nullptr }; static int32_t UnloadDivxDecoder() { if (!s_decoderLibrary) { return 0; } InitializeDivxDecoder = nullptr; SetOutputFormat = nullptr; DivxDecode = nullptr; UnInitializeDivxDecoder = nullptr; dlclose(s_decoderLibrary); s_decoderLibrary = nullptr; return 1; } static int32_t LoadDivxDecoder() { if (InitializeDivxDecoder && SetOutputFormat && DivxDecode && UnInitializeDivxDecoder) { return 1; } const char** decoderName = s_decoderNames; while (!s_decoderLibrary && *decoderName) { s_decoderLibrary = dlopen(*decoderName, RTLD_LAZY); ++decoderName; } if (!s_decoderLibrary) { return 0; } InitializeDivxDecoder = reinterpret_cast(dlsym(s_decoderLibrary, "InitializeDivxDecoder")); SetOutputFormat = reinterpret_cast(dlsym(s_decoderLibrary, "SetOutputFormat")); DivxDecode = reinterpret_cast(dlsym(s_decoderLibrary, "DivxDecode")); UnInitializeDivxDecoder = reinterpret_cast(dlsym(s_decoderLibrary, "UnInitializeDivxDecoder")); if (!InitializeDivxDecoder || !SetOutputFormat || !DivxDecode || !UnInitializeDivxDecoder) { UnloadDivxDecoder(); return 0; } return 1; } static uint32_t s_decoderIndex = 0; static uint32_t s_strideData[144] = { 512, 256, 0, 3200, 256, 256, 2048, 3200, 32, 256, 3072, 3200, 512, 128, 819200, 3200, 256, 128, 821248, 3200, 32, 128, 822272, 3200, 512, 512, 0, 4096, 512, 512, 2048, 4096, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 512, 512, 0, 3200, 256, 512, 2048, 3200, 32, 512, 3072, 3200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 512, 512, 0, 4096, 512, 512, 2048, 4096, 512, 64, 2097152, 4096, 512, 64, 2099200, 4096, 0, 0, 0, 0, 0, 0, 0, 0, 512, 256, 0, 3200, 256, 256, 2048, 3200, 32, 256, 3072, 3200, 512, 128, 819200, 3200, 256, 128, 821248, 3200, 32, 128, 822272, 3200, 512, 512, 0, 4096, 512, 512, 2048, 4096, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; static int32_t s_movieTextureUpdate[] = { 0, 6, 512, 256, // Format: 0, Texture: 0 0, 6, 256, 256, // Format: 0, Texture: 1 0, 6, 32, 256, // Format: 0, Texture: 2 0, 0, 512, 122, // Format: 0, Texture: 3 0, 0, 256, 122, // Format: 0, Texture: 4 0, 0, 32, 122, // Format: 0, Texture: 5 0, 24, 512, 512, // Format: 1, Texture: 0 0, 0, 512, 488, // Format: 1, Texture: 1 0, 0, 0, 0, // Format: 1, Texture: 2 0, 0, 0, 0, // Format: 1, Texture: 3 0, 0, 0, 0, // Format: 1, Texture: 4 0, 0, 0, 0, // Format: 1, Texture: 5 0, 32, 512, 480, // Format: 2, Texture: 0 0, 32, 256, 480, // Format: 2, Texture: 1 0, 32, 32, 480, // Format: 2, Texture: 2 0, 0, 0, 0, // Format: 2, Texture: 3 0, 0, 0, 0, // Format: 2, Texture: 4 0, 0, 0, 0, // Format: 2, Texture: 5 0, 0, 512, 512, // Format: 3, Texture: 0 0, 0, 512, 512, // Format: 3, Texture: 1 0, 0, 512, 64, // Format: 3, Texture: 2 0, 0, 512, 64, // Format: 3, Texture: 3 0, 0, 0, 0, // Format: 3, Texture: 4 0, 0, 0, 0, // Format: 3, Texture: 5 0, 21, 512, 256, // Format: 4, Texture: 0 0, 21, 256, 256, // Format: 4, Texture: 1 0, 21, 32, 256, // Format: 4, Texture: 2 0, 0, 512, 107, // Format: 4, Texture: 3 0, 0, 256, 107, // Format: 4, Texture: 4 0, 0, 32, 107, // Format: 4, Texture: 5 0, 38, 512, 512, // Format: 5, Texture: 0 0, 0, 512, 474, // Format: 5, Texture: 1 0, 0, 0, 0, // Format: 5, Texture: 2 0, 0, 0, 0, // Format: 5, Texture: 3 0, 0, 0, 0, // Format: 5, Texture: 4 0, 0, 0, 0 // Format: 5, Texture: 5 }; static float s_layout[144] = { 0.0f, 0.63999999f, 0.11f, -0.33000001f, 0.63999999f, 0.95999998f, 0.11f, -0.33000001f, 0.95999998f, 1.0f, 0.11f, -0.33000001f, 0.0f, 0.63999999f, 0.33000001f, 0.11f, 0.63999999f, 0.95999998f, 0.33000001f, 0.11f, 0.95999998f, 1.0f, 0.33000001f, 0.11f, 0.0f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, 0.333f, -0.333f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.63999999f, 0.41999999f, -0.41999999f, 0.63999999f, 0.95999998f, 0.41999999f, -0.41999999f, 0.95999998f, 1.0f, 0.41999999f, -0.41999999f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.32659999f, -0.41999999f, 0.5f, 1.0f, 0.32659999f, -0.41999999f, 0.0f, 0.5f, 0.41999999f, 0.32659999f, 0.5f, 1.0f, 0.41999999f, 0.32659999f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.63999999f, 0.11f, -0.33000001f, 0.63999999f, 0.95999998f, 0.11f, -0.33000001f, 0.95999998f, 1.0f, 0.11f, -0.33000001f, 0.0f, 0.63999999f, 0.33000001f, 0.11f, 0.63999999f, 0.95999998f, 0.33000001f, 0.11f, 0.95999998f, 1.0f, 0.33000001f, 0.11f, 0.0f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, 0.333f, -0.333f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; static const uint32_t s_textureCountByFormat[6] = { 6, 2, 3, 4, 6, 2 }; static const uint32_t s_imageDataOffsets[6] = { 19200, 98304, 102400, 0, 67200, 155648 }; int32_t CSimpleMovieFrame::s_metatable; int32_t CSimpleMovieFrame::s_objectType; void CSimpleMovieFrame::CreateScriptMetaTable() { lua_State* L = FrameScript_GetContext(); int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleMovieFrame::RegisterScriptMethods); CSimpleMovieFrame::s_metatable = ref; } int32_t CSimpleMovieFrame::GetObjectType() { if (!CSimpleMovieFrame::s_objectType) { CSimpleMovieFrame::s_objectType = ++FrameScript_Object::s_objectTypes; } return CSimpleMovieFrame::s_objectType; } void CSimpleMovieFrame::RegisterScriptMethods(lua_State* L) { CSimpleFrame::RegisterScriptMethods(L); FrameScript_Object::FillScriptMethodTable(L, SimpleMovieFrameMethods, NUM_SIMPLE_MOVIE_FRAME_SCRIPT_METHODS); } void CSimpleMovieFrame::RenderMovie(void* param) { auto movieFrame = reinterpret_cast(param); if (movieFrame->m_isPlaying) { movieFrame->UpdateTiming(); movieFrame->Render(); } } void CSimpleMovieFrame::TextureCallback(EGxTexCommand command, uint32_t width, uint32_t height, uint32_t, uint32_t, void* userData, uint32_t& texelStrideInBytes, const void*& texels) { if (command == GxTex_Latch) { auto data = reinterpret_cast(userData); STORM_ASSERT(width == data->data[0]); STORM_ASSERT(height == data->data[1]); texels = &data->buffer[data->data[2]]; texelStrideInBytes = data->data[3]; } } FrameScript_Object::ScriptIx* CSimpleMovieFrame::GetScriptByName(const char* name, ScriptData& data) { auto parentScript = CSimpleFrame::GetScriptByName(name, data); if (parentScript) { return parentScript; } if (!SStrCmpI(name, "OnMovieFinished", STORM_MAX_STR)) { return &this->m_onMovieFinished; } if (!SStrCmpI(name, "OnMovieShowSubtitle", STORM_MAX_STR)) { data.wrapper = "return function(self,text) %s end"; return &this->m_onMovieShowSubtitle; } if (!SStrCmpI(name, "OnMovieHideSubtitle", STORM_MAX_STR)) { return &this->m_onMovieHideSubtitle; } return nullptr; } bool CSimpleMovieFrame::IsA(int32_t type) { return type == CSimpleMovieFrame::s_objectType || type == CSimpleFrame::s_objectType || type == CScriptRegion::s_objectType || type == CScriptObject::s_objectType; } int32_t CSimpleMovieFrame::GetScriptMetaTable() { return CSimpleMovieFrame::s_metatable; } void CSimpleMovieFrame::OnFrameRender(CRenderBatch* batch, uint32_t layer) { this->CSimpleFrame::OnFrameRender(batch, layer); if (layer == DRAWLAYER_ARTWORK) { batch->QueueCallback(&CSimpleMovieFrame::RenderMovie, this); } } CSimpleMovieFrame::CSimpleMovieFrame(CSimpleFrame* parent) : CSimpleFrame(parent) { } int32_t CSimpleMovieFrame::StartMovie(const char* filename, int32_t volume) { if (!LoadDivxDecoder()) { return 0; } if (!this->ParseAVIFile(filename) || !this->OpenVideo()) { return 0; } SStrCopy(this->m_filename, filename, 256); this->m_volume = volume; // this->OpenAudio(this, filename, volume, 0); // this->OpenCaptions(this, filename); this->m_isPlaying = 1; return 1; } void CSimpleMovieFrame::StopMovie() { if (!this->m_isPlaying) { return; } // UnloadDivxDecoder // CloseAudio // CloseCaptions this->m_isInterrupted = 0; this->m_isPlaying = 0; if (this->m_onMovieFinished.luaRef) { this->RunScript(this->m_onMovieFinished, 0, nullptr); } } int32_t CSimpleMovieFrame::ParseAVIFile(const char* filename) { char path[STORM_MAX_PATH]; // WARNING(workaround): Remove "Data/enGB/" substring SStrPrintf(path, STORM_MAX_PATH, "Data/enGB/%s.avi", filename); SFile* videoFile = nullptr; if (!SFile::OpenEx(nullptr, path, 1, &videoFile)) { return 0; } // -- ParseAVIHeader -- #pragma pack(push, 1) struct { char id[4]; uint32_t length; char format[4]; } block; #pragma pack(pop) if (!SFile::Read(videoFile, &block, 12, nullptr, nullptr, nullptr) || SStrCmpI(block.id, "RIFF", 4) || SStrCmpI(block.format, "AVI ", 4)) { SFile::Close(videoFile); return 0; } uint32_t fileSize = SFile::GetFileSize(videoFile, nullptr); char* data = nullptr; uint32_t dataSize = 0; uint8_t* indexData = nullptr; uint32_t indexDataSize = 0; uint32_t moviStart = 0; while (true) { uint32_t position = SFile::SetFilePointer(videoFile, 0, nullptr, 1); if (position >= fileSize) { break; } if (!SFile::Read(videoFile, &block, 8, nullptr, nullptr, nullptr)) { break; } if (SStrCmpI(block.id, "LIST", 4)) { if (SStrCmpI(block.id, "idx1", 4)) { SFile::SetFilePointer(videoFile, block.length, nullptr, 1); } else { indexDataSize = block.length; indexData = reinterpret_cast(alloca(indexDataSize)); if (!SFile::Read(videoFile, indexData, indexDataSize, nullptr, nullptr, nullptr)) { break; } } } else { if (!SFile::Read(videoFile, &block, 4, nullptr, nullptr, nullptr)) { break; } block.length -= 4; if (SStrCmpI(block.id, "hdrl", 4)) { if (SStrCmpI(block.id, "movi", 4)) { SFile::SetFilePointer(videoFile, block.length, nullptr, 1); } else { moviStart = SFile::SetFilePointer(videoFile, 0, nullptr, 1); SFile::SetFilePointer(videoFile, block.length, nullptr, 1); } } else { dataSize = block.length; data = reinterpret_cast(alloca(dataSize)); if (!SFile::Read(videoFile, data, dataSize, nullptr, nullptr, nullptr)) { break; } } } } int32_t v41 = 0; int32_t v39 = -1; int32_t v30 = -1; uint32_t offset = 0; while (offset < dataSize) { if (!SStrCmpI(&data[offset], "LIST", 4)) { offset += 12; continue; } uint32_t length = *reinterpret_cast(&data[offset + 4]); if (SStrCmpI(&data[offset], "strh", 4u)) { if (SStrCmpI(&data[offset], "strf", 4)) { offset += 8; } else { offset += 8; if (v39 >= 0 && v30 < 0) { this->m_videoWidth = *reinterpret_cast(&data[offset + 4]); this->m_videoHeight = *reinterpret_cast(&data[offset + 8]); } } } else { offset += 8; if (!SStrCmpI(&data[offset], "vids", 4)) { uint32_t scale = *reinterpret_cast(&data[offset + 20]); uint32_t rate = *reinterpret_cast(&data[offset + 24]); this->m_frameRate = static_cast(rate) / static_cast(scale); this->m_numFrames = *reinterpret_cast(&data[offset + 32]); v39 = v41; } if (!SStrCmpI(&data[offset], "auds", 4)) { v30 = v41; } ++v41; } offset += length; } // -- ParseAVIIndex -- this->m_videoBytes = 0; this->m_audioBytes = 0; offset = 0; while (offset < indexDataSize) { // IsVideoChunk if (indexData[offset + 2] == 100) { this->m_videoBytes += *reinterpret_cast(&indexData[offset + 12]) + 4; } // IsAudioChunk if (indexData[offset + 2] == 119) { this->m_audioBytes += *reinterpret_cast(&indexData[offset + 12]); } offset += 16; } this->m_videoData = reinterpret_cast(ALLOC(this->m_videoBytes)); this->m_audioData = reinterpret_cast(ALLOC(this->m_audioBytes)); char* videoData = this->m_videoData; char* audioData = this->m_audioData; offset = 0; while (offset < indexDataSize) { // IsVideoChunk if (indexData[offset + 2] == 100) { uint32_t frameSize = *reinterpret_cast(&indexData[offset + 12]); uint32_t frameOffset = *reinterpret_cast(&indexData[offset + 8]) + moviStart + 4; memcpy(videoData, &frameSize, 4); videoData += 4; SFile::SetFilePointer(videoFile, frameOffset, nullptr, 0); SFile::Read(videoFile, videoData, frameSize, nullptr, nullptr, nullptr); videoData += frameSize; } // IsAudioChunk if (indexData[offset + 2] == 119) { uint32_t frameSize = *reinterpret_cast(&indexData[offset + 12]); uint32_t frameOffset = *reinterpret_cast(&indexData[offset + 8]) + moviStart + 4; SFile::SetFilePointer(videoFile, frameOffset, nullptr, 0); SFile::Read(videoFile, audioData, frameSize, nullptr, nullptr, nullptr); audioData += frameSize; } offset += 16; } this->m_currentFrameData = this->m_videoData; SFile::Close(videoFile); return dataSize > 0; } int32_t CSimpleMovieFrame::OpenVideo() { this->m_startTime = OsGetAsyncTimeMs(); this->m_elapsedTime = 0; this->m_decoder = ++s_decoderIndex; this->m_currentFrame = 0; this->m_prevFrame = -1; this->m_lastKeyFrame = 0; this->m_frameAudioSync = 0; if (InitializeDivxDecoder(this->m_decoder, this->m_videoWidth, this->m_videoHeight) || SetOutputFormat(this->m_decoder, 1, this->m_videoWidth, this->m_videoHeight)) { this->CloseVideo(); return 0; } if (this->m_videoWidth == 1024) { if (this->m_videoHeight == 576) { this->m_textureFormat = 3; } else if (this->m_videoHeight == 436) { this->m_textureFormat = 5; } else { this->m_textureFormat = 1; } } else if (this->m_videoWidth == 800) { if (this->m_videoHeight == 448) { this->m_textureFormat = 2; } else if (this->m_videoHeight == 342 || this->m_videoHeight == 340) { this->m_textureFormat = 4; } else { this->m_textureFormat = 0; } } else { this->CloseVideo(); return 0; } const uint32_t widthByFormat[6] = { 800, 1024, 800, 1024, 800, 1024 }; const uint32_t heightByFormat[6] = { 384, 512, 512, 576, 384, 512 }; const uint32_t imageSize = widthByFormat[this->m_textureFormat] * heightByFormat[this->m_textureFormat] * 4; this->m_imageData = reinterpret_cast(ALLOC_ZERO(imageSize)); if (!this->m_imageData) { CloseVideo(); return 0; } int32_t hasTextures = 1; for (uint32_t i = 0; i < s_textureCountByFormat[this->m_textureFormat]; ++i) { uint32_t stride = (6 * this->m_textureFormat + i) * 4; this->m_textureData[i].data = &s_strideData[stride]; this->m_textureData[i].buffer = this->m_imageData; hasTextures &= GxTexCreate( s_strideData[stride], s_strideData[stride + 1], GxTex_Argb8888, CGxTexFlags(), &this->m_textureData[i], CSimpleMovieFrame::TextureCallback, this->m_textures[i]); } if (hasTextures) { for (uint32_t i = 0; i < s_textureCountByFormat[this->m_textureFormat]; ++i) { GxTexUpdate(this->m_textures[i], 0, 0, 0, 0, 1); } } return 1; } void CSimpleMovieFrame::CloseVideo() { if (this->m_decoder) { UnInitializeDivxDecoder(this->m_decoder); --s_decoderIndex; this->m_decoder = 0; } if (this->m_imageData) { FREE(this->m_imageData); this->m_imageData = nullptr; } if (this->m_videoData) { FREE(this->m_videoData); this->m_videoData = nullptr; } for (uint32_t i = 0; i < 6; ++i) { GxTexDestroy(this->m_textures[i]); } } int32_t CSimpleMovieFrame::UpdateTiming() { bool isAudioPlaying = false; /* SE2::IsPlaying(this->m_audioChannel) */ if (isAudioPlaying) { //this->m_elapsedTime = SE2::GetPositionInMS(this->m_audioChannel); //this->m_startTime = OsGetAsyncTimeMs() - this->m_elapsedTime; } else { this->m_elapsedTime = OsGetAsyncTimeMs() - this->m_startTime; } int32_t currentFrame = static_cast(this->m_elapsedTime * this->m_frameRate * 0.001 + 0.5); if (isAudioPlaying) { // TODO } currentFrame += this->m_frameAudioSync; if (currentFrame <= this->m_prevFrame) { currentFrame = this->m_prevFrame; } this->m_currentFrame = currentFrame; if (currentFrame >= this->m_numFrames) { this->m_isInterrupted = 1; } if (this->m_isInterrupted) { this->StopMovie(); return 0; } if (currentFrame == this->m_prevFrame) { return 0; } if (currentFrame != this->m_prevFrame + 1) { ++this->m_lastKeyFrame; } while (this->m_prevFrame < this->m_currentFrame - 1) { this->DecodeFrame(false); ++this->m_prevFrame; } if (!this->DecodeFrame(true)) { this->m_isInterrupted = 1; } this->m_prevFrame = this->m_currentFrame; // TODO: Subtitle stuff return 1; } int32_t CSimpleMovieFrame::DecodeFrame(bool update) { decoder_data_t frame; frame.output = this->m_imageData + s_imageDataOffsets[this->m_textureFormat]; frame.input = this->m_currentFrameData + 4; frame.input_size = *reinterpret_cast(this->m_currentFrameData); frame.update = update ? 1 : 0; frame.zero0 = 0; frame.zero1 = 0; this->m_currentFrameData += frame.input_size + 4; if (DivxDecode(this->m_decoder, &frame, 0)) { return 0; } if (!update) { return 1; } for (uint32_t i = 0; i < s_textureCountByFormat[this->m_textureFormat]; ++i) { uint32_t v9 = 4 * (i + 6 * this->m_textureFormat); // WARNING(workaround): Uncomment the code below when GxTexUpdate will be working properly /* GxTexUpdate( this->m_textures[i], s_movieTextureUpdate[v9 + 0], s_movieTextureUpdate[v9 + 1], s_movieTextureUpdate[v9 + 2], s_movieTextureUpdate[v9 + 3], 1); */ GxTexUpdate(this->m_textures[i], 0, 0, 0, 0, 1); } return 1; } void CSimpleMovieFrame::Render() { float minX; float maxX; float minY; float maxY; float minZ; float maxZ; GxXformViewport(minX, maxX, minY, maxY, minZ, maxZ); GxXformSetViewport(0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f); CImVector clearColor = { 0x00, 0x00, 0x00, 0xFF }; GxSceneClear(3, clearColor); C44Matrix matrix; GxuXformCreateOrtho(0.0, 1.0, -0.5, 0.5, 0.0, 500.0, matrix); GxXformSetView(C44Matrix()); GxXformSetProjection(matrix); static uint32_t s_movieRenderFlag = 0; static C3Vector s_movieFrameNormalVec; static C2Vector s_movieFrameTexVec[4]; if ((s_movieRenderFlag & 1) == 0) { s_movieFrameNormalVec.x = 0.0; s_movieFrameNormalVec.y = 0.0; s_movieFrameNormalVec.z = 1.0; s_movieRenderFlag |= 1; } if ((s_movieRenderFlag & 2) == 0) { s_movieFrameTexVec[0].x = 0.0f; s_movieFrameTexVec[0].y = 0.0f; s_movieFrameTexVec[1].x = 1.0f; s_movieFrameTexVec[1].y = 0.0f; s_movieFrameTexVec[2].x = 0.0f; s_movieFrameTexVec[2].y = 1.0f; s_movieFrameTexVec[3].x = 1.0f; s_movieFrameTexVec[3].y = 1.0f; s_movieRenderFlag |= 2; } GxRsPush(); GxRsSet(GxRs_Lighting, 0); GxRsSet(GxRs_Fog, 0); GxRsSet(GxRs_BlendingMode, 0); GxRsSetAlphaRef(); float aspectCompensation = CoordinateGetAspectCompensation(); for (uint32_t i = 0; i < s_textureCountByFormat[this->m_textureFormat]; ++i) { float* rect = &s_layout[24 * this->m_textureFormat + 4 * i]; float v16 = rect[3] * aspectCompensation; float v17 = rect[2] * aspectCompensation; C3Vector position[] = { { rect[0], v16, 0.0f }, { rect[1], v16, 0.0f }, { rect[0], v17, 0.0f }, { rect[1], v17, 0.0f } }; GxPrimLockVertexPtrs(4, position, sizeof(C3Vector), &s_movieFrameNormalVec, 0, nullptr, 0, nullptr, 0, s_movieFrameTexVec, sizeof(C2Vector), nullptr, 0); GxRsSet(GxRs_Texture0, this->m_textures[i]); uint16_t indices[] = { 0, 1, 2, 3 }; GxDrawLockedElements(GxPrim_TriangleStrip, 4, indices); GxPrimUnlockVertexPtrs(); } GxRsPop(); GxXformSetViewport(minX, maxX, minY, maxY, minZ, maxZ); }