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: this node reads no AOV and
# generates its output from these values.
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 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.
--
-- There is no input 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.
--
-- There is no input 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.compute({
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, so the same pattern rebuilt in NumPy from those values matches its output exactly. Any difference 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.