docs

GPU Shader Programs

Bind stock shader programs, configure vertex layouts, and supply draw constants.

On this page

Ultralight uses seven shader programs to render geometry. Each program pairs a vertex shader with a pixel shader, and the SDK ships precompiled files and source headers for every supported graphics API.

On each draw, your driver binds the requested program and supplies vertices, uniforms, and textures in the format the shaders expect. If you're building on the reference drivers in the SDK's platform folder, they already handle these steps.

Binding Shader Programs

Each draw command specifies its program in GPUState::shader_type. Your driver binds the matching vertex and pixel shader pair before drawing, typically inside a state helper such as MyGPUDriver::ApplyState() from Implementing a GPUDriver.

A conforming driver must support every program in the table— the library doesn't query for per-program support, though it checks other capabilities through GetDeviceCaps().

Program Shaders Vertex Layout Usage
Fill vertex_quad + fill Quad Colors, images, gradients, rounded rectangles, and box shadows
FillPath vertex_path + fill_path Path Tessellated vector paths
FilterBasic vertex_quad + filter_basic Quad Basic CSS and SVG filters
FilterBlur vertex_quad + filter_blur Quad Blur filters
FilterDropShadow vertex_quad + filter_dropshadow Quad Drop-shadow filters
FillPhoton vertex_quad + fill_photon Quad Analytic vector paths and text
FillPhotonGrid vertex_photon_grid + fill_photon_grid Instance Grid-based analytic rendering

Configuring Vertex Layouts

The renderer provides geometry through vertex buffers described by VertexBuffer::format. Each format corresponds to a packed struct with no padding.

Positions use pixel coordinates. The obj field holds object-space coordinates used for anti-aliasing in path and quad geometry.

Although named an instance layout, Vertex_2f_2ui uses standard indexed draw calls without hardware instancing APIs.

Struct Format Fields Size
Vertex_2f_4ub_2f _2f_4ub_2f pos[2] float, color[4] RGBA byte, obj[2] float 20 bytes
Vertex_2f_4ub_2f_2f_28f _2f_4ub_2f_2f_28f pos[2] float, color[4] RGBA byte, tex[2] float, obj[2] float, data0..data6 (4 floats each) 140 bytes
Vertex_2f_2ui _2f_2ui pos[2] float, header_addr uint32, path_slot uint32 16 bytes

🚧 Bind Integer Attributes

The header_addr and path_slot fields in Vertex_2f_2ui are unsigned 32-bit integers. Bind them through the integer attribute path in the graphics API (eg, glVertexAttribIPointer or an R32_UINT input element)— treating them as floating-point attributes causes the GPU to misread their values silently.

Updating the Constant Block

Populate an 800-byte uniform buffer at register 0 for both shader stages using data from GPUState:

C++
///
/// The constant block the stock shaders read, filled from GPUState.
///
struct Uniforms {
  float state[4];         // 0, viewport width, viewport height, 1
  Matrix4x4 transform;    // GPUState::transform times the projection
  int32_t integer[8];     // GPUState::uniform_integer
  float scalar[8];        // GPUState::uniform_scalar
  vec4 vector[8];         // GPUState::uniform_vector
  int32_t clip_size[4];   // x = GPUState::clip_size, the rest 0
  Matrix4x4 clip[8];      // GPUState::clip
};

Copy the matrix data from GPUState::clip directly into the buffer. For the transform field, multiply GPUState::transform by the projection matrix, as described in Implementing a GPUDriver.

Binding Texture Slots

The driver binds textures from GPUState::texture_1_id through texture_3_id to registers 0 through 2, skipping any slot set to 0.

For vector draws with FillPhoton and FillPhotonGrid, the renderer passes raw data pages rather than sampled images. The shaders read these textures directly through texel loads, leaving slot 2 unused.

Slot Data Format Register
texture_1_id Curve page RGBA16F 0
texture_3_id Constants, glyph LUT, and ramp RGBA32F 2
texture_4_id Index and header page RGBA16UI 3

Configure data page textures without filtering or mipmaps. The index page requires an unsigned integer texture view with its own register— it must not share a binding point with floating-point views.

When drawing with FillPhotonGrid, bind the data page textures to both the vertex and pixel shader stages.

Shipped Shaders

The SDK provides stock shaders under platform/shaders/generated/ in formats tailored for each graphics backend.

Folder Contents
headers/d3d11/, headers/d3d12/ Compiled Direct3D bytecode stored in C byte arrays
headers/glsl/ GLSL source code stored in C string headers, compiled at runtime
metal/ Metal Shading Language source code, compiled into libraries at build time
spirv/ SPIR-V binary files, suitable for Vulkan pipelines

SPIR-V Descriptor Bindings

The precompiled SPIR-V shaders place every resource into descriptor set 0 and share a single layout— one descriptor set layout serves every pipeline. Textures bind as separate sampled images, and a single sampler descriptor sits at binding 16.

Binding Descriptor Data Used By
0 Uniform buffer Constant block Every shader
1 Sampled image texture_1_id fill, fill_photon, fill_photon_grid, filter_basic, filter_blur, filter_dropshadow
2 Sampled image texture_2_id fill, filter_basic, filter_dropshadow
3 Sampled image texture_3_id fill, fill_photon, vertex_photon_grid
4 Sampled image (unsigned integer) texture_4_id fill_photon, fill_photon_grid
16 Sampler Texture sampler fill, filter_basic, filter_blur, filter_dropshadow