Skip to main content

Python shader composition library with GLSL backend

Project description

shadekit

A Python library for composing GLSL shaders programmatically. Instead of writing shader source as raw strings, you describe them using Python — with a type system that maps directly to GLSL, an AST built through operator overloading, and a high-level Program API that handles stage assembly.

It targets GLSL 4.30 core by default and has no runtime dependencies. Python 3.12+ required.


Quick start

from shadekit.glsl import Program
from shadekit.types import Vec3, Vec4, Mat4, Sampler2D, Float
from shadekit.glsl import vec4, texture, dot, normalize
from shadekit.decorators import shader_function

# A reusable helper defined once, referenced in any stage
@shader_function
def luminance(c: Vec3) -> Float:
    return dot(c, vec4(0.299, 0.587, 0.114, 0.0).xyz)

prog = Program()

u_mvp   = prog.uniform("u_mvp",   Mat4,      stage="vertex")
u_tex   = prog.uniform("u_albedo", Sampler2D, stage="fragment", binding=0)
pos     = prog.vertex_input(0, "Pos", Vec3)
v_uv    = prog.varying("v_uv", Vec3)
f_color = prog.output(0, "f_color", Vec4)

@prog.vertex
def vs():
    return [
        Assignment(v_uv,          pos),
        Assignment("gl_Position", u_mvp * vec4(pos, 1.0)),
    ]

@prog.fragment
def fs():
    sample = texture(u_tex, v_uv)
    lum    = luminance(sample.xyz)
    return [
        Assignment(f_color, vec4(sample.xyz * lum, 1.0)),
        Return(Void),
    ]

vert_src, frag_src = prog.build()

prog.build() returns a pair of Stage objects. They're just strings with a .kind attribute and a .save(path) convenience method, so you can pass them directly to your OpenGL bindings.


Types

Every GLSL type is a Python class. They're not meant to be instantiated — they're used as type annotations and carry metadata that drives code generation.

Scalars: Float, Double, Int, UInt, Bool

Vectors: Vec2/3/4, DVec2/3/4, IVec2/3/4, UVec2/3/4, BVec2/3/4

Matrices: Mat2, Mat3, Mat4

Samplers and images: the full set from GLSL 4.30 — Sampler2D, Sampler3D, SamplerCube, shadow samplers, integer and unsigned variants, image types (Image2D, IImage2D, etc.), and AtomicUint.

Arrays:

from shadekit.types import ArrayType, Float
FloatArray256 = ArrayType(Float, 256)  # → "float[256]"

Structs:

from dataclasses import dataclass
from shadekit.types import Vec3, Float
from shadekit.decorators import shader_struct

@shader_struct
@dataclass
class Material:
    albedo: Vec3
    roughness: Float

# Material.__glsl_declaration__ → "struct Material { vec3 albedo; float roughness; };"

Shader functions

@shader_function turns a Python function into a reusable GLSL function. The body runs at decoration time with Variable placeholder arguments, and the result is captured as an AST.

from shadekit.decorators import shader_function
from shadekit.types import Vec3, Float
from shadekit.glsl import dot, normalize, clamp

@shader_function
def diffuse(normal: Vec3, light_dir: Vec3) -> Float:
    n = normalize(normal)
    l = normalize(light_dir)
    return clamp(dot(n, l), 0.0, 1.0)

diffuse.signature()
# → "float diffuse(vec3 normal, vec3 light_dir)"

Calling the decorated function produces a FunctionCall AST node, not a Python value. Dependencies between shader functions are detected automatically — if diffuse calls another @shader_function, Program.build() will include it and sort them in the right order.


AST

You generally don't need to build AST nodes by hand — the builtins and constructors do it for you. But when you need to, the full node set is available.

from shadekit.ast import Variable, Literal, BinaryOp, If, For, Assignment, Declaration

x = Variable("x", Float)
y = Variable("y", Float)

# Operator overloading builds the tree
expr = x * y + Literal(1.0, Float)   # BinaryOp(+, BinaryOp(*, x, y), Literal(1.0))

# Equality uses .eq() / .ne() — not == / != — because those have Python semantics
check = x.eq(y)   # BinaryOp(==, x, y)

# Control flow
branch = If(
    condition=x.gt(Literal(0.0, Float)),
    body=[Assignment(y, x)],
    else_body=[Assignment(y, -x)],
)

Supported statement types: Declaration, Assignment, CompoundAssignment, Return, Discard, If, For, While, DoWhile, Switch, Break, Continue, ExpressionStatement.


Built-ins

All standard GLSL built-in functions are available from shadekit.glsl:

from shadekit.glsl import (
    normalize, dot, cross, reflect, refract,
    texture, texelFetch,
    mix, clamp, smoothstep, step,
    sin, cos, atan, pow, sqrt, log2,
    abs, floor, ceil, fract, mod, sign,
    transpose, inverse, determinant,
    atomicAdd, barrier, memoryBarrier,
    dFdx, dFdy,
    # ... and many more
)

Constructors:

from shadekit.glsl import vec2, vec3, vec4, mat4, ivec2, uvec4

Program API

Program is the main entry point for full shader programs. It tracks uniforms, inputs, outputs, varyings, SSBOs, structs, and compute resources across stages, then assembles everything into valid GLSL.

prog = Program(version="430", profile="core")

# Extensions and defines
prog.extension("GL_ARB_gpu_shader5", "enable")
prog.define("MAX_LIGHTS", 8)
prog.define("HAS_NORMALS", stage="vertex")

# Vertex inputs
pos    = prog.vertex_input(0, "Pos",    Vec3)
normal = prog.vertex_input(1, "Normal", Vec3)

# Uniforms
u_mvp = prog.uniform("u_mvp",  Mat4,      stage="vertex")
u_tex = prog.uniform("u_tex",  Sampler2D, stage="fragment", binding=0)

# Varyings (between vertex and fragment)
v_normal  = prog.varying("v_normal", Vec3)
v_id      = prog.varying("v_id",     Int,  flat=True)
v_uv      = prog.varying("v_uv",     Vec2, noperspective=True)

# Fragment output
f_color = prog.output(0, "f_color", Vec4)

# SSBO
prog.storage(0, "Positions", "vec4 data[];", readonly=True, stage="vertex")

# Struct
prog.struct(Material.__glsl_type__, stage="fragment")

vert_src, frag_src = prog.build()

For compute shaders:

shared_data = prog.shared("s_data", ArrayType(Float, 256))
prog.local_size(256)          # 1D
# prog.local_size(16, 16)     # 2D
compute_src = prog.build_compute()

You can also load existing GLSL and layer defines on top of it:

prog = Program.from_glsl(existing_vert_src, existing_frag_src)
prog.define("ENABLE_SHADOWS")
vert, frag = prog.build()

Compiler utilities

A few lower-level tools are exposed if you need them directly:

from shadekit.compiler import (
    fold_constants,            # constant folding pass over an AST
    eliminate_dead_functions,  # dead code elimination
    DependencyGraph,           # topological sort of @shader_function deps
    ShaderCache,               # hash-based shader source cache
    collect_transitive_deps,   # walk all transitive ShaderFunction dependencies
)

These run automatically inside Program.build(). You'd use them directly only if you're working at the ShaderBuilder level or writing your own pipeline.


ShaderBuilder

ShaderBuilder is the lower-level API that Program wraps. Use it if you need direct control over what goes into each stage.

from shadekit.glsl import ShaderBuilder, ShaderStage

b = ShaderBuilder(version="430", profile="core")
b.add_define("FOO", stage=ShaderStage.VERTEX)
b.add_uniform("mat4", "u_mvp", ShaderStage.VERTEX)
b.add_varying("vec3", "v_normal")
b.add_vertex_lines(["void main() {", "  gl_Position = vec4(0.0);", "}"])

vert_src, frag_src = b.build()

ShaderStage is an IntFlag: VERTEX, FRAGMENT, BOTH, COMPUTE.


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

shadekit-0.1.0.tar.gz (72.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

shadekit-0.1.0-py3-none-any.whl (55.9 kB view details)

Uploaded Python 3

File details

Details for the file shadekit-0.1.0.tar.gz.

File metadata

  • Download URL: shadekit-0.1.0.tar.gz
  • Upload date:
  • Size: 72.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for shadekit-0.1.0.tar.gz
Algorithm Hash digest
SHA256 04b7bc32fb1273dc66c5eebee9dc098cd32233b61c98e46e93df9c9a9c29c0ac
MD5 1281b602eff142c646e6cff8df3f0e30
BLAKE2b-256 a37d6e68d3eafa23c332573ad62f6828ec9d3f14699251bfd74d9b2836a2fb14

See more details on using hashes here.

Provenance

The following attestation bundles were made for shadekit-0.1.0.tar.gz:

Publisher: publish.yml on calebbuffa/shadekit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file shadekit-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: shadekit-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 55.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for shadekit-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9e9512c0fada6a3e423a1427a919ad43f5af2c269729378ebb54a21e92e60f3b
MD5 07db83e537181e06c08dac7f607e60b3
BLAKE2b-256 f76c74439cb1550e23d3953222d422862da9c2b511c46282e2ba8dcdf7073be7

See more details on using hashes here.

Provenance

The following attestation bundles were made for shadekit-0.1.0-py3-none-any.whl:

Publisher: publish.yml on calebbuffa/shadekit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page