Luz: Plugin Architecture Update

Back again with another update on my experimental renderer Luz, which I’ve made some drastic changes to over the last couple of months. This change in design came from the idea of turning Luz into a stand-alone viewport application in which plugins can be loaded to write to the Luz viewport using various CPU or GPU based rendering approaches.

Plugin Demo: Vulkan Triangles

Here is a showcase of the first successfully developed plugin using Vulkan for Luz. When it comes to prototyping, it’s back to the holy triangle. Nothing more, nothing less.

Instead of focusing on raytracing, Luz now supports a wide range of plugins to be developed that can be viewed and analyzed within its viewport. Plugins are loaded into Luz at runtime and can draw into the framebuffer allocated by Luz. With this “black box” approach for viewing the final image of a render call, window handling can be reused across different plugins and the developer (me) can fully focus on developing the 3D-techniques without having to reinvent the wheel by making new window handling systems for each project.

Plugin Architecture

A plugin is a dynamic-link library underneath the surface and to make this work in practice, both Luz and the plugin shares the same interface. Only two functions are necessary at the moment for the plugin model: “GetInterface” and “FreeInterface”. Since plugins might have different usages, I’ve allowed for there to be different interfaces in case the standard protocol lacks the necessary flexibility.


#ifndef TRIANGLES_INTERFACE_H
#define TRIANGLES_INTERFACE_H

#include "Interfaces/VulkanPluginInterface.h"

extern "C" 
{
    bool GetPluginInterface(VulkanPluginInterface** pInterface);
    typedef bool(*GETINTERFACE)(VulkanPluginInterface** pInterface);

    bool FreePluginInterface(VulkanPluginInterface** pInterface);
    typedef bool(*FREEINTERFACE)(VulkanPluginInterface** pInterface);
}

#endif

One important detail to mention here is that both the plugin and Luz needs to use the same CRT in order to share the same heap, in case they require knowledge of allocated resources on each side of the boundary. If they don’t match, you can end up having some heap validation errors that don’t make a whole lot of sense, simply because the addresses acquired won’t match. Another rule I have enforced is that neither the plugin or the application allocates dynamic memory within each others domain. Once the interface has been established between the plugin and Luz, we can access the following functions below:


#ifndef PLUGIN_INTERFACES_I_VULKAN_PLUGIN_H
#define PLUGIN_INTERFACES_I_VULKAN_PLUGIN_H

struct VulkanPluginInterface
{
	virtual bool initialize() = 0;
	virtual bool release() = 0;
	virtual bool execute() = 0;
	virtual bool reset() = 0;

	virtual bool getImageData(void* pBuffer, const unsigned int width, const unsigned int height) = 0;

	virtual bool sendData(const char* label, void* data) = 0;

	virtual float getLastRenderTime() = 0;
	
	virtual const char* getLastErrorMsg() = 0;

	virtual const char* getPluginName() = 0;
};

#endif

User Interface

Each plugin is also accompanied by a JSON based .ui file where UI elements can be specified. Input fields, scalars, checkboxes, headers and many more ImGui elements can be specified here to customize the controls exposed by the plugin to modify its behavior. Each interaction with the UI elements in Luz is then sent back to the plugin using events and these are mapped against the contents of the .ui file so the plugin will always knows which UI elements received updates.

{
  "Document" : {
    "Description" : "Vulkan Plugin Configuration"
  },
  "Framebuffer": {
	"Resolutions": "800x600,1024x768",
	"Modes": "Interactive",
	"Hardware": "GPU",
	"Methods": "Copy",
	"SupportMultithreading": true,
	"DataFormat": "Byte"
  },
  "Menu" : {
	"Elements": [
	  { "Type": "BeginHeader", "Title": "Pipeline" },
		{ "Type": "FloatScalar", "Title": "Rotation Factor" },
	    { "Type": "Tick", "Title": "Backface Culling", "Reset": true },
	    { "Type": "Dropdown", "Title": "Polygon Mode", "Args": "Fill,Line", "Reset": true },
	  { "Type": "EndHeader" }
	]
  }
}

Hope you’ve enjoyed this demo, I’ll be back again in the near future with more demos and features added to Luz 🙂

Leave a comment