🔃

【DaVinci Resolve】 ポインタ型と数値型の相互変換 【Fusion】

2025/03/07に公開

🐥はじめに

みなさん、こんにちは。Mugです🐼
これは📽DaVinci ResolveのFusionについての記事です

Fusion ではSetData/GetDataというAPIがあり、これを使用すると
Tool(Node)間で任意データをやり取りすることができます👍
しかし、SetData/GetDataには制限があり、Number,Point,string,tableのような
基本型しか使用することができません😭

Fusionで使われているLuaではC言語の関数を呼び出すことができ
その結果をポインタで受け取ることがあります
それを前述のSetData/GetDataでどうにか取り回すことができないかな?
と言うのが今回のお話です😤

すごーくニッチ🤏な内容です

🏫 SetData/GetData

まずは SetData/GetData の使い方からです☝️
Fusion:SetDataでデータを保存し、Fusion:GetDataで保存したデータを読み取ります
FusionとはFusion objectを表していてselfcompに置き換えて使用します

SetData

任意の値(Number, Point, table)を共有データとして保存します

引数:

  1. key(string)
  2. value(Number, Point, table)
self:SetData("key", value)

GetData

SetDataで保存された値を取得します
戻り値として対応するデータが返ってきます

引数:

  1. key(string)、SetDataで指定した文字列
local value = self:GetData("key")

Samples

Sample 1

この例では Rectangle1 のStart Render Scriptsで、self(Rectangle1)へ
"zoom", "angle"という数値データを保存(SetData)しています
それを Transform1 で取得(GetData)し、Size,Angleの値として設定しています 😤

sample1
sample 1

Sample 2

この例ではcompオブジェクトに対して文字列データの保存・取得を行っています 😤

sample2
sample 2

📚 FFI Library

DaVinci Resolve で使用されている Lua の runtime はLua JITです
この Lua JIT では FFI Libraryというものが使用でき
これによりC言語で作成されたライブラリを使用することができます

Load FFI Library

FFI Libraryはデフォルトでは読み込まれません
FFI Libraryには、まずrequireを用いて明示的にLibraryを読み込む必要があります

local ffi = require("ffi")

型定義

C言語ライブラリを使用するにはexportされた関数のプロトタイプ宣言, 型定義を行う必要があり
そのためにffi.cdefを使用します
[[]]の間にC言語の書式で記述します

以下公式のチュートリアルから抜粋

ffi.cdef[[
void Sleep(int ms);
int poll(struct pollfd *fds, unsigned long nfds, int timeout);
]]

実行

関数宣言までできればあとは通常のLua関数と同じように呼び出せます
以下は公式のチュートリアルから抜粋です

local sleep
if ffi.os == "Windows" then
  function sleep(s)
    ffi.C.Sleep(s*1000)
  end
else
  function sleep(s)
    ffi.C.poll(nil, 0, s*1000)
  end
end

for i=1, 10 do
  print("count: " .. i)
  sleep(0.5)
end

まとめ

スクリプト全文を記載します
👇こちらをDaVinci Resolveのconsoleに貼り付ければ動きを確認することができます😊✌️

FFIチュートリアルスクリプト全文
local ffi = require("ffi")

ffi.cdef[[
void Sleep(int ms);
int poll(struct pollfd *fds, unsigned long nfds, int timeout);
]]

local sleep
if ffi.os == "Windows" then
  function sleep(s)
    ffi.C.Sleep(s*1000)
  end
else
  function sleep(s)
    ffi.C.poll(nil, 0, s*1000)
  end
end

for i=1, 10 do
  print("count: " .. i)
  sleep(0.5)
end

🔃 ポインタ型/数値型 相互変換

さて、ここからが本題です🐼

下記はmallocしたメモリに書き込み、それをdumpするサンプルです

local ffi = require("ffi")

ffi.cdef [[
void* malloc(size_t size);
void free(void *p);
]]

local size = 10
local mallocAddress = ffi.C.malloc(size)
local luaNativeData = ffi.cast("unsigned char*", mallocAddress)

dump(mallocAddress)
dump(luaNativeData)

local offset = 100
for i = 0, (size - 1) do
    luaNativeData[i] = i + 100
end

for i = 0, (size - 1) do
    print("[" .. i .. "]: " .. luaNativeData[i])
end

ffi.C.free(mallocAddress)

実行すると👇下記のように出力されます

cdata<void *>: 0x024bb5317100
cdata<unsigned char *>: 0x024bb5317100
[0]: 100
[1]: 101
[2]: 102
[3]: 103
[4]: 104
[5]: 105
[6]: 106
[7]: 107
[8]: 108
[9]: 109

ポインタ(cdata型)を直接SetDataする

では次に、別のtoolから使えるようにluaNativeDataSetDataしてみます
すると...crashします😭😭😭

-- freeをcomp:SetDataに置き換え
-- ffi.C.free(mallocAddress)
comp:SetData("native data", luaNativeData)

ポインタを数値型にcastする?

ポインタを直接SetDataすると異常終了するので
数値型にcastすればよいのでは?🤔
と思うかもしれません

しかし、現代の💻PCではポインタは64bit型に格納する必要があり
これもまたSetDataで保存することができません

👇を実行すると

local ffi = require("ffi")

ffi.cdef [[
void* malloc(size_t size);
void free(void *p);
]]

local size = 10
local mallocAddress = ffi.C.malloc(size)
local luaNativeData = ffi.cast("unsigned char*", mallocAddress)
local uint64Cast = ffi.cast("uint64_t", mallocAddress)

dump(mallocAddress)
dump(luaNativeData)
dump(uint64Cast)
print(string.format("uint64Cast hex: 0x%x", uint64Cast))

-- 以下SetDataのコメントアウトを外すとcrashします
-- comp:SetData("native data", uint64Cast)

ffi.C.free(mallocAddress)

👇下記のように出力されます
mallocAddressuint64Castが同じ値を指していることと
uint64CastのdumpにULL(uint64_t型)とついていることがポイントです

cdata<void *>: 0x021fc769aba0
cdata<unsigned char *>: 0x021fc769aba0
2335512832928ULL
uint64Cast hex: 0x21fc769aba0

uint32_tにcastしてみる

では何ならSetDataできるのかと言うと
公式ページに書かれている変換表でoutputがnumberになる型です
今回はuint32_tを使います

local ffi = require("ffi")

ffi.cdef [[
void* malloc(size_t size);
void free(void *p);
]]

local size = 10
local mallocAddress = ffi.C.malloc(size)
local uint32Cast = ffi.cast("uint32_t", mallocAddress)

dump(mallocAddress)
dump(uint32Cast)

-- 以下SetDataのコメントアウトを外すとcrashします
-- comp:SetData("native data", uint32Cast)

ffi.C.free(mallocAddress)

これを実行した結果が👇下記です
残念ですが、まだcdata型なのでSetDataできません😭

cdata<void *>: 0x021fc769da40
cdata<unsigned char *>: 0x021fc769da40
cdata<unsigned int>: 0x021f3d256848

cdata<uint32_t>を数値型へ変換する

ではこのcdata<unsigned int>を数値型に変換してみましょう

local ffi = require("ffi")

ffi.cdef [[
void* malloc(size_t size);
void free(void *p);
]]

local size = 10
local mallocAddress = ffi.C.malloc(size)
local uint32Cast = ffi.cast("uint32_t", mallocAddress)

dump(mallocAddress)

-- tonumberで数値型に変換する
local numberUint32 = tonumber(uint32Cast)
dump(numberUint32)
print(string.format("numberUint32 hex: 0x%x", numberUint32))

ffi.C.free(mallocAddress)

👇数値になりました!!😋

cdata<void *>: 0x021fc769dbe0
3345603552
numberUint32 hex: 0xc769dbe0

しかし❗
アドレスが一致しません😭
これは ポインタ(アドレス)を32bitで保存している ため、
上位bitが落ちてしまっているためです😖

共用体

単純にuint32_tへcastしてしまうとbit数が足りないため正しくアドレスが残りません
そこで、 共用体 の出番です😤

共用体を使って2つのuint32_tへ分割してアドレスを保存します

local ffi = require("ffi")

-- i64構造体とwrapper共用体を定義
ffi.cdef [[
typedef struct split_u64{unsigned int l; unsigned int h;} split_u64_t;
typedef union wrapper{ split_u64_t i; unsigned char* p; } wrapper_t;

void* malloc(size_t size);
void free(void *p);
]]

local size = 10
local mallocAddress = ffi.C.malloc(size)
dump(mallocAddress)

-- 共用体のpにアドレスをいれることで
-- iから32bitに分割された数値として取得できる
local wrapper = ffi.new("wrapper_t")
wrapper.p = mallocAddress

local highAddress = tonumber(wrapper.i.h)
local lowAddress = tonumber(wrapper.i.l)
print(string.format("wrapper hex: high=0x%x, low=0x%08x", highAddress, lowAddress))

ffi.C.free(mallocAddress)

👇ついにできました!!😊✌️

cdata<void *>: 0x0222e8484b40
numberUint32 hex: high=0x222, low=0xe8484b40

まとめ

ついにアドレスを数値に変換することができました🎉
ではSetData/GetDataしてみます🫡

local ffi = require("ffi")

ffi.cdef [[
typedef struct split_u64{uint32_t l; uint32_t h;} split_u64_t;
typedef union wrapper{ split_u64_t i; uint8_t* p; } wrapper_t;
void* malloc(size_t size);
void free(void *p);
]]

local size = 10
local mallocAddress = ffi.C.malloc(size)
local luaNativeData = ffi.cast("unsigned char*", mallocAddress)

dump(mallocAddress)

local offset = 100
for i = 0, (size - 1) do
    luaNativeData[i] = i + 100
end

local wrapper = ffi.new("wrapper_t")
wrapper.p = mallocAddress

-- table(配列)で2つに分割されたアドレスを保存
comp:SetData("native pointer", {wrapper.i.l, wrapper.i.h})

-- table(配列)からアドレスを復元
local addressTable = comp:GetData("native pointer")
local decodeWrapper = ffi.new("wrapper_t")
decodeWrapper.i.l = addressTable[1]
decodeWrapper.i.h = addressTable[2]

dump(decodeWrapper.p)

for i = 0, (size - 1) do
    print("[" .. i .. "]: " .. decodeWrapper.p[i])
end

ffi.C.free(decodeWrapper.p)

👇かんぺきです😋

cdata<void *>: 0x0288afc83830
cdata<unsigned char *>: 0x0288afc83830
[0]: 100
[1]: 101
[2]: 102
[3]: 103
[4]: 104
[5]: 105
[6]: 106
[7]: 107
[8]: 108
[9]: 109

🐔おわりに

これでTool(node)間のCライブラリとのやり取りが楽になるかもしれませんが
有効的な活用方法は謎です🤔🤔🤔😭

GitHubで編集を提案

Discussion