Using Haxe Lua to generate tools for BlackMagic Fusion/Davinci Resolve?

Hi, Justin D and others!

I’m experimenting with using Haxe for generating tools in BlackMagic Fusion (grafix FX software, also included in Davinci Resolve video editing sw.). What I want to accomplish is generating script code that programmatically can build complex vector graphics that could be mainpulated over time using Fusion’s animation capabilites, and rendering video output in the end.

Fusion internally uses lua for it’s tools (python is also an option but more of a workaround it seems), and there is two ways that I can accomplish working lua code:

  1. Using Haxe for simple generation of custom .lua-compatible code-files.
    I’ve tried this, and it works fine, as I just use Haxe logic for building a custom text file where I generate the .lua content. That means that the Haxe Lua target isn’t involved at all, I’m running in simple --interp mode. Quick, hacky and limited to my own “meta-programming”.

  2. Using actiual Haxe Lua target to generate the .lua code
    (This is what I’m interested of here!) This means that I would mock the libraries included in the BlackMagic Fusion Lua environment, so that I could wire up the functionality in actual Haxe code, and compile it to Haxe generated Lua code that would run in Fusion.
    Below is a minimal Fusion .lua code example showing the type of code that I would like to get as a result.
    It clearly shows that a lot of functionality is set to the runtime “self”, like the FuRegisterClass method, the Width and Height properties etc.

Is it doable to mock these, so that something like the code example below can be generated?

Jonas

----------------------------------------------------
-- BlackMagick Fusion/Davinci Resolve
-- Minimal test example for generating simple line graphic
-- .lua code, but named .fuse when used as tool in Fusion
----------------------------------------------------

FuRegisterClass("Minimal", CT_SourceTool, {
	REGS_Name = "Minimal",
	REGS_Category = "TestFuses",
})	

function Create()
	OutImage = self:AddOutput("Output", "Output", {
		LINKID_DataType = "Image",
		LINK_Main = 1,
		})
end

function Process(req)
	-- Standard set up for Creator tools
	local realwidth = Width;
	local realheight = Height;
	
	-- We'll handle proxy ourselves
	Width = Width / Scale
	Height = Height / Scale
	Scale = 1
	
	-- Attributes for new images
	local imgattrs = {
		IMG_Document = self.Comp,
		{ IMG_Channel = "Red", },
		{ IMG_Channel = "Green", },
		{ IMG_Channel = "Blue", },
		{ IMG_Channel = "Alpha", },
		IMG_Width = Width,
		IMG_Height = Height,
		IMG_XScale = XAspect,
		IMG_YScale = YAspect,
		IMAT_OriginalWidth = realwidth,
		IMAT_OriginalHeight = realheight,
		IMG_Quality = not req:IsQuick(),
		IMG_MotionBlurQuality = not req:IsNoMotionBlur(),
		}

	-- Set up image
	local img = Image(imgattrs)
	local out = img:CopyOf()
	local p = Pixel({R=0,G=0,B=0,A=0})
	img:Fill(p) -- Clear the image so the next frame doesn't contain the previous one.
	out:Fill(p)

	-------------------------------------------------

	local line = Shape()
	line:MoveTo(0, 0)
	line:LineTo(1, 1)
	line = line:OutlineOfShape(0.01,"OLT_Solid")	
	local ic = ImageChannel(out, 8)
	ic:ShapeFill(line)
	local cs = ChannelStyle()
	cs.Color = Pixel({R = 1, G = 0, B = 0, A = 1})
	if self.Status == "OK" then
		ic:PutToImage("CM_Merge", cs)
	end

	OutImage:Set(req, out)
	
end

With this code

class Main {
	static var self(get,never):SelfType;
	static inline function get_self():SelfType
		return untyped __lua__('self');

	@:expose('Create')
	static function create() {
		self.OutImage = self.AddOutput("Output", "Output", {
			LINKID_DataType: "Image",
			LINK_Main: 1
		});
	}

	@:expose('Process')
	static function process(req:RequestType) {
		//...
	}

	static function main() {
		self.FuRegisterClass("Minimal", CT_SourceTool, {
			REGS_Name: "Minimal",
			REGS_Category: "TestFuses",
		});
	}
}

//put real types here
extern class CT_SourceTool {}
typedef OutImageType = {};
typedef RequestType = {};
typedef SelfType = {
	var OutImage:OutImageType;
	function AddOutput(v1:String, v2:String, cfg:{LINKID_DataType:String, LINK_Main:Int}):OutImageType;
	function FuRegisterClass(str:String, cls:Class<Dynamic>, cfg:{REGS_Name:String, REGS_Category:String}):Void;
}

I get the following lua file:

Main.create = function() 
  self.OutImage = self:AddOutput("Output", "Output", _hx_o({__fields__={LINKID_DataType=true,LINK_Main=true},LINKID_DataType="Image",LINK_Main=1}));
end
_hx_exports["Create"] = Main.create
Main.process = function(req) 
end
_hx_exports["Process"] = Main.process
Main.main = function() 
  self:FuRegisterClass("Minimal", CT_SourceTool, _hx_o({__fields__={REGS_Name=true,REGS_Category=true},REGS_Name="Minimal",REGS_Category="TestFuses"}));
end
//<...>
return _hx_exports;

Idk if you can utilize _hx_exports in your case.
But as a last resort you can always use untyped __lua__ to set Create and Process functions anywhere you want.

Thank you, Alexandr!
Your suggestions give me a much bettar understanding of how to approach this.

It’s not a straightforward journey, though. The first problem is that the global “self” isn’t present when the Fusion application boots, and the FuRegisterClass should be run. So the self.FuRegisterClass approach doesn’t work.

Will reach out to the Fusion forum for ideas about what kind of global object this FuRegisterClass method is registered to.

I’m experimenting with using Haxe for generating tools in BlackMagic Fusion

Neat! Glad to see a Lua use case pop up here. These tools are often quite complex, so having compiled Haxe output here makes a lot of sense.

Using Haxe for simple generation of custom .lua-compatible code-files.
Yeah just about any language can do that. Boooooring. I think Haxe can definitely be a better mechanism.

Alexander’s snippet that he gave is a good summary of how I would go about this:

  1. Use “expose” decorators to mark the Process/Create methods.
  2. Refer to this “self” type as a static var within the Haxe Type system.

The only other question I’d have is whether this SelfType is really static or not. It seems like it might be an instance type? If so, you might be able to make this even more clean by using an abstract type definition on top of a self type instance that BlackMagic fusion gives you. However, sometimes just dealing with static methods is far simpler… Lua doesn’t really distinguish between static and instance methods anyways, so it might even feel more natural within the framework.