diff --git a/LuaSTG/CMakeLists.txt b/LuaSTG/CMakeLists.txt index 031383156..ac8a6dedd 100644 --- a/LuaSTG/CMakeLists.txt +++ b/LuaSTG/CMakeLists.txt @@ -128,6 +128,10 @@ 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/VideoBindingHelpers.hpp + LuaSTG/LuaBinding/VideoBindingHelpers.cpp LuaSTG/LuaBinding/modern/RenderTarget.hpp LuaSTG/LuaBinding/modern/RenderTarget.cpp LuaSTG/LuaBinding/modern/DepthStencilBuffer.hpp diff --git a/LuaSTG/LuaSTG/GameResource/Implement/ResourceTextureImpl.hpp b/LuaSTG/LuaSTG/GameResource/Implement/ResourceTextureImpl.hpp index bc7c5357d..66ddc7864 100644 --- a/LuaSTG/LuaSTG/GameResource/Implement/ResourceTextureImpl.hpp +++ b/LuaSTG/LuaSTG/GameResource/Implement/ResourceTextureImpl.hpp @@ -21,6 +21,7 @@ namespace luastg core::IRenderTarget* GetRenderTarget() { return m_rt.get(); } core::IDepthStencilBuffer* GetDepthStencilBuffer() { return m_ds.get(); } bool IsRenderTarget() { return m_is_rendertarget; } + bool IsVideoTexture() { return m_texture && m_texture->isVideoTexture(); } bool HasDepthStencilBuffer() { return m_enable_depthbuffer; } public: // 纹理容器 @@ -47,6 +48,7 @@ namespace luastg core::IRenderTarget* GetRenderTarget() override { return m_rt.get(); } core::IDepthStencilBuffer* GetDepthStencilBuffer() override { return m_ds.get(); } bool IsRenderTarget() override { return true; } + bool IsVideoTexture() override { return false; } bool HasDepthStencilBuffer() override { return !!m_ds; } // RenderTargetStackResourceTextureImpl diff --git a/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp b/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp index ecda732f2..0ab5cf8c0 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp +++ b/LuaSTG/LuaSTG/GameResource/ResourceDebug.cpp @@ -81,18 +81,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..e7fc9f19c 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp +++ b/LuaSTG/LuaSTG/GameResource/ResourcePool.cpp @@ -9,6 +9,7 @@ #include "GameResource/Implement/ResourcePostEffectShaderImpl.hpp" #include "GameResource/Implement/ResourceModelImpl.hpp" #include "core/FileSystem.hpp" +#include "core/AudioEngine.hpp" #include "AppFrame.h" #include "lua/plus.hpp" @@ -227,6 +228,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/GameResource/ResourceTexture.hpp b/LuaSTG/LuaSTG/GameResource/ResourceTexture.hpp index b692ef27e..ac6c54690 100644 --- a/LuaSTG/LuaSTG/GameResource/ResourceTexture.hpp +++ b/LuaSTG/LuaSTG/GameResource/ResourceTexture.hpp @@ -12,6 +12,7 @@ namespace luastg virtual core::IRenderTarget* GetRenderTarget() = 0; virtual core::IDepthStencilBuffer* GetDepthStencilBuffer() = 0; virtual bool IsRenderTarget() = 0; + virtual bool IsVideoTexture() = 0; virtual bool HasDepthStencilBuffer() = 0; }; }; diff --git a/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp b/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp index 3f9cf3545..072469814 100644 --- a/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp +++ b/LuaSTG/LuaSTG/LuaBinding/LW_ResourceMgr.cpp @@ -1,6 +1,9 @@ #include "LuaBinding/LuaWrapper.hpp" +#include "LuaBinding/VideoBindingHelpers.hpp" #include "lua/plus.hpp" #include "AppFrame.h" +#include "d3d11/VideoTexture.hpp" +#include "core/VideoDecoder.hpp" void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { @@ -52,6 +55,25 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept return luaL_error(L, "can't load texture from file '%s'.", path); return 0; } + static int LoadVideo(lua_State* L) noexcept + { + lua::stack_t const ctx(L); + 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; + bool const has_options = ctx.index_of_top() >= 3 && ctx.is_table(3); + if (has_options) + video::parseVideoOptions(L, 3, opt); + + if (!pActivedPool->LoadVideo(name, path, has_options ? &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); @@ -399,6 +421,14 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept lua_pushboolean(L, p->IsRenderTarget()); return 1; } + static int IsVideoTexture(lua_State* L) noexcept + { + core::SmartReference p = LRES.FindTexture(luaL_checkstring(L, 1)); + if (!p) + return luaL_error(L, "texture '%s' not found.", luaL_checkstring(L, 1)); + lua_pushboolean(L, p->IsVideoTexture()); + return 1; + } static int SetTexturePreMulAlphaState(lua_State* L) noexcept { core::SmartReference p = LRES.FindTexture(luaL_checkstring(L, 1)); @@ -671,6 +701,98 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept LRES.CacheTTFFontString(luaL_checkstring(L, 1), str, len); return 0; } + + // Video control functions + + static int VideoSeek(lua_State* L) noexcept { + lua::stack_t const ctx(L); + const char* name = luaL_checkstring(L, 1); + double time = luaL_checknumber(L, 2); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found or is not a video texture.", name); + bool ok = decoder->seek(time); + ctx.push_value(ok); + return 1; + } + + static int VideoSetLooping(lua_State* L) noexcept { + lua::stack_t const ctx(L); + const char* name = luaL_checkstring(L, 1); + bool loop = ctx.get_value(2); + auto decoder = video::getDecoderFromResourceName(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 = video::getDecoderFromResourceName(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 { + lua::stack_t const ctx(L); + const char* name = luaL_checkstring(L, 1); + double time = luaL_checknumber(L, 2); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found or is not a video texture.", name); + bool ok = decoder->updateToTime(time); + ctx.push_value(ok); + return 1; + } + + static int VideoGetInfo(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + video::pushVideoInfoToLua(L, decoder); + return 1; + } + + static int VideoGetVideoStreams(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + video::pushVideoStreamsToLua(L, decoder); + return 1; + } + + static int VideoGetAudioStreams(lua_State* L) noexcept { + const char* name = luaL_checkstring(L, 1); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found.", name); + video::pushAudioStreamsToLua(L, decoder); + return 1; + } + + static int VideoReopen(lua_State* L) noexcept { + lua::stack_t const ctx(L); + const char* name = luaL_checkstring(L, 1); + auto decoder = video::getDecoderFromResourceName(name); + if (!decoder) + return luaL_error(L, "video texture '%s' not found or is not a video texture.", name); + core::VideoOpenOptions opt = decoder->getLastOpenOptions(); + if (ctx.index_of_top() >= 2 && ctx.is_table(2)) + video::parseVideoOptions(L, 2, opt); + if (!decoder->reopen(opt)) { + ctx.push_value(false); + return 1; + } + ctx.push_value(true); + return 1; + } }; luaL_Reg const lib[] = { @@ -678,6 +800,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 }, @@ -690,6 +813,7 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { "LoadModel", &Wrapper::LoadModel }, { "CreateRenderTarget", &Wrapper::CreateRenderTarget }, { "IsRenderTarget", &Wrapper::IsRenderTarget }, + { "IsVideoTexture", &Wrapper::IsVideoTexture }, { "SetTexturePreMulAlphaState", &Wrapper::SetTexturePreMulAlphaState }, { "SetTextureSamplerState", &Wrapper::SetTextureSamplerState }, { "GetTextureSize", &Wrapper::GetTextureSize }, @@ -710,6 +834,17 @@ void luastg::binding::ResourceManager::Register(lua_State* L) noexcept { "SetFontState", &Wrapper::SetFontState }, { "CacheTTFString", &Wrapper::CacheTTFString }, + + // Video control functions + { "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/VideoBindingHelpers.cpp b/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.cpp new file mode 100644 index 000000000..904bae9a6 --- /dev/null +++ b/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.cpp @@ -0,0 +1,18 @@ +#include "VideoBindingHelpers.hpp" +#include "AppFrame.h" + +namespace luastg::binding::video { + core::IVideoDecoder* getDecoderFromResourceName(const char* name) noexcept { + auto texture = LRES.FindTexture(name); + if (!texture) { + return nullptr; + } + + auto texture2d = texture->GetTexture(); + if (!texture2d) { + return nullptr; + } + + return getDecoderFromTexture(texture2d); + } +} diff --git a/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.hpp b/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.hpp new file mode 100644 index 000000000..7fb9d95a2 --- /dev/null +++ b/LuaSTG/LuaSTG/LuaBinding/VideoBindingHelpers.hpp @@ -0,0 +1,123 @@ +#pragma once +#include "core/VideoDecoder.hpp" +#include "core/Texture2D.hpp" +#include "lua/plus.hpp" +#include + +namespace luastg::binding::video { + // Parse VideoOpenOptions from Lua table + inline void parseVideoOptions(lua_State* L, int index, core::VideoOpenOptions& opt) noexcept { + lua::stack_t stack(L); + lua::stack_index_t table_idx(index); + + if (!stack.is_table(table_idx)) { + return; + } + + opt.video_stream_index = stack.get_map_value(table_idx, "video_stream", opt.video_stream_index); + opt.output_width = stack.get_map_value(table_idx, "width", opt.output_width); + opt.output_height = stack.get_map_value(table_idx, "height", opt.output_height); + opt.premultiplied_alpha = stack.get_map_value(table_idx, "premultiplied_alpha", opt.premultiplied_alpha); + opt.looping = stack.get_map_value(table_idx, "looping", opt.looping); + opt.loop_end = stack.get_map_value(table_idx, "loop_end", opt.loop_end); + opt.loop_duration = stack.get_map_value(table_idx, "loop_duration", opt.loop_duration); + } + + // Get video decoder from texture object + inline core::IVideoDecoder* getDecoderFromTexture(core::ITexture2D* texture) noexcept { + if (!texture || !texture->isVideoTexture()) { + return nullptr; + } + return texture->getVideoDecoder(); + } + + // Get video decoder from resource name (via resource manager) + core::IVideoDecoder* getDecoderFromResourceName(const char* name) noexcept; + + // Push video stream info array to Lua stack + inline void pushVideoStreamsToLua(lua_State* L, core::IVideoDecoder* decoder) { + lua::stack_t stack(L); + + if (!decoder) { + stack.create_array(0); + return; + } + + std::vector list; + auto callback = [](core::VideoStreamInfo const& info, void* userdata) { + static_cast*>(userdata)->push_back(info); + }; + decoder->getVideoStreams(callback, &list); + + auto array_idx = stack.create_array(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + auto item_idx = stack.create_map(5); + stack.set_map_value(item_idx, "index", list[i].index); + stack.set_map_value(item_idx, "width", list[i].width); + stack.set_map_value(item_idx, "height", list[i].height); + stack.set_map_value(item_idx, "fps", list[i].fps); + stack.set_map_value(item_idx, "duration", list[i].duration_seconds); + stack.set_array_value(array_idx, i + 1, item_idx); + } + } + + // Push audio stream info array to Lua stack + inline void pushAudioStreamsToLua(lua_State* L, core::IVideoDecoder* decoder) { + lua::stack_t stack(L); + + if (!decoder) { + stack.create_array(0); + return; + } + + std::vector list; + auto callback = [](core::AudioStreamInfo const& info, void* userdata) { + static_cast*>(userdata)->push_back(info); + }; + decoder->getAudioStreams(callback, &list); + + auto array_idx = stack.create_array(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + auto item_idx = stack.create_map(4); + stack.set_map_value(item_idx, "index", list[i].index); + stack.set_map_value(item_idx, "channels", list[i].channels); + stack.set_map_value(item_idx, "sample_rate", list[i].sample_rate); + stack.set_map_value(item_idx, "duration", list[i].duration_seconds); + stack.set_array_value(array_idx, i + 1, item_idx); + } + } + + // Push video info table to Lua stack + inline void pushVideoInfoToLua(lua_State* L, core::IVideoDecoder* decoder) { + lua::stack_t stack(L); + + if (!decoder) { + stack.create_map(0); + return; + } + + auto map_idx = stack.create_map(10); + + stack.set_map_value(map_idx, "duration", decoder->getDuration()); + stack.set_map_value(map_idx, "time", decoder->getCurrentTime()); + stack.set_map_value(map_idx, "looping", decoder->isLooping()); + + double loop_end = 0.0, loop_duration = 0.0; + decoder->getLoopRange(&loop_end, &loop_duration); + stack.set_map_value(map_idx, "loop_end", loop_end); + stack.set_map_value(map_idx, "loop_duration", loop_duration); + + auto size = decoder->getVideoSize(); + stack.set_map_value(map_idx, "width", size.x); + stack.set_map_value(map_idx, "height", size.y); + + stack.set_map_value(map_idx, "video_stream", decoder->getVideoStreamIndex()); + + double frame_interval = decoder->getFrameInterval(); + stack.set_map_value(map_idx, "frame_interval", frame_interval); + + if (frame_interval > 0.0) { + stack.set_map_value(map_idx, "fps", 1.0 / frame_interval); + } + } +} diff --git a/LuaSTG/LuaSTG/LuaBinding/modern/Video.cpp b/LuaSTG/LuaSTG/LuaBinding/modern/Video.cpp new file mode 100644 index 000000000..852e0ed51 --- /dev/null +++ b/LuaSTG/LuaSTG/LuaBinding/modern/Video.cpp @@ -0,0 +1,289 @@ +#include "Video.hpp" +#include "Texture2D.hpp" +#include "VideoBindingHelpers.hpp" +#include "lua/plus.hpp" +#include "AppFrame.h" +#include "core/VideoDecoder.hpp" + +namespace luastg::binding { + const 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) + + // 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; + } + + template + static int withDecoder(lua_State* vm, Func&& func) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + return func(ctx, decoder); + } + ctx.push_value(0.0); + return 1; + } + + static int getDuration(lua_State* vm) { + return withDecoder(vm, [](auto& ctx, auto decoder) { + ctx.push_value(decoder->getDuration()); + return 1; + }); + } + static int getCurrentTime(lua_State* vm) { + return withDecoder(vm, [](auto& ctx, auto decoder) { + ctx.push_value(decoder->getCurrentTime()); + return 1; + }); + } + static int getFPS(lua_State* vm) { + return withDecoder(vm, [](auto& ctx, auto decoder) { + auto interval = decoder->getFrameInterval(); + ctx.push_value(interval > 0.0 ? 1.0 / interval : 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 = video::getDecoderFromTexture(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 = video::getDecoderFromTexture(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 = video::getDecoderFromTexture(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 = video::getDecoderFromTexture(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 = video::getDecoderFromTexture(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 = video::getDecoderFromTexture(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 = video::getDecoderFromTexture(self->data)) { + video::pushVideoStreamsToLua(vm, decoder); + } else { + lua::stack_t const ctx(vm); + ctx.create_array(0); + } + return 1; + } + static int getAudioStreams(lua_State* vm) { + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(self->data)) { + video::pushAudioStreamsToLua(vm, decoder); + } else { + lua::stack_t const ctx(vm); + ctx.create_array(0); + } + return 1; + } + static int getVideoStreamIndex(lua_State* vm) { + lua::stack_t const ctx(vm); + auto const self = as(vm, 1); + if (auto decoder = video::getDecoderFromTexture(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 = video::getDecoderFromTexture(self->data)) { + core::VideoOpenOptions opt = decoder->getLastOpenOptions(); + if (ctx.index_of_top() >= 2) { + video::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{}; + if (ctx.index_of_top() >= 2) { + video::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