Vulkan Subsystem
Subsystem nos.sys.vulkan
features an API for plugins to communicate with vulkan & send commands to the GPU.
Minimal example
For a plugin or a subsystem, to define a dependency to nosVulkan, add the nos.sys.vulkan
dependency in the noscfg file as shown in Test.noscfg
. Then in the file that contains the NOS_INIT
macro, include <nosVulkanSubsystem/nosVulkanSubsystem.h>
for nos.sys.vulkan
API, then in the nosExportNodeFunctions
function request the Vulkan subsystem using nosEngine.RequestSubsystem
function. Then nosVulkan
global variable can be used anywhere to communicate with the nos.sys.vulkan
API. <nosVulkanSubsystem/Helpers.hpp>
contains helper functions and <nosVulkanSubsystem/Types_generated.h>
contains flatbuffers headers for Texture.
{
"info": {
"id": {
"name": "nos.test",
"version": "1.0.0"
},
"display_name": "Test",
"description": "Test plugin.",
"dependencies": [
{
"name": "nos.sys.vulkan",
"version": "1.0.0"
}
]
},
"node_definitions":...
}
#include <Nodos/PluginAPI.h>
#include <nosVulkanSubsystem/nosVulkanSubsystem.h>
#include <nosVulkanSubsystem/Helpers.hpp>
NOS_INIT();
nosVulkanSubsystem* `nos.sys.vulkan` = nullptr;//`nos.sys.vulkan` is a variable that is declared as extern in nosVulkanSubsystem.h
extern "C"
{
NOSAPI_ATTR nosResult NOSAPI_CALL nosExportNodeFunctions(size_t* outCount, nosNodeFunctions** outFunctions)
{
*outCount = (size_t)(1);
if (!outFunctions)
return NOS_RESULT_SUCCESS;
auto ret = nosEngine.RequestSubsystem(NOS_NAME_STATIC(NOS_VULKAN_SUBSYSTEM_NAME), 1, 0, (void**)&nosVulkan);
//System might not have nosVulkanSubsystem with the requested version, so be sure to check for it.
if (ret != NOS_RESULT_SUCCESS)
return ret;
//Register nodes etc...
outFunctions[0]->ClassName = NOS_NAME_STATIC("nos.test.CopyTestLicensed");
outFunctions[0]->ExecuteNode = [](void* ctx, const nosNodeExecuteArgs* args)
{
nosCmd cmd;
nosVulkan->Begin("(nos.test.CopyTest) Copy", &cmd);
auto values = nos::GetPinValues(args);
nosResourceShareInfo input = nos::vkss::DeserializeTextureInfo(values[NOS_NAME_STATIC("Input")]);
nosResourceShareInfo output = nos::vkss::DeserializeTextureInfo(values[NOS_NAME_STATIC("Output")]);
nosVulkan->Copy(cmd, &input, &output, 0);
nosVulkan->End(cmd, NOS_FALSE);
return NOS_RESULT_SUCCESS;
};
return NOS_RESULT_SUCCESS;
}
}
Warning
Don't forget to check for the availability of the subsystem. Since the subsystem may not be available.
Shaders
Compiling & Registering
There are 2 ways of compiling and registering a shader. One is by defining it in a Shader Only Node and another is by registering it in registerXXX()
calls.
To be able to execute shaders, a related ShaderPass
should be created. Compute shaders should create ComputePass
, fragment and vertex shader should create RenderPass
. A RenderPass
has to have a fragment shader but vertex shader is optional (if not specified, full quad vertex shader is used).
Shader Only Nodes
To add a shader only node(such as Color Correct
), add the node to plugin's noscfg and create a nosdef for the node. In the nosdef, add pins and other fields as if the node is a regular node. Then, in the node definition, fill the "contents
field as shown in the example. The path given in shader
field is relative to the plugin's noscfg file. If the file extension doesn't end with .spv
, nos.sys.vulkan
tries to compile the given file using glslc and dxc, whichever compiles. Shader only nodes do not need a .dll file and the plugin doesn't need to export their node functions.
{
"nodes": [
{
"class_name": "ColorCorrect",
"contents_type": "Job",
"contents": {
"type": "nos.sys.vulkan.GPUNode",
"options": {
"shader": "../Shaders/ColorCorrect.hlsl",
"stage": "FRAGMENT"
}
},
"pins": ...
}
]
}
Registering a shader or a GPU pass
To compile a shader, caller should provide either human-readable (either HLSL or GLSL) source file path, human-readable source file text, SPIR-V blob file path or SPIR-V blob data to ShaderInfo2.Source
.
To create a pass, a globally unique PassName
and shaders should be provided. To create a RenderPass, fragment shader should be passed to nosPassInfo.Shader
and vertex shader (optional) should be passed to nosPassInfo.VertexShader
.
Types
nosResourceType
Refer to nosResourceShareInfo.nosResourceInfo
struct nosResourceInfo
{
nosResourceType Type;
union {
nosTextureInfo Texture;
nosBufferInfo Buffer;
};
};
Type
must be set to adequate nosResourceType
based on which resource info is used.
Functions
Most operations in nos.sys.vulkan
API uses a nosCmd
struct to record commands and most calls are not synchronized between CPU and GPU.
Begin
nosResult Begin(const char* name, nosCmd* outCmd)
Parameters:
name
: Debug name for the commands that will be recorded using this cmd.
outCmd
: Filled with the handle for a command buffer that can be used with other calls.
End
nosResult End(nosCmd cmd, nosBool forceSubmit);
Marks the end of command buffer's use.
Parameters:
forceSubmit
: Submits the command buffer to the GPU. Recorded commands are not executed until the command buffer is submitted to the GPU, regular nodes in the node path can get away with not submitting the command buffer. Nodes that communicate between the CPU and the GPU or other graphics API's needs to submit the command buffer in appopriate points. Mind that submitting the command buffer has a significant performance hit. This does not wait for the GPU, refer to End2. Command buffers used outside of the Scheduler thread are submitted even if this parameter is false.
End2
nosResult End2(nosCmd cmd, nosBool forceSubmit, nosGPUEvent* outEventHandle);
Marks the end of command buffer's use.
Parameters:
forceSubmit
: Submits the command buffer to the GPU. Command buffers used outside of the Scheduler thread are submitted even if this parameter is false. Refer to End for more detailed information.
outEventHandle
: If not null, fills the value with an event that will be signalled when the GPU completes execution of the commands in the command buffer. Mind that this does not submits the command buffer to GPU unless forceSubmit
is true
, waiting it before the command buffer is submitted will result in a timeout or deadlock. Returned event must be waited at some point using WaitGpuEvent, unless the event results in a leak.
nosResult Copy(nosCmd, const nosResourceShareInfo* src, const nosResourceShareInfo* dst, const char* benchmark);
nosResult RunPass(nosCmd, const nosRunPassParams* params);
nosResult RunPass2(nosCmd, const nosRunPass2Params* params);
nosResult RunComputePass(nosCmd, const nosRunComputePassParams* params);
nosResult Clear(nosCmd, const nosResourceShareInfo* texture, nosVec4 color);
nosResult Download(nosCmd, const nosResourceShareInfo* texture, nosResourceShareInfo* outBuffer);
nosResult ImageLoad(nosCmd, const void* buf, nosVec2u extent, nosFormat format, nosResourceShareInfo* inOut);
nosResult CreateResource(nosResourceShareInfo* inout);
nosResult ImportResource(nosResourceShareInfo* inout);
nosResult DestroyResource(const nosResourceShareInfo* resource);
nosResult ReloadShaders(nosName nodeName);
uint8_t* Map(const nosResourceShareInfo* buffer);
nosResult GetColorTexture(nosVec4 color, nosResourceShareInfo* out);
nosResult GetStockTexture(nosResourceShareInfo* out);
nosResult CreateSemaphore(uint64_t pid, uint64_t externalOSHandle, nosSemaphore* outSemaphore);
nosResult DestroySemaphore(nosSemaphore semaphore);
nosResult AddSignalSemaphoreToCmd(nosCmd cmd, nosSemaphore semaphore, uint64_t signalValue);
nosResult AddWaitSemaphoreToCmd(nosCmd cmd, nosSemaphore semaphore, uint64_t waitValue);
nosResult SignalSemaphore(nosSemaphore semaphore, uint64_t value);
nosResult RegisterShaders(size_t count, nosShaderInfo* shaders);
nosResult RegisterPasses(size_t count, nosPassInfo* passInfos);
WaitGpuEvent
nosResult WaitGpuEvent(nosGPUEvent* eventHandle, uint64_t timeoutNs);
Waits for the gpu to complete the work until the given event or timeout, then deletes the event.
Parameters:
eventHandle
: Event handle to wait, will be set to null afterwards.
timeoutNs
: Timeout in nanoseconds, pass UINT64_T for max and 0 for deleting the event without any wait.