example data store
This commit is contained in:
@@ -277,3 +277,4 @@ If `SLANG_ROOT` is not set, the workflow falls back to the repo-local default un
|
|||||||
- Mipmapping for shader-declared textures
|
- Mipmapping for shader-declared textures
|
||||||
- Anotate included shaders
|
- Anotate included shaders
|
||||||
- allow 3 vector exposed controls
|
- allow 3 vector exposed controls
|
||||||
|
- add nearest sampling to the extra shader pass
|
||||||
@@ -219,6 +219,14 @@ Behavior:
|
|||||||
- one designated pass writes the next feedback surface
|
- one designated pass writes the next feedback surface
|
||||||
- feedback is previous-frame state, not same-frame pass chaining
|
- feedback is previous-frame state, not same-frame pass chaining
|
||||||
|
|
||||||
|
Guardrails:
|
||||||
|
|
||||||
|
- Feedback is best suited to image-like state such as trails, masks, luminance fields, decay maps, and shader-local analysis buffers.
|
||||||
|
- Feedback is not a precise long-term data store. The surface uses `RGBA16F`, so repeated accumulation, exact counters, and tightly packed metadata can drift or clamp over time.
|
||||||
|
- The feedback surface is currently filtered like an image, not configured as strict texel-addressed storage. If you reserve texels as data slots, sample them carefully and do not assume exact CPU-style array semantics.
|
||||||
|
- Each feedback-enabled layer allocates two full-resolution feedback textures for ping-pong state. This increases VRAM use and adds one extra full-frame feedback copy per rendered frame.
|
||||||
|
- In multipass shaders, feedback remains previous-frame state even when a pass also consumes same-frame pass outputs. Do not treat feedback as another same-frame intermediate buffer.
|
||||||
|
|
||||||
Single-pass example:
|
Single-pass example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -355,13 +363,16 @@ Color/precision notes:
|
|||||||
The wrapper provides:
|
The wrapper provides:
|
||||||
|
|
||||||
```slang
|
```slang
|
||||||
|
float4 sampleLayerInput(float2 uv);
|
||||||
float4 sampleVideo(float2 uv);
|
float4 sampleVideo(float2 uv);
|
||||||
float4 sampleSourceHistory(int framesAgo, float2 uv);
|
float4 sampleSourceHistory(int framesAgo, float2 uv);
|
||||||
float4 sampleTemporalHistory(int framesAgo, float2 uv);
|
float4 sampleTemporalHistory(int framesAgo, float2 uv);
|
||||||
float4 sampleFeedback(float2 uv);
|
float4 sampleFeedback(float2 uv);
|
||||||
```
|
```
|
||||||
|
|
||||||
`sampleVideo` samples the live decoded source video.
|
`sampleLayerInput` samples the input arriving at this shader layer before any of the layer's own passes run. If this layer follows another shader, it sees that previous shader's output. If this is the first shader layer, it sees the decoded source image.
|
||||||
|
|
||||||
|
`sampleVideo` samples the current pass input texture. In single-pass shaders this is usually the layer input. In multipass shaders it may instead be a named pass output or `previousPass`, depending on the manifest routing for that pass.
|
||||||
|
|
||||||
`sampleSourceHistory` samples previous decoded source frames. `framesAgo` is clamped into the available range. If no history is available, it falls back to `sampleVideo`.
|
`sampleSourceHistory` samples previous decoded source frames. `framesAgo` is clamped into the available range. If no history is available, it falls back to `sampleVideo`.
|
||||||
|
|
||||||
@@ -379,6 +390,17 @@ float4 shadeVideo(ShaderContext context)
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Layer-input example:
|
||||||
|
|
||||||
|
```slang
|
||||||
|
float4 finishPass(ShaderContext context)
|
||||||
|
{
|
||||||
|
float3 baseColor = sampleLayerInput(context.uv).rgb;
|
||||||
|
float3 passResult = context.sourceColor.rgb;
|
||||||
|
return float4(baseColor + passResult * 0.25, 1.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Feedback example:
|
Feedback example:
|
||||||
|
|
||||||
```slang
|
```slang
|
||||||
@@ -413,6 +435,12 @@ In that multipass case:
|
|||||||
- `finishFrame` receives the same-frame pass output through normal multipass routing
|
- `finishFrame` receives the same-frame pass output through normal multipass routing
|
||||||
- the `writePass` decides which pass output becomes next frame's feedback
|
- the `writePass` decides which pass output becomes next frame's feedback
|
||||||
|
|
||||||
|
That means:
|
||||||
|
|
||||||
|
- use `context.sourceColor` or `sampleVideo()` when you want this pass's routed input
|
||||||
|
- use `sampleLayerInput()` when you want the pre-pass layer input
|
||||||
|
- use `sampleFeedback()` when you want previous-frame persistent shader-local state
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
Manifest parameters are exposed to Slang as global values with the same `id`.
|
Manifest parameters are exposed to Slang as global values with the same `id`.
|
||||||
|
|||||||
66
shaders/feedback-data-blocks/shader.json
Normal file
66
shaders/feedback-data-blocks/shader.json
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"id": "feedback-data-blocks",
|
||||||
|
"name": "Feedback Data Blocks",
|
||||||
|
"description": "Demonstrates using the feedback surface as coarse data storage by reserving eight 3x3 texel cells for sampled colors and a hidden metadata cell for timed or trigger-driven refresh state.",
|
||||||
|
"category": "Feedback",
|
||||||
|
"entryPoint": "storeProbeData",
|
||||||
|
"passes": [
|
||||||
|
{
|
||||||
|
"id": "store",
|
||||||
|
"source": "shader.slang",
|
||||||
|
"entryPoint": "storeProbeData",
|
||||||
|
"output": "dataBuffer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "display",
|
||||||
|
"source": "shader.slang",
|
||||||
|
"entryPoint": "displayProbeData",
|
||||||
|
"inputs": [
|
||||||
|
"dataBuffer"
|
||||||
|
],
|
||||||
|
"output": "layerOutput"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"feedback": {
|
||||||
|
"enabled": true,
|
||||||
|
"writePass": "store"
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"id": "refresh",
|
||||||
|
"label": "Refresh",
|
||||||
|
"type": "trigger",
|
||||||
|
"description": "Forces the stored probe colors to resample immediately."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "refreshSeconds",
|
||||||
|
"label": "Refresh Seconds",
|
||||||
|
"type": "float",
|
||||||
|
"default": 15.0,
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 60.0,
|
||||||
|
"step": 0.1,
|
||||||
|
"description": "Automatic interval for resampling all stored probe colors."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "overlayOpacity",
|
||||||
|
"label": "Overlay Opacity",
|
||||||
|
"type": "float",
|
||||||
|
"default": 1.0,
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0,
|
||||||
|
"step": 0.01,
|
||||||
|
"description": "Strength of the swatch overlay drawn from the stored data cells."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "swatchSize",
|
||||||
|
"label": "Swatch Size",
|
||||||
|
"type": "vec2",
|
||||||
|
"default": [0.045, 0.055],
|
||||||
|
"min": [0.02, 0.02],
|
||||||
|
"max": [0.12, 0.12],
|
||||||
|
"step": [0.001, 0.001],
|
||||||
|
"description": "Size of the top-left preview swatches that show the stored cell values."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
152
shaders/feedback-data-blocks/shader.slang
Normal file
152
shaders/feedback-data-blocks/shader.slang
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
static const int kProbeCount = 8;
|
||||||
|
static const int kMetadataIndex = 8;
|
||||||
|
|
||||||
|
float2 probeUvForIndex(int index)
|
||||||
|
{
|
||||||
|
if (index == 0)
|
||||||
|
return float2(0.18, 0.28);
|
||||||
|
if (index == 1)
|
||||||
|
return float2(0.39, 0.28);
|
||||||
|
if (index == 2)
|
||||||
|
return float2(0.61, 0.28);
|
||||||
|
if (index == 3)
|
||||||
|
return float2(0.82, 0.28);
|
||||||
|
if (index == 4)
|
||||||
|
return float2(0.18, 0.72);
|
||||||
|
if (index == 5)
|
||||||
|
return float2(0.39, 0.72);
|
||||||
|
if (index == 6)
|
||||||
|
return float2(0.61, 0.72);
|
||||||
|
return float2(0.82, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 cellCenterPixelForIndex(int index)
|
||||||
|
{
|
||||||
|
return float2(1.0 + float(index) * 3.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 cellCenterUvForIndex(ShaderContext context, int index)
|
||||||
|
{
|
||||||
|
return (cellCenterPixelForIndex(index) + 0.5) / context.outputResolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pixelIsInsideCell(float2 pixelCoord, int index)
|
||||||
|
{
|
||||||
|
float minX = float(index) * 3.0;
|
||||||
|
float maxX = minX + 3.0;
|
||||||
|
return pixelCoord.x >= minX && pixelCoord.x < maxX && pixelCoord.y >= 0.0 && pixelCoord.y < 3.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 readStoredCell(ShaderContext context, int index)
|
||||||
|
{
|
||||||
|
if (context.feedbackAvailable <= 0)
|
||||||
|
return float4(0.0, 0.0, 0.0, 0.0);
|
||||||
|
return sampleFeedback(cellCenterUvForIndex(context, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool shouldRefreshStoredData(ShaderContext context)
|
||||||
|
{
|
||||||
|
if (context.feedbackAvailable <= 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
float4 metadata = readStoredCell(context, kMetadataIndex);
|
||||||
|
float previousRefreshBucket = metadata.r;
|
||||||
|
float previousTriggerCount = metadata.g;
|
||||||
|
float refreshInterval = max(refreshSeconds, 0.001);
|
||||||
|
float currentRefreshBucket = floor(context.time / refreshInterval);
|
||||||
|
float currentTriggerCount = float(refresh);
|
||||||
|
|
||||||
|
return currentRefreshBucket > previousRefreshBucket + 0.5 || currentTriggerCount > previousTriggerCount + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 metadataValueForFrame(ShaderContext context, bool refreshNow)
|
||||||
|
{
|
||||||
|
float refreshInterval = max(refreshSeconds, 0.001);
|
||||||
|
float currentRefreshBucket = floor(context.time / refreshInterval);
|
||||||
|
float currentTriggerCount = float(refresh);
|
||||||
|
|
||||||
|
if (!refreshNow && context.feedbackAvailable > 0)
|
||||||
|
return readStoredCell(context, kMetadataIndex);
|
||||||
|
|
||||||
|
return float4(currentRefreshBucket, currentTriggerCount, refreshTime, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 storedProbeValueForFrame(ShaderContext context, int index, bool refreshNow)
|
||||||
|
{
|
||||||
|
float3 liveColor = sampleLayerInput(probeUvForIndex(index)).rgb;
|
||||||
|
if (refreshNow || context.feedbackAvailable <= 0)
|
||||||
|
return float4(liveColor, 1.0);
|
||||||
|
return readStoredCell(context, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 storeProbeData(ShaderContext context)
|
||||||
|
{
|
||||||
|
// Reserve nine 3x3 texel cells along the top edge of the feedback surface:
|
||||||
|
// eight cells for visible probe colors and one hidden metadata cell that
|
||||||
|
// tracks the timed refresh bucket and last trigger count.
|
||||||
|
float2 pixelCoord = floor(context.uv * context.outputResolution);
|
||||||
|
bool refreshNow = shouldRefreshStoredData(context);
|
||||||
|
|
||||||
|
for (int index = 0; index < kProbeCount; ++index)
|
||||||
|
{
|
||||||
|
if (pixelIsInsideCell(pixelCoord, index))
|
||||||
|
return storedProbeValueForFrame(context, index, refreshNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pixelIsInsideCell(pixelCoord, kMetadataIndex))
|
||||||
|
return metadataValueForFrame(context, refreshNow);
|
||||||
|
|
||||||
|
return float4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float rectMask(float2 uv, float2 minUv, float2 maxUv)
|
||||||
|
{
|
||||||
|
if (uv.x < minUv.x || uv.x > maxUv.x)
|
||||||
|
return 0.0;
|
||||||
|
if (uv.y < minUv.y || uv.y > maxUv.y)
|
||||||
|
return 0.0;
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float borderMask(float2 uv, float2 minUv, float2 maxUv, float thickness)
|
||||||
|
{
|
||||||
|
float outer = rectMask(uv, minUv, maxUv);
|
||||||
|
float inner = rectMask(uv, minUv + thickness, maxUv - thickness);
|
||||||
|
return saturate(outer - inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
float4 displayProbeData(ShaderContext context)
|
||||||
|
{
|
||||||
|
float3 baseColor = sampleLayerInput(context.uv).rgb;
|
||||||
|
float3 swatchColor = baseColor;
|
||||||
|
float swatchMask = 0.0;
|
||||||
|
|
||||||
|
float2 panelOrigin = float2(0.03, 0.04);
|
||||||
|
float2 gap = float2(swatchSize.x + 0.012, swatchSize.y + 0.012);
|
||||||
|
float borderThickness = min(swatchSize.x, swatchSize.y) * 0.08;
|
||||||
|
|
||||||
|
for (int index = 0; index < kProbeCount; ++index)
|
||||||
|
{
|
||||||
|
int column = index % 4;
|
||||||
|
int row = index / 4;
|
||||||
|
float2 swatchMin = panelOrigin + float2(float(column) * gap.x, float(row) * gap.y);
|
||||||
|
float2 swatchMax = swatchMin + swatchSize;
|
||||||
|
float3 storedColor = sampleVideo(cellCenterUvForIndex(context, index)).rgb;
|
||||||
|
float fill = rectMask(context.uv, swatchMin, swatchMax);
|
||||||
|
float outline = borderMask(context.uv, swatchMin, swatchMax, borderThickness);
|
||||||
|
if (fill > 0.5)
|
||||||
|
{
|
||||||
|
swatchColor = storedColor;
|
||||||
|
swatchMask = 1.0;
|
||||||
|
}
|
||||||
|
if (outline > 0.5)
|
||||||
|
{
|
||||||
|
swatchColor = float3(0.0, 0.0, 0.0);
|
||||||
|
swatchMask = 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float opacity = saturate(overlayOpacity) * swatchMask;
|
||||||
|
float3 displayColor = lerp(baseColor, swatchColor, opacity);
|
||||||
|
return float4(saturate(displayColor), 1.0);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user