mittens-engine 0.7.0

A Vulkan and OpenXR scene engine with ECS, reactive signals, and Meow Meow scripting
# TextureComponent

This document describes the current implemented behavior of `TextureComponent`.

It covers the scene-facing authoring forms, how they map to runtime behavior, and how the
backend resolves each source type.

For render-graph-published runtime textures specifically, also see
[docs/spec/render-to-texture.md](docs/spec/render-to-texture.md).

## 1. What `TextureComponent` is

`TextureComponent` is the component that gives a renderable a texture image.

Current implementation:

- authored component type: `TextureComponent`
- intended placement: as a descendant of a `RenderableComponent`
- registration path: `TextureComponent::init()` emits `RegisterTexture`
- runtime owner: `TextureSystem`

The component stores three related pieces of state:

- `source: TextureSource`
- `format: CatEngineTextureFormat`
- `render_image: Option<String>`

The important distinction is:

- `source` describes an asset or runtime-provided GPU handle
- `render_image` describes a renderer-published runtime image selector

Today those are separate fields rather than one unified source enum.

Relevant implementation:

- [src/engine/ecs/component/texture.rs]src/engine/ecs/component/texture.rs

## 2. Authoring modes

There are three practical ways textures show up in the current engine.

### A. URI-backed authored textures

This is the ordinary asset path mode.

Examples:

```text
Texture.with_uri("assets/textures/cat-face-neutral.dds")
Texture.from_dds("assets/textures/cat-face-neutral.dds")
Texture.from_png("assets/images/foo.png")
```

Equivalent engine-side constructors:

- `TextureComponent::new(uri)`
- `TextureComponent::with_uri(uri)`
- `TextureComponent::from_png(uri)`
- `TextureComponent::from_dds(uri)`

Implications:

- the texture is expected to come from disk
- `TextureSystem` is responsible for reading, decoding, uploading, and caching it
- if multiple components use the same URI, they share the uploaded `TextureHandle` through the
  URI cache

### B. Render-image-backed authored textures

This is the runtime image / render-graph publication path.

Examples:

```text
Texture.render_image("render_graph.emissive_pass.output")
Texture.render_image("render_graph.bloom.blur")
```

Implications:

- this is **not** a disk-backed image load
- `TextureSystem` allocates or reuses a stable runtime `TextureHandle`
- the renderer updates the GPU image behind that handle as frames are rendered
- this is the current way MMS and scene content sample post-process outputs

### C. Handle-backed runtime textures

This is mostly an internal/runtime path:

- `TextureComponent::from_handle(handle)`
- `TextureSource::Handle(TextureHandle)`

Implications:

- the image is already uploaded
- `TextureSystem` does not need to decode or load it from disk
- this is runtime-only state and is not serialized the same way URI-backed textures are

This is the lowest-level mode and is typically used by engine subsystems rather than authored
scene files.

## 3. URI-backed textures: PNG / DDS / generic URI

`TextureComponent` currently distinguishes formats with `CatEngineTextureFormat`:

- `Rgba8`
- `DdsBc7`

Format inference is simple:

- `.dds``DdsBc7`
- everything else → `Rgba8`

That means:

- `Texture.with_uri("foo.dds")` and `Texture.from_dds("foo.dds")` both resolve to BC7 mode
- `Texture.with_uri("foo.png")` and `Texture.from_png("foo.png")` both resolve to RGBA8 mode
- `from_png` / `from_dds` are mostly explicit authoring helpers, not a separate backend system

### PNG / generic image path

For `Rgba8` textures, `TextureSystem`:

1. resolves the filesystem path
2. reads the bytes from disk
3. decodes them with the `image` crate
4. converts to RGBA8
5. uploads the pixels through `TextureUploader::upload_texture_rgba8(...)`

This is the path for PNG and other image formats supported by `image`.

### DDS / BC7 path

For `DdsBc7` textures, `TextureSystem`:

1. resolves the filesystem path
2. reads the bytes from disk
3. parses the DDS container with `ddsfile`
4. verifies the top level is BC7
5. uploads the BC7 block data through `TextureUploader::upload_texture_bc7(...)`

Important current limitation:

- only BC7 DDS is supported here
- the top mip is used

Relevant implementation:

- [src/engine/ecs/system/texture_system.rs]src/engine/ecs/system/texture_system.rs

## 4. Render-image mode

`render_image` is the bridge between renderer-owned images and scene-facing textured content.

Example:

```text
let bloom_tex = Texture.render_image("render_graph.bloom.blur")
```

Backend flow:

1. `TextureComponent` stores `render_image = Some(selector)`
2. `TextureSystem` asks `VisualWorld` for a stable runtime `TextureHandle` for that selector
3. if none exists yet, it allocates a small placeholder texture and stores that handle
4. the render graph publishes a pass output to the same selector
5. `VulkanoRenderer` copies the pass output into the GPU image behind that stable handle

Implications:

- the `TextureHandle` identity is stable
- the image contents update over time
- consumers sample it like an ordinary texture once attached to a renderable
- updates are effectively frame-boundary / one-frame-late style runtime updates, not immediate

This is the current implemented path used for bloom/emissive preview surfaces and other
render-graph-published images.

Relevant implementation:

- [docs/spec/render-to-texture.md]docs/spec/render-to-texture.md
- [src/engine/ecs/system/texture_system.rs]src/engine/ecs/system/texture_system.rs
- [src/engine/graphics/visual_world.rs]src/engine/graphics/visual_world.rs
- [src/engine/graphics/vulkano_renderer.rs]src/engine/graphics/vulkano_renderer.rs

## 5. GLTF-imported textures

GLTF textures use a slightly different path from explicit PNG/DDS authoring.

When a GLTF primitive has a base-color texture:

1. `GLTFSystem` imports and decodes the image during GLTF loading
2. it assigns a virtual texture key, typically shaped like `gltf_name:image_name_or_index`
3. later, `GLTFSystem::flush_imports(...)` uploads that texture and registers the uploaded handle
   in `TextureSystem` via `register_cached_texture(...)`
4. spawned renderables get a `TextureComponent::new(image_name_or_index)` child using that virtual key
5. `TextureSystem` resolves the virtual key from its cache instead of reading a file from disk

Implications:

- GLTF textures are still represented through `TextureComponent`, but they are **not** loaded from
  disk by `TextureSystem`
- the URI-like string is a virtual cache key, not necessarily a real filesystem path
- this is why `TextureSource::Uri` covers both real file paths and virtual keys

The current v1 heuristic for virtual GLTF keys is basically “contains `:` and is not `file://...`”.

Relevant implementation:

- [src/engine/ecs/system/gltf_system.rs]src/engine/ecs/system/gltf_system.rs
- [src/engine/ecs/system/texture_system.rs]src/engine/ecs/system/texture_system.rs#L341-L347

## 6. Caching and attachment behavior

`TextureSystem` is attachment-driven.

When a `TextureComponent` is registered:

- it records the texture source info
- it walks upward to find an ancestor `RenderableComponent`
- it defers actual attachment until that renderable has a `VisualWorld` instance handle

At flush time:

- if the texture is already a GPU handle, it attaches immediately
- if it is a render-image selector, it resolves/allocates the stable runtime handle
- if it is a cached URI key (including GLTF virtual keys), it reuses the cached handle
- if it is a real URI, it loads and uploads it, then caches the result by URI

This means authored texture components can exist before the renderable is fully registered,
and texture uploads happen only when the renderable is actually attachable.

## 7. Serialization behavior

Current encode/decode behavior is intentionally simple:

- URI-backed textures serialize as `uri`
- render-image-backed textures serialize as `render_image`
- handle-backed textures are runtime-only and are not serialized as stable authored data

This is one reason the current API feels split: `render_image` is persisted alongside `uri`,
but `TextureSource::Handle` is not.

## 8. Current limitations and future direction

Current limitations:

- `TextureComponent` splits ordinary sources and render-image selectors awkwardly
- `render_image` is selector-string based rather than a first-class render-image handle/reference
- authored reuse of one `Texture` binding in MMS is still a reused component expression, not a
  single live shared texture object
- the final authored form where plain `Texture {}` can be contextually written/read by different
  systems is still future work

Future design work is tracked in:

- [docs/draft/render-image.md]docs/draft/render-image.md
- [docs/task/refactor/render-image-contextual-binding.md]../task/refactor/render-image-contextual-binding.md

## 9. Practical guidance

Use:

- `Texture.from_dds(...)` for authored BC7 DDS assets
- `Texture.from_png(...)` or `Texture.with_uri(...)` for ordinary file-backed images
- `Texture.render_image("...")` when you want to sample a renderer-published runtime image

Think of the current modes like this:

- **URI mode** = load this image asset
- **GLTF virtual URI mode** = reuse an imported texture already uploaded by GLTF
- **render_image mode** = sample a runtime image published by the renderer
- **handle mode** = internal/runtime pre-uploaded texture binding