Files
oak-editor/crates/oakplugin/cbits/oak_test_plugin.c
T
Mike-Solar 3efdee5a10 fix: share one language-test mutex; read the OFX marker via Win32 env
i18n: test_lock() and lang_test_lock() used to be TWO different static
mutexes, so the i18n tests and the ~30 app/actions/dialogs tests that
mutate the language global never excluded each other. Windows thread
scheduling exposed the race: tr_falls_back_to_english_then_the_key got
the English value because another test flipped the language mid-assert.
Both entry points now lock the same mutex.

oak_test_plugin.c: on Windows the plugin DLL has its own CRT
environment block, so getenv() never sees what the host's
std::env::set_var set via SetEnvironmentVariableW — the interact
lifecycle test's marker file stayed empty ("lifecycle actions
missing: []"). Read the marker path through GetEnvironmentVariableA
on _WIN32.
2026-08-21 13:36:19 +08:00

847 lines
32 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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 <http://www.gnu.org/licenses/>.
*/
/*
* 最小测试插件(M11 §2.4 的交付物;build.rs 编译为共享库,
* 运行时装配成 oak-test-plugin.ofx.bundle 供 host 扫描)。
*
* filter 上下文:
* - 参数:gainDouble,默认 0
* - clipSource(输入,RGBA)、Output
* - describe/describeInContext 建参数与 clip
* - getClipPreferences:全链路 F32+RGBA、帧率 24
* - render:把输出图像填成常量 0.5RGBA float),alpha=1
* - getRoDproject sizegetRoI:原样返回 region
* - isIdentity:不设 → 非透传;
* - begin/endSequenceRender:记日志(状态码 OK)。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#ifdef _WIN32
#include <windows.h>
#endif
/* Windows 上插件 DLL 带自己的 CRT 环境块:宿主 Rust 侧的
* std::env::set_var 走 SetEnvironmentVariableW,只更新 Win32 进程
* 环境,插件里 getenv 读的 CRT `_environ` 看不到它。marker 路径
* 统一经 Win32 进程环境读取(POSIX 下就是 getenv)。 */
static const char *marker_env(const char *name)
{
#ifdef _WIN32
static char buf[32768];
DWORD n = GetEnvironmentVariableA(name, buf, (DWORD)sizeof buf);
if (n == 0 || n >= sizeof buf)
return NULL;
return buf;
#else
return getenv(name);
#endif
}
#include "ofxCore.h"
#include "ofxColour.h"
#include "ofxDrawSuite.h"
#include "ofxGPURender.h"
#include "ofxImageEffect.h"
#include "ofxInteract.h"
#include "ofxKeySyms.h"
#include "ofxMessage.h"
#include "ofxParam.h"
#include "ofxProgress.h"
#include "ofxProperty.h"
/* GL 端到端测试的真实绘制需要 GL 命令(仅 macOS 有真实 CGL 桥;
* Linux/Windows 的 GL 桥是 stub,绘制代码随 __APPLE__ 排除)。 */
#ifdef __APPLE__
#include <OpenGL/gl.h>
#endif
/* kOfxImageEffectGLFormatRGBA 在 vendored ofxOpenGLRender.h 是 stub
* 未收录(OpenFX 1.4 规范名),按规范定义。kOfxImageEffectPropIsIdentity
* 同理(vendored ofxImageEffect.h 只文档化了该属性,未给宏)。 */
#define GL_FORMAT_RGBA "OfxImageEffectGLFormatRGBA"
#define kOfxImageEffectPropIsIdentity "OfxImageEffectPropIsIdentity"
static OfxHost *g_host = NULL;
static const OfxPropertySuiteV1 *g_propSuite = NULL;
static const OfxImageEffectSuiteV1 *g_imageEffectSuite = NULL;
static const OfxParameterSuiteV1 *g_paramSuite = NULL;
static const OfxMessageSuiteV1 *g_messageSuite = NULL;
static const OfxProgressSuiteV1 *g_progressSuite = NULL;
static const OfxImageEffectOpenGLRenderSuiteV1 *g_glSuite = NULL;
static const OfxDrawSuiteV1 *g_drawSuite = NULL;
static const OfxInteractSuiteV1 *g_interactSuite = NULL;
/* 宿主扩展(任务契约;vendored ofxInteract.h 未收录的 action 名与属性
* 名)。kOfxInteractPropViewportSize 取 OFX 1.3 的规范名
* "OfxInteractPropViewport"vendored 1.5 头已删);BackgroundImage 与
* NewInteract/Idle 是本宿主扩展。 */
#ifndef kOfxActionNewInteract
#define kOfxActionNewInteract "OfxActionNewInteract"
#endif
#ifndef kOfxInteractActionIdle
#define kOfxInteractActionIdle "OfxInteractActionIdle"
#endif
#ifndef kOfxInteractPropViewportSize
#define kOfxInteractPropViewportSize "OfxInteractPropViewport"
#endif
#ifndef kOfxInteractPropBackgroundImage
#define kOfxInteractPropBackgroundImage "OfxInteractPropBackgroundImage"
#endif
/* push button 的 instanceChanged 调用计数(宿主按下按钮后应路由
* kOfxActionInstanceChanged/UserEdited 到这里)。 */
static int g_button_instance_changed = 0;
/* ---------- suite 便捷封装 ---------- */
/* 记录一次 push button 的 instanceChanged:计数自增,并在
* OAK_TEST_PLUGIN_INSTANCECHANGED_MARKER 指向的文件追加一行(宿主
* 测试断言用;未设环境变量时静默)。 */
static void record_button_instance_changed(void)
{
g_button_instance_changed++;
const char *marker = marker_env("OAK_TEST_PLUGIN_INSTANCECHANGED_MARKER");
if (!marker)
return;
FILE *f = fopen(marker, "a");
if (!f)
return;
fprintf(f, "button instanceChanged count=%d\n", g_button_instance_changed);
fclose(f);
}
static OfxStatus propSetString(OfxPropertySetHandle h, const char *name, int index, const char *v)
{
return g_propSuite->propSetString(h, name, index, v);
}
static OfxStatus propSetStringN(OfxPropertySetHandle h, const char *name, int count, const char **v)
{
return g_propSuite->propSetStringN(h, name, count, v);
}
static OfxStatus propSetPointer(OfxPropertySetHandle h, const char *name, int index, void *v)
{
return g_propSuite->propSetPointer(h, name, index, v);
}
static OfxStatus propSetDoubleN(OfxPropertySetHandle h, const char *name, int count, const double *v)
{
return g_propSuite->propSetDoubleN(h, name, count, v);
}
static OfxStatus propGetDouble(OfxPropertySetHandle h, const char *name, int index, double *v)
{
return g_propSuite->propGetDouble(h, name, index, v);
}
static OfxStatus propGetInt(OfxPropertySetHandle h, const char *name, int index, int *v)
{
return g_propSuite->propGetInt(h, name, index, v);
}
static OfxStatus propGetIntN(OfxPropertySetHandle h, const char *name, int count, int *v)
{
return g_propSuite->propGetIntN(h, name, count, v);
}
static OfxStatus propGetPointer(OfxPropertySetHandle h, const char *name, int index, void **v)
{
return g_propSuite->propGetPointer(h, name, index, v);
}
/* 协商期 per-clip 属性名:前缀_clip */
static void setClipPref(OfxPropertySetHandle out, const char *prefix, const char *clip, const char *value)
{
char name[256];
snprintf(name, sizeof(name), "%s_%s", prefix, clip);
propSetString(out, name, 0, value);
}
/* ---------- setHostmandatory 第一个调用) ---------- */
static void setHost(OfxHost *host)
{
g_host = host;
if (!host)
return;
g_propSuite = (const OfxPropertySuiteV1 *)host->fetchSuite(host->host, kOfxPropertySuite, 1);
g_imageEffectSuite =
(const OfxImageEffectSuiteV1 *)host->fetchSuite(host->host, kOfxImageEffectSuite, 1);
g_paramSuite = (const OfxParameterSuiteV1 *)host->fetchSuite(host->host, kOfxParameterSuite, 1);
g_messageSuite = (const OfxMessageSuiteV1 *)host->fetchSuite(host->host, kOfxMessageSuite, 1);
g_progressSuite = (const OfxProgressSuiteV1 *)host->fetchSuite(host->host, kOfxProgressSuite, 1);
g_glSuite = (const OfxImageEffectOpenGLRenderSuiteV1 *)host->fetchSuite(
host->host, kOfxOpenGLRenderSuite, 1);
g_drawSuite = (const OfxDrawSuiteV1 *)host->fetchSuite(host->host, kOfxDrawSuite, 1);
g_interactSuite = (const OfxInteractSuiteV1 *)host->fetchSuite(host->host, kOfxInteractSuite, 1);
}
/* ---------- action 实现 ---------- */
static OfxStatus actionDescribe(const void *handle, int is_gl)
{
const char *comps[] = { kOfxImageComponentRGBA, kOfxImageComponentRGB, kOfxImageComponentAlpha };
propSetString(handle, kOfxPropLabel, 0, "Oak Test Plugin");
propSetStringN(handle, kOfxImageEffectPropSupportedContexts, 1,
&(const char *){ kOfxImageEffectContextFilter });
/* ofxColourM11 §4):声明 OCIO 色彩管理能力与可用配置。 */
propSetString(handle, kOfxImageEffectPropColourManagementStyle, 0,
kOfxImageEffectColourManagementOCIO);
{
const char *configs[] = { "ofx-native-v1.5_aces-v1.3_ocio-v2.3" };
propSetStringN(handle, kOfxImageEffectPropColourManagementAvailableConfigs, 1, configs);
}
/* GL 能力(M11 §4):GL 变体声明 "true" + 支持位深(F32)。
* CPU 变体保持默认 "false"。 */
if (is_gl) {
propSetString(handle, kOfxImageEffectPropOpenGLRenderSupported, 0, "true");
{
const char *depths[] = { kOfxBitDepthFloat };
propSetStringN(handle, kOfxOpenGLPropPixelDepth, 1, depths);
}
}
/* 参数族:gainDouble,带 display min/max)、modeChoice
* 两选项)、debugDoublesecret)、labelString)。 */
OfxPropertySetHandle paramProps = NULL;
OfxStatus st = g_paramSuite->paramDefine((OfxParamSetHandle)handle, kOfxParamTypeDouble, "gain", &paramProps);
if (st != kOfxStatOK)
return st;
propSetString(paramProps, kOfxPropLabel, 0, "Gain");
{
const double min = -2.0, max = 2.0;
g_propSuite->propSetDouble(paramProps, kOfxParamPropDisplayMin, 0, min);
g_propSuite->propSetDouble(paramProps, kOfxParamPropDisplayMax, 0, max);
}
st = g_paramSuite->paramDefine((OfxParamSetHandle)handle, kOfxParamTypeChoice, "mode", &paramProps);
if (st != kOfxStatOK)
return st;
propSetString(paramProps, kOfxPropLabel, 0, "Mode");
{
const char *options[] = { "Fast", "High" };
g_propSuite->propSetStringN(paramProps, kOfxParamPropChoiceOption, 2, options);
}
st = g_paramSuite->paramDefine((OfxParamSetHandle)handle, kOfxParamTypeDouble, "debug", &paramProps);
if (st != kOfxStatOK)
return st;
g_propSuite->propSetInt(paramProps, kOfxParamPropSecret, 0, 1);
st = g_paramSuite->paramDefine((OfxParamSetHandle)handle, kOfxParamTypeString, "label", &paramProps);
if (st != kOfxStatOK)
return st;
propSetString(paramProps, kOfxPropLabel, 0, "Label");
/* push button:无值,按下经 instanceChanged (UserEdited) 通知
* 插件(ofxCore.h kOfxActionInstanceChanged)。 */
st = g_paramSuite->paramDefine((OfxParamSetHandle)handle, kOfxParamTypePushButton, "button", &paramProps);
if (st != kOfxStatOK)
return st;
propSetString(paramProps, kOfxPropLabel, 0, "Button");
/* clipSource + Output。 */
OfxPropertySetHandle clipProps = NULL;
st = g_imageEffectSuite->clipDefine((OfxImageEffectHandle)handle, "Source", &clipProps);
if (st != kOfxStatOK)
return st;
propSetStringN(clipProps, kOfxImageEffectPropSupportedComponents, 3, comps);
propSetString(clipProps, kOfxPropLabel, 0, "Source");
st = g_imageEffectSuite->clipDefine((OfxImageEffectHandle)handle, "Output", &clipProps);
if (st != kOfxStatOK)
return st;
propSetStringN(clipProps, kOfxImageEffectPropSupportedComponents, 3, comps);
return kOfxStatOK;
}
static OfxStatus actionGetClipPreferences(OfxPropertySetHandle outArgs)
{
setClipPref(outArgs, "OfxImageClipPropComponents", "Source", kOfxImageComponentRGBA);
setClipPref(outArgs, "OfxImageClipPropDepth", "Source", kOfxBitDepthFloat);
setClipPref(outArgs, "OfxImageClipPropComponents", "Output", kOfxImageComponentRGBA);
setClipPref(outArgs, "OfxImageClipPropDepth", "Output", kOfxBitDepthFloat);
g_propSuite->propSetDouble(outArgs, kOfxImageEffectPropFrameRate, 0, 24.0);
g_propSuite->propSetString(outArgs, kOfxImageClipPropFieldOrder, 0, kOfxImageFieldNone);
return kOfxStatOK;
}
static OfxStatus actionGetRoD(OfxPropertySetHandle outArgs)
{
const double rod[4] = { 0.0, 0.0, 1920.0, 1080.0 };
propSetDoubleN(outArgs, kOfxImageEffectPropRegionOfDefinition, 4, rod);
return kOfxStatOK;
}
static OfxStatus actionGetRoI(OfxImageEffectHandle inst, OfxPropertySetHandle inArgs,
OfxPropertySetHandle outArgs)
{
/* 对每个输入 clip 回填 region(此处原样返回 Output 的 window 即可)。 */
OfxImageClipHandle clip = NULL;
OfxPropertySetHandle clipProps = NULL;
OfxStatus st = g_imageEffectSuite->clipGetHandle(inst, "Source", &clip, &clipProps);
if (st != kOfxStatOK)
return st;
double region[4] = { 0.0, 0.0, 0.0, 0.0 };
st = g_propSuite->propGetDoubleN(inArgs, kOfxImageEffectPropRegionOfInterest, 4, region);
if (st != kOfxStatOK)
return st;
char name[256];
snprintf(name, sizeof(name), "OfxImageEffectPropRegionOfInterest_%s", "Source");
st = propSetDoubleN(outArgs, name, 4, region);
fprintf(stderr, "DBG getRoI read=(%g,%g,%g,%g) set st=%d\n", region[0], region[1], region[2], region[3], st);
if (st != kOfxStatOK)
return st;
return kOfxStatOK;
}
static OfxStatus actionRender(OfxImageEffectHandle inst, OfxPropertySetHandle inArgs)
{
double time = 0.0;
propGetDouble(inArgs, kOfxPropTime, 0, &time);
OfxImageClipHandle clip = NULL;
OfxPropertySetHandle clipProps = NULL;
OfxStatus st = g_imageEffectSuite->clipGetHandle(inst, "Output", &clip, &clipProps);
if (st != kOfxStatOK)
return st;
OfxPropertySetHandle image = NULL;
st = g_imageEffectSuite->clipGetImage(clip, time, NULL, &image);
if (st != kOfxStatOK)
return st;
void *data = NULL;
int rowBytes = 0;
int bounds[4] = { 0, 0, 0, 0 };
propGetPointer(image, kOfxImagePropData, 0, &data);
propGetInt(image, kOfxImagePropRowBytes, 0, &rowBytes);
propGetIntN(image, kOfxImagePropBounds, 4, bounds);
/* 进度:Start → Update(0.5)(取消则中止)→ End。 */
if (g_progressSuite) {
g_progressSuite->progressStart((OfxImageEffectHandle)inst, "render");
OfxStatus ps = g_progressSuite->progressUpdate((OfxImageEffectHandle)inst, 0.5);
if (ps != kOfxStatOK) {
g_progressSuite->progressEnd((OfxImageEffectHandle)inst);
return kOfxStatFailed;
}
}
int w = bounds[2] - bounds[0];
int h = bounds[3] - bounds[1];
if (data && w > 0 && h > 0) {
/* 常量 0.5RGBA float);alpha=1。 */
for (int y = 0; y < h; y++) {
float *row = (float *)((char *)data + (size_t)y * (size_t)rowBytes);
for (int x = 0; x < w; x++) {
row[x * 4 + 0] = 0.5f;
row[x * 4 + 1] = 0.5f;
row[x * 4 + 2] = 0.5f;
row[x * 4 + 3] = 1.0f;
}
}
}
g_imageEffectSuite->clipReleaseImage(image);
if (g_progressSuite) {
g_progressSuite->progressEnd((OfxImageEffectHandle)inst);
}
return kOfxStatOK;
}
/* ---------- ofxColourM11 §4):GetOutputColourspace ---------- */
static OfxStatus actionGetOutputColourspace(OfxPropertySetHandle inArgs,
OfxPropertySetHandle outArgs)
{
/* 优先采纳宿主偏好的第一个色彩空间;否则交叉引用 Source clip
* ofxColour.hcross-reference 是合法回写,宿主须解析)。 */
int n = 0;
g_propSuite->propGetDimension(inArgs, kOfxImageClipPropPreferredColourspaces, &n);
if (n > 0) {
char *pref = NULL;
OfxStatus st = g_propSuite->propGetString(
inArgs, kOfxImageClipPropPreferredColourspaces, 0, &pref);
if (st == kOfxStatOK && pref) {
propSetString(outArgs, kOfxImageClipPropColourspace, 0, pref);
return kOfxStatOK;
}
}
propSetString(outArgs, kOfxImageClipPropColourspace, 0, "OfxColourspace_Source");
return kOfxStatOK;
}
/* ---------- isIdentity 变体:透传 Source ---------- */
static OfxStatus actionIsIdentity(OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs)
{
/* 恒透传 Source(时间不变)——宿主应短路 render 直接拷贝 Source
* 的帧。 */
propSetString(outArgs, kOfxImageEffectPropIsIdentity, 0, "Source");
return kOfxStatOK;
}
/* ---------- GL 变体(M11 §4):attach/detach 与 GL render ---------- */
static OfxStatus actionGLAttached(OfxImageEffectHandle handle)
{
if (g_messageSuite) {
g_messageSuite->message(handle, kOfxMessageMessage, "gl-test", "gl-attached");
}
return kOfxStatOK;
}
static OfxStatus actionGLDetached(OfxImageEffectHandle handle)
{
if (g_messageSuite) {
g_messageSuite->message(handle, kOfxMessageMessage, "gl-test", "gl-detached");
}
return kOfxStatOK;
}
/* GL render:经 OpenGL suite 取 Source 与 Output 纹理并上报索引
* (宿主侧测试经 message 捕获断言)。GL 未使能时回退 CPU render。 */
static void draw_gl_test_pattern(void)
{
#ifdef __APPLE__
/* 把当前绑定帧缓冲(宿主为输出帧挂的 FBO)清成已知颜色。 */
glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
#else
(void)0;
#endif
}
static OfxStatus actionRenderGL(OfxImageEffectHandle inst, OfxPropertySetHandle inArgs)
{
int gl_enabled = 0;
g_propSuite->propGetInt(inArgs, kOfxImageEffectPropOpenGLEnabled, 0, &gl_enabled);
if (!gl_enabled) {
return actionRender(inst, inArgs);
}
if (!g_glSuite) {
return kOfxStatErrMissingHostFeature;
}
double time = 0.0;
propGetDouble(inArgs, kOfxPropTime, 0, &time);
/* 输入 clipclipLoadTexture(请求 RGBA)。 */
OfxImageClipHandle clip = NULL;
OfxPropertySetHandle clipProps = NULL;
OfxStatus st = g_imageEffectSuite->clipGetHandle(inst, "Source", &clip, &clipProps);
if (st != kOfxStatOK)
return st;
OfxPropertySetHandle tex = NULL;
st = g_glSuite->clipLoadTexture(clip, time, GL_FORMAT_RGBA, NULL, &tex);
if (st != kOfxStatOK)
return st;
int src_index = 0;
g_propSuite->propGetInt(tex, kOfxImageEffectPropOpenGLTextureIndex, 0, &src_index);
if (g_messageSuite) {
g_messageSuite->message(inst, kOfxMessageMessage, "gl-test", "gl-source-index=%d", src_index);
}
st = g_glSuite->clipFreeTexture(tex);
if (st != kOfxStatOK)
return st;
/* 输出 clipclipLoadTexture(Output)format 忽略)。 */
st = g_imageEffectSuite->clipGetHandle(inst, "Output", &clip, &clipProps);
if (st != kOfxStatOK)
return st;
tex = NULL;
st = g_glSuite->clipLoadTexture(clip, time, NULL, NULL, &tex);
if (st != kOfxStatOK)
return st;
int out_index = 0;
g_propSuite->propGetInt(tex, kOfxImageEffectPropOpenGLTextureIndex, 0, &out_index);
if (g_messageSuite) {
g_messageSuite->message(inst, kOfxMessageMessage, "gl-test", "gl-output-index=%d", out_index);
}
st = g_glSuite->clipFreeTexture(tex);
if (st != kOfxStatOK)
return st;
/* 真实绘制:把宿主已绑定的输出 FBO 清成已知颜色
* (0.1, 0.2, 0.3, 1.0)——宿主 glReadPixels 回读后应得到该颜色,
* 这是"插件真实走 GL 路径且输出正确"的端到端证据。 */
draw_gl_test_pattern();
return kOfxStatOK;
}
static OfxStatus mainEntry(const char *action, const void *handle, OfxPropertySetHandle inArgs,
OfxPropertySetHandle outArgs)
{
if (strcmp(action, kOfxActionDescribe) == 0 ||
strcmp(action, kOfxImageEffectActionDescribeInContext) == 0) {
return actionDescribe(handle, 0);
}
if (strcmp(action, kOfxActionCreateInstance) == 0) {
/* 经 message suite 发一条(facade 处理器断言用)。 */
if (g_messageSuite) {
g_messageSuite->message(handle, kOfxMessageMessage, "test-plugin",
"created id=%d", 7);
}
return kOfxStatOK;
}
if (strcmp(action, kOfxActionDestroyInstance) == 0 ||
strcmp(action, kOfxActionLoad) == 0 ||
strcmp(action, kOfxActionUnload) == 0 ||
strcmp(action, kOfxImageEffectActionBeginSequenceRender) == 0 ||
strcmp(action, kOfxImageEffectActionEndSequenceRender) == 0 ||
strcmp(action, kOfxImageEffectActionIsIdentity) == 0) {
return kOfxStatOK;
}
if (strcmp(action, kOfxImageEffectActionGetClipPreferences) == 0) {
return actionGetClipPreferences(outArgs);
}
if (strcmp(action, kOfxImageEffectActionGetRegionOfDefinition) == 0) {
return actionGetRoD(outArgs);
}
if (strcmp(action, kOfxImageEffectActionGetRegionsOfInterest) == 0) {
return actionGetRoI((OfxImageEffectHandle)handle, inArgs, outArgs);
}
if (strcmp(action, kOfxImageEffectActionGetOutputColourspace) == 0) {
return actionGetOutputColourspace(inArgs, outArgs);
}
if (strcmp(action, kOfxImageEffectActionRender) == 0) {
return actionRender((OfxImageEffectHandle)handle, inArgs);
}
if (strcmp(action, kOfxActionInstanceChanged) == 0) {
/* 宿主按下 push buttonkOfxPropName == "button"、
* kOfxPropChangeReason == kOfxChangeUserEdited。只对 button
* 计数(其余参数变更静默)。 */
char *name = NULL;
g_propSuite->propGetString(inArgs, kOfxPropName, 0, &name);
if (name && strcmp(name, "button") == 0) {
record_button_instance_changed();
}
return kOfxStatOK;
}
return kOfxStatReplyDefault;
}
/* GL 变体入口:describe 带 GL 声明;render 走 GL suiteattach/
* detach 上报。 */
static OfxStatus mainEntryGL(const char *action, const void *handle,
OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs)
{
if (strcmp(action, kOfxActionDescribe) == 0 ||
strcmp(action, kOfxImageEffectActionDescribeInContext) == 0) {
return actionDescribe(handle, 1);
}
if (strcmp(action, kOfxActionCreateInstance) == 0) {
return kOfxStatOK;
}
if (strcmp(action, kOfxActionDestroyInstance) == 0 ||
strcmp(action, kOfxActionLoad) == 0 ||
strcmp(action, kOfxActionUnload) == 0 ||
strcmp(action, kOfxImageEffectActionBeginSequenceRender) == 0 ||
strcmp(action, kOfxImageEffectActionEndSequenceRender) == 0 ||
strcmp(action, kOfxImageEffectActionIsIdentity) == 0) {
return kOfxStatOK;
}
if (strcmp(action, kOfxActionOpenGLContextAttached) == 0) {
return actionGLAttached((OfxImageEffectHandle)handle);
}
if (strcmp(action, kOfxActionOpenGLContextDetached) == 0) {
return actionGLDetached((OfxImageEffectHandle)handle);
}
if (strcmp(action, kOfxImageEffectActionGetClipPreferences) == 0) {
return actionGetClipPreferences(outArgs);
}
if (strcmp(action, kOfxImageEffectActionGetRegionOfDefinition) == 0) {
return actionGetRoD(outArgs);
}
if (strcmp(action, kOfxImageEffectActionGetRegionsOfInterest) == 0) {
return actionGetRoI((OfxImageEffectHandle)handle, inArgs, outArgs);
}
if (strcmp(action, kOfxImageEffectActionGetOutputColourspace) == 0) {
return actionGetOutputColourspace(inArgs, outArgs);
}
if (strcmp(action, kOfxImageEffectActionRender) == 0) {
return actionRenderGL((OfxImageEffectHandle)handle, inArgs);
}
return kOfxStatReplyDefault;
}
/* 恒透传变体入口:isIdentity 返回 Source;其余同 CPU 插件。 */
static OfxStatus mainEntryID(const char *action, const void *handle,
OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs)
{
if (strcmp(action, kOfxActionDescribe) == 0 ||
strcmp(action, kOfxImageEffectActionDescribeInContext) == 0) {
return actionDescribe(handle, 0);
}
if (strcmp(action, kOfxImageEffectActionIsIdentity) == 0) {
return actionIsIdentity(inArgs, outArgs);
}
if (strcmp(action, kOfxImageEffectActionGetOutputColourspace) == 0) {
return actionGetOutputColourspace(inArgs, outArgs);
}
return mainEntry(action, handle, inArgs, outArgs);
}
/* ---------- interact 变体(M11 §5):自定义 UI 宿主验证 ---------- */
/* interact 调用记录:追加到 OAK_TEST_PLUGIN_INTERACT_MARKER 指向的文件
* (宿主测试断言用;未设环境变量时静默)。 */
static void interact_record(const char *fmt, ...)
{
const char *marker = marker_env("OAK_TEST_PLUGIN_INTERACT_MARKER");
if (!marker)
return;
FILE *f = fopen(marker, "a");
if (!f)
return;
va_list ap;
va_start(ap, fmt);
vfprintf(f, fmt, ap);
va_end(ap);
fclose(f);
}
/* 记录 pen 事件的真实参数(inArgs 的 canonical/视口/压力属性)。 */
static void record_pen(const char *action, OfxPropertySetHandle inArgs)
{
int vp[2] = { 0, 0 };
double canon[2] = { 0.0, 0.0 };
double pressure = 0.0;
g_propSuite->propGetIntN(inArgs, kOfxInteractPropPenViewportPosition, 2, vp);
g_propSuite->propGetDoubleN(inArgs, kOfxInteractPropPenPosition, 2, canon);
g_propSuite->propGetDouble(inArgs, kOfxInteractPropPenPressure, 0, &pressure);
interact_record("%s vp=%d,%d canon=%g,%g pressure=%g\n", action, vp[0], vp[1], canon[0],
canon[1], pressure);
}
/* 记录 key 事件的真实参数(keySym/keyString)。 */
static void record_key(const char *action, OfxPropertySetHandle inArgs)
{
int sym = 0;
char *s = NULL;
g_propSuite->propGetInt(inArgs, kOfxPropKeySym, 0, &sym);
g_propSuite->propGetString(inArgs, kOfxPropKeyString, 0, &s);
interact_record("%s sym=%d str=%s\n", action, sym, s ? s : "");
}
/* kOfxInteractActionDraw:读 inArgsviewport/pixelScale/time/
* backgroundImage/drawContext),经 Draw suite setColour + 原生 GL 画
* 已知色块(矩形 10..30 canonical)。macOS 上真实绘制;非 macOS 跳过
* GL。返回 OK。 */
static OfxStatus actionInteractDraw(OfxPropertySetHandle inArgs)
{
double vp[2] = { 0.0, 0.0 };
double scale[2] = { 1.0, 1.0 };
double t = 0.0;
void *bg = NULL;
void *drawCtx = NULL;
g_propSuite->propGetDoubleN(inArgs, kOfxInteractPropViewportSize, 2, vp);
g_propSuite->propGetDoubleN(inArgs, kOfxInteractPropPixelScale, 2, scale);
g_propSuite->propGetDouble(inArgs, kOfxPropTime, 0, &t);
g_propSuite->propGetPointer(inArgs, kOfxInteractPropBackgroundImage, 0, &bg);
g_propSuite->propGetPointer(inArgs, kOfxInteractPropDrawContext, 0, &drawCtx);
/* Draw suite 状态真实读写:setColour 写、getColour 读(宿主色板)。 */
double setcol[4] = { 0.0, 0.0, 0.0, 0.0 };
double getcol[4] = { 0.0, 0.0, 0.0, 0.0 };
int setcol_st = kOfxStatFailed, getcol_st = kOfxStatFailed;
if (g_drawSuite && drawCtx) {
OfxRGBAColourF col = { 0.9f, 0.1f, 0.2f, 1.0f };
setcol_st = g_drawSuite->setColour(drawCtx, &col);
setcol[0] = col.r;
setcol[1] = col.g;
setcol[2] = col.b;
setcol[3] = col.a;
OfxRGBAColourF bgc = { 0.0f, 0.0f, 0.0f, 0.0f };
getcol_st = g_drawSuite->getColour(drawCtx, kOfxStandardColourOverlayBackground, &bgc);
getcol[0] = bgc.r;
getcol[1] = bgc.g;
getcol[2] = bgc.b;
getcol[3] = bgc.a;
}
int draw_st = kOfxStatFailed;
#ifdef __APPLE__
/* 清成暗背景 + Draw suite 画实心矩形(canonical 坐标;宿主经正交
* 投影映射到视口)。 */
glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
if (g_drawSuite && drawCtx) {
OfxPointD pts[2] = { { 10.0, 10.0 }, { 30.0, 30.0 } };
draw_st = g_drawSuite->draw(drawCtx, kOfxDrawPrimitiveRectangle, pts, 2);
}
#else
(void)0;
#endif
interact_record("draw vp=%gx%g scale=%gx%g t=%g bg=%p setcol_st=%d setcol=%g,%g,%g,%g "
"getcol_st=%d getcol=%g,%g,%g,%g draw_st=%d\n",
vp[0], vp[1], scale[0], scale[1], t, bg, setcol_st, setcol[0], setcol[1],
setcol[2], setcol[3], getcol_st, getcol[0], getcol[1], getcol[2], getcol[3],
draw_st);
return kOfxStatOK;
}
/* interact 入口(经 kOfxImageEffectPluginPropOverlayInteractV2 声明):
* 只处理 interact 的 action;效果侧 action 一律 ReplyDefault(不会被
* 宿主以效果 handle 调到这里)。 */
static OfxStatus mainEntryInteract(const char *action, const void *handle,
OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs)
{
(void)handle;
(void)outArgs;
if (strcmp(action, kOfxActionNewInteract) == 0) {
interact_record("new_interact\n");
return kOfxStatOK;
}
if (strcmp(action, kOfxActionDescribe) == 0) {
interact_record("describe\n");
return kOfxStatOK;
}
if (strcmp(action, kOfxActionCreateInstance) == 0) {
interact_record("create\n");
return kOfxStatOK;
}
if (strcmp(action, kOfxActionDestroyInstance) == 0) {
interact_record("destroy\n");
return kOfxStatOK;
}
if (strcmp(action, kOfxInteractActionDraw) == 0) {
return actionInteractDraw(inArgs);
}
if (strcmp(action, kOfxInteractActionPenMotion) == 0) {
record_pen("pen_motion", inArgs);
return kOfxStatOK;
}
if (strcmp(action, kOfxInteractActionPenDown) == 0) {
record_pen("pen_down", inArgs);
return kOfxStatOK;
}
if (strcmp(action, kOfxInteractActionPenUp) == 0) {
record_pen("pen_up", inArgs);
return kOfxStatOK;
}
if (strcmp(action, kOfxInteractActionKeyDown) == 0) {
int sym = 0;
g_propSuite->propGetInt(inArgs, kOfxPropKeySym, 0, &sym);
record_key("key_down", inArgs);
/* Escape → ReplyDefault(宿主应透传该状态:插件未处理,事件
* 可交视图中的其他对象)。 */
if (sym == kOfxKey_Escape)
return kOfxStatReplyDefault;
return kOfxStatOK;
}
if (strcmp(action, kOfxInteractActionKeyUp) == 0) {
record_key("key_up", inArgs);
return kOfxStatOK;
}
if (strcmp(action, kOfxInteractActionIdle) == 0) {
interact_record("idle\n");
return kOfxStatOK;
}
/* GainFocus/LoseFocus 等:默认(ReplyDefault = 未处理,宿主可继续)。 */
return kOfxStatReplyDefault;
}
/* interact 变体的效果侧 main entrydescribe 建参数/clip 并声明 overlay
* interact V2 入口(指向 mainEntryInteract);其余效果 action 委托 base
* mainEntry。 */
static OfxStatus mainEntryInteractEffect(const char *action, const void *handle,
OfxPropertySetHandle inArgs, OfxPropertySetHandle outArgs)
{
if (strcmp(action, kOfxActionDescribe) == 0 ||
strcmp(action, kOfxImageEffectActionDescribeInContext) == 0) {
OfxStatus st = actionDescribe(handle, 0);
if (st != kOfxStatOK)
return st;
return propSetPointer(handle, kOfxImageEffectPluginPropOverlayInteractV2, 0,
(void *)mainEntryInteract);
}
return mainEntry(action, handle, inArgs, outArgs);
}
/* ---------- 导出 ---------- */
static const OfxPlugin test_plugin = {
/* pluginApi */ kOfxImageEffectPluginApi,
/* apiVersion */ kOfxImageEffectPluginApiVersion,
/* pluginIdentifier */ "org.oak.test-plugin",
/* pluginVersionMajor */ 1,
/* pluginVersionMinor */ 0,
/* setHost */ setHost,
/* mainEntry */ mainEntry,
};
static const OfxPlugin test_plugin_gl = {
/* pluginApi */ kOfxImageEffectPluginApi,
/* apiVersion */ kOfxImageEffectPluginApiVersion,
/* pluginIdentifier */ "org.oak.test-plugin.gl",
/* pluginVersionMajor */ 1,
/* pluginVersionMinor */ 0,
/* setHost */ setHost,
/* mainEntry */ mainEntryGL,
};
static const OfxPlugin test_plugin_id = {
/* pluginApi */ kOfxImageEffectPluginApi,
/* apiVersion */ kOfxImageEffectPluginApiVersion,
/* pluginIdentifier */ "org.oak.test-plugin.identity",
/* pluginVersionMajor */ 1,
/* pluginVersionMinor */ 0,
/* setHost */ setHost,
/* mainEntry */ mainEntryID,
};
static const OfxPlugin test_plugin_interact = {
/* pluginApi */ kOfxImageEffectPluginApi,
/* apiVersion */ kOfxImageEffectPluginApiVersion,
/* pluginIdentifier */ "org.oak.test-plugin.interact",
/* pluginVersionMajor */ 1,
/* pluginVersionMinor */ 0,
/* setHost */ setHost,
/* mainEntry */ mainEntryInteractEffect,
};
OfxExport int OfxGetNumberOfPlugins(void)
{
return 4;
}
OfxExport OfxPlugin *OfxGetPlugin(int nth)
{
if (nth == 0)
return (OfxPlugin *)&test_plugin;
if (nth == 1)
return (OfxPlugin *)&test_plugin_gl;
if (nth == 2)
return (OfxPlugin *)&test_plugin_id;
if (nth == 3)
return (OfxPlugin *)&test_plugin_interact;
return NULL;
}