Byte Engine Docs

Interface

Declare BESL stage values, descriptors, shared storage, and stage metadata.

Use the shader interface to connect a BESL program to stage inputs and outputs, GPU resources, shared memory, and the native pipeline contract.

Inputs and outputs

Give each stage input and output a location:

uv: input<vec2f, 0>;
color: output<vec4f, 0>;

Locations range from 0 through 255. Each input location and each output location must be unique in its interface.

Add a positive element count when a mesh stage emits per-primitive values:

out_instance_index: output<u32, 0, 126>;

Task payload and workgroup storage

Task and mesh stages can share payload arrays. Task and compute stages can also declare storage shared by every invocation in one workgroup:

visible_meshlets: task_payload<u32, 32>;
visible_count: workgroup<atomicu32>;
scratch: workgroup<f32, 64>;

Omit the count for one shared value. Add a positive count for shared arrays. Use workgroup_barrier() before reading values written by other invocations.

Push constants

Use push constants for small uniform values outside normal descriptor bindings:

push_constant: push_constant {
	material_index: u32,
}

Descriptors

A descriptor declares a named binding between the shader and one GPU resource. Write the resource type first, followed by its flat slot and access mode:

name: descriptor<ResourceType, SLOT, ACCESS>;

The complete syntax supports an optional resource count. Buffer descriptors can also select their memory class:

name: descriptor<ResourceType, SLOT, ACCESS, COUNT>;
name: descriptor<BufferStruct, SLOT, ACCESS, MEMORY_CLASS>;
name: descriptor<BufferStruct, SLOT, ACCESS, MEMORY_CLASS, COUNT>;

This example declares buffer, texture, texture-array, and storage-image bindings:

Frame: struct {
	view_projection: mat4f,
}

VertexData: struct {
	positions: vec3f[1024],
}

frame: descriptor<Frame, 0, read, constant>;
vertices: descriptor<VertexData, 1, read, device>;
albedo: descriptor<Texture2D, 2, read>;
shadow_maps: descriptor<Texture2DArray, 3, read, 4>;
output_image: descriptor<StorageImage<rgba16f>, 7, write>;

The descriptor resource type selects the reflected binding kind. BESL supports these resource types:

Resource typeBindingUse
A previously declared struct, such as FrameStorage bufferBind structured data. Declare the struct before the descriptor.
Texture2DCombined 2D texture and samplerSample one two-dimensional texture.
Texture2DArrayCombined 2D-array texture and samplerSample layers from one two-dimensional texture array.
Texture3DCombined 3D texture and samplerSample a three-dimensional texture.
TextureCubeCombined cube texture and samplerSample one cube texture.
TextureCubeArrayCombined cube-array texture and samplerSample cube textures from an array.
StorageImageStorage imageLoad or store an image whose format remains backend-defined.
StorageImage<FORMAT>Typed storage imageLoad or store an image with an explicit backend image format, such as r8, r32f, r32ui, or rgba16f.

Set ACCESS to read, write, or read_write. The access must include every operation the shader performs on the resource.

For a buffer, set MEMORY_CLASS to constant for small, read-only values shared by a dispatch or draw. Use device for large buffers, data that varies between shader threads, and every writable buffer. If you omit the memory class, a buffer uses device. Image and texture descriptors cannot declare a memory class.

Set COUNT to a positive integer to declare an array of resources. When a descriptor has no buffer memory class, the count is the fourth argument. When a buffer descriptor declares its memory class, the count is the fifth argument.

Reflection retains the authored name and flat slot for each descriptor reachable from main.

Storage-buffer counters use atomicu32 members with atomic_add, atomic_compare_exchange, atomic_load, and atomic_store. Integer storage images can be read as scalars with image_load_u32:

Counters: struct {
	values: atomicu32[1024],
}

counters: descriptor<Counters, 5, read_write, device>;
index_image: descriptor<StorageImage<r32ui>, 6, read>;

Standalone stage metadata

Standalone .besl assets require an adjacent .besl.bead file. Set stage to Vertex, Fragment, Compute, Task, or Mesh.

Compute, task, and mesh stages can set workgroup with up to three positive dimensions. Omitted dimensions default to 1, and omitting workgroup uses [1, 1, 1].

{ "stage": "Compute", "workgroup": [8, 8, 1] }

You can also name the dimensions. This JSON5 form is equivalent to [8, 8, 1]:

{ stage: "Compute", workgroup: { width: 8, height: 8 } }

Task stages require a positive maximum_mesh_threadgroups value:

{ "stage": "Task", "workgroup": [32, 1, 1], "maximum_mesh_threadgroups": 32 }

Mesh stages require positive maximum_vertices and maximum_primitives values:

{ "stage": "Mesh", "workgroup": [128, 1, 1], "maximum_vertices": 64, "maximum_primitives": 126 }

Vertex and Fragment stages don't accept workgroup or mesh limits.

Stage and backend support

The normal generators support these stage and output-language combinations:

StageGLSLHLSLMSL
VertexSupportedSupportedSupported
FragmentSupportedSupportedSupported
ComputeSupportedSupportedSupported
TaskNot supportedSupported as an amplification shaderSupported as an object stage
MeshSupported through GL_EXT_mesh_shaderSupportedSupported

The generated language support doesn't guarantee that a device exposes the required shader stage or extension. Half-precision and subgroup operations also require their target capabilities. BESL validates subgroup intrinsics for compute stages only; see compute-stage intrinsics.

Next, use BESL intrinsics to read and update the values declared by the interface.

On this page