# Forge Atlas Rendering

> Add atlas-based texture rendering to an SDL GPU project. Load atlas metadata from JSON, remap UVs in the fragment shader, and reduce texture bind state changes.

- **Type:** Skill
- **Install:** `agentstack add skill-nebulavenus-forge-gpu-forge-atlas-rendering`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Nebulavenus](https://agentstack.voostack.com/s/nebulavenus)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Zlib
- **Upstream author:** [Nebulavenus](https://github.com/Nebulavenus)
- **Source:** https://github.com/Nebulavenus/forge-gpu/tree/main/.claude/skills/forge-atlas-rendering

## Install

```sh
agentstack add skill-nebulavenus-forge-gpu-forge-atlas-rendering
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Atlas-Based Texture Rendering

Add texture atlas rendering to an SDL GPU project. Loads pre-packed atlas
metadata (UV offset/scale per material), remaps UVs in the fragment shader,
and binds a single atlas texture instead of N per-material textures.

Based on [GPU Lesson 47](../../../lessons/gpu/47-texture-atlas-rendering/).

## Prerequisites

- An atlas image (PNG) and metadata file (atlas.json) generated by the
  pipeline's atlas plugin ([Asset Lesson 17](../../../lessons/assets/17-texture-atlas/))
- `forge_pipeline.h` for `forge_pipeline_load_atlas()`
- cJSON (third_party/cJSON) for JSON parsing

## Atlas metadata format

```json
{
  "version": 1,
  "width": 2048,
  "height": 2048,
  "padding": 4,
  "utilization": 0.499,
  "entries": {
    "material_name": {
      "x": 4, "y": 4, "width": 256, "height": 256,
      "u_offset": 0.002, "v_offset": 0.002,
      "u_scale": 0.125, "v_scale": 0.125
    }
  }
}
```

## C setup

```c
#define FORGE_PIPELINE_IMPLEMENTATION
#include "pipeline/forge_pipeline.h"

/* Load atlas metadata */
ForgePipelineAtlas atlas_meta;
if (!forge_pipeline_load_atlas("assets/atlas.json", &atlas_meta)) {
    SDL_Log("Failed to load atlas metadata");
    return SDL_APP_FAILURE;
}

/* Load atlas texture (single image containing all materials) */
SDL_GPUTexture *atlas_texture = load_png_texture(device, "assets/atlas.png");

/* Create sampler with clamp-to-edge to prevent atlas bleeding */
SDL_GPUSamplerCreateInfo si;
SDL_zero(si);
si.min_filter     = SDL_GPU_FILTER_LINEAR;
si.mag_filter     = SDL_GPU_FILTER_LINEAR;
si.mipmap_mode    = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
si.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
si.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
si.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
```

## Key API calls

- `forge_pipeline_load_atlas(path, &atlas)` — parse atlas.json into
  `ForgePipelineAtlas` with per-entry UV offset/scale
- `forge_pipeline_free_atlas(&atlas)` — release parsed atlas metadata
- `SDL_BindGPUFragmentSamplers(pass, 0, &bind, 1)` — bind the atlas texture
  once before the draw loop (the state change this pattern eliminates)
- `SDL_PushGPUFragmentUniformData(cmd, 0, &fu, sizeof(fu))` — push per-object
  UV transform selecting the correct atlas region

## Fragment shader (HLSL)

```hlsl
cbuffer FragUniforms : register(b0, space3) {
    float4 uv_transform;    /* xy = offset, zw = scale */
    /* ... lighting params ... */
};

Texture2D    material_tex : register(t0, space2);
SamplerState material_smp : register(s0, space2);

float4 main(PSInput input) : SV_Target {
    /* Atlas UV remapping: transform original UVs to atlas coordinates */
    float2 atlas_uv = input.uv * uv_transform.zw + uv_transform.xy;
    float4 albedo = material_tex.Sample(material_smp, atlas_uv);
    /* ... lighting ... */
}
```

## Atlas entry lookup

Build a material-to-entry index table at init time to avoid per-frame string
comparisons and to decouple material order from atlas JSON insertion order:

```c
/* Init: build material → atlas entry index lookup */
int atlas_entry_idx[MATERIAL_COUNT];
for (int i = 0; i = 0) {
        ForgePipelineAtlasEntry *e = &atlas_meta.entries[idx];
        uv[0] = e->u_offset;  uv[1] = e->v_offset;
        uv[2] = e->u_scale;   uv[3] = e->v_scale;
    }

    forge_scene_draw_textured_mesh_no_bind(
        scene, vb, ib, index_count, model, uv);
}
```

## Backwards compatibility

For models that don't use an atlas, pass identity UV transform values:

```c
fu.uv_transform[0] = 0.0f;  fu.uv_transform[1] = 0.0f;
fu.uv_transform[2] = 1.0f;  fu.uv_transform[3] = 1.0f;
```

This makes the shader work identically for both atlas and individual
texture modes — only the bound texture and uniform values differ.

## Cleanup

```c
forge_pipeline_free_atlas(&atlas_meta);
SDL_ReleaseGPUTexture(device, atlas_texture);
SDL_ReleaseGPUSampler(device, sampler);
```

## Common mistakes

- **Using `REPEAT` address mode**: atlas textures must use `CLAMP_TO_EDGE` —
  repeat mode wraps UVs into adjacent material regions, producing visible
  seams and color bleeding.
- **Forgetting identity transform for non-atlas objects**: when mixing atlas
  and individual textures in the same shader, pass
  `vec4_create(0.0f, 0.0f, 1.0f, 1.0f)` as the UV transform for individual
  textures so the remap becomes a no-op.
- **Looking up entries by string every frame**: atlas entry lookup by material
  name involves string comparison. Build an index mapping (material → entry
  index) once at init time, not per-frame.
- **Ignoring mipmap bleeding**: at lower mip levels, bilinear filtering
  samples across atlas region boundaries. The atlas packer adds padding to
  mitigate this, but padding halves at each mip level — visible artifacts
  appear at mip 3+.

## Tradeoffs

- **No tiling**: clamp-to-edge prevents sampling adjacent materials but
  disables texture wrapping
- **Mipmap bleeding**: padding halves at each mip level; visible at mip 3+
- **Resolution uniformity**: all materials share the atlas resolution budget

## CMakeLists.txt

```cmake
add_executable(my-target WIN32
    main.c
    ${CMAKE_SOURCE_DIR}/third_party/cJSON/cJSON.c
)
target_include_directories(my-target PRIVATE
    ${FORGE_COMMON_DIR}
    ${CMAKE_SOURCE_DIR}/third_party/cJSON
)
target_link_libraries(my-target PRIVATE SDL3::SDL3
    $>:m>)
```

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Nebulavenus](https://github.com/Nebulavenus)
- **Source:** [Nebulavenus/forge-gpu](https://github.com/Nebulavenus/forge-gpu)
- **License:** Zlib

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-nebulavenus-forge-gpu-forge-atlas-rendering
- Seller: https://agentstack.voostack.com/s/nebulavenus
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
