diff --git a/LuaSTG/CMakeLists.txt b/LuaSTG/CMakeLists.txt index 031383156..6afccc10c 100644 --- a/LuaSTG/CMakeLists.txt +++ b/LuaSTG/CMakeLists.txt @@ -128,6 +128,8 @@ set(LUASTG_ENGINE_SOURCES LuaSTG/LuaBinding/modern/SwapChain.cpp LuaSTG/LuaBinding/modern/Texture2D.hpp LuaSTG/LuaBinding/modern/Texture2D.cpp + LuaSTG/LuaBinding/modern/Video.hpp + LuaSTG/LuaBinding/modern/Video.cpp LuaSTG/LuaBinding/modern/RenderTarget.hpp LuaSTG/LuaBinding/modern/RenderTarget.cpp LuaSTG/LuaBinding/modern/DepthStencilBuffer.hpp diff --git a/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp b/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp index ecda732f2..a72cd4621 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp +++ b/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp @@ -1,6 +1,7 @@ #include "GameResource/ResourceManager.h" #ifdef USING_DEAR_IMGUI #include "imgui.h" +#include "d3d11/VideoTexture.hpp" #endif static std::string bytes_count_to_string(unsigned long long size) @@ -81,18 +82,21 @@ namespace luastg }; auto draw_texture = [](IResourceTexture* p_res, bool show_info, float scale) -> void { - auto const size = p_res->GetTexture()->getSize(); + auto* p_tex = p_res->GetTexture(); + auto const size = p_tex->getSize(); if (show_info) { ImGui::Text("Size: %u x %u", size.x, size.y); - ImGui::Text("RenderTarget: %s", p_res->IsRenderTarget() ? "Yes" : "Not"); - ImGui::Text("Dynamic: %s", p_res->IsRenderTarget() ? "Yes" : "Not"); - unsigned long long mem_usage = size.x * size.y * 4; + char const* type_str = p_tex->isVideoTexture() ? "Video" : (p_res->IsRenderTarget() ? "RenderTarget" : "Texture"); + ImGui::Text("Type: %s", type_str); + ImGui::Text("Dynamic: %s", p_tex->isDynamic() ? "Yes" : "Not"); + unsigned long long display_mem = (unsigned long long)size.x * size.y * 4; + unsigned long long mem_usage = display_mem; ImGui::Text("Adapter Memory Usage (Approximate): %s", bytes_count_to_string(mem_usage).c_str()); } ImGui::PushStyleVar(ImGuiStyleVar_ImageBorderSize, 1.0); ImGui::Image( - reinterpret_cast(p_res->GetTexture()->getNativeView()), + reinterpret_cast(p_tex->getNativeView()), ImVec2(scale * (float)size.x, scale * (float)size.y), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f)); diff --git a/LuaSTG/LuaSTG/GameResource/ResourceManager.h b/LuaSTG/LuaSTG/GameResource/ResourceManager.h index 431926cde..0bb5d45b4 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceManager.h +++ b/LuaSTG/LuaSTG/GameResource/ResourceManager.h @@ -1,192 +1,195 @@ -#pragma once -#include "core/SmartReference.hpp" -#include "GameResource/ResourceTexture.hpp" -#include "GameResource/ResourceSprite.hpp" -#include "GameResource/ResourceAnimation.hpp" -#include "GameResource/ResourceMusic.hpp" -#include "GameResource/ResourceSoundEffect.hpp" -#include "GameResource/ResourceParticle.hpp" -#include "GameResource/ResourceFont.hpp" -#include "GameResource/ResourcePostEffectShader.hpp" -#include "GameResource/ResourceModel.hpp" -#include "lua.hpp" -#include "xxhash.h" - -namespace luastg -{ - class ResourceMgr; - - // 资源池类型 - enum class ResourcePoolType - { - None = 0, - Global, - Stage - }; - - // 资源池 - class ResourcePool - { - friend class ResourceMgr; - public: - struct dictionary_key_t - { - #if (SIZE_MAX == UINT32_MAX) - XXH32_hash_t const hash{}; - XXH32_hash_t const check{}; - #elif (SIZE_MAX == UINT64_MAX) - XXH64_hash_t const hash{}; - #else - static_assert(false, "unsupported platform"); - #endif - - inline dictionary_key_t(std::string_view key) noexcept - #if (SIZE_MAX == UINT32_MAX) - : hash(XXH32(key.data(), key.size(), 0)) - , check(XXH32(key.data(), key.size(), 0x65766F6C)) - #elif (SIZE_MAX == UINT64_MAX) - : hash(XXH3_64bits(key.data(), key.size())) - #else - : __invalid_member() - #endif - { - } - - inline bool operator==(dictionary_key_t const& right) const noexcept - { - #if (SIZE_MAX == UINT32_MAX) - return hash == right.hash && check == right.check; - #elif (SIZE_MAX == UINT64_MAX) - return hash == right.hash; - #else - static_assert(false, "unsupported platform"); - #endif - } - }; - struct dictionary_key_hash_t - { - inline size_t operator()(dictionary_key_t const& key) const noexcept - { - static_assert(sizeof(size_t) == sizeof(decltype(dictionary_key_t::hash))); - return key.hash; - } - }; - template - using dictionary_t = std::pmr::unordered_map; - private: - ResourceMgr* m_pMgr; - ResourcePoolType m_iType; - std::pmr::unsynchronized_pool_resource m_memory_resource; - dictionary_t> m_TexturePool; - dictionary_t> m_SpritePool; - dictionary_t> m_AnimationPool; - dictionary_t> m_MusicPool; - dictionary_t> m_SoundSpritePool; - dictionary_t> m_ParticlePool; - dictionary_t> m_SpriteFontPool; - dictionary_t> m_TTFFontPool; - dictionary_t> m_FXPool; - dictionary_t> m_ModelPool; - private: - const char* getResourcePoolTypeName(); - public: - void Clear() noexcept; - void RemoveResource(ResourceType t, const char* name) noexcept; - bool CheckResourceExists(ResourceType t, std::string_view name) const noexcept; - int ExportResourceList(lua_State* L, ResourceType t) const noexcept; - - // 纹理 - bool LoadTexture(const char* name, const char* path, bool mipmaps = true) noexcept; - bool CreateTexture(const char* name, int width, int height) noexcept; - // 渲染目标 - bool CreateRenderTarget(const char* name, int width = 0, int height = 0, bool depth_buffer = false) noexcept; - // 图片精灵 - bool CreateSprite(const char* name, const char* texname, - double x, double y, double w, double h, - double a, double b, bool rect = false) noexcept; - // 动画精灵 - bool CreateAnimation(const char* name, const char* texname, - double x, double y, double w, double h, int n, int m, int intv, - double a, double b, bool rect = false) noexcept; - bool CreateAnimation(const char* name, - std::vector> const& sprite_list, - int intv, - double a, double b, bool rect = false) noexcept; - // 音乐 - bool LoadMusic(const char* name, const char* path, double start, double end, bool once_decode) noexcept; - // 音效 - bool LoadSoundEffect(const char* name, const char* path) noexcept; - // 粒子特效(HGE) - bool LoadParticle(const char* name, const hgeParticleSystemInfo& info, const char* img_name, - double a, double b, bool rect = false, bool _nolog = false) noexcept; - bool LoadParticle(const char* name, const char* path, const char* img_name, - double a, double b, bool rect = false) noexcept; - // 装载纹理字体(HGE) - bool LoadSpriteFont(const char* name, const char* path, bool mipmaps = true) noexcept; - // 装载纹理字体(fancy2d) - bool LoadSpriteFont(const char* name, const char* path, const char* tex_path, bool mipmaps = true) noexcept; - // 加载矢量字体 - bool LoadTTFFont(const char* name, const char* path, float width, float height) noexcept; - bool LoadTrueTypeFont(const char* name, core::Graphics::TrueTypeFontInfo* fonts, size_t count) noexcept; - // 特效 - bool LoadFX(const char* name, const char* path) noexcept; - // 模型 - bool LoadModel(const char* name, const char* path) noexcept; - - core::SmartReference GetTexture(std::string_view name) noexcept; - core::SmartReference GetSprite(std::string_view name) noexcept; - core::SmartReference GetAnimation(std::string_view name) noexcept; - core::SmartReference GetMusic(std::string_view name) noexcept; - core::SmartReference GetSound(std::string_view name) noexcept; - core::SmartReference GetParticle(std::string_view name) noexcept; - core::SmartReference GetSpriteFont(std::string_view name) noexcept; - core::SmartReference GetTTFFont(std::string_view name) noexcept; - core::SmartReference GetFX(std::string_view name) noexcept; - core::SmartReference GetModel(std::string_view name) noexcept; - public: - ResourcePool(ResourceMgr* mgr, ResourcePoolType t); - ResourcePool& operator=(const ResourcePool&) = delete; - ResourcePool(const ResourcePool&) = delete; - }; - - // 资源管理器 - class ResourceMgr - { - private: - ResourcePoolType m_ActivedPool = ResourcePoolType::Global; - ResourcePool m_GlobalResourcePool; - ResourcePool m_StageResourcePool; - public: - ResourcePoolType GetActivedPoolType() noexcept; - void SetActivedPoolType(ResourcePoolType t) noexcept; - ResourcePool* GetActivedPool() noexcept; - ResourcePool* GetResourcePool(ResourcePoolType t) noexcept; - void ClearAllResource() noexcept; - - core::SmartReference FindTexture(const char* name) noexcept; - core::SmartReference FindSprite(const char* name) noexcept; - core::SmartReference FindAnimation(const char* name) noexcept; - core::SmartReference FindMusic(const char* name) noexcept; - core::SmartReference FindSound(const char* name) noexcept; - core::SmartReference FindParticle(const char* name) noexcept; - core::SmartReference FindSpriteFont(const char* name) noexcept; - core::SmartReference FindTTFFont(const char* name) noexcept; - core::SmartReference FindFX(const char* name) noexcept; - core::SmartReference FindModel(const char* name) noexcept; - - bool GetTextureSize(const char* name, core::Vector2U& out) noexcept; - void CacheTTFFontString(const char* name, const char* text, size_t len) noexcept; - void UpdateSound(); - private: - static bool g_ResourceLoadingLog; - float m_GlobalImageScaleFactor = 1.0f; - public: - static void SetResourceLoadingLog(bool b); - static bool GetResourceLoadingLog(); - float GetGlobalImageScaleFactor() const noexcept { return m_GlobalImageScaleFactor; } - void SetGlobalImageScaleFactor(float s) noexcept { m_GlobalImageScaleFactor = s; } - void ShowResourceManagerDebugWindow(bool* p_open = nullptr); - public: - ResourceMgr(); - }; -} +#pragma once +#include "core/SmartReference.hpp" +#include "core/VideoDecoder.hpp" +#include "GameResource/ResourceTexture.hpp" +#include "GameResource/ResourceSprite.hpp" +#include "GameResource/ResourceAnimation.hpp" +#include "GameResource/ResourceMusic.hpp" +#include "GameResource/ResourceSoundEffect.hpp" +#include "GameResource/ResourceParticle.hpp" +#include "GameResource/ResourceFont.hpp" +#include "GameResource/ResourcePostEffectShader.hpp" +#include "GameResource/ResourceModel.hpp" +#include "lua.hpp" +#include "xxhash.h" + +namespace luastg +{ + class ResourceMgr; + + // 资源池类型 + enum class ResourcePoolType + { + None = 0, + Global, + Stage + }; + + // 资源池 + class ResourcePool + { + friend class ResourceMgr; + public: + struct dictionary_key_t + { + #if (SIZE_MAX == UINT32_MAX) + XXH32_hash_t const hash{}; + XXH32_hash_t const check{}; + #elif (SIZE_MAX == UINT64_MAX) + XXH64_hash_t const hash{}; + #else + static_assert(false, "unsupported platform"); + #endif + + inline dictionary_key_t(std::string_view key) noexcept + #if (SIZE_MAX == UINT32_MAX) + : hash(XXH32(key.data(), key.size(), 0)) + , check(XXH32(key.data(), key.size(), 0x65766F6C)) + #elif (SIZE_MAX == UINT64_MAX) + : hash(XXH3_64bits(key.data(), key.size())) + #else + : __invalid_member() + #endif + { + } + + inline bool operator==(dictionary_key_t const& right) const noexcept + { + #if (SIZE_MAX == UINT32_MAX) + return hash == right.hash && check == right.check; + #elif (SIZE_MAX == UINT64_MAX) + return hash == right.hash; + #else + static_assert(false, "unsupported platform"); + #endif + } + }; + struct dictionary_key_hash_t + { + inline size_t operator()(dictionary_key_t const& key) const noexcept + { + static_assert(sizeof(size_t) == sizeof(decltype(dictionary_key_t::hash))); + return key.hash; + } + }; + template + using dictionary_t = std::pmr::unordered_map; + private: + ResourceMgr* m_pMgr; + ResourcePoolType m_iType; + std::pmr::unsynchronized_pool_resource m_memory_resource; + dictionary_t> m_TexturePool; + dictionary_t> m_SpritePool; + dictionary_t> m_AnimationPool; + dictionary_t> m_MusicPool; + dictionary_t> m_SoundSpritePool; + dictionary_t> m_ParticlePool; + dictionary_t> m_SpriteFontPool; + dictionary_t> m_TTFFontPool; + dictionary_t> m_FXPool; + dictionary_t> m_ModelPool; + private: + const char* getResourcePoolTypeName(); + public: + void Clear() noexcept; + void RemoveResource(ResourceType t, const char* name) noexcept; + bool CheckResourceExists(ResourceType t, std::string_view name) const noexcept; + int ExportResourceList(lua_State* L, ResourceType t) const noexcept; + + // 纹理 + bool LoadTexture(const char* name, const char* path, bool mipmaps = true) noexcept; + bool CreateTexture(const char* name, int width, int height) noexcept; + // 视频纹理(options 为 nullptr 时使用默认选项) + bool LoadVideo(const char* name, const char* path, core::VideoOpenOptions const* options = nullptr) noexcept; + // 渲染目标 + bool CreateRenderTarget(const char* name, int width = 0, int height = 0, bool depth_buffer = false) noexcept; + // 图片精灵 + bool CreateSprite(const char* name, const char* texname, + double x, double y, double w, double h, + double a, double b, bool rect = false) noexcept; + // 动画精灵 + bool CreateAnimation(const char* name, const char* texname, + double x, double y, double w, double h, int n, int m, int intv, + double a, double b, bool rect = false) noexcept; + bool CreateAnimation(const char* name, + std::vector> const& sprite_list, + int intv, + double a, double b, bool rect = false) noexcept; + // 音乐 + bool LoadMusic(const char* name, const char* path, double start, double end, bool once_decode) noexcept; + // 音效 + bool LoadSoundEffect(const char* name, const char* path) noexcept; + // 粒子特效(HGE) + bool LoadParticle(const char* name, const hgeParticleSystemInfo& info, const char* img_name, + double a, double b, bool rect = false, bool _nolog = false) noexcept; + bool LoadParticle(const char* name, const char* path, const char* img_name, + double a, double b, bool rect = false) noexcept; + // 装载纹理字体(HGE) + bool LoadSpriteFont(const char* name, const char* path, bool mipmaps = true) noexcept; + // 装载纹理字体(fancy2d) + bool LoadSpriteFont(const char* name, const char* path, const char* tex_path, bool mipmaps = true) noexcept; + // 加载矢量字体 + bool LoadTTFFont(const char* name, const char* path, float width, float height) noexcept; + bool LoadTrueTypeFont(const char* name, core::Graphics::TrueTypeFontInfo* fonts, size_t count) noexcept; + // 特效 + bool LoadFX(const char* name, const char* path) noexcept; + // 模型 + bool LoadModel(const char* name, const char* path) noexcept; + + core::SmartReference GetTexture(std::string_view name) noexcept; + core::SmartReference GetSprite(std::string_view name) noexcept; + core::SmartReference GetAnimation(std::string_view name) noexcept; + core::SmartReference GetMusic(std::string_view name) noexcept; + core::SmartReference GetSound(std::string_view name) noexcept; + core::SmartReference GetParticle(std::string_view name) noexcept; + core::SmartReference GetSpriteFont(std::string_view name) noexcept; + core::SmartReference GetTTFFont(std::string_view name) noexcept; + core::SmartReference GetFX(std::string_view name) noexcept; + core::SmartReference GetModel(std::string_view name) noexcept; + public: + ResourcePool(ResourceMgr* mgr, ResourcePoolType t); + ResourcePool& operator=(const ResourcePool&) = delete; + ResourcePool(const ResourcePool&) = delete; + }; + + // 资源管理器 + class ResourceMgr + { + private: + ResourcePoolType m_ActivedPool = ResourcePoolType::Global; + ResourcePool m_GlobalResourcePool; + ResourcePool m_StageResourcePool; + public: + ResourcePoolType GetActivedPoolType() noexcept; + void SetActivedPoolType(ResourcePoolType t) noexcept; + ResourcePool* GetActivedPool() noexcept; + ResourcePool* GetResourcePool(ResourcePoolType t) noexcept; + void ClearAllResource() noexcept; + + core::SmartReference FindTexture(const char* name) noexcept; + core::SmartReference FindSprite(const char* name) noexcept; + core::SmartReference FindAnimation(const char* name) noexcept; + core::SmartReference FindMusic(const char* name) noexcept; + core::SmartReference FindSound(const char* name) noexcept; + core::SmartReference FindParticle(const char* name) noexcept; + core::SmartReference FindSpriteFont(const char* name) noexcept; + core::SmartReference FindTTFFont(const char* name) noexcept; + core::SmartReference FindFX(const char* name) noexcept; + core::SmartReference FindModel(const char* name) noexcept; + + bool GetTextureSize(const char* name, core::Vector2U& out) noexcept; + void CacheTTFFontString(const char* name, const char* text, size_t len) noexcept; + void UpdateSound(); + private: + static bool g_ResourceLoadingLog; + float m_GlobalImageScaleFactor = 1.0f; + public: + static void SetResourceLoadingLog(bool b); + static bool GetResourceLoadingLog(); + float GetGlobalImageScaleFactor() const noexcept { return m_GlobalImageScaleFactor; } + void SetGlobalImageScaleFactor(float s) noexcept { m_GlobalImageScaleFactor = s; } + void ShowResourceManagerDebugWindow(bool* p_open = nullptr); + public: + ResourceMgr(); + }; +} diff --git a/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp b/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp index 89a9c08d9..ed0e9f48c 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp +++ b/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp @@ -9,6 +9,8 @@ #include "GameResource/Implement/ResourcePostEffectShaderImpl.hpp" #include "GameResource/Implement/ResourceModelImpl.hpp" #include "core/FileSystem.hpp" +#include "core/AudioEngine.hpp" +#include "d3d11/VideoTexture.hpp" #include "AppFrame.h" #include "lua/plus.hpp" @@ -227,6 +229,47 @@ namespace luastg return true; } + bool ResourcePool::LoadVideo(const char* name, const char* path, core::VideoOpenOptions const* options) noexcept + { + if (m_TexturePool.find(std::string_view(name)) != m_TexturePool.end()) + { + if (ResourceMgr::GetResourceLoadingLog()) + { + spdlog::warn("[luastg] LoadVideo: 纹理 '{}' 已存在,加载操作已取消", name); + } + return true; + } + + core::SmartReference p_texture; + bool ok = options + ? LAPP.getGraphicsDevice()->createVideoTexture(path, *options, p_texture.put()) + : LAPP.getGraphicsDevice()->createVideoTexture(path, p_texture.put()); + if (!ok) + { + spdlog::error("[luastg] 从 '{}' 创建视频纹理 '{}' 失败", path, name); + return false; + } + + try + { + core::SmartReference tRes; + tRes.attach(new ResourceTextureImpl(name, p_texture.get())); + m_TexturePool.emplace(name, tRes); + } + catch (std::exception const& e) + { + spdlog::error("[luastg] LoadVideo: 创建视频纹理 '{}' 失败 ({})", name, e.what()); + return false; + } + + if (ResourceMgr::GetResourceLoadingLog()) + { + spdlog::info("[luastg] LoadVideo: 已从 '{}' 加载视频 '{}' ({})", path, name, getResourcePoolTypeName()); + } + + return true; + } + bool ResourcePool::CreateTexture(const char* name, int width, int height) noexcept { if (m_TexturePool.find(std::string_view(name)) != m_TexturePool.end()) diff --git a/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp b/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp index 3f9cf3545..7a6b1e221 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp +++ b/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp @@ -1,6 +1,12 @@ #include "LuaBinding/LuaWrapper.hpp" #include "lua/plus.hpp" #include "AppFrame.h" +#include "d3d11/VideoTexture.hpp" +#include "core/VideoDecoder.hpp" +#include "core/AudioEngine.hpp" +#include +#include +#include void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { @@ -52,6 +58,45 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept return luaL_error(L, "can't load texture from file '%s'.", path); return 0; } + static void ReadVideoOptions(lua_State* L, int index, core::VideoOpenOptions& opt) noexcept { + if (!lua_istable(L, index)) return; + lua_getfield(L, index, "video_stream"); + if (lua_isnumber(L, -1)) opt.video_stream_index = (uint32_t)lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "width"); + if (lua_isnumber(L, -1)) opt.output_width = (uint32_t)lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "height"); + if (lua_isnumber(L, -1)) opt.output_height = (uint32_t)lua_tointeger(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "premultiplied_alpha"); + if (lua_isboolean(L, -1)) opt.premultiplied_alpha = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + lua_getfield(L, index, "looping"); + if (lua_isboolean(L, -1)) opt.looping = lua_toboolean(L, -1) != 0; + lua_pop(L, 1); + lua_getfield(L, index, "loop_end"); + if (lua_isnumber(L, -1)) opt.loop_end = (double)lua_tonumber(L, -1); + lua_pop(L, 1); + lua_getfield(L, index, "loop_duration"); + if (lua_isnumber(L, -1)) opt.loop_duration = (double)lua_tonumber(L, -1); + lua_pop(L, 1); + } + static int LoadVideo(lua_State* L) noexcept + { + const char* name = luaL_checkstring(L, 1); + const char* path = luaL_checkstring(L, 2); + + ResourcePool* pActivedPool = LRES.GetActivedPool(); + if (!pActivedPool) + return luaL_error(L, "can't load resource at this time."); + core::VideoOpenOptions opt; + if (lua_gettop(L) >= 3 && lua_istable(L, 3)) + ReadVideoOptions(L, 3, opt); + if (!pActivedPool->LoadVideo(name, path, lua_gettop(L) >= 3 && lua_istable(L, 3) ? &opt : nullptr)) + return luaL_error(L, "can't load video from file '%s'.", path); + return 0; + } static int LoadSprite(lua_State* L) noexcept { const char* name = luaL_checkstring(L, 1); @@ -671,6 +716,190 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept LRES.CacheTTFFontString(luaL_checkstring(L, 1), str, len); return 0; } + + // 视频控制函数 + static core::IVideoDecoder* GetVideoDecoder(const char* name) noexcept { + auto tex = LRES.FindTexture(name); + if (!tex) return nullptr; + + auto texture2d = tex->GetTexture(); + if (!texture2d) return nullptr; + + // 尝试转换为VideoTexture + auto video_texture = dynamic_cast(texture2d); + if (!video_texture) return nullptr; + + return video_texture->getVideoDecoder(); + } + + static int VideoSeek(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + double time = luaL_checknumber(L, 2); + auto tex = LRES.FindTexture(name); + if (!tex) + return luaL_error(L, "video texture '%s' not found.", name); + auto* vt = dynamic_cast(tex->GetTexture()); + if (!vt) + return luaL_error(L, "texture '%s' is not a video texture.", name); + auto* decoder = vt->getVideoDecoder(); + if (!decoder) + return luaL_error(L, "video texture '%s' has no video decoder.", name); + bool ok = decoder->seek(time); + lua_pushboolean(L, ok); + return 1; + } + + static int VideoSetLooping(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + bool loop = lua_toboolean(L, 2) != 0; + auto decoder = GetVideoDecoder(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + decoder->setLooping(loop); + return 0; + } + static int VideoSetLoopRange(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = GetVideoDecoder(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + double loop_end = luaL_checknumber(L, 2); + double loop_duration = luaL_checknumber(L, 3); + decoder->setLoopRange(loop_end, loop_duration); + return 0; + } + static int VideoUpdate(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + double time = luaL_checknumber(L, 2); + auto tex = LRES.FindTexture(name); + if (!tex) + return luaL_error(L, "video texture '%s' not found.", name); + auto* vt = dynamic_cast(tex->GetTexture()); + if (!vt) + return luaL_error(L, "texture '%s' is not a video texture.", name); + auto* decoder = vt->getVideoDecoder(); + if (!decoder) + return luaL_error(L, "video texture '%s' has no video decoder.", name); + bool ok = decoder->updateToTime(time); + lua_pushboolean(L, ok); + return 1; + } + static int VideoGetInfo(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = GetVideoDecoder(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + + lua_createtable(L, 0, 8); + + lua_pushnumber(L, decoder->getDuration()); + lua_setfield(L, -2, "duration"); + + lua_pushnumber(L, decoder->getCurrentTime()); + lua_setfield(L, -2, "time"); + + lua_pushboolean(L, decoder->isLooping()); + lua_setfield(L, -2, "looping"); + + double loop_end = 0.0, loop_duration = 0.0; + decoder->getLoopRange(&loop_end, &loop_duration); + lua_pushnumber(L, loop_end); + lua_setfield(L, -2, "loop_end"); + lua_pushnumber(L, loop_duration); + lua_setfield(L, -2, "loop_duration"); + + auto size = decoder->getVideoSize(); + lua_pushinteger(L, size.x); + lua_setfield(L, -2, "width"); + + lua_pushinteger(L, size.y); + lua_setfield(L, -2, "height"); + + lua_pushinteger(L, (lua_Integer)decoder->getVideoStreamIndex()); + lua_setfield(L, -2, "video_stream"); + + double fi = decoder->getFrameInterval(); + lua_pushnumber(L, fi); + lua_setfield(L, -2, "frame_interval"); + if (fi > 0.0) { + lua_pushnumber(L, 1.0 / fi); + lua_setfield(L, -2, "fps"); + } + return 1; + } + static int VideoGetVideoStreams(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = GetVideoDecoder(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + std::vector list; + auto cb = [](core::VideoStreamInfo const& info, void* ud) { + static_cast*>(ud)->push_back(info); + }; + decoder->getVideoStreams(cb, &list); + lua_createtable(L, (int)list.size(), 0); + for (size_t i = 0; i < list.size(); ++i) { + lua_createtable(L, 0, 5); + lua_pushinteger(L, (lua_Integer)list[i].index); + lua_setfield(L, -2, "index"); + lua_pushinteger(L, (lua_Integer)list[i].width); + lua_setfield(L, -2, "width"); + lua_pushinteger(L, (lua_Integer)list[i].height); + lua_setfield(L, -2, "height"); + lua_pushnumber(L, list[i].fps); + lua_setfield(L, -2, "fps"); + lua_pushnumber(L, list[i].duration_seconds); + lua_setfield(L, -2, "duration"); + lua_rawseti(L, -2, (int)i + 1); + } + return 1; + } + static int VideoGetAudioStreams(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = GetVideoDecoder(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + std::vector list; + auto cb = [](core::AudioStreamInfo const& info, void* ud) { + static_cast*>(ud)->push_back(info); + }; + decoder->getAudioStreams(cb, &list); + lua_createtable(L, (int)list.size(), 0); + for (size_t i = 0; i < list.size(); ++i) { + lua_createtable(L, 0, 4); + lua_pushinteger(L, (lua_Integer)list[i].index); + lua_setfield(L, -2, "index"); + lua_pushinteger(L, (lua_Integer)list[i].channels); + lua_setfield(L, -2, "channels"); + lua_pushinteger(L, (lua_Integer)list[i].sample_rate); + lua_setfield(L, -2, "sample_rate"); + lua_pushnumber(L, list[i].duration_seconds); + lua_setfield(L, -2, "duration"); + lua_rawseti(L, -2, (int)i + 1); + } + return 1; + } + static int VideoReopen(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto tex = LRES.FindTexture(name); + if (!tex) + return luaL_error(L, "texture '%s' not found.", name); + auto* vt = dynamic_cast(tex->GetTexture()); + if (!vt) + return luaL_error(L, "texture '%s' is not a video texture.", name); + core::IVideoDecoder* dec = vt->getVideoDecoder(); + if (!dec) + return luaL_error(L, "texture '%s' does not have a valid video decoder.", name); + core::VideoOpenOptions opt = dec->getLastOpenOptions(); + if (lua_gettop(L) >= 2 && lua_istable(L, 2)) + ReadVideoOptions(L, 2, opt); + if (!dec->reopen(opt)) { + lua_pushboolean(L, false); + return 1; + } + lua_pushboolean(L, true); + return 1; + } }; luaL_Reg const lib[] = { @@ -678,6 +907,7 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { "SetResourceStatus", &Wrapper::SetResourceStatus }, { "GetResourceStatus", &Wrapper::GetResourceStatus }, { "LoadTexture", &Wrapper::LoadTexture }, + { "LoadVideo", &Wrapper::LoadVideo }, { "LoadImage", &Wrapper::LoadSprite }, { "LoadAnimation", &Wrapper::LoadAnimation }, { "LoadPS", &Wrapper::LoadPS }, @@ -710,6 +940,17 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { "SetFontState", &Wrapper::SetFontState }, { "CacheTTFString", &Wrapper::CacheTTFString }, + + // 视频控制函数 + { "VideoSeek", &Wrapper::VideoSeek }, + { "VideoSetLooping", &Wrapper::VideoSetLooping }, + { "VideoSetLoopRange", &Wrapper::VideoSetLoopRange }, + { "VideoUpdate", &Wrapper::VideoUpdate }, + { "VideoGetInfo", &Wrapper::VideoGetInfo }, + { "VideoGetVideoStreams", &Wrapper::VideoGetVideoStreams }, + { "VideoGetAudioStreams", &Wrapper::VideoGetAudioStreams }, + { "VideoReopen", &Wrapper::VideoReopen }, + { NULL, NULL }, }; diff --git a/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp b/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp index 67c0e4b52..b668ca65f 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp +++ b/LuaSTG/LuaSTG/LuaBinding/LuaWrapper.cpp @@ -7,6 +7,7 @@ #include "LuaBinding/modern/Window.hpp" #include "LuaBinding/modern/SwapChain.hpp" #include "LuaBinding/modern/Texture2D.hpp" +#include "LuaBinding/modern/Video.hpp" #include "LuaBinding/modern/RenderTarget.hpp" #include "LuaBinding/modern/DepthStencilBuffer.hpp" #include "LuaBinding/modern/Mesh.hpp" @@ -82,6 +83,7 @@ namespace luastg::binding Window_Windows11Extension::registerClass(L); SwapChain::registerClass(L); Texture2D::registerClass(L); + Video::registerClass(L); RenderTarget::registerClass(L); DepthStencilBuffer::registerClass(L); Mesh::registerClass(L); diff --git a/LuaSTG/LuaSTG/LuaBinding/modern/Video.cpp b/LuaSTG/LuaSTG/LuaBinding/modern/Video.cpp new file mode 100644 index 000000000..633618dc1 --- /dev/null +++ b/LuaSTG/LuaSTG/LuaBinding/modern/Video.cpp @@ -0,0 +1,381 @@ +#include "Video.hpp" +#include "Texture2D.hpp" +#include "lua/plus.hpp" +#include "AppFrame.h" +#include "core/VideoDecoder.hpp" + +namespace luastg::binding { + std::string_view Video::class_name{ "lstg.Video" }; + + struct VideoBinding : Video { + // meta methods + + // NOLINTBEGIN(*-reserved-identifier) + + static int __gc(lua_State* vm) { + if (auto const self = as(vm, 1); self->data) { + self->data->release(); + self->data = nullptr; + } + return 0; + } + static int __tostring(lua_State* vm) { + lua::stack_t const ctx(vm); + [[maybe_unused]] auto const self = as(vm, 1); + ctx.push_value(class_name); + return 1; + } + static int __eq(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (is(vm, 2)) { + auto const other = as(vm, 2); + ctx.push_value(self->data == other->data); + } else { + ctx.push_value(false); + } + return 1; + } + + // NOLINTEND(*-reserved-identifier) + + // helper: get video decoder from texture + static core::IVideoDecoder* getDecoder(core::ITexture2D* texture) { + if (!texture || !texture->isVideoTexture()) { + return nullptr; + } + return texture->getVideoDecoder(); + } + + // helper: parse VideoOpenOptions from Lua table at given stack index + static void parseVideoOptions(lua_State* vm, int table_index, core::VideoOpenOptions& opt) { + if (!lua_istable(vm, table_index)) { + return; + } + + lua_getfield(vm, table_index, "video_stream"); + if (lua_isnumber(vm, -1)) { + opt.video_stream_index = static_cast(lua_tointeger(vm, -1)); + } + lua_pop(vm, 1); + + lua_getfield(vm, table_index, "width"); + if (lua_isnumber(vm, -1)) { + opt.output_width = static_cast(lua_tointeger(vm, -1)); + } + lua_pop(vm, 1); + + lua_getfield(vm, table_index, "height"); + if (lua_isnumber(vm, -1)) { + opt.output_height = static_cast(lua_tointeger(vm, -1)); + } + lua_pop(vm, 1); + + lua_getfield(vm, table_index, "premultiplied_alpha"); + if (lua_isboolean(vm, -1)) { + opt.premultiplied_alpha = lua_toboolean(vm, -1); + } + lua_pop(vm, 1); + + lua_getfield(vm, table_index, "looping"); + if (lua_isboolean(vm, -1)) { + opt.looping = lua_toboolean(vm, -1); + } + lua_pop(vm, 1); + + lua_getfield(vm, table_index, "loop_end"); + if (lua_isnumber(vm, -1)) { + opt.loop_end = lua_tonumber(vm, -1); + } + lua_pop(vm, 1); + + lua_getfield(vm, table_index, "loop_duration"); + if (lua_isnumber(vm, -1)) { + opt.loop_duration = lua_tonumber(vm, -1); + } + lua_pop(vm, 1); + } + + // method - video info + + static int getWidth(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + ctx.push_value(self->data->getSize().x); + return 1; + } + static int getHeight(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + ctx.push_value(self->data->getSize().y); + return 1; + } + static int getDuration(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = getDecoder(self->data)) { + ctx.push_value(decoder->getDuration()); + } else { + ctx.push_value(0.0); + } + return 1; + } + static int getCurrentTime(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = getDecoder(self->data)) { + ctx.push_value(decoder->getCurrentTime()); + } else { + ctx.push_value(0.0); + } + return 1; + } + static int getFPS(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = getDecoder(self->data)) { + auto interval = decoder->getFrameInterval(); + ctx.push_value(interval > 0.0 ? 1.0 / interval : 0.0); + } else { + ctx.push_value(0.0); + } + return 1; + } + static int isLooping(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = getDecoder(self->data)) { + ctx.push_value(decoder->isLooping()); + } else { + ctx.push_value(false); + } + return 1; + } + static int getTexture(lua_State* vm) { + auto const self = as(vm, 1); + auto const texture = Texture2D::create(vm); + texture->data = self->data; + if (texture->data) { + texture->data->retain(); + } + return 1; + } + + // method - playback control + + static int seek(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + auto const time = ctx.get_value(2); + if (auto decoder = getDecoder(self->data)) { + ctx.push_value(decoder->seek(time)); + } else { + ctx.push_value(false); + } + return 1; + } + static int update(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + auto const time = ctx.get_value(2); + if (auto decoder = getDecoder(self->data)) { + ctx.push_value(decoder->updateToTime(time)); + } else { + ctx.push_value(false); + } + return 1; + } + static int setLooping(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + auto const loop = ctx.get_value(2); + if (auto decoder = getDecoder(self->data)) { + decoder->setLooping(loop); + } + return 0; + } + static int setLoopRange(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + auto const loop_end = ctx.get_value(2); + auto const loop_duration = ctx.get_value(3); + if (auto decoder = getDecoder(self->data)) { + decoder->setLoopRange(loop_end, loop_duration); + } + return 0; + } + static int getLoopRange(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = getDecoder(self->data)) { + double loop_end = 0.0, loop_duration = 0.0; + decoder->getLoopRange(&loop_end, &loop_duration); + ctx.push_value(loop_end); + ctx.push_value(loop_duration); + return 2; + } + ctx.push_value(0.0); + ctx.push_value(0.0); + return 2; + } + + // method - stream info + + static int getVideoStreams(lua_State* vm) { + auto const self = as(vm, 1); + if (auto decoder = getDecoder(self->data)) { + std::vector list; + auto cb = [](core::VideoStreamInfo const& info, void* ud) { + static_cast*>(ud)->push_back(info); + }; + decoder->getVideoStreams(cb, &list); + lua_createtable(vm, (int)list.size(), 0); + for (size_t i = 0; i < list.size(); ++i) { + lua_createtable(vm, 0, 5); + lua_pushinteger(vm, (lua_Integer)list[i].index); + lua_setfield(vm, -2, "index"); + lua_pushinteger(vm, (lua_Integer)list[i].width); + lua_setfield(vm, -2, "width"); + lua_pushinteger(vm, (lua_Integer)list[i].height); + lua_setfield(vm, -2, "height"); + lua_pushnumber(vm, list[i].fps); + lua_setfield(vm, -2, "fps"); + lua_pushnumber(vm, list[i].duration_seconds); + lua_setfield(vm, -2, "duration"); + lua_rawseti(vm, -2, (int)i + 1); + } + } else { + lua_createtable(vm, 0, 0); + } + return 1; + } + static int getAudioStreams(lua_State* vm) { + auto const self = as(vm, 1); + if (auto decoder = getDecoder(self->data)) { + std::vector list; + auto cb = [](core::AudioStreamInfo const& info, void* ud) { + static_cast*>(ud)->push_back(info); + }; + decoder->getAudioStreams(cb, &list); + lua_createtable(vm, (int)list.size(), 0); + for (size_t i = 0; i < list.size(); ++i) { + lua_createtable(vm, 0, 4); + lua_pushinteger(vm, (lua_Integer)list[i].index); + lua_setfield(vm, -2, "index"); + lua_pushinteger(vm, (lua_Integer)list[i].channels); + lua_setfield(vm, -2, "channels"); + lua_pushinteger(vm, (lua_Integer)list[i].sample_rate); + lua_setfield(vm, -2, "sample_rate"); + lua_pushnumber(vm, list[i].duration_seconds); + lua_setfield(vm, -2, "duration"); + lua_rawseti(vm, -2, (int)i + 1); + } + } else { + lua_createtable(vm, 0, 0); + } + return 1; + } + static int getVideoStreamIndex(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = getDecoder(self->data)) { + ctx.push_value(decoder->getVideoStreamIndex()); + } else { + ctx.push_value(0); + } + return 1; + } + static int reopen(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = getDecoder(self->data)) { + core::VideoOpenOptions opt = decoder->getLastOpenOptions(); + // 读取 options 表 + if (lua_gettop(vm) >= 2) { + parseVideoOptions(vm, 2, opt); + } + ctx.push_value(decoder->reopen(opt)); + } else { + ctx.push_value(false); + } + return 1; + } + + // static method + + static int create(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const path = ctx.get_value(1); + + core::VideoOpenOptions opt{}; + // 读取 options 表 + if (lua_gettop(vm) >= 2) { + parseVideoOptions(vm, 2, opt); + } + + core::SmartReference texture; + if (!LAPP.getGraphicsDevice()->createVideoTexture(path, opt, texture.put())) { + auto const error_message = std::format( + "create Video from file '{}' failed", path); + return luaL_error(vm, error_message.c_str()); + } + auto const self = Video::create(vm); + self->data = texture.detach(); + return 1; + } + }; + + bool Video::is(lua_State* vm, int const index) { + lua::stack_t const ctx(vm); + return ctx.is_metatable(index, class_name); + } + Video* Video::as(lua_State* vm, int const index) { + lua::stack_t const ctx(vm); + return ctx.as_userdata