// Oak Video Editor - Non-Linear Video Editor
// Copyright (C) 2026 Oak Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//! OfxImageEffectOpenGLRenderSuiteV1(M11 第 2 期):clipLoadTexture /
//! clipFreeTexture / flushResources。
//!
//! 语义对照 ofxGPURender.h(vendored,OpenGL Render Suite 一节)与
//! HostSupport 插件侧实现(HS: ofxhImageEffect.cpp:2296-2367):
//!
//! - `clipLoadTexture(clip, time, format, region, *texture)`:把 clip
//! 在 `time` 的图像加载为 GL 纹理。纹理句柄是**属性集**(标签 0),
//! 含 ofxGPURender.h 规定的 12 个属性(OpenGLTextureIndex/
//! OpenGLTextureTarget/PixelDepth/Components/PreMultiplication/
//! RenderScale/PixelAspectRatio/Bounds/RegionOfDefinition/RowBytes/
//! Field/UniqueIdentifier);宿主侧存强引用([`LIVE_TEXTURES`]),
//! clipFreeTexture 摘除即释放(对应 HS 的 get/release 配对)。
//! **OpenGLTextureIndex 是真实 GL 纹理名**(GL 模式,见
//! [`crate::gl_bridge`] 方案 B):Output clip 是宿主为输出帧建的
//! GL 纹理(经 GlCtx 注入,render 驱动负责建/FBO 挂载/回读/删除);
//! 输入 clip 是本 suite 在 GL 渲染期经桥上传图像得到的 GL 纹理
//! (clipFreeTexture 删除)。0 = CPU 回退(无 GL 名,插件按规范回退)。
//! - Output clip:`format` 忽略,宿主返回已附着的输出纹理句柄——
//! 绑定渲染目标的动作(ofxGPURender.h "the host must bind the
//! resulting texture as the current color buffer")由调用方约定:
//! GL render 驱动在进入 render_gl 前已把输出 GL 纹理挂进 FBO 并
//! 绑定(等价 C++ 的 `PluginRenderer::attach_output_texture`)。
//! clipFreeTexture 对 Output 只释放句柄、不删纹理(宿主还要回读它,
//! 回读后由 render 驱动删除)。
//! - 输入纹理在 GL 模式经 [`crate::gl_bridge::create_input_texture`]
//! 上传(CPU 帧 → GL 纹理);CPU 模式(无 GL 上下文)维持
//! [`crate::render`] 的 CPU 纹理。纹理格式:全链路 F32 约束下,
//! 像素深度按 clip 协商结果(恒 F32);`format` 参数
//! (kOfxImageEffectGLFormat*)若请求的分量与协商分量不符,Phase 2
//! 不做转换 → Failed(规范要求 "host ensures it gives the requested
//! format"——宁可显式失败也不静默给错格式)。ofxGPURender.h 注明
//! "宿主无需按 Clip Preferences 把图像重映射到插件请求的位深",
//! 插件以纹理句柄的 PixelDepth/Components 为准。
//! - `flushResources`:宿主在 render 之间不缓存 GPU 资源(纹理随
//! clipFreeTexture 立即释放)→ 无可释放 → kOfxStatReplyDefault
//! (规范:"nothing the host could do")。
//! - GL 上下文规则(ofxGPURender.h "OpenGL Current Context"):宿主
//! 只在 Render/BeginSequenceRender/EndSequenceRender/Attach/Detach
//! 期间要求上下文 current;本实现的约定是 render 驱动一次 acquire
//! 整个 GL render action([`crate::gl_bridge::acquire`]),本 suite
//! 回调期间上下文恒 current,经 [`crate::suites::gl_ctx`] TLS 取
//! 渲染器句柄。
use std::collections::HashMap;
use std::ffi::{c_char, c_double, c_int, c_void, CStr, CString};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::Mutex;
use crate::clip::ClipInstance;
use crate::image::Image;
use crate::property::{PropertySet, Value};
use crate::suites::{status, tag};
// ---- GL 常量(ofxGPURender.h;GLFormat 字符串为 ofxOpenGLRender.h
// OpenFX 1.4 的规范名,vendored 头文件是 stub 未收录,按规范定义)----
/// kOfxImageEffectPropOpenGLTextureIndex(ofxGPURender.h:135)。
pub(crate) const GL_TEXTURE_INDEX: &str = "OfxImageEffectPropOpenGLTextureIndex";
/// kOfxImageEffectPropOpenGLTextureTarget(ofxGPURender.h:152)。
pub(crate) const GL_TEXTURE_TARGET: &str = "OfxImageEffectPropOpenGLTextureTarget";
/// kOfxImageEffectGLFormatRGBA(ofxOpenGLRender.h 1.4)。
pub(crate) const GL_FORMAT_RGBA: &str = "OfxImageEffectGLFormatRGBA";
/// kOfxImageEffectGLFormatRGB。
pub(crate) const GL_FORMAT_RGB: &str = "OfxImageEffectGLFormatRGB";
/// kOfxImageEffectGLFormatAlpha。
pub(crate) const GL_FORMAT_ALPHA: &str = "OfxImageEffectGLFormatAlpha";
/// kOfxImageEffectGLFormatLuminance。
pub(crate) const GL_FORMAT_LUMINANCE: &str = "OfxImageEffectGLFormatLuminance";
/// kOfxImageEffectGLFormatLuminanceAlpha。
pub(crate) const GL_FORMAT_LUMINANCE_ALPHA: &str = "OfxImageEffectGLFormatLuminanceAlpha";
/// GL_TEXTURE_2D 的 GLenum 值(0x0DE1;纹理句柄的 OpenGLTextureTarget
/// 属性。GL 规范值,非 OFX 宏)。
pub(crate) const GL_TEXTURE_2D: i32 = 0x0DE1;
/// kOfxImageEffectPropPreMultiplication 的非预乘默认值。
pub(crate) const PREMULT_NONE: &str = "OfxImagePreMultipliedNone";
/// GL 像素深度协商(ofxGPURender.h:65-89 kOfxOpenGLPropPixelDepth):
/// 插件描述符声明的 GL 渲染支持位深列表(可选)。
///
/// 返回 Some(实际位深) 表示 GL 模式可行,None 表示管线无法满足
/// 插件声明 → 宿主应回退 CPU 渲染(ofxGPURender.h "the host will
/// try to provide buffers/textures in one of the supported formats";
/// Phase 2 全链路 F32,无法提供其他位深):
/// - 列表缺失/为空 → Some(Float)(宿主自选,规范默认);
/// - 列表含 Float → Some(Float);
/// - 列表存在且不含 Float → None(管线约束;GL 模式不可行)。
pub(crate) fn pick_gl_pixel_depth(
plugin_props: &crate::property::PropertySet,
) -> Option<&'static str> {
use crate::property::Value;
let dim = plugin_props.dimension(crate::host::PROP_GL_PIXEL_DEPTH);
if dim == 0 {
return Some("OfxBitDepthFloat");
}
for i in 0..dim {
if let Some(Value::String(s)) = plugin_props.get(crate::host::PROP_GL_PIXEL_DEPTH, i) {
if s.to_string_lossy() == "OfxBitDepthFloat" {
return Some("OfxBitDepthFloat");
}
}
}
None
}
// ---- 存活纹理表 -----------------------------------------------------------
/// 存活 GL 纹理表:clipLoadTexture 产出(props 地址 → 属性集 +
/// 纹理值 + 是否输出 clip + 真实 GL 纹理名[可选]);clipFreeTexture
/// 摘除即释放——对应 HS 的 get/release 配对
/// (HS: ofxhImageEffect.cpp:2336-2351)。纹理是 oakrender 值(drop
/// 自动释放后端 token;原 `texture_free` 调用面随值模型删除)。
///
/// `gl_texture`:GL 模式下输入 clip 经 [`crate::gl_bridge`] 建的真实
/// GL 纹理名(输出 clip 恒 None——宿主自建并负责生命周期,见
/// [`crate::gl_bridge`] 模块文档);摘除时经
/// [`delete_gl_texture_if_gl`] 删除。
///
/// 属性集必须**装箱**(Box 稳定堆地址):纹理句柄指向它,函数返回后
/// 必须仍存活;栈上临时变量会悬垂(phase-2 实现初版的 bug)。
static LIVE_TEXTURES: std::sync::LazyLock<
Mutex, crate::render::Texture, bool, Option)>>,
> = std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
/// 登记纹理(clipLoadTexture 内部;`props` 装箱后取地址为句柄基址)。
/// `gl_texture`:真实 GL 纹理名(输入 clip GL 模式;无则 None)。
fn register(
props: Box,
texture: crate::render::Texture,
is_output: bool,
gl_texture: Option,
) -> usize {
let addr = &*props as *const PropertySet as usize;
LIVE_TEXTURES
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(addr, (props, texture, is_output, gl_texture));
addr
}
/// 删除 GL 纹理(上下文 current 时;否则留给下次 GL 渲染前的兜底——
/// 正常路径 clipFreeTexture/purge 都在 GL render 期,上下文必 current)。
fn delete_gl_texture_if_gl(name: i32) {
if crate::suites::gl_ctx().is_some() {
crate::gl_bridge::delete_gl_texture(name);
}
}
/// 释放全部残留输入纹理(GL render action 返回后的安全网:规范要求
/// 插件在 action 返回前 clipFreeTexture 全部句柄;遗漏的输入纹理在
/// 此释放,输出纹理保留——宿主还要读它)。GL 输入纹理同步删除
/// (上下文 current;render_gl 在清 GlCtx TLS 前调用本函数)。
pub(crate) fn purge_leftovers() {
let mut live = LIVE_TEXTURES.lock().unwrap_or_else(|e| e.into_inner());
let dropped: Vec