librashader/CLI.md

24 KiB

librashader CLI

Helpers and utilities to reflect and debug 'slang' shaders and presets

Usage: librashader-cli <COMMAND>

Commands:
  render      Render a shader preset against an image
  compare     Compare two runtimes and get a similarity score between the two runtimes rendering the same frame
  parse       Parse a preset and get a JSON representation of the data
  preprocess  Get the raw GLSL output of a preprocessed shader
  transpile   Transpile a shader in a given preset to the given format
  reflect     Reflect the shader relative to a preset, giving information about semantics used in a slang shader
  help        Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

librashader-cli provides a CLI interface to much of librashader's functionality to reflect and debug 'slang' shaders and presets.

Installation

If cargo is available, librashader-cli can be installed from crates.io.

$ cargo install librashader-cli

Applying a shader preset to an image

Render a shader preset against an image

Usage: librashader-cli render [OPTIONS] --preset <PRESET> --image <IMAGE> --out <OUT> --runtime <RUNTIME>

Options:
  -p, --preset <PRESET>
          The path to the shader preset to load

  -w, --wildcards <WILDCARDS>...
          Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR wildcards are always added to the preset parsing context.

          For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman

  -f, --frame <FRAME>
          The frame to render.

          The renderer will run up to the number of frames specified here to ensure feedback and history.

          [default: 0]

      --params <PARAMS>...
          Parameters to pass to the shader preset, comma separated with equals signs.

          For example, crt_gamma=2.5,halation_weight=0.001

      --passes-enabled <PASSES_ENABLED>
          Set the number of passes enabled for the preset

  -i, --image <IMAGE>
          The path to the input image

      --frame-direction <FRAME_DIRECTION>
          The direction of rendering. -1 indicates that the frames are played in reverse order

          [default: 1]

      --rotation <ROTATION>
          The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg

          [default: 0]

      --total-subframes <TOTAL_SUBFRAMES>
          The total number of subframes ran. Default is 1

          [default: 1]

      --current-subframe <CURRENT_SUBFRAME>
          The current sub frame. Default is 1

          [default: 1]

  -o, --out <OUT>
          The path to the output image

          If `-`, writes the image in PNG format to stdout.

  -r, --runtime <RUNTIME>
          The runtime to use to render the shader preset

          [possible values: opengl3, opengl4, vulkan, wgpu, d3d9, d3d11, d3d12, metal]

  -h, --help
          Print help (see a summary with '-h')


The render command can be used to apply a shader preset to an image. The available runtimes will depend on the platform that librashader-cli was built for.

For example, to apply crt-royale.slangp to image.png using the OpenGL 3.3 runtime

$  librashader-cli render -i image.png -p crt-royale.slangp -r opengl3 -o out.png

Some presets have animations that rely on a frame counter. The --frame/-f argument can be used to select which frame to render.

$  librashader-cli render -i image.png -p MBZ__0__SMOOTH-ADV.slangp -f 120 -r opengl3 -o out.png

Comparing the similarities of two runtimes

Compare two runtimes and get a similarity score between the two runtimes rendering the same frame

Usage: librashader-cli compare [OPTIONS] --preset <PRESET> --image <IMAGE> --left <LEFT> --right <RIGHT>

Options:
  -p, --preset <PRESET>
          The path to the shader preset to load

  -w, --wildcards <WILDCARDS>...
          Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR wildcards are always added to the preset parsing context.

          For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman

  -f, --frame <FRAME>
          The frame to render.

          The renderer will run up to the number of frames specified here to ensure feedback and history.

          [default: 0]

      --params <PARAMS>...
          Parameters to pass to the shader preset, comma separated with equals signs.

          For example, crt_gamma=2.5,halation_weight=0.001

      --passes-enabled <PASSES_ENABLED>
          Set the number of passes enabled for the preset

  -i, --image <IMAGE>
          The path to the input image

      --frame-direction <FRAME_DIRECTION>
          The direction of rendering. -1 indicates that the frames are played in reverse order

          [default: 1]

      --rotation <ROTATION>
          The rotation of the output. 0 = 0deg, 1 = 90deg, 2 = 180deg, 3 = 270deg

          [default: 0]

      --total-subframes <TOTAL_SUBFRAMES>
          The total number of subframes ran. Default is 1

          [default: 1]

      --current-subframe <CURRENT_SUBFRAME>
          The current sub frame. Default is 1

          [default: 1]

  -l, --left <LEFT>
          The runtime to compare against

          [possible values: opengl3, opengl4, vulkan, wgpu, d3d9, d3d11, d3d12, metal]

  -r, --right <RIGHT>
          The runtime to compare to

          [possible values: opengl3, opengl4, vulkan, wgpu, d3d9, d3d11, d3d12, metal]

  -o, --out <OUT>
          The path to write the similarity image.

          If `-`, writes the image to stdout.

  -h, --help
          Print help (see a summary with '-h')

The compare command can be used to get the similarity of two different runtimes, returning a similarity score and similarity image. The available runtimes will depend on the platform that librashader-cli was built for. This is mainly used for debug and testing purposes; two runtimes should output highly identical (> 0.99 similarity) with a near black similarity image.

Parsing a shader preset

Parse a preset and get a JSON representation of the data

Usage: librashader-cli parse [OPTIONS] --preset <PRESET>

Options:
  -p, --preset <PRESET>
          The path to the shader preset to load

  -w, --wildcards <WILDCARDS>...
          Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR wildcards are always added to the preset parsing context.

          For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman

  -h, --help
          Print help (see a summary with '-h')

The parse command can be used to parse a shader preset and get a JSON represenation of its data. Wildcards can be specified with the --wildcards / -w argument. All paths will be resolved relative to the preset.

Getting preset information for CRT Royale

The following command

$  librashader-cli parse -p crt-geom.slangp

will output the following JSON

{
  "shader_count": 12,
  "shaders": [
    {
      "id": 0,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-first-pass-linearize-crt-gamma-bob-fields.slang",
      "alias": "ORIG_LINEARIZED",
      "filter": "Nearest",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": true,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        },
        "y": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        }
      }
    },
    {
      "id": 1,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-vertical-interlacing.slang",
      "alias": "VERTICAL_SCANLINES",
      "filter": "Linear",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": true,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        },
        "y": {
          "scale_type": "Viewport",
          "factor": {
            "Float": 1.0
          }
        }
      }
    },
    {
      "id": 2,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-bloom-approx.slang",
      "alias": "BLOOM_APPROX",
      "filter": "Linear",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": true,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Absolute",
          "factor": {
            "Absolute": 320
          }
        },
        "y": {
          "scale_type": "Absolute",
          "factor": {
            "Absolute": 240
          }
        }
      }
    },
    {
      "id": 3,
      "name": "/tmp/shaders_slang/blurs/shaders/royale/blur9fast-vertical.slang",
      "alias": null,
      "filter": "Linear",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": true,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        },
        "y": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        }
      }
    },
    {
      "id": 4,
      "name": "/tmp/shaders_slang/blurs/shaders/royale/blur9fast-horizontal.slang",
      "alias": "HALATION_BLUR",
      "filter": "Linear",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": true,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        },
        "y": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        }
      }
    },
    {
      "id": 5,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-mask-resize-vertical.slang",
      "alias": null,
      "filter": "Linear",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": false,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Absolute",
          "factor": {
            "Absolute": 64
          }
        },
        "y": {
          "scale_type": "Viewport",
          "factor": {
            "Float": 0.0625
          }
        }
      }
    },
    {
      "id": 6,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-mask-resize-horizontal.slang",
      "alias": "MASK_RESIZE",
      "filter": "Nearest",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": false,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Viewport",
          "factor": {
            "Float": 0.0625
          }
        },
        "y": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        }
      }
    },
    {
      "id": 7,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-scanlines-horizontal-apply-mask.slang",
      "alias": "MASKED_SCANLINES",
      "filter": "Linear",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": true,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Viewport",
          "factor": {
            "Float": 1.0
          }
        },
        "y": {
          "scale_type": "Viewport",
          "factor": {
            "Float": 1.0
          }
        }
      }
    },
    {
      "id": 8,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-brightpass.slang",
      "alias": "BRIGHTPASS",
      "filter": "Linear",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": true,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Viewport",
          "factor": {
            "Float": 1.0
          }
        },
        "y": {
          "scale_type": "Viewport",
          "factor": {
            "Float": 1.0
          }
        }
      }
    },
    {
      "id": 9,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-bloom-vertical.slang",
      "alias": null,
      "filter": "Linear",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": true,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        },
        "y": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        }
      }
    },
    {
      "id": 10,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-bloom-horizontal-reconstitute.slang",
      "alias": null,
      "filter": "Linear",
      "wrap_mode": "ClampToBorder",
      "frame_count_mod": 0,
      "srgb_framebuffer": true,
      "float_framebuffer": false,
      "mipmap_input": false,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        },
        "y": {
          "scale_type": "Input",
          "factor": {
            "Float": 1.0
          }
        }
      }
    },
    {
      "id": 11,
      "name": "/tmp/shaders_slang/crt/shaders/crt-royale/src/crt-royale-geometry-aa-last-pass.slang",
      "alias": null,
      "filter": "Linear",
      "wrap_mode": "ClampToEdge",
      "frame_count_mod": 0,
      "srgb_framebuffer": false,
      "float_framebuffer": false,
      "mipmap_input": true,
      "scaling": {
        "valid": true,
        "x": {
          "scale_type": "Viewport",
          "factor": {
            "Float": 1.0
          }
        },
        "y": {
          "scale_type": "Viewport",
          "factor": {
            "Float": 1.0
          }
        }
      }
    }
  ],
  "textures": [
    {
      "name": "mask_grille_texture_small",
      "path": "/tmp/shaders_slang/crt/shaders/crt-royale/TileableLinearApertureGrille15Wide8And5d5SpacingResizeTo64.png",
      "wrap_mode": "Repeat",
      "filter_mode": "Linear",
      "mipmap": false
    },
    {
      "name": "mask_grille_texture_large",
      "path": "/tmp/shaders_slang/crt/shaders/crt-royale/TileableLinearApertureGrille15Wide8And5d5Spacing.png",
      "wrap_mode": "Repeat",
      "filter_mode": "Linear",
      "mipmap": true
    },
    {
      "name": "mask_slot_texture_small",
      "path": "/tmp/shaders_slang/crt/shaders/crt-royale/TileableLinearSlotMaskTall15Wide9And4d5Horizontal9d14VerticalSpacingResizeTo64.png",
      "wrap_mode": "Repeat",
      "filter_mode": "Linear",
      "mipmap": false
    },
    {
      "name": "mask_slot_texture_large",
      "path": "/tmp/shaders_slang/crt/shaders/crt-royale/TileableLinearSlotMaskTall15Wide9And4d5Horizontal9d14VerticalSpacing.png",
      "wrap_mode": "Repeat",
      "filter_mode": "Linear",
      "mipmap": true
    },
    {
      "name": "mask_shadow_texture_small",
      "path": "/tmp/shaders_slang/crt/shaders/crt-royale/TileableLinearShadowMaskEDPResizeTo64.png",
      "wrap_mode": "Repeat",
      "filter_mode": "Linear",
      "mipmap": false
    },
    {
      "name": "mask_shadow_texture_large",
      "path": "/tmp/shaders_slang/crt/shaders/crt-royale/TileableLinearShadowMaskEDP.png",
      "wrap_mode": "Repeat",
      "filter_mode": "Linear",
      "mipmap": true
    }
  ],
  "parameters": []
}

Getting the preprocessed GLSL source of a .slang shader

Get the raw GLSL output of a preprocessed shader

Usage: librashader-cli preprocess --shader <SHADER> --output <OUTPUT>

Options:
  -s, --shader <SHADER>
          The path to the slang shader

  -o, --output <OUTPUT>
          The item to output.

          `json` will print a JSON representation of the preprocessed shader.

          [possible values: fragment, vertex, params, passformat, json]

  -h, --help
          Print help (see a summary with '-h')

The preprocess command can be used to invoke the preprocessor on .slang shader source files and output the raw GLSL source that gets passed to SPIRV-Cross during shader compilation.

For example, to get the preprocessed fragment shader source code

$ librashader-cli preprocess -s crt-geom.slang -o fragment 

preprocess can also be used to get information about the pass format, parameters, or meta information inferred from #pragma declarations in the source code.

For example (using jq to truncate the output)

$ librashader-cli preprocess -s crt-geom.slang -o params | jq 'first(.[])'

will return the following JSON

{
  "CRTgamma": {
    "id": "CRTgamma",
    "description": "CRTGeom Target Gamma",
    "initial": 2.4,
    "minimum": 0.1,
    "maximum": 5.0,
    "step": 0.1
  }
}

Convert a .slang to a target shader format

Transpile a shader in a given preset to the given format

Usage: librashader-cli transpile --shader <SHADER> --stage <STAGE> --format <FORMAT>

Options:
  -s, --shader <SHADER>
          The path to the slang shader

  -o, --stage <STAGE>
          The shader stage to output

          [possible values: fragment, vertex]

  -f, --format <FORMAT>
          The output format

          [possible values: glsl, hlsl, wgsl, msl, spirv]

  -v, --version <VERSION>
          The version of the output format to parse as, if applicable

          For GLSL, this should be an string corresponding to a GLSL version (e.g. '330', or '300es', or '300 es').

          For HLSL, this is a shader model version as an integer (50), or a version in the format MAJ_MIN (5_0), or MAJ.MIN (5.0).

          For MSL, this is the shader language version as an integer in format <MMmmpp>(30100), or a version in the format MAJ_MIN (3_1), or MAJ.MIN (3.1).

  -h, --help
          Print help (see a summary with '-h')

The transpile command can be used to convert a .slang shader source file to a target format used by a runtime. GLSL (under OpenGL semantics), HLSL, WGSL, MSL, and SPIR-V disassembly is supported as output formats.

For example, to get the pixel shader in Shader Model 6.0 HLSL for crt-geom.slang

$ librashader-cli transpile -s crt-geom.slang -o fragment -f hlsl -v 60

Getting detailed reflection information for a shader

Reflect the shader relative to a preset, giving information about semantics used in a slang shader.

Usage: librashader-cli reflect [OPTIONS] --preset <PRESET> --index <INDEX>

Options:
  -p, --preset <PRESET>
          The path to the shader preset to load

  -w, --wildcards <WILDCARDS>...
          Additional wildcard options, comma separated with equals signs. The PRESET and PRESET_DIR wildcards are always added to the preset parsing context.

          For example, CONTENT-DIR=MyVerticalGames,GAME=mspacman

  -i, --index <INDEX>
          The pass index to use

  -b, --backend <BACKEND>
          [default: cross]
          [possible values: cross, naga]

  -h, --help
          Print help (see a summary with '-h') 

The reflect command can be used to get reflection detailed information regarding uniform and texture bindings for a shader pass, relative to a preset. As semantics for LUT images are defined by the preset definition, reflection information is only valid for a shader source file relative to its shader preset.

The default backend to do reflection is with SPIRV-Cross. Reflections via Naga (used in the wgpu runtime) are also available if desired, and may have different results than SPIRV-Cross.

Getting reflection information for CRT Geom

crt-geom.slangp only has a single pass, but we still need to specify the pass in relation to its preset.

$ librashader-cli reflect -p crt-geom.slangp -i 0 

The above command will output the following JSON

{
  "ubo": {
    "binding": 0,
    "size": 96,
    "stage_mask": "VERTEX | FRAGMENT"
  },
  "push_constant": {
    "binding": null,
    "size": 96,
    "stage_mask": "VERTEX | FRAGMENT"
  },
  "meta": {
    "param": {
      "ysize": {
        "offset": {
          "ubo": null,
          "push": 80
        },
        "size": 1,
        "id": "ysize"
      },
      "xsize": {
        "offset": {
          "ubo": null,
          "push": 76
        },
        "size": 1,
        "id": "xsize"
      },
      "invert_aspect": {
        "offset": {
          "ubo": null,
          "push": 68
        },
        "size": 1,
        "id": "invert_aspect"
      },
      "x_tilt": {
        "offset": {
          "ubo": null,
          "push": 28
        },
        "size": 1,
        "id": "x_tilt"
      },
      "y_tilt": {
        "offset": {
          "ubo": null,
          "push": 32
        },
        "size": 1,
        "id": "y_tilt"
      },
      "R": {
        "offset": {
          "ubo": null,
          "push": 16
        },
        "size": 1,
        "id": "R"
      },
      "d": {
        "offset": {
          "ubo": null,
          "push": 12
        },
        "size": 1,
        "id": "d"
      },
      "vertical_scanlines": {
        "offset": {
          "ubo": null,
          "push": 72
        },
        "size": 1,
        "id": "vertical_scanlines"
      },
      "SHARPER": {
        "offset": {
          "ubo": null,
          "push": 48
        },
        "size": 1,
        "id": "SHARPER"
      },
      "interlace_detect": {
        "offset": {
          "ubo": null,
          "push": 60
        },
        "size": 1,
        "id": "interlace_detect"
      },
      "CURVATURE": {
        "offset": {
          "ubo": null,
          "push": 56
        },
        "size": 1,
        "id": "CURVATURE"
      },
      "overscan_x": {
        "offset": {
          "ubo": null,
          "push": 36
        },
        "size": 1,
        "id": "overscan_x"
      },
      "overscan_y": {
        "offset": {
          "ubo": null,
          "push": 40
        },
        "size": 1,
        "id": "overscan_y"
      },
      "cornersize": {
        "offset": {
          "ubo": null,
          "push": 20
        },
        "size": 1,
        "id": "cornersize"
      },
      "cornersmooth": {
        "offset": {
          "ubo": null,
          "push": 24
        },
        "size": 1,
        "id": "cornersmooth"
      },
      "CRTgamma": {
        "offset": {
          "ubo": null,
          "push": 4
        },
        "size": 1,
        "id": "CRTgamma"
      },
      "scanline_weight": {
        "offset": {
          "ubo": null,
          "push": 52
        },
        "size": 1,
        "id": "scanline_weight"
      },
      "lum": {
        "offset": {
          "ubo": null,
          "push": 64
        },
        "size": 1,
        "id": "lum"
      },
      "DOTMASK": {
        "offset": {
          "ubo": null,
          "push": 44
        },
        "size": 1,
        "id": "DOTMASK"
      },
      "monitorgamma": {
        "offset": {
          "ubo": null,
          "push": 8
        },
        "size": 1,
        "id": "monitorgamma"
      }
    },
    "unique": {
      "MVP": {
        "offset": {
          "ubo": 0,
          "push": null
        },
        "size": 16,
        "id": "MVP"
      },
      "Output": {
        "offset": {
          "ubo": 64,
          "push": null
        },
        "size": 4,
        "id": "OutputSize"
      },
      "FrameCount": {
        "offset": {
          "ubo": null,
          "push": 0
        },
        "size": 1,
        "id": "FrameCount"
      }
    },
    "texture": {
      "Source": {
        "binding": 2
      }
    },
    "texture_size": {
      "Source": {
        "offset": {
          "ubo": 80,
          "push": null
        },
        "stage_mask": "VERTEX | FRAGMENT",
        "id": "SourceSize"
      }
    }
  }
}