Added xyla shader
This commit is contained in:
@@ -252,4 +252,6 @@ If neither variable is set, the workflow falls back to the repo-local defaults u
|
|||||||
- Continue source cleanup/refactoring. Pass 2 done
|
- Continue source cleanup/refactoring. Pass 2 done
|
||||||
- Support a separate sound shader `.slang` file in shader packages. (https://www.shadertoy.com/view/XsBXWt)
|
- Support a separate sound shader `.slang` file in shader packages. (https://www.shadertoy.com/view/XsBXWt)
|
||||||
- Add WebView2
|
- Add WebView2
|
||||||
- move to MSDF, typography rasterisation
|
- move to MSDF, typography rasterisation
|
||||||
|
- better shader search UI
|
||||||
|
- LUT applicator
|
||||||
|
|||||||
@@ -57,6 +57,31 @@ float4 shadeVideo(ShaderContext context)
|
|||||||
|
|
||||||
With `autoReload` enabled in `config/runtime-host.json`, edits to shader source, manifests, and declared texture assets are picked up automatically.
|
With `autoReload` enabled in `config/runtime-host.json`, edits to shader source, manifests, and declared texture assets are picked up automatically.
|
||||||
|
|
||||||
|
## Guidance For Shaders
|
||||||
|
|
||||||
|
When generating a new shader package, prefer matching the existing runtime contract over copying code verbatim from Shadertoy, GLSL sandbox sites, or WebGL demos.
|
||||||
|
|
||||||
|
Important rules:
|
||||||
|
|
||||||
|
- Generate a complete package: `shaders/<id>/shader.json` and `shaders/<id>/shader.slang`.
|
||||||
|
- Use `float4 shadeVideo(ShaderContext context)` unless the manifest explicitly sets a different `entryPoint`.
|
||||||
|
- Do not create `mainImage`, `main`, `fragColor`, `iResolution`, `iTime`, `iChannel0`, or a fragment shader attribute layout. The runtime wrapper provides the real fragment entry point.
|
||||||
|
- Replace Shadertoy `fragCoord` with `context.uv * context.outputResolution`.
|
||||||
|
- Replace `iResolution.xy` with `context.outputResolution`.
|
||||||
|
- Replace `iTime` with `context.time`.
|
||||||
|
- Replace `iFrame` with `context.frameCount`.
|
||||||
|
- Replace source-video `iChannel0` sampling with `sampleVideo(uv)` or `context.sourceColor`.
|
||||||
|
- Use Slang/HLSL names and syntax: `float2`, `float3`, `float4`, `float2x2`, `lerp`, `frac`, `saturate`, and `mul(matrix, vector)`.
|
||||||
|
- Do not use GLSL-only types/functions such as `vec2`, `vec3`, `vec4`, `mat2`, `mix`, `fract`, `mod`, `texture`, or `mainImage`.
|
||||||
|
- Keep parameter IDs, texture IDs, font IDs, and function entry points as valid shader identifiers: letters, numbers, and underscores only, starting with a letter or underscore.
|
||||||
|
- Add only controls that are actually used by the shader.
|
||||||
|
- Prefer a small number of clear controls with conservative defaults.
|
||||||
|
- Keep shaders deterministic unless randomness is an explicit feature. For stable pseudo-randomness, hash from `uv`, pixel coordinates, `frameCount`, or trigger values rather than using unavailable global state.
|
||||||
|
- If adapting third-party code, include attribution and source URL in the manifest description when the license allows adaptation.
|
||||||
|
- If the source license is unclear or incompatible, do not add the shader package.
|
||||||
|
|
||||||
|
Before finishing, compile-check the shader through the runtime wrapper or launch the app and verify the shader appears without an error in the selector.
|
||||||
|
|
||||||
## Manifest Fields
|
## Manifest Fields
|
||||||
|
|
||||||
`shader.json` is the runtime-facing description of the shader.
|
`shader.json` is the runtime-facing description of the shader.
|
||||||
@@ -460,6 +485,19 @@ See `shaders/temporal-ghost-trail/` and `shaders/temporal-low-fps/` for examples
|
|||||||
- Use `context.inputResolution` when sampling source video by input pixel size.
|
- Use `context.inputResolution` when sampling source video by input pixel size.
|
||||||
- `sourceColor` and `sampleVideo` return RGBA values in normalized `0..1` range.
|
- `sourceColor` and `sampleVideo` return RGBA values in normalized `0..1` range.
|
||||||
- Prefer `saturate(color)` or explicit `clamp` before returning if your math can overshoot.
|
- Prefer `saturate(color)` or explicit `clamp` before returning if your math can overshoot.
|
||||||
|
- For generated calibration charts, test patterns, gradients, and exposure ramps, state whether patch values are linear-light, display-referred gamma encoded, Rec.709 encoded, or intentionally artistic.
|
||||||
|
- For one-stop exposure patches, each patch should normally be `baseLevel * 2^patchIndex` before any display/tone encoding.
|
||||||
|
- For Rec.709 OETF encoding, use:
|
||||||
|
|
||||||
|
```slang
|
||||||
|
float rec709Oetf(float linearLevel)
|
||||||
|
{
|
||||||
|
float value = saturate(linearLevel);
|
||||||
|
if (value < 0.018)
|
||||||
|
return 4.5 * value;
|
||||||
|
return 1.099 * pow(value, 0.45) - 0.099;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Pixel-size example:
|
Pixel-size example:
|
||||||
|
|
||||||
@@ -468,6 +506,28 @@ float2 pixel = 1.0 / max(context.outputResolution, float2(1.0));
|
|||||||
float4 right = sampleVideo(context.uv + float2(pixel.x, 0.0));
|
float4 right = sampleVideo(context.uv + float2(pixel.x, 0.0));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Animation And Timing Notes
|
||||||
|
|
||||||
|
- `context.time` is elapsed runtime time in seconds and is the default animation source for generative shaders.
|
||||||
|
- `context.frameCount` increments once per rendered output frame and is useful when an effect must be frame-locked.
|
||||||
|
- Avoid expensive CPU-like timing logic in the shader; animation should usually be a simple function of `context.time`, `context.frameCount`, trigger uniforms, or parameters.
|
||||||
|
- If a shader appears to judder only while animated, first test whether freezing its time removes the issue. That usually separates animation cadence issues from rendering or transfer issues.
|
||||||
|
- Do not add custom timer uniforms to the wrapper. Use the fields already in `ShaderContext`.
|
||||||
|
|
||||||
|
## Performance Notes
|
||||||
|
|
||||||
|
The app has to meet a fixed video frame cadence, so avoid shader code that only looks good in unconstrained browser demos.
|
||||||
|
|
||||||
|
Guidelines:
|
||||||
|
|
||||||
|
- Keep loops bounded with compile-time constants where possible.
|
||||||
|
- Avoid very high per-pixel raymarch counts by default. If a heavy loop is needed, expose a quality/steps control with a safe default.
|
||||||
|
- Prefer early exits only when they are simple; highly divergent branches can be expensive across a full frame.
|
||||||
|
- Avoid repeated texture sampling in large loops unless the effect really needs it.
|
||||||
|
- Use `context.outputResolution` carefully. A 1080p frame is over 2 million fragments; a tiny extra loop can become expensive.
|
||||||
|
- The UI render time may measure CPU command submission rather than true GPU execution time, so visual frame issues can still be GPU-related even when reported render time is small.
|
||||||
|
- Do not write debug files, allocate resources, or assume CPU-side work can happen from `shader.slang`. Shader code is GPU-only.
|
||||||
|
|
||||||
## Reload And Generated Files
|
## Reload And Generated Files
|
||||||
|
|
||||||
When a shader compiles, the runtime writes generated files under `runtime/shader_cache/`:
|
When a shader compiles, the runtime writes generated files under `runtime/shader_cache/`:
|
||||||
|
|||||||
@@ -41,15 +41,6 @@
|
|||||||
"max": 3.0,
|
"max": 3.0,
|
||||||
"step": 0.01
|
"step": 0.01
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "pixelFilter",
|
|
||||||
"label": "Pixel Filter",
|
|
||||||
"type": "float",
|
|
||||||
"default": 745.0,
|
|
||||||
"min": 120.0,
|
|
||||||
"max": 1600.0,
|
|
||||||
"step": 1.0
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "contrast",
|
"id": "contrast",
|
||||||
"label": "Contrast",
|
"label": "Contrast",
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
float4 balatroSwirl(float2 screenSize, float2 screenCoords, float time)
|
float4 balatroSwirl(float2 screenSize, float2 screenCoords, float time)
|
||||||
{
|
{
|
||||||
const float pi = 3.14159265359;
|
const float pi = 3.14159265359;
|
||||||
float safePixelFilter = max(pixelFilter, 1.0);
|
|
||||||
float safeScreenLength = max(length(screenSize), 1.0);
|
float safeScreenLength = max(length(screenSize), 1.0);
|
||||||
float pixelSize = safeScreenLength / safePixelFilter;
|
float2 uv = (screenCoords - 0.5 * screenSize) / safeScreenLength - offset;
|
||||||
float2 uv = (floor(screenCoords * (1.0 / pixelSize)) * pixelSize - 0.5 * screenSize) / safeScreenLength - offset;
|
|
||||||
float uvLength = length(uv);
|
float uvLength = length(uv);
|
||||||
|
|
||||||
float speed = spinRotation * spinEase * 0.2;
|
float speed = spinRotation * spinEase * 0.2;
|
||||||
|
|||||||
122
shaders/xyla-exposure-chart/shader.json
Normal file
122
shaders/xyla-exposure-chart/shader.json
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"id": "xyla-exposure-chart",
|
||||||
|
"name": "XYLA Exposure Chart",
|
||||||
|
"description": "Procedural grayscale exposure chart inspired by XYLA-style dynamic range charts, with each patch one stop brighter than the previous.",
|
||||||
|
"category": "Calibration",
|
||||||
|
"entryPoint": "shadeVideo",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"id": "patchCount",
|
||||||
|
"label": "Patch Count",
|
||||||
|
"type": "float",
|
||||||
|
"default": 15.0,
|
||||||
|
"min": 2.0,
|
||||||
|
"max": 21.0,
|
||||||
|
"step": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "baseLevel",
|
||||||
|
"label": "Base Level",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.00006103515625,
|
||||||
|
"min": 0.000001,
|
||||||
|
"max": 0.01,
|
||||||
|
"step": 0.000001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "peakLevel",
|
||||||
|
"label": "Peak Level",
|
||||||
|
"type": "float",
|
||||||
|
"default": 1.0,
|
||||||
|
"min": 0.01,
|
||||||
|
"max": 1.0,
|
||||||
|
"step": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gammaEncode",
|
||||||
|
"label": "Display Gamma",
|
||||||
|
"type": "float",
|
||||||
|
"default": 1.0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 2.6,
|
||||||
|
"step": 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "toneCurve",
|
||||||
|
"label": "Tone Curve",
|
||||||
|
"type": "enum",
|
||||||
|
"default": "rec709",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "linear",
|
||||||
|
"label": "Linear"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "gamma",
|
||||||
|
"label": "Display Gamma"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "rec709",
|
||||||
|
"label": "Rec.709"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "chartScale",
|
||||||
|
"label": "Chart Scale",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.86,
|
||||||
|
"min": 0.25,
|
||||||
|
"max": 1.0,
|
||||||
|
"step": 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gapSize",
|
||||||
|
"label": "Gap Size",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.18,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 0.45,
|
||||||
|
"step": 0.01
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vertical",
|
||||||
|
"label": "Vertical",
|
||||||
|
"type": "bool",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reverseOrder",
|
||||||
|
"label": "Reverse Order",
|
||||||
|
"type": "bool",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "backgroundLevel",
|
||||||
|
"label": "Background",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 0.2,
|
||||||
|
"step": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "borderLevel",
|
||||||
|
"label": "Border",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.08,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0,
|
||||||
|
"step": 0.001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sourceMix",
|
||||||
|
"label": "Source Mix",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0,
|
||||||
|
"step": 0.01
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
78
shaders/xyla-exposure-chart/shader.slang
Normal file
78
shaders/xyla-exposure-chart/shader.slang
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
float boxMask(float2 point, float2 halfSize, float feather)
|
||||||
|
{
|
||||||
|
float2 distanceToEdge = abs(point) - halfSize;
|
||||||
|
float outsideDistance = length(max(distanceToEdge, float2(0.0, 0.0)));
|
||||||
|
float insideDistance = min(max(distanceToEdge.x, distanceToEdge.y), 0.0);
|
||||||
|
float signedDistance = outsideDistance + insideDistance;
|
||||||
|
return 1.0 - smoothstep(0.0, max(feather, 0.00001), signedDistance);
|
||||||
|
}
|
||||||
|
|
||||||
|
float rec709Oetf(float linearLevel)
|
||||||
|
{
|
||||||
|
float value = saturate(linearLevel);
|
||||||
|
if (value < 0.018)
|
||||||
|
return 4.5 * value;
|
||||||
|
return 1.099 * pow(value, 0.45) - 0.099;
|
||||||
|
}
|
||||||
|
|
||||||
|
float applyToneCurve(float linearLevel)
|
||||||
|
{
|
||||||
|
float value = saturate(linearLevel);
|
||||||
|
if (toneCurve == 1)
|
||||||
|
{
|
||||||
|
float safeGamma = max(gammaEncode, 0.001);
|
||||||
|
return pow(value, 1.0 / safeGamma);
|
||||||
|
}
|
||||||
|
if (toneCurve == 2)
|
||||||
|
return rec709Oetf(value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
float patchBrightness(int patchIndex, int count)
|
||||||
|
{
|
||||||
|
int clampedIndex = clamp(patchIndex, 0, max(count - 1, 0));
|
||||||
|
float linearLevel = baseLevel * exp2(float(clampedIndex));
|
||||||
|
linearLevel = min(linearLevel, peakLevel);
|
||||||
|
return applyToneCurve(linearLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 shadeVideo(ShaderContext context)
|
||||||
|
{
|
||||||
|
float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
|
||||||
|
float2 uv = saturate(context.uv);
|
||||||
|
float2 centered = uv - 0.5;
|
||||||
|
float feather = 1.5 / min(resolution.x, resolution.y);
|
||||||
|
|
||||||
|
int count = int(clamp(round(patchCount), 2.0, 21.0));
|
||||||
|
float2 chartHalfSize = vertical
|
||||||
|
? float2(0.18, 0.46) * chartScale
|
||||||
|
: float2(0.46, 0.18) * chartScale;
|
||||||
|
float chartMask = boxMask(centered, chartHalfSize, feather);
|
||||||
|
float borderMask = chartMask - boxMask(centered, max(chartHalfSize - float2(feather * 3.0, feather * 3.0), float2(0.0, 0.0)), feather);
|
||||||
|
|
||||||
|
float axis = vertical ? centered.y : centered.x;
|
||||||
|
float crossAxis = vertical ? centered.x : centered.y;
|
||||||
|
float axisHalfSize = vertical ? chartHalfSize.y : chartHalfSize.x;
|
||||||
|
float crossHalfSize = vertical ? chartHalfSize.x : chartHalfSize.y;
|
||||||
|
float normalizedAxis = (axis + axisHalfSize) / max(axisHalfSize * 2.0, 0.0001);
|
||||||
|
float patchPosition = clamp(normalizedAxis, 0.0, 0.999999) * float(count);
|
||||||
|
int patchIndex = int(floor(patchPosition));
|
||||||
|
if (reverseOrder)
|
||||||
|
patchIndex = count - 1 - patchIndex;
|
||||||
|
|
||||||
|
float patchSlotCenter = (floor(patchPosition) + 0.5) / float(count);
|
||||||
|
float localAxis = abs(normalizedAxis - patchSlotCenter) * float(count) * 2.0;
|
||||||
|
float safeGapSize = saturate(gapSize);
|
||||||
|
float axisMask = 1.0 - smoothstep(1.0 - safeGapSize, 1.0 - safeGapSize + feather * float(count) * 2.0, localAxis);
|
||||||
|
float crossMask = 1.0 - smoothstep(crossHalfSize, crossHalfSize + feather, abs(crossAxis));
|
||||||
|
float insideAxis = step(0.0, normalizedAxis) * step(normalizedAxis, 1.0);
|
||||||
|
float patchMask = axisMask * crossMask * insideAxis;
|
||||||
|
|
||||||
|
float level = patchBrightness(patchIndex, count);
|
||||||
|
float chartBackground = saturate(backgroundLevel) * chartMask;
|
||||||
|
float border = saturate(borderLevel) * borderMask;
|
||||||
|
float grayscale = max(max(chartBackground, border), level * patchMask);
|
||||||
|
|
||||||
|
float4 chartColor = float4(grayscale, grayscale, grayscale, 1.0);
|
||||||
|
return saturate(lerp(chartColor, context.sourceColor, sourceMix));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user