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.