Appearance
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 asColor32[](SDR) andVector4[](HDR). Float-format textures get native HDR; all others get simulated HDR via normalised SDR.Gpu-- blits the slice into a namedRenderTexturefor 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 RenderTextureEditor 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:
| Operation | What it does |
|---|---|
ChannelSplit | Splits multi-channel texture into individual grayscale outputs |
ChannelPack | Packs separate textures into one multi-channel output |
Atlas | Packs multiple textures into a single atlas with metadata |
ImportProfile | Applies 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 aVector4channel 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.csDependencies
| Dependency | Kind |
|---|---|
kids.kapish | runtime (Carrot core Unity package) |
kids.kapish.textures | runtime (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 doneBoth 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
.automatfiles 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 --
AutomatReadSourceimplementsIDisposable. Dispose when done to release slice caches. - Release GPU sources -- call
AutomatProcessSourceGpu.Release()to freeRenderTextureresources. - 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.