Write a Node With No Input#
Goal. Build an AOV out of authored values alone, with no AOV coming in.
Before you start. Types and Values, and a node that runs.
The Shape#
Declare typed attributes and an output, and no opaque inputs: port at all:
CheckerKernel.usda, from the runnable generating-node example#def Shader "CheckerKernel"
{
uniform token info:implementationSource = "sourceAsset"
uniform asset info:spg:sourceAsset = @CheckerKernel.cu@
uniform token info:spg:sourceAsset:subIdentifier = "checker"
# Every input here is a typed attribute. There is no `opaque inputs:` port,
# because this node reads no AOV: it generates its output from these values.
#
# The colours are exact at 8 bits, so the host can predict the output byte
# for byte without depending on how a float is rounded into a texture.
int inputs:width = 512
int inputs:height = 512
int inputs:squareSize = 32
float3 inputs:colorA = (1.0, 1.0, 1.0)
float3 inputs:colorB = (0.0, 0.0, 0.2)
opaque outputs:Checker
}
The shader definition is the same either language, apart from what
info:spg:sourceAsset points at.
How It Works#
A resource-input is optional; a node needs no AOV to run. Nothing else changes: the launch script is still required, the entry point is still named the same way, and the output is published like any other.
Nothing hands you a shape, so state one. A node with a resource-input can take its width and height from that input’s descriptor. This one has no input descriptor to read, so the size comes from somewhere you control, which usually means typed attributes:
CheckerKernel.cu.lua, from the runnable generating-node example#-- Launch script for a node with no resource-input.
--
-- Nothing hands this node a resource, so there is no descriptor to take a shape
-- from. The size comes from typed USD attributes instead, like every other
-- value the node uses.
function checker(inputs, outputs)
local width = inputs["width"].value
local height = inputs["height"].value
assert(width > 0 and height > 0, "width and height must be positive")
assert(inputs["squareSize"].value > 0, "squareSize must be positive")
outputs["Checker"] = cuda.image(width, height, cuda.uchar4)
return cuda.kernel({
args = {
cuda.int(width), -- -> int width
cuda.int(height), -- -> int height
cuda.int(inputs["squareSize"]), -- -> int squareSize
cuda.array(inputs["colorA"]), -- -> const float* colorA
cuda.array(inputs["colorB"]), -- -> const float* colorB
cuda.SurfaceObject(outputs["Checker"]), -- -> cudaSurfaceObject_t output
},
block = { 16, 16 },
grid = { math.ceil(width / 16), math.ceil(height / 16) },
})
end
CheckerKernel.slang.lua, from the runnable generating-node example#-- Launch script for a node with no resource-input, on Slang.
--
-- Nothing hands this node a resource, so there is no descriptor to take a shape
-- from. The size comes from typed USD attributes instead. The values are packed
-- into one parameter block, in the order the shader's Params struct declares.
function checker(inputs, outputs)
local width = inputs["width"].value
local height = inputs["height"].value
assert(width > 0 and height > 0, "width and height must be positive")
assert(inputs["squareSize"].value > 0, "squareSize must be positive")
outputs["Checker"] = slang.image(width, height, slang.uchar4)
return slang.dispatch({
bind = {
slang.ParameterBlock(
slang.int(inputs["width"]), -- -> int width
slang.int(inputs["height"]), -- -> int height
slang.int(inputs["squareSize"]), -- -> int squareSize
slang.float3(inputs["colorA"]), -- -> float3 colorA
slang.float3(inputs["colorB"]) -- -> float3 colorB
),
slang.RWTexture2D(outputs["Checker"]), -- -> RWTexture2D<float4> g_OutChecker
},
-- The shader's [numthreads(16, 16, 1)] places the dispatch.
})
end
Verify It Worked#
Build something you can recompute exactly on the host, then compare pixel for pixel. The
checkerboard example draws its pattern from an
authored square size and two authored colours, rebuilds the same pattern in NumPy, and prints
pixels differing from the host-computed pattern: 0. Anything above zero means a value did not
arrive or the launch did not cover the output.
When It Goes Wrong#
Nothing appears: nothing asked for the output, so the node never ran. Give it a RenderVar and list it in
orderedVars. Refer to Nothing Appears.