Skip to main content

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.

Zur Installation springen

Quellinformationen

Repository
Nebulavenus/forge-gpu
Letzte Quellaktivität
22. März 2026 um 09:48
Erkannte Sprache von SKILL.md
Englisch
Sterne
38
Forks
7

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
forge-atlas-rendering
description
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.
trigger
atlas rendering, texture atlas GPU, atlas UV remap, reduce texture binds
# 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 < MATERIAL_COUNT; i++) { atlas_entry_idx[i] = -1; for (int j = 0; j < atlas_meta.entry_count; j++) { if (SDL_strcmp(atlas_meta.entries[j].name, MATERIAL_NAMES[i]) == 0) { atlas_entry_idx[i] = j; break; } } } ``` ## Draw loop Use `forge_scene_bind_textured_resources()` once before the loop, then `forge_scene_draw_textured_mesh_no_bind()` per mesh to avoid redundant pipeline and sampler binds: ```c /* Bind atlas texture + pipeline ONCE before the draw loop */ forge_scene_bind_textured_resources(scene, atlas_texture, sampler); for (int i = 0; i < material_count; i++) { /* Look up this material's UV transform via precomputed index */ int idx = atlas_entry_idx[i]; float uv[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; /* identity fallback */ if (idx >= 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 $<$<NOT:$<C_COMPILER_ID:MSVC>>:m>) ```
Auf GitHub ansehen