This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Callisto Game Engine

A desktop game engine written in Odin.

Callisto is a desktop game engine written in Odin with a Vulkan renderer. It is still early in development and the API is subject to change. When there is a stable release I will add documentation here.

At the moment, the notes here are to record decisions that I have made.

In the meantime, see the Callisto GitHub repository for a brief overview of how the engine is used, or Callisto-Sandbox for a sample implementation.

1 - Introduction

Document Conventions

Normative Terminology

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY and OPTIONAL, when uppercase and emboldened, are normative and have special meaning. These words are to be interpreted as described in BCP 14.

Technical Terminology

slice - A bounded view into an array represented by a pointer or index of the first element of the slice, and a count of elements.

External specifications

Bradner, S., Key words for use in RFCs to Indicate Requirement Levels, BCP 14, RFC 2119, March 1997. Leiba, B., Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words, BCP 14, RFC 8174, May 2017. https://www.rfc-editor.org/info/bcp14

What’s next?

2 - Assets

Game data stored on the disk.

Assets are classified as either Primitive or Aggregate. Primitive assets MUST contain only information about themselves, while aggregate assets MAY contain references to other assets.

Planned primitives:

  • Image
  • Mesh
  • Audio
  • Input action map
  • Shader

Planned aggregates:

  • Material (shader + images)
  • Model (mesh + materials)
  • Construct (fixed-length transform hierarchy)
  • Level

File format

This section is a work in progress. I plan to use Concise Binary Object Representation (CBOR) for serialization, as it provides some desired attributes to the asset files:

  • Named fields to avoid data loss when struct definitions change
  • Small file size
  • Existing Odin core library

File loading

Inspiration from Timothy Cain’s video, Arcanum “dat” files, development builds use a raw OS file system, release builds use packed bundles.

  • data locality on the disk
  • “seek” is much faster than “open”
  • avoid file descriptor limits
  • less overhead for many tiny assets (block alignment, OS file nodes)
  • easy patches/modding with sequential overriding of the asset database
  • more efficient compression

2.1 - Mesh

A primitive asset for storing vertex data.

Mesh overview

Mesh assets store an object’s vertex data, and how the vertices are connected to form triangles.

This page is currently being rewritten to support the new asset format.

2.2 - Material

An aggregate asset to describe how meshes are rendered.

WIP

Materials are an asset that associate a Shader with properties such as Textures and number values.

3 - Graphics

Details about the renderer, shaders, render passes etc.

3.1 - Coordinates for rendering

Transforming coordinates from world space to other spaces.

World coordinates

World coordinates are right-handed, +Z forward, +Y up, -X right.

Why?

  • Want right handed coords
  • glTF default, easy import
  • Y-up intuitive for gravity

Projection and Clip space

Vulkan’s NDC space is defined as [x: -1, y: -1] top-left, [x: 1, y: 1] bottom-right, and z depth in the range [0, 1].

Callisto uses a reversed-depth, infinite far plane perspective projection.

  • Better distribution of floating point precision
  • No far plane clipping artifacts

Because of the reversed depth, shaders must use Greater depth test (higher depth is closer).

To get from Callisto coords to Vulkan coords, the x and y axes must be flipped.

perspective :: proc(fovy, aspect, near: f32) -> (mat: Matrix4x4) {
    ep :: math.pow(2, -20)          // ep is a small value to prevent float rounding errors 
    g := 1 / math.tan(0.5 * fovy)
    
    // Column-major matrix
    //
    // -g/s     0       0       0
    //  0      -g       0       0
    //  0       0       ep      near * (1-ep) 
    //  0       0       1       0

    mat[0, 0] = -g / aspect
    mat[1, 1] = -g
    mat[2, 2] = ep
    mat[2, 3] = 1
    mat[3, 2] = near * (1 - ep)


    return mat
}