Source code for simvx.core.graphics.shader
"""Shader — pure source code (no GPU dependencies).
Backends (SDL3, Vulkan) handle compilation to GPU code.
"""
from __future__ import annotations
import logging
from dataclasses import dataclass
from pathlib import Path
log = logging.getLogger(__name__)
[docs]
@dataclass
class ShaderResource:
"""Describes a shader resource binding (uniforms, samplers, etc.)."""
name: str
binding: int
type: str # "uniform_buffer", "storage_buffer", "sampler", etc.
size: int = 0
[docs]
class Shader:
"""Shader source code. Backend-agnostic.
Stores GLSL source and metadata. Backends compile to SPIR-V or native GPU code.
Example:
vert_src = "layout(location=0) in vec3 pos; void main() { ... }"
frag_src = "uniform vec4 colour; void main() { ... }"
shader = Shader(vert_src, "vertex")
# Then backend compiles it when rendering
"""
def __init__(
self,
source: str,
stage: str, # "vertex" or "fragment"
entrypoint: str = "main",
defines: dict[str, str | int | bool] | None = None,
):
"""Initialize shader with GLSL source.
Args:
source: GLSL source code (not a file path)
stage: "vertex" or "fragment"
entrypoint: Shader entry point function name
defines: Preprocessor defines {"DEFINE": "value"}
"""
self.source = source
self.stage = stage.lower()
self.entrypoint = entrypoint
self.defines = defines or {}
if self.stage not in ("vertex", "fragment"):
raise ValueError(f"stage must be 'vertex' or 'fragment', got {stage}")
@property
def is_vertex(self) -> bool:
return self.stage == "vertex"
@property
def is_fragment(self) -> bool:
return self.stage == "fragment"
[docs]
def to_dict(self) -> dict:
"""Serialize to dict for storage."""
return {
"__type__": "Shader",
"source": self.source,
"stage": self.stage,
"entrypoint": self.entrypoint,
"defines": self.defines,
}
[docs]
@classmethod
def from_dict(cls, d: dict) -> Shader:
"""Deserialize from dict."""
return cls(
source=d["source"],
stage=d["stage"],
entrypoint=d.get("entrypoint", "main"),
defines=d.get("defines"),
)
[docs]
@classmethod
def from_file(cls, path: str | Path, stage: str) -> Shader:
"""Load GLSL source from file."""
path = Path(path)
source = path.read_text()
return cls(source, stage)