added new layer input pass
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m25s
CI / Windows Release Package (push) Successful in 2m28s

This commit is contained in:
Aiden
2026-05-10 21:00:34 +10:00
parent 7777cfc194
commit 944773c248
9 changed files with 84 additions and 41 deletions

View File

@@ -188,6 +188,7 @@ std::vector<RenderPassDescriptor> OpenGLRenderPass::BuildLayerPassDescriptors(
pass.passId = passProgram.passId; pass.passId = passProgram.passId;
pass.layerId = state.layerId; pass.layerId = state.layerId;
pass.shaderId = state.shaderId; pass.shaderId = state.shaderId;
pass.layerInputTexture = layerInputTexture;
pass.sourceTexture = passSourceTexture; pass.sourceTexture = passSourceTexture;
pass.sourceFramebuffer = passIndex == 0 ? layerInputFramebuffer : passSourceFramebuffer; pass.sourceFramebuffer = passIndex == 0 ? layerInputFramebuffer : passSourceFramebuffer;
pass.destinationTexture = passDestinationTexture; pass.destinationTexture = passDestinationTexture;
@@ -226,6 +227,7 @@ void OpenGLRenderPass::RenderLayerPass(
return; return;
RenderShaderProgram( RenderShaderProgram(
pass.layerInputTexture,
pass.sourceTexture, pass.sourceTexture,
pass.destinationFramebuffer, pass.destinationFramebuffer,
*pass.passProgram, *pass.passProgram,
@@ -243,6 +245,7 @@ void OpenGLRenderPass::RenderLayerPass(
} }
void OpenGLRenderPass::RenderShaderProgram( void OpenGLRenderPass::RenderShaderProgram(
GLuint layerInputTexture,
GLuint sourceTexture, GLuint sourceTexture,
GLuint destinationFrameBuffer, GLuint destinationFrameBuffer,
PassProgram& passProgram, PassProgram& passProgram,
@@ -267,7 +270,7 @@ void OpenGLRenderPass::RenderShaderProgram(
const std::vector<GLuint> temporalHistoryTextures = mRenderer.TemporalHistory().ResolveTemporalHistoryTextures(state, sourceTexture, state.isTemporal ? historyCap : 0); const std::vector<GLuint> temporalHistoryTextures = mRenderer.TemporalHistory().ResolveTemporalHistoryTextures(state, sourceTexture, state.isTemporal ? historyCap : 0);
const GLuint feedbackTexture = mRenderer.FeedbackBuffers().ResolveReadTexture(state); const GLuint feedbackTexture = mRenderer.FeedbackBuffers().ResolveReadTexture(state);
const ShaderTextureBindings::RuntimeTextureBindingPlan texturePlan = const ShaderTextureBindings::RuntimeTextureBindingPlan texturePlan =
mTextureBindings.BuildLayerRuntimeBindingPlan(passProgram, sourceTexture, state, feedbackTexture, sourceHistoryTextures, temporalHistoryTextures); mTextureBindings.BuildLayerRuntimeBindingPlan(passProgram, sourceTexture, layerInputTexture, state, feedbackTexture, sourceHistoryTextures, temporalHistoryTextures);
mTextureBindings.BindRuntimeTexturePlan(texturePlan); mTextureBindings.BindRuntimeTexturePlan(texturePlan);
glBindVertexArray(mRenderer.FullscreenVertexArray()); glBindVertexArray(mRenderer.FullscreenVertexArray());
glUseProgram(passProgram.program); glUseProgram(passProgram.program);

View File

@@ -44,6 +44,7 @@ private:
const TextBindingUpdater& updateTextBinding, const TextBindingUpdater& updateTextBinding,
const GlobalParamsUpdater& updateGlobalParams); const GlobalParamsUpdater& updateGlobalParams);
void RenderShaderProgram( void RenderShaderProgram(
GLuint layerInputTexture,
GLuint sourceTexture, GLuint sourceTexture,
GLuint destinationFrameBuffer, GLuint destinationFrameBuffer,
PassProgram& passProgram, PassProgram& passProgram,

View File

@@ -28,6 +28,7 @@ struct RenderPassDescriptor
std::string passId; std::string passId;
std::string layerId; std::string layerId;
std::string shaderId; std::string shaderId;
GLuint layerInputTexture = 0;
GLuint sourceTexture = 0; GLuint sourceTexture = 0;
GLuint sourceFramebuffer = 0; GLuint sourceFramebuffer = 0;
GLuint destinationTexture = 0; GLuint destinationTexture = 0;

View File

@@ -2,6 +2,7 @@
#include <gl/gl.h> #include <gl/gl.h>
constexpr GLuint kLayerInputTextureUnit = 0;
constexpr GLuint kDecodedVideoTextureUnit = 1; constexpr GLuint kDecodedVideoTextureUnit = 1;
constexpr GLuint kSourceHistoryTextureUnitBase = 2; constexpr GLuint kSourceHistoryTextureUnitBase = 2;
constexpr GLuint kPackedVideoTextureUnit = 2; constexpr GLuint kPackedVideoTextureUnit = 2;

View File

@@ -117,7 +117,11 @@ void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const Run
{ {
const GLuint shaderTextureBase = ResolveShaderTextureBase(state, historyCap); const GLuint shaderTextureBase = ResolveShaderTextureBase(state, historyCap);
const GLint videoInputLocation = glGetUniformLocation(program, "gVideoInput"); const GLint layerInputLocation = FindSamplerUniformLocation(program, "gLayerInput");
if (layerInputLocation >= 0)
glUniform1i(layerInputLocation, static_cast<GLint>(kLayerInputTextureUnit));
const GLint videoInputLocation = FindSamplerUniformLocation(program, "gVideoInput");
if (videoInputLocation >= 0) if (videoInputLocation >= 0)
glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit)); glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit));
@@ -136,7 +140,7 @@ void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const Run
if (state.feedback.enabled) if (state.feedback.enabled)
{ {
const GLint feedbackSamplerLocation = glGetUniformLocation(program, "gFeedbackState"); const GLint feedbackSamplerLocation = FindSamplerUniformLocation(program, "gFeedbackState");
if (feedbackSamplerLocation >= 0) if (feedbackSamplerLocation >= 0)
glUniform1i(feedbackSamplerLocation, static_cast<GLint>(ResolveFeedbackTextureUnit(state, historyCap))); glUniform1i(feedbackSamplerLocation, static_cast<GLint>(ResolveFeedbackTextureUnit(state, historyCap)));
} }
@@ -160,12 +164,14 @@ void ShaderTextureBindings::AssignLayerSamplerUniforms(GLuint program, const Run
ShaderTextureBindings::RuntimeTextureBindingPlan ShaderTextureBindings::BuildLayerRuntimeBindingPlan( ShaderTextureBindings::RuntimeTextureBindingPlan ShaderTextureBindings::BuildLayerRuntimeBindingPlan(
const PassProgram& passProgram, const PassProgram& passProgram,
GLuint layerInputTexture, GLuint layerInputTexture,
GLuint originalLayerInputTexture,
const RuntimeRenderState& state, const RuntimeRenderState& state,
GLuint feedbackTexture, GLuint feedbackTexture,
const std::vector<GLuint>& sourceHistoryTextures, const std::vector<GLuint>& sourceHistoryTextures,
const std::vector<GLuint>& temporalHistoryTextures) const const std::vector<GLuint>& temporalHistoryTextures) const
{ {
RuntimeTextureBindingPlan plan; RuntimeTextureBindingPlan plan;
plan.bindings.push_back({ "originalLayerInput", "gLayerInput", originalLayerInputTexture, kLayerInputTextureUnit });
plan.bindings.push_back({ "layerInput", "gVideoInput", layerInputTexture, kDecodedVideoTextureUnit }); plan.bindings.push_back({ "layerInput", "gVideoInput", layerInputTexture, kDecodedVideoTextureUnit });
for (std::size_t index = 0; index < sourceHistoryTextures.size(); ++index) for (std::size_t index = 0; index < sourceHistoryTextures.size(); ++index)

View File

@@ -35,6 +35,7 @@ public:
RuntimeTextureBindingPlan BuildLayerRuntimeBindingPlan( RuntimeTextureBindingPlan BuildLayerRuntimeBindingPlan(
const PassProgram& passProgram, const PassProgram& passProgram,
GLuint layerInputTexture, GLuint layerInputTexture,
GLuint originalLayerInputTexture,
const RuntimeRenderState& state, const RuntimeRenderState& state,
GLuint feedbackTexture, GLuint feedbackTexture,
const std::vector<GLuint>& sourceHistoryTextures, const std::vector<GLuint>& sourceHistoryTextures,

View File

@@ -39,6 +39,7 @@ cbuffer GlobalParams
{{PARAMETER_UNIFORMS}}}; {{PARAMETER_UNIFORMS}}};
Sampler2D<float4> gVideoInput; Sampler2D<float4> gVideoInput;
Sampler2D<float4> gLayerInput;
{{SOURCE_HISTORY_SAMPLERS}}{{TEMPORAL_HISTORY_SAMPLERS}}{{FEEDBACK_SAMPLER}}{{TEXTURE_SAMPLERS}} {{SOURCE_HISTORY_SAMPLERS}}{{TEMPORAL_HISTORY_SAMPLERS}}{{FEEDBACK_SAMPLER}}{{TEXTURE_SAMPLERS}}
{{TEXT_SAMPLERS}} {{TEXT_SAMPLERS}}
float4 sampleVideo(float2 tc) float4 sampleVideo(float2 tc)
@@ -46,6 +47,11 @@ float4 sampleVideo(float2 tc)
return gVideoInput.Sample(tc); return gVideoInput.Sample(tc);
} }
float4 sampleLayerInput(float2 tc)
{
return gLayerInput.Sample(tc);
}
float4 sampleSourceHistory(int framesAgo, float2 tc) float4 sampleSourceHistory(int framesAgo, float2 tc)
{ {
if (gSourceHistoryLength <= 0) if (gSourceHistoryLength <= 0)

View File

@@ -1,18 +1,36 @@
{ {
"id": "feedback-highlight-accumulator", "id": "feedback-highlight-accumulator",
"name": "Feedback Highlight Accumulator", "name": "Feedback Highlight Accumulator",
"description": "Demonstrates shader-local feedback by accumulating only the brightest-looking parts of the image over a timed window, then resetting and starting again. The highlight selection is an approximation based on a luminance threshold rather than an exact percentile reduction.", "description": "Demonstrates shader-local feedback by storing a rolling 4-frame per-pixel highlight history in RGBA and displaying only the newest 3 frames. The highlight selection is an approximation based on a luminance threshold rather than an exact percentile reduction.",
"category": "Feedback", "category": "Feedback",
"entryPoint": "shadeVideo", "entryPoint": "buildHistory",
"passes": [
{
"id": "history",
"source": "shader.slang",
"entryPoint": "buildHistory",
"output": "historyBuffer"
},
{
"id": "display",
"source": "shader.slang",
"entryPoint": "displayHistory",
"inputs": [
"historyBuffer"
],
"output": "layerOutput"
}
],
"feedback": { "feedback": {
"enabled": true "enabled": true,
"writePass": "history"
}, },
"parameters": [ "parameters": [
{ {
"id": "highlightThreshold", "id": "highlightThreshold",
"label": "Highlight Threshold", "label": "Highlight Threshold",
"type": "float", "type": "float",
"default": 0.99, "default": 0.92,
"min": 0.5, "min": 0.5,
"max": 1.5, "max": 1.5,
"step": 0.001, "step": 0.001,
@@ -30,23 +48,23 @@
}, },
{ {
"id": "accumulateAmount", "id": "accumulateAmount",
"label": "Accumulate Amount", "label": "Contribution Amount",
"type": "float", "type": "float",
"default": 0.2, "default": 0.6,
"min": 0.0, "min": 0.0,
"max": 2.0, "max": 2.0,
"step": 0.001, "step": 0.001,
"description": "How much of each bright sample gets added into the running feedback image." "description": "Strength of each frame's highlight contribution before it is shifted into the rolling history."
}, },
{ {
"id": "displayGain", "id": "displayGain",
"label": "Display Gain", "label": "Display Gain",
"type": "float", "type": "float",
"default": 1.0, "default": 2.5,
"min": 0.0, "min": 0.0,
"max": 4.0, "max": 8.0,
"step": 0.01, "step": 0.01,
"description": "Brightness applied to the accumulated feedback when displayed." "description": "Brightness applied to the sum of the most recent three stored highlight samples."
}, },
{ {
"id": "baseMix", "id": "baseMix",
@@ -56,17 +74,7 @@
"min": 0.0, "min": 0.0,
"max": 1.0, "max": 1.0,
"step": 0.01, "step": 0.01,
"description": "Amount of the live source image kept under the accumulation." "description": "Amount of the live source image kept under the recent highlight history display."
},
{
"id": "resetSeconds",
"label": "Reset Seconds",
"type": "float",
"default": 10.0,
"min": 1.0,
"max": 60.0,
"step": 0.1,
"description": "Length of each accumulation window before the feedback resets."
} }
] ]
} }

View File

@@ -3,29 +3,45 @@ float luminance(float3 color)
return dot(color, float3(0.2126, 0.7152, 0.0722)); return dot(color, float3(0.2126, 0.7152, 0.0722));
} }
float currentResetPhase(float timeSeconds, float resetInterval) float4 buildHistory(ShaderContext context)
{ {
float safeInterval = max(resetInterval, 0.001);
float cycleIndex = floor(timeSeconds / safeInterval);
return frac(cycleIndex * 0.5) >= 0.5 ? 1.0 : 0.0;
}
float4 shadeVideo(ShaderContext context)
{
float safeResetSeconds = max(resetSeconds, 0.001);
float phase = currentResetPhase(context.time, safeResetSeconds);
float4 previousFeedback = sampleFeedback(context.uv); float4 previousFeedback = sampleFeedback(context.uv);
bool phaseMatches = context.feedbackAvailable > 0 && abs(previousFeedback.a - phase) < 0.25; float4 previousHistory = context.feedbackAvailable > 0
float3 previousAccumulation = phaseMatches ? previousFeedback.rgb : float3(0.0, 0.0, 0.0); ? previousFeedback
: float4(0.0, 0.0, 0.0, 0.0);
// Highlight selection is always based on the live source image only.
// The stored feedback never feeds back into the threshold test.
float currentLuma = luminance(context.sourceColor.rgb); float currentLuma = luminance(context.sourceColor.rgb);
float thresholdWidth = max(softness, 0.0001); float thresholdWidth = max(softness, 0.0001);
float brightMask = smoothstep(highlightThreshold - thresholdWidth, highlightThreshold + thresholdWidth, currentLuma); float brightMask = smoothstep(highlightThreshold - thresholdWidth, highlightThreshold + thresholdWidth, currentLuma);
float3 currentContribution = context.sourceColor.rgb * brightMask * max(accumulateAmount, 0.0); // Treat RGBA as a 4-slot rolling scalar history:
float3 nextAccumulation = saturate(previousAccumulation + currentContribution); // R = current frame, G = 1 frame ago, B = 2 frames ago, A = 3 frames ago.
// Each frame shifts the history forward and drops the oldest sample.
float currentContribution = currentLuma * brightMask * max(accumulateAmount, 0.0);
float4 nextHistory = float4(
currentContribution,
previousHistory.r,
previousHistory.g,
previousHistory.b
);
float3 displayColor = context.sourceColor.rgb * saturate(baseMix) + nextAccumulation * max(displayGain, 0.0); return saturate(nextHistory);
return float4(saturate(displayColor), phase); }
float4 displayHistory(ShaderContext context)
{
// In the display pass, context.sourceColor is the same-frame historyBuffer
// produced by buildHistory().
float4 history = context.sourceColor;
float recentEnergy = history.r + history.g + history.b;
float3 originalColor = sampleLayerInput(context.uv).rgb;
float highlightBoost = recentEnergy * max(displayGain, 0.0);
float3 sourceHue = originalColor / max(max(originalColor.r, originalColor.g), max(originalColor.b, 0.0001));
float3 displayColor =
originalColor +
sourceHue * highlightBoost * 1.5;
return float4(saturate(displayColor), 1.0);
} }