Skip to content

kids.kapish.automat

unity

GPU blits, channel packing, procedural texture generation, IO helpers, and texture transform modules for Unity.

Installation

Add to your Unity project's package manifest:

json
{
  "kids.kapish.automat": "file:../../src/unity/kids.kapish.automat"
}

Requires Unity 6000.0+.

Architecture

Two-Stage Pipeline: Read then Process

Automat splits texture ingestion into two explicit stages.

Read loads raw texture assets into AutomatReadSource wrappers that capture all metadata (size, format, sRGB, mipmap count, slice count, compression state). Multi-slice textures (Texture2DArray, Texture3D) are expanded so each slice becomes individually addressable.

Process takes the read sources and produces per-slice AutomatProcessSource objects with lazy CPU and GPU accessors:

  • Cpu -- extracts pixel data as Color32[] (SDR) and Vector4[] (HDR). Float-format textures get native HDR; all others get simulated HDR via normalised SDR.
  • Gpu -- blits the slice into a named RenderTexture for shader-based processing.

Both are lazily evaluated -- no work happens until the accessor is first touched.

Automat.Read()              → AutomatReadBuilder
  .Add2D(texture)           → adds source
  .Add2DArray(texture)      → adds source (expanded to N slices)
  .ToProcess()              → AutomatProcessBuilder
    .Sources["name"].Cpu    → lazy CPU pixel data
    .Sources["name"].Gpu    → lazy GPU RenderTexture

Editor Pipeline: Import-Aware

The editor layer adds AutomatEditorImportBuilder which wraps the read stage with import settings management. When ToRead() is called, it compares required import settings against the current importer state and auto-reimports any assets that diverge -- then hands off to the standard read pipeline.

Import settings use Optional<T> fields. Only properties that are explicitly set are compared and applied; everything else is left untouched on the importer. This allows partial profiles (e.g. "force readable, don't care about compression").

Rule-Based Automation

The .automat file system provides declarative, directory-scoped texture processing rules. Rules are JSON files named .automat placed alongside texture assets. The resolver walks up the directory tree (like .editorconfig), collecting rules from root to leaf -- leaf rules take priority.

Each rule specifies a glob pattern and an operation:

OperationWhat it does
ChannelSplitSplits multi-channel texture into individual grayscale outputs
ChannelPackPacks separate textures into one multi-channel output
AtlasPacks multiple textures into a single atlas with metadata
ImportProfileApplies a ScriptableObject import profile to matched assets

The AutomatAssetPostprocessor watches for texture imports and .automat file changes, triggering the pipeline automatically. Dirty checking is timestamp-based -- outputs are only regenerated when sources are newer.

GPU Blit Operations

Channel split, channel pack, and atlas blit are all GPU operations using dedicated hidden shaders:

  • ChannelPack (Hidden/Automat/ChannelPack) -- four source textures, each with a Vector4 channel mask. The fragment shader dot-products each sample against its mask to extract the desired channel, then assembles RGBA.
  • ChannelSplit (Hidden/Automat/ChannelSplit) -- single source, single mask. Outputs grayscale (the extracted channel replicated to RGB).
  • AtlasBlit (Hidden/Automat/AtlasBlit) -- remaps a full-screen quad to a sub-rect in atlas UV space, then samples the source texture.

All three run with ZTest Always Cull Off ZWrite Off -- pure blit, no depth involvement.

File Structure

Runtime/
├── Automat.cs                    # Static entry point
├── AutomatTextureType.cs         # Texture2D/2DArray/3D enum
├── Read/
│   ├── AutomatReadBuilder.cs     # Fluent texture loader
│   └── AutomatReadSource.cs      # Loaded texture wrapper with slice caches
├── Process/
│   ├── AutomatProcessBuilder.cs  # Process source collection
│   ├── AutomatProcessSource.cs   # Per-slice source with lazy Cpu/Gpu
│   ├── AutomatProcessSourceCpu.cs # CPU pixel data (SDR + HDR)
│   └── AutomatProcessSourceGpu.cs # GPU RenderTexture wrapper
└── Shaders/
    ├── AutomatAtlasBlit.shader
    ├── AutomatChannelPack.shader
    └── AutomatChannelSplit.shader

Editor/
├── AutomatEditor.cs              # Extension methods + menu items
├── Import/
│   ├── AutomatEditorImportBuilder.cs
│   ├── AutomatEditorImportSource.cs
│   ├── AutomatEditorImportSourceBuilder.cs
│   └── Settings/
│       ├── IAutomatEditorImportSettingsTexture.cs    # Base interface + ApplyTo
│       ├── IAutomatEditorImportSettingsTexture2D.cs
│       ├── IAutomatEditorImportSettingsTexture2DArray.cs
│       ├── IAutomatEditorImportSettingsTexture3D.cs
│       ├── AutomatEditorImportSettingsTexture.cs     # Code-first settings
│       ├── AutomatEditorImportSettingsTexture2D.cs
│       ├── AutomatEditorImportSettingsTexture2DArray.cs
│       ├── AutomatEditorImportSettingsTexture3D.cs
│       ├── AutomatEditorImportProfileTexture.cs      # ScriptableObject profiles
│       ├── AutomatEditorImportProfileTexture2D.cs
│       ├── AutomatEditorImportProfileTexture2DArray.cs
│       └── AutomatEditorImportProfileTexture3D.cs
├── Operations/
│   ├── AutomatAtlasOperation.cs
│   ├── AutomatBlitUtility.cs
│   ├── AutomatChannelPackOperation.cs
│   ├── AutomatChannelSplitOperation.cs
│   └── AutomatImportProfileOperation.cs
├── Pipeline/
│   ├── AutomatAssetPostprocessor.cs
│   ├── AutomatDirtyCheck.cs
│   └── AutomatPipeline.cs
└── Rules/
    ├── AutomatGlobMatcher.cs
    ├── AutomatRule.cs
    ├── AutomatRuleAtlas.cs
    ├── AutomatRuleChannelPack.cs
    ├── AutomatRuleChannelSplit.cs
    ├── AutomatRuleFile.cs
    ├── AutomatRuleOperation.cs
    └── AutomatRuleResolver.cs

Dependencies

DependencyKind
kids.kapishruntime (Carrot core Unity package)
kids.kapish.texturesruntime (texture utilities, blit helpers, slice extraction)

Build

Import via Unity Package Manager. Requires Unity 6000.0+.


Usage Guide

GPU blits, channel packing, atlas generation, import profiles, and texture IO for Unity.

Runtime: Reading and Processing Textures

Load textures into the pipeline

csharp
using Carrot.Automat;
using Carrot.Automat.Process;
using UnityEngine;

// Load individual textures
Texture2D diffuse = /* your texture */;
Texture2DArray tileSet = /* your array */;

AutomatProcessBuilder process = Automat.Read()
    .Add2D("diffuse", diffuse)
    .Add2DArray("tiles", tileSet)
    .ToProcess();

Each Texture2DArray or Texture3D is automatically expanded into individual slices. A 4-slice array named "tiles" produces process sources for each slice.

Access pixel data (CPU)

csharp
using Carrot.Automat.Process;

AutomatProcessSource source = process.Sources["diffuse"];
AutomatProcessSourceCpu cpu = source.Cpu.Value;

// cpu exposes Color32[] (SDR) and Vector4[] (HDR)
// Float-format textures get native HDR; others get simulated HDR
if (cpu.HdrMode == AutomatProcessHdrMode.Native)
{
    // True HDR data available
}

Access as RenderTexture (GPU)

csharp
using Carrot.Automat.Process;

AutomatProcessSourceGpu gpu = process.Sources["diffuse"].Gpu.Value;
RenderTexture rt = gpu.RenderTexture;

// Use rt for GPU operations (blit, compute, etc.)
// ...

gpu.Release(); // Clean up when done

Both Cpu and Gpu are lazy -- no work happens until .Value is first accessed.

Load multiple textures at once

csharp
using Carrot.Automat;
using UnityEngine;

var process = Automat.Read()
    .Add2DSet(new[] { texA, texB, texC })
    .Add3D("volume", myTexture3D)
    .ToProcess();

Add2DSet with a name prefix produces indexed names ("name@0", "name@1", etc.). Without a prefix, it uses each texture's .name.

Editor: Import-Aware Pipeline

Load textures by asset path with import settings

csharp
using Carrot.Automat.Editor;
using Carrot.Automat.Editor.Import.Settings;
using Carrot.Automat.Process;

// Define required import settings (only set what you care about)
var settings = new AutomatEditorImportSettingsTexture2D(
    readable: true,
    sRGB: false,
    maxSize: 2048,
    compression: UnityEditor.TextureImporterCompression.Uncompressed
);

AutomatProcessBuilder process = AutomatEditor.Import()
    .Add2D("albedo", settings, "Assets/Textures/hero_Albedo.png")
    .Add2D("normal", settings, "Assets/Textures/hero_Normal.png")
    .ToProcess();

ToProcess() (via ToRead()) automatically compares required settings against each asset's current importer state. If any setting diverges, the asset is reimported before processing continues. Only Optional<T> properties that are explicitly set are compared -- everything else is left untouched.

Load by asset path (without import settings)

csharp
using Carrot.Automat;
using Carrot.Automat.Editor;

var process = Automat.Read()
    .Add2D("Assets/Textures/hero_Albedo.png")
    .Add2DSet("Assets/Textures/tile_0.png", "Assets/Textures/tile_1.png")
    .ToProcess();

Extension methods on AutomatReadBuilder in the Editor assembly load textures via AssetDatabase.

Tag sources with metadata

csharp
using Carrot.Automat.Editor;
using Carrot.Automat.Editor.Import.Settings;

AutomatEditor.Import()
    .Add2D("albedo", settings, "Assets/Textures/hero_Albedo.png", src =>
    {
        src.AddFlag("srgb")
           .AddTag("role", "albedo")
           .AddTag("priority", 1);
    })
    .ToProcess();

Tags are available on AutomatEditorImportSource.Tags for downstream processing logic.

Import profiles (ScriptableObject)

Create reusable import profiles via Assets > Create > Automat > Import > Texture2D Profile (or Texture2DArray/Texture3D). These are ScriptableObject assets with Optional<T> fields exposed in the inspector -- only enabled fields are applied.

Rule-Based Automation (.automat files)

Place a .automat JSON file alongside your textures to automate processing. Rules are resolved by walking up the directory tree (like .editorconfig) -- leaf-level rules take priority.

Channel split: ORM to individual maps

json
{
  "rules": [
    {
      "name": "Split ORM",
      "pattern": "*_ORM.png",
      "operation": "ChannelSplit",
      "output": "{name}_{suffix}.png",
      "channelSplit": {
        "outputs": {
          "Occlusion": [0],
          "Roughness": [1],
          "Metallic": [2]
        }
      }
    }
  ]
}

Given hero_ORM.png, this produces hero_Occlusion.png, hero_Roughness.png, and hero_Metallic.png. Each output is a grayscale texture containing the specified channel (0=R, 1=G, 2=B, 3=A).

Channel pack: individual maps to ORM

json
{
  "rules": [
    {
      "name": "Pack ORM",
      "pattern": "*_Occlusion.png",
      "operation": "ChannelPack",
      "output": "{name}_ORM.png",
      "channelPack": {
        "channels": {
          "r": "_Occlusion",
          "g": "_Roughness",
          "b": "_Metallic"
        },
        "sourceChannel": "r"
      }
    }
  ]
}

The pattern triggers on any *_Occlusion.png file. The base name is extracted (e.g. hero from hero_Occlusion.png), then each channel is resolved by appending its suffix to the base name. Override the source channel per-entry with "_Roughness:g" syntax.

Atlas packing

json
{
  "rules": [
    {
      "name": "Icon Atlas",
      "pattern": "icon_*.png",
      "operation": "Atlas",
      "output": "IconAtlas.png",
      "atlas": {
        "maxSize": 2048,
        "padding": 2,
        "packingMethod": "BestAreaFit",
        "sortStrategy": "WidthAreaHeight"
      }
    }
  ]
}

All textures matching the pattern in the same directory are packed into a single atlas. A JSON metadata sidecar is written alongside the atlas with pixel rects, UV rects, and fill ratio for each entry.

Import profile application

json
{
  "rules": [
    {
      "name": "UI Textures",
      "pattern": "ui_*.png",
      "operation": "ImportProfile",
      "profile": "Assets/Profiles/UITextureProfile.asset"
    }
  ]
}

Processing

Rules are processed automatically when textures are imported or .automat files change. You can also trigger manually:

  • Carrot > Automat > Process All -- finds all .automat files and processes all matching textures
  • Carrot > Automat > Process Selected -- processes selected assets/folders in the Project window
  • Right-click > Automat > Process -- context menu on selected assets

Outputs are only regenerated when sources are newer (timestamp-based dirty checking).

Glob Patterns

Rule patterns support:

  • * -- matches any characters except path separators
  • ? -- matches a single character
  • ** -- matches any characters including path separators

Matching is case-insensitive.

Tips

  • Lazy evaluation -- CPU and GPU data are only extracted when first accessed. Build your pipeline, then only touch what you need.
  • Dispose read sources -- AutomatReadSource implements IDisposable. Dispose when done to release slice caches.
  • Release GPU sources -- call AutomatProcessSourceGpu.Release() to free RenderTexture resources.
  • Partial import settings -- only set the properties you care about. Everything else passes through unchanged.
  • Rule inheritance -- place broad rules at the project root and specific overrides in subdirectories.

Carrot