diff --git a/.gitmodules b/.gitmodules index d0c91b5a..fc488956 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "lib/libdmusic"] path = lib/libdmusic url = https://github.com/frabert/libdmusic.git +[submodule "lib/ffmpeg"] + path = lib/ffmpeg + url = https://github.com/FFmpeg/FFmpeg.git diff --git a/.travis.yml b/.travis.yml index f6a76843..1c722c27 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,6 +40,7 @@ matrix: - libasound2-dev - alsa-utils - alsa-oss + - nasm script: ./bin/build_unix.sh before_deploy: ./bin/archive_unix.sh - os: linux @@ -81,6 +82,6 @@ matrix: compiler: clang before_install: - brew update - - brew install libsndfile + - brew install libsndfile nasm script: ./bin/build_unix.sh before_deploy: ./bin/archive_unix.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 43ece235..14c6fe24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,8 @@ file(GLOB ENGINE_SRC "src/utils/*.h" "src/math/*.cpp" "src/math/*.h" + "src/media/*.cpp" + "src/media/*.h" "src/ui/*.cpp" "src/ui/*.h" "src/logic/*.cpp" @@ -287,6 +289,17 @@ include_directories(${CMAKE_SOURCE_DIR}/lib/libdmusic/include) target_link_libraries(engine dmusic) +# ------------------ FFmpeg ------------------ + +include("cmake/ffmpeg.cmake") +ffmpeg_build("${CMAKE_CURRENT_SOURCE_DIR}/lib/ffmpeg" "${CMAKE_CURRENT_BINARY_DIR}/lib/ffmpeg" + avcodec avutil avformat +) +if (FFMPEG_ENABLED) + add_definitions(-DRE_ENABLE_FFMPEG) + target_link_libraries(engine FFMPEG_avformat FFMPEG_avcodec FFMPEG_avutil) +endif() + # ------------------ Other ------------------ include_directories(src) diff --git a/cmake/ffmpeg.cmake b/cmake/ffmpeg.cmake new file mode 100644 index 00000000..57544d1e --- /dev/null +++ b/cmake/ffmpeg.cmake @@ -0,0 +1,45 @@ + +function(FFMPEG_BUILD SOURCE_DIR BUILD_DIR) + if (UNIX) + include(ExternalProject) + + set(BINARY_DIR "${BUILD_DIR}/build") + set(INSTALL_DIR "${BUILD_DIR}/install") + + if(NOT EXISTS "${BINARY_DIR}") + file(MAKE_DIRECTORY "${BINARY_DIR}") + endif() + + ExternalProject_Add(FFMPEG_PROJECT + SOURCE_DIR "${SOURCE_DIR}" + CONFIGURE_COMMAND "${SOURCE_DIR}/configure" "--prefix=\"${INSTALL_DIR}\"" + --disable-iconv + --disable-programs + --disable-doc + --disable-everything + --disable-zlib + --enable-demuxer=bink + --enable-decoder=bink,binkaudio_dct,binkaudio_rdft + --enable-protocol=file + BINARY_DIR "${BINARY_DIR}" + BUILD_COMMAND make + ) + + foreach(LIBRARY ${ARGN}) + set(LOCATION "${INSTALL_DIR}/lib/lib${LIBRARY}.a") + set(INCLUDE_DIR "${INSTALL_DIR}/include") + message(STATUS "FFMPEG library ${LIBRARY} expected at ${LOCATION} (include path: ${INCLUDE_DIR})") + + add_library(FFMPEG_${LIBRARY} IMPORTED STATIC) + set_property(TARGET FFMPEG_${LIBRARY} PROPERTY IMPORTED_LOCATION "${LOCATION}") + + file(MAKE_DIRECTORY "${INCLUDE_DIR}") # workaround for CMake Issue #15052 + set_property(TARGET FFMPEG_${LIBRARY} PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${INCLUDE_DIR}") + add_dependencies(FFMPEG_${LIBRARY} FFMPEG_PROJECT) + endforeach() + + set(FFMPEG_ENABLED 1 PARENT_SCOPE) + else() + message(WARNING "No support for video player on this platform") + endif() +endfunction() diff --git a/lib/ffmpeg b/lib/ffmpeg new file mode 160000 index 00000000..e28b1fa6 --- /dev/null +++ b/lib/ffmpeg @@ -0,0 +1 @@ +Subproject commit e28b1fa6e99c9a50d1b647c5418e5f75a3f957e4 diff --git a/src/content/Texture.cpp b/src/content/Texture.cpp index 9b07de56..2c50127e 100644 --- a/src/content/Texture.cpp +++ b/src/content/Texture.cpp @@ -42,6 +42,7 @@ Handle::TextureHandle TextureAllocator::loadTextureDDS(const std::vector([this, h](Engine::BaseEngine* pEngine) { + finalizeLoad(h); + }); +} + bool TextureAllocator::finalizeLoad(Handle::TextureHandle h) { Texture& tx = m_Allocator.getElement(h); + // if there's already a texture loaded for the object, destroy it + if (tx.m_TextureHandle.idx != bgfx::kInvalidHandle) { + bgfx::destroy(tx.m_TextureHandle); + tx.m_TextureHandle.idx = bgfx::kInvalidHandle; + } + std::uint32_t textureFlags = BGFX_TEXTURE_NONE; if (this->m_Engine.getEngineArgs().noTextureFiltering) @@ -197,8 +212,10 @@ bool TextureAllocator::finalizeLoad(Handle::TextureHandle h) //stbi_image_free(out); // Couldn't load this one? - if (bth.idx == bgfx::kInvalidHandle) + if (bth.idx == bgfx::kInvalidHandle) { + LogWarn() << "Could not finalize texture load"; return false; + } tx.m_TextureHandle = bth; } diff --git a/src/content/Texture.h b/src/content/Texture.h index 126d1ef0..3aed44b8 100644 --- a/src/content/Texture.h +++ b/src/content/Texture.h @@ -57,6 +57,8 @@ namespace Textures * @return Rough estimation about how much memory the loaded textures need on the GPU in bytes */ size_t getEstimatedGPUMemoryConsumption() { return m_EstimatedGPUBytes; } + + void asyncFinalizeLoad(Handle::TextureHandle h); protected: /** * Pushes the loaded data to the GPU. Needs to run on the main-thread. diff --git a/src/engine/BaseEngine.cpp b/src/engine/BaseEngine.cpp index a43cbbf8..1a9f53fc 100644 --- a/src/engine/BaseEngine.cpp +++ b/src/engine/BaseEngine.cpp @@ -118,6 +118,8 @@ void BaseEngine::initEngine(int argc, char** argv) m_AudioEngine = new Audio::AudioEngine(snd_device); + m_VideoPlayer = new Media::VideoPlayer(*this); + // Init HUD m_pFontCache = new UI::zFontCache(*this); m_pHUD = new UI::Hud(*this); @@ -126,7 +128,10 @@ void BaseEngine::initEngine(int argc, char** argv) void BaseEngine::frameUpdate(double dt, uint16_t width, uint16_t height) { - onFrameUpdate(dt * getGameClock().getGameEngineSpeedFactor(), width, height); + if (m_VideoPlayer->active()) + m_VideoPlayer->frameUpdate(dt, width, height); + else + onFrameUpdate(dt * getGameClock().getGameEngineSpeedFactor(), width, height); } void BaseEngine::loadArchives() diff --git a/src/engine/BaseEngine.h b/src/engine/BaseEngine.h index 1101be79..343f6af3 100644 --- a/src/engine/BaseEngine.h +++ b/src/engine/BaseEngine.h @@ -9,8 +9,10 @@ #include #include #include +#include #include #include +#include namespace UI { @@ -116,6 +118,9 @@ namespace Engine * @return Base-level UI-View. Parent of all other views. */ UI::View& getRootUIView() { return m_RootUIView; } + + Media::VideoPlayer& getVideoPlayer() { return *m_VideoPlayer; } + /** * // TODO: Move to GameEngine, or pass GameEngine to world! * @return HUD @@ -220,6 +225,8 @@ namespace Engine Audio::AudioEngine* m_AudioEngine = nullptr; + Media::VideoPlayer* m_VideoPlayer = nullptr; + /** * Base UI-View */ diff --git a/src/logic/scriptExternals/Externals.cpp b/src/logic/scriptExternals/Externals.cpp index 0e5122b6..7c6ec6a6 100644 --- a/src/logic/scriptExternals/Externals.cpp +++ b/src/logic/scriptExternals/Externals.cpp @@ -1503,4 +1503,26 @@ void ::Logic::ScriptExternals::registerEngineExternals(World::WorldInstance& wor npc.playerController->drawWeaponMelee(true); } }); + + vm->registerExternalFunction("playvideo", [=](Daedalus::DaedalusVM& vm) { + engine->getVideoPlayer().play(vm.popString()); + // this function is actually declared as int, but the return value is never used in the original scripts + // and the Gothic compiler doesn't pop unused expressions + vm.setReturn(0); + }); + + vm->registerExternalFunction("playvideoex", [=](Daedalus::DaedalusVM& vm) { + if (verbose) LogInfo() << "playvideoex"; + int exitsession = vm.popDataValue(); + if (verbose) LogInfo() << "exitsession: " << exitsession; + int screenblend = vm.popDataValue(); + if (verbose) LogInfo() << "screenblend: " << screenblend; + std::string filename = vm.popString(); + if (verbose) LogInfo() << "filename: " << filename; + + engine->getVideoPlayer().play(filename); // TODO: use exitseesion and screenblend parameters + // this function is actually declared as int, but the return value is never used in the original scripts + // and the Gothic compiler doesn't pop unused expressions + vm.setReturn(0); + }); } diff --git a/src/logic/scriptExternals/Stubs.cpp b/src/logic/scriptExternals/Stubs.cpp index 3fbb1122..b5cd833a 100644 --- a/src/logic/scriptExternals/Stubs.cpp +++ b/src/logic/scriptExternals/Stubs.cpp @@ -2,6 +2,8 @@ #include #include +#include + void ::Logic::ScriptExternals::registerStubs(Daedalus::DaedalusVM& vm, bool verbose) { vm.registerExternalFunction("npc_getequippedarmor", [=](Daedalus::DaedalusVM& vm) { @@ -838,28 +840,6 @@ void ::Logic::ScriptExternals::registerStubs(Daedalus::DaedalusVM& vm, bool verb vm.setReturn(0); }); - vm.registerExternalFunction("playvideo", [=](Daedalus::DaedalusVM& vm) { - if (verbose) LogInfo() << "playvideo"; - std::string filename = vm.popString(); - if (verbose) LogInfo() << "filename: " << filename; - // this function is actually declared as int, but the return value is never used in the original scripts - // and the Gothic compiler doesn't pop unused expressions - vm.setReturn(0); - }); - - vm.registerExternalFunction("playvideoex", [=](Daedalus::DaedalusVM& vm) { - if (verbose) LogInfo() << "playvideoex"; - int exitsession = vm.popDataValue(); - if (verbose) LogInfo() << "exitsession: " << exitsession; - int screenblend = vm.popDataValue(); - if (verbose) LogInfo() << "screenblend: " << screenblend; - std::string filename = vm.popString(); - if (verbose) LogInfo() << "filename: " << filename; - // this function is actually declared as int, but the return value is never used in the original scripts - // and the Gothic compiler doesn't pop unused expressions - vm.setReturn(0); - }); - vm.registerExternalFunction("printdialog", [=](Daedalus::DaedalusVM& vm) { if (verbose) LogInfo() << "printdialog"; int i5 = vm.popDataValue(); diff --git a/src/media/Video.cpp b/src/media/Video.cpp new file mode 100644 index 00000000..7f5b1f80 --- /dev/null +++ b/src/media/Video.cpp @@ -0,0 +1,397 @@ +// +// Created by mplucinski on 06.04.18. +// + +#include + +#include +#include +#include +#include +#include +#include "Video.h" +#include "ui/ImageView.h" + +#ifdef RE_ENABLE_FFMPEG +extern "C" { +#include +#include +#include +} + +namespace { + +struct deleteAVCodecContext +{ + void operator()(AVCodecContext *ptr) + { + if (ptr) + avcodec_free_context(&ptr); + } +}; + +struct deleteAVFormatContext +{ + void operator()(AVFormatContext *ptr) + { + if (ptr) + avformat_close_input(&ptr); + } +}; + +struct deleteAVFrame +{ + void operator()(AVFrame *ptr) + { + if (ptr) + av_frame_free(&ptr); + } +}; + +struct deleteAVFrameRef +{ + void operator()(AVFrame *ptr) + { + if (ptr) + av_frame_unref(ptr); + deleteAVFrame{}(ptr); + } +}; + +using AVCodecContextPtr = std::unique_ptr; +using AVFormatContextPtr = std::unique_ptr; +using AVFramePtr = std::unique_ptr; +using AVFrameRefPtr = std::unique_ptr; + +} + +namespace Media { + class Video: public std::enable_shared_from_this