shader / harness.py
tejadhith's picture
Upload folder using huggingface_hub
67f71c2 verified
"""
Subprocess-isolated shader rendering harness.
Runs each render in a separate process to isolate from driver crashes,
infinite loops, and GPU state corruption.
Usage:
from envs.shader.harness import render
result = render("void mainImage(out vec4 c, in vec2 f) { c = vec4(1,0,0,1); }")
print(result.compiled, result.rendered)
"""
import json
import subprocess
import sys
from base64 import b64decode
from dataclasses import dataclass, field
from pathlib import Path
RENDER_SCRIPT = str(Path(__file__).parent / "render.py")
@dataclass
class RenderResult:
"""Result of a shader render attempt."""
compiled: bool = False
rendered: bool = False
errors: list[str] = field(default_factory=list)
frame: bytes | None = None
width: int = 0
height: int = 0
timed_out: bool = False
def render(code, resolution=(1280, 720), time=0.0, frame=0,
timeout=10.0, mouse=(0.0, 0.0, 0.0, 0.0)):
"""
Render a Shadertoy GLSL shader in an isolated subprocess.
Args:
code: Shadertoy-dialect GLSL fragment shader source
resolution: (width, height) in pixels
time: iTime value in seconds
frame: iFrame value
timeout: max seconds to wait for the subprocess
mouse: (x, y, click_x, click_y) for iMouse
Returns:
RenderResult with compilation/render status, errors, and pixel data
"""
request = json.dumps({
"code": code,
"resolution": list(resolution),
"time": time,
"frame": frame,
"mouse": list(mouse),
})
try:
proc = subprocess.run(
[sys.executable, RENDER_SCRIPT],
input=request,
capture_output=True,
text=True,
timeout=timeout,
)
except subprocess.TimeoutExpired:
return RenderResult(
timed_out=True,
errors=["subprocess timed out"],
width=resolution[0], height=resolution[1],
)
if proc.returncode != 0:
stderr = proc.stderr.strip() if proc.stderr else "unknown error"
return RenderResult(
errors=[f"subprocess crashed (exit {proc.returncode}): {stderr}"],
width=resolution[0], height=resolution[1],
)
try:
data = json.loads(proc.stdout)
except (json.JSONDecodeError, ValueError):
return RenderResult(
errors=["failed to parse subprocess output"],
width=resolution[0], height=resolution[1],
)
frame_bytes = None
if data.get("frame"):
frame_bytes = b64decode(data["frame"])
return RenderResult(
compiled=data.get("compiled", False),
rendered=data.get("rendered", False),
errors=data.get("errors", []),
frame=frame_bytes,
width=data.get("width", resolution[0]),
height=data.get("height", resolution[1]),
)