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_addrandpath_slotfields inVertex_2f_2uiare unsigned 32-bit integers. Bind them through the integer attribute path in the graphics API (eg,glVertexAttribIPointeror anR32_UINTinput 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:
///
/// 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 |