Texture Loading & GPU Binding

The goal of this project was to extend an existing engine architecture to design a fully functional custom engine system which could then be used to integrate with other systems to create a game in the custom engine.

Key Features

  • Built a TextureBuilder tool to preprocess image assets into engine ready binary formats
  • Implemented a Texture class with the necessary methods to initialize and load the textures with appropriate platform specific implementations.
    Texture Class
    Texture class interface and responsibilities
  • Integrated GPU slot based texture binding into the render submission pipeline
  • Extended vertex formats, shaders, and render data structures to support UV coordinates from the Maya plugin
  • Implemented reference counting for the Texture class to ensure resource management for safe GPU texture lifetimes

System Architecture

The texture system is split into two primary components: TextureBuilder, a build time component and a runtime engine layer. This separation allows asset processing to occur offline while keeping runtime loading fast and predictable.

Texture System diagram
Build-time processing + runtime loading/binding
  • TextureBuilder (Tooling Layer): Converts the source image files and copies it into the expected binary format that the engine requires at runtime.
  • Texture Runtime Module (Engine Layer): Loads texture data from the binary file, manages GPU resources, and handles binding during rendering.

Asset Pipeline

Textures are processed offline using a custom builder tool, similar to mesh and shader builders in the engine. Bitmap (.bmp) format was selected due to its predictable binary layout, which simplifies parsing and improves reliability at runtime.

At runtime, the engine loads the prebuilt binary texture data using platform specific file I/O and uploads it to the GPU through the appropriate graphics API.

Texture Rendering
Textured rendering using the custom pipeline

Rendering Integration

  • Textures are bound to explicit GPU slots prior to draw calls
  • Shader code uses shared macros to abstract texture declaration and sampling
  • Render submission structures were extended to carry texture references and slot indices
  • Texture binding occurs during the render frame before mesh rendering

Challenges & Engineering Decisions

An initial plan to support PNG files was abandoned due to the complexity of parsing fragmented binary data. The system was redesigned around bitmap files, which provide deterministic offsets for width, height, and pixel data.

Supporting both Direct3D and OpenGL required careful abstraction of shader interfaces and GPU binding logic. Shared shader macros were introduced to ensure consistent behavior across graphics APIs.

Direct3D Direct3D texture sampling and binding
Direct3D-side texture declaration and sampling
OpenGL OpenGL texture sampling and binding
OpenGL-side texture declaration and sampling

Resource lifetime management was another key challenge. A reference-counting system was implemented to ensure textures remain valid for the duration of their usage and are properly released during cleanup.

Example Usage

Texture::cTexture* groundTexture = nullptr;
Texture::cTexture::Load(groundTexture, "ground.texture");

Graphics::Submit(
    mesh,
    effect,
    transform,
    groundTexture,
    0 // texture slot
);

What I Learned

This project helped me understand more about the design choices while developing a system and how to overcome them. The choices made to support certain file types to the methods being easily callable with the appropriate parameters was something I had to think about with emphasis on proper error handling and appropriate debugging tools at the disposal for the developers.

Texture System Gif
Runtime demo: loading, binding, and rendering