Multipass shaders
This commit is contained in:
@@ -47,7 +47,9 @@ Included now:
|
|||||||
- shared-context GL prepare worker for runtime shader program compile/link
|
- shared-context GL prepare worker for runtime shader program compile/link
|
||||||
- render-thread-only GL program swap once a prepared program is ready
|
- render-thread-only GL program swap once a prepared program is ready
|
||||||
- manifest-driven stateless single-pass shader packages
|
- manifest-driven stateless single-pass shader packages
|
||||||
- HTTP shader list populated from supported stateless single-pass shader packages
|
- manifest-driven stateless named-pass shader packages
|
||||||
|
- atomic render-plan swap after every pass program is prepared
|
||||||
|
- HTTP shader list populated from supported stateless full-frame shader packages
|
||||||
- default float, vec2, color, boolean, enum, and trigger parameters
|
- default float, vec2, color, boolean, enum, and trigger parameters
|
||||||
- small JSON writer for future HTTP/WebSocket payloads
|
- small JSON writer for future HTTP/WebSocket payloads
|
||||||
- JSON serialization for cadence telemetry snapshots
|
- JSON serialization for cadence telemetry snapshots
|
||||||
@@ -60,7 +62,6 @@ Included now:
|
|||||||
Intentionally not included yet:
|
Intentionally not included yet:
|
||||||
|
|
||||||
- DeckLink input
|
- DeckLink input
|
||||||
- multipass shader rendering
|
|
||||||
- temporal/history/feedback shader storage
|
- temporal/history/feedback shader storage
|
||||||
- texture/LUT asset upload
|
- texture/LUT asset upload
|
||||||
- text-parameter rasterization
|
- text-parameter rasterization
|
||||||
@@ -86,6 +87,8 @@ This tracks parity with `apps/LoopThroughWithOpenGLCompositing`.
|
|||||||
- [x] Shared-context GL shader/program preparation
|
- [x] Shared-context GL shader/program preparation
|
||||||
- [x] Render-thread program swap at a frame boundary
|
- [x] Render-thread program swap at a frame boundary
|
||||||
- [x] Stateless single-pass shader rendering
|
- [x] Stateless single-pass shader rendering
|
||||||
|
- [x] Stateless named-pass shader rendering
|
||||||
|
- [x] Atomic multipass render-plan commit
|
||||||
- [x] Shader add/remove control path
|
- [x] Shader add/remove control path
|
||||||
- [x] Previous-layer texture handoff for stacked shaders
|
- [x] Previous-layer texture handoff for stacked shaders
|
||||||
- [x] Supported shader list in HTTP/UI state
|
- [x] Supported shader list in HTTP/UI state
|
||||||
@@ -99,7 +102,6 @@ This tracks parity with `apps/LoopThroughWithOpenGLCompositing`.
|
|||||||
- [ ] DeckLink input capture
|
- [ ] DeckLink input capture
|
||||||
- [ ] Input frame upload into the render scene
|
- [ ] Input frame upload into the render scene
|
||||||
- [ ] Live video input bound to `gVideoInput`
|
- [ ] Live video input bound to `gVideoInput`
|
||||||
- [ ] Multipass shader rendering
|
|
||||||
- [ ] Temporal history buffers
|
- [ ] Temporal history buffers
|
||||||
- [ ] Feedback buffers
|
- [ ] Feedback buffers
|
||||||
- [ ] Texture asset loading and upload
|
- [ ] Texture asset loading and upload
|
||||||
@@ -244,9 +246,12 @@ On startup the app begins compiling the selected shader package on a background
|
|||||||
|
|
||||||
The render thread keeps drawing the simple motion renderer while Slang compiles. It does not choose packages, launch Slang, or track build lifecycle. Once a completed shader artifact is published, the render-thread-owned runtime scene queues changed layers to a shared-context GL prepare worker. That worker compiles/links runtime shader programs off the cadence thread. The render thread only swaps in an already-prepared GL program at a frame boundary. If either the Slang build or GL preparation fails, the app keeps rendering the current renderer or simple motion fallback.
|
The render thread keeps drawing the simple motion renderer while Slang compiles. It does not choose packages, launch Slang, or track build lifecycle. Once a completed shader artifact is published, the render-thread-owned runtime scene queues changed layers to a shared-context GL prepare worker. That worker compiles/links runtime shader programs off the cadence thread. The render thread only swaps in an already-prepared GL program at a frame boundary. If either the Slang build or GL preparation fails, the app keeps rendering the current renderer or simple motion fallback.
|
||||||
|
|
||||||
Current runtime shader support is deliberately limited to stateless single-pass packages:
|
Current runtime shader support is deliberately limited to stateless full-frame packages:
|
||||||
|
|
||||||
- one pass only
|
- one or more named passes
|
||||||
|
- one sampled source input per pass
|
||||||
|
- named intermediate outputs routed by the pass manifest
|
||||||
|
- final visible output must be named `layerOutput`
|
||||||
- no temporal history
|
- no temporal history
|
||||||
- no feedback storage
|
- no feedback storage
|
||||||
- no texture/LUT assets yet
|
- no texture/LUT assets yet
|
||||||
@@ -255,11 +260,11 @@ Current runtime shader support is deliberately limited to stateless single-pass
|
|||||||
- the first layer receives a small fallback source texture until DeckLink input is added
|
- the first layer receives a small fallback source texture until DeckLink input is added
|
||||||
- stacked layers receive the previous ready layer output through both `gVideoInput` and `gLayerInput`
|
- stacked layers receive the previous ready layer output through both `gVideoInput` and `gLayerInput`
|
||||||
|
|
||||||
The `/api/state` shader list uses the same support rules as runtime shader compilation and reports only packages this app can run today. Unsupported manifest feature sets such as multipass, temporal, feedback, texture-backed, font-backed, or text-parameter shaders are hidden from the control UI for now.
|
The `/api/state` shader list uses the same support rules as runtime shader compilation and reports only packages this app can run today. Unsupported manifest feature sets such as temporal, feedback, texture-backed, font-backed, or text-parameter shaders are hidden from the control UI for now.
|
||||||
|
|
||||||
Runtime shaders are exposed through `RuntimeLayerModel` as display layers with manifest parameter defaults. The model also records whether each layer has a render-ready artifact. Add/remove POST controls mutate this app-owned model and may start background shader builds.
|
Runtime shaders are exposed through `RuntimeLayerModel` as display layers with manifest parameter defaults. The model also records whether each layer has a render-ready artifact. Add/remove POST controls mutate this app-owned model and may start background shader builds.
|
||||||
|
|
||||||
When a layer becomes render-ready, the app publishes the ready render-layer snapshot to the render thread. The render thread owns the GL-side `RuntimeRenderScene`, diffs that snapshot at a frame boundary, queues new or changed programs to the shared-context prepare worker, swaps in prepared programs when available, removes obsolete GL programs, and renders ready layers in order. Stacked stateless full-frame shaders render through internal ping-pong targets so each layer can sample the previous layer through `gLayerInput`; the final ready layer renders to the output target.
|
When a layer becomes render-ready, the app publishes the ready render-layer snapshot to the render thread. The render thread owns the GL-side `RuntimeRenderScene`, diffs that snapshot at a frame boundary, queues new or changed pass programs to the shared-context prepare worker, swaps in a full prepared render plan only after every pass is ready, removes obsolete GL programs, and renders ready layers in order. Stacked stateless full-frame shaders render through internal ping-pong targets so each layer can sample the previous layer through `gLayerInput`; multipass shaders route named intermediate outputs through their manifest-declared pass inputs, and the final ready layer renders to the output target.
|
||||||
|
|
||||||
Successful handoff signs:
|
Successful handoff signs:
|
||||||
|
|
||||||
|
|||||||
@@ -39,14 +39,18 @@ bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompo
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layerIt->renderer)
|
for (LayerProgram::PassProgram& pass : layerIt->passes)
|
||||||
layerIt->renderer->ShutdownGl();
|
{
|
||||||
|
if (pass.renderer)
|
||||||
|
pass.renderer->ShutdownGl();
|
||||||
|
}
|
||||||
|
ReleasePendingPrograms(*layerIt);
|
||||||
layerIt = mLayers.erase(layerIt);
|
layerIt = mLayers.erase(layerIt);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
|
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
|
||||||
{
|
{
|
||||||
if (layer.artifact.fragmentShaderSource.empty())
|
if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const std::string fingerprint = Fingerprint(layer.artifact);
|
const std::string fingerprint = Fingerprint(layer.artifact);
|
||||||
@@ -55,16 +59,25 @@ bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompo
|
|||||||
{
|
{
|
||||||
LayerProgram next;
|
LayerProgram next;
|
||||||
next.layerId = layer.id;
|
next.layerId = layer.id;
|
||||||
next.renderer = std::make_unique<RuntimeShaderRenderer>();
|
|
||||||
mLayers.push_back(std::move(next));
|
mLayers.push_back(std::move(next));
|
||||||
program = &mLayers.back();
|
program = &mLayers.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (program->shaderId == layer.shaderId && program->sourceFingerprint == fingerprint && program->renderer && program->renderer->HasProgram())
|
bool hasReadyPass = false;
|
||||||
|
for (const LayerProgram::PassProgram& pass : program->passes)
|
||||||
|
{
|
||||||
|
if (pass.renderer && pass.renderer->HasProgram())
|
||||||
|
{
|
||||||
|
hasReadyPass = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (program->shaderId == layer.shaderId && program->sourceFingerprint == fingerprint && hasReadyPass)
|
||||||
continue;
|
continue;
|
||||||
if (program->pendingFingerprint == fingerprint)
|
if (program->pendingFingerprint == fingerprint)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
ReleasePendingPrograms(*program);
|
||||||
program->shaderId = layer.shaderId;
|
program->shaderId = layer.shaderId;
|
||||||
program->pendingFingerprint = fingerprint;
|
program->pendingFingerprint = fingerprint;
|
||||||
layersToPrepare.push_back(layer);
|
layersToPrepare.push_back(layer);
|
||||||
@@ -84,9 +97,14 @@ bool RuntimeRenderScene::HasLayers()
|
|||||||
for (const std::string& layerId : mLayerOrder)
|
for (const std::string& layerId : mLayerOrder)
|
||||||
{
|
{
|
||||||
const LayerProgram* layer = FindLayer(layerId);
|
const LayerProgram* layer = FindLayer(layerId);
|
||||||
if (layer && layer->renderer && layer->renderer->HasProgram())
|
if (!layer)
|
||||||
|
continue;
|
||||||
|
for (const LayerProgram::PassProgram& pass : layer->passes)
|
||||||
|
{
|
||||||
|
if (pass.renderer && pass.renderer->HasProgram())
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,9 +116,16 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
|
|||||||
for (const std::string& layerId : mLayerOrder)
|
for (const std::string& layerId : mLayerOrder)
|
||||||
{
|
{
|
||||||
LayerProgram* layer = FindLayer(layerId);
|
LayerProgram* layer = FindLayer(layerId);
|
||||||
if (!layer || !layer->renderer || !layer->renderer->HasProgram())
|
if (!layer)
|
||||||
continue;
|
continue;
|
||||||
|
for (const LayerProgram::PassProgram& pass : layer->passes)
|
||||||
|
{
|
||||||
|
if (pass.renderer && pass.renderer->HasProgram())
|
||||||
|
{
|
||||||
readyLayers.push_back(layer);
|
readyLayers.push_back(layer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (readyLayers.empty())
|
if (readyLayers.empty())
|
||||||
@@ -111,14 +136,14 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
|
|||||||
|
|
||||||
if (readyLayers.size() == 1)
|
if (readyLayers.size() == 1)
|
||||||
{
|
{
|
||||||
readyLayers.front()->renderer->RenderFrame(frameIndex, width, height);
|
RenderLayer(*readyLayers.front(), frameIndex, width, height, 0, static_cast<GLuint>(outputFramebuffer), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!EnsureLayerTargets(width, height))
|
if (!EnsureLayerTargets(width, height))
|
||||||
{
|
{
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
|
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
|
||||||
readyLayers.back()->renderer->RenderFrame(frameIndex, width, height);
|
RenderLayer(*readyLayers.back(), frameIndex, width, height, 0, static_cast<GLuint>(outputFramebuffer), true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,12 +155,11 @@ void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsign
|
|||||||
if (isFinalLayer)
|
if (isFinalLayer)
|
||||||
{
|
{
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
|
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
|
||||||
readyLayers[layerIndex]->renderer->RenderFrame(frameIndex, width, height, layerInputTexture, layerInputTexture);
|
RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, layerInputTexture, static_cast<GLuint>(outputFramebuffer), true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[nextTargetIndex]);
|
RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, layerInputTexture, mLayerFramebuffers[nextTargetIndex], true);
|
||||||
readyLayers[layerIndex]->renderer->RenderFrame(frameIndex, width, height, layerInputTexture, layerInputTexture);
|
|
||||||
layerInputTexture = mLayerTextures[nextTargetIndex];
|
layerInputTexture = mLayerTextures[nextTargetIndex];
|
||||||
nextTargetIndex = 1 - nextTargetIndex;
|
nextTargetIndex = 1 - nextTargetIndex;
|
||||||
}
|
}
|
||||||
@@ -146,8 +170,12 @@ void RuntimeRenderScene::ShutdownGl()
|
|||||||
mPrepareWorker.Stop();
|
mPrepareWorker.Stop();
|
||||||
for (LayerProgram& layer : mLayers)
|
for (LayerProgram& layer : mLayers)
|
||||||
{
|
{
|
||||||
if (layer.renderer)
|
for (LayerProgram::PassProgram& pass : layer.passes)
|
||||||
layer.renderer->ShutdownGl();
|
{
|
||||||
|
if (pass.renderer)
|
||||||
|
pass.renderer->ShutdownGl();
|
||||||
|
}
|
||||||
|
ReleasePendingPrograms(layer);
|
||||||
}
|
}
|
||||||
mLayers.clear();
|
mLayers.clear();
|
||||||
mLayerOrder.clear();
|
mLayerOrder.clear();
|
||||||
@@ -172,28 +200,165 @@ void RuntimeRenderScene::ConsumePreparedPrograms()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool replacesExistingPendingPass = false;
|
||||||
|
for (RuntimePreparedShaderProgram& existing : layer->pendingPreparedPrograms)
|
||||||
|
{
|
||||||
|
if (existing.passId != preparedProgram.passId)
|
||||||
|
continue;
|
||||||
|
existing.ReleaseGl();
|
||||||
|
existing = std::move(preparedProgram);
|
||||||
|
replacesExistingPendingPass = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!replacesExistingPendingPass)
|
||||||
|
layer->pendingPreparedPrograms.push_back(std::move(preparedProgram));
|
||||||
|
TryCommitPendingPrograms(*layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeRenderScene::ReleasePendingPrograms(LayerProgram& layer)
|
||||||
|
{
|
||||||
|
for (RuntimePreparedShaderProgram& program : layer.pendingPreparedPrograms)
|
||||||
|
program.ReleaseGl();
|
||||||
|
layer.pendingPreparedPrograms.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RuntimeRenderScene::TryCommitPendingPrograms(LayerProgram& layer)
|
||||||
|
{
|
||||||
|
if (layer.pendingPreparedPrograms.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const RuntimeShaderArtifact& artifact = layer.pendingPreparedPrograms.front().artifact;
|
||||||
|
const std::size_t expectedPassCount = artifact.passes.empty() ? 1 : artifact.passes.size();
|
||||||
|
if (layer.pendingPreparedPrograms.size() < expectedPassCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<LayerProgram::PassProgram> nextPasses;
|
||||||
|
nextPasses.reserve(expectedPassCount);
|
||||||
|
for (const RuntimeShaderPassArtifact& passArtifact : artifact.passes)
|
||||||
|
{
|
||||||
|
auto preparedIt = std::find_if(
|
||||||
|
layer.pendingPreparedPrograms.begin(),
|
||||||
|
layer.pendingPreparedPrograms.end(),
|
||||||
|
[&passArtifact](const RuntimePreparedShaderProgram& prepared) {
|
||||||
|
return prepared.passId == passArtifact.passId;
|
||||||
|
});
|
||||||
|
if (preparedIt == layer.pendingPreparedPrograms.end())
|
||||||
|
return;
|
||||||
|
|
||||||
std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
|
std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!nextRenderer->CommitPreparedProgram(preparedProgram, error))
|
if (!nextRenderer->CommitPreparedProgram(*preparedIt, error))
|
||||||
{
|
{
|
||||||
preparedProgram.ReleaseGl();
|
ReleasePendingPrograms(layer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayerProgram::PassProgram nextPass;
|
||||||
|
nextPass.passId = preparedIt->passId;
|
||||||
|
nextPass.inputNames = preparedIt->inputNames;
|
||||||
|
nextPass.outputName = preparedIt->outputName.empty() ? preparedIt->passId : preparedIt->outputName;
|
||||||
|
nextPass.renderer = std::move(nextRenderer);
|
||||||
|
nextPasses.push_back(std::move(nextPass));
|
||||||
|
}
|
||||||
|
if (artifact.passes.empty())
|
||||||
|
{
|
||||||
|
RuntimePreparedShaderProgram& prepared = layer.pendingPreparedPrograms.front();
|
||||||
|
std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
|
||||||
|
std::string error;
|
||||||
|
if (!nextRenderer->CommitPreparedProgram(prepared, error))
|
||||||
|
{
|
||||||
|
ReleasePendingPrograms(layer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LayerProgram::PassProgram nextPass;
|
||||||
|
nextPass.passId = prepared.passId;
|
||||||
|
nextPass.inputNames = prepared.inputNames;
|
||||||
|
nextPass.outputName = prepared.outputName.empty() ? prepared.passId : prepared.outputName;
|
||||||
|
nextPass.renderer = std::move(nextRenderer);
|
||||||
|
nextPasses.push_back(std::move(nextPass));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (LayerProgram::PassProgram& pass : layer.passes)
|
||||||
|
{
|
||||||
|
if (pass.renderer)
|
||||||
|
pass.renderer->ShutdownGl();
|
||||||
|
}
|
||||||
|
layer.passes = std::move(nextPasses);
|
||||||
|
layer.shaderId = artifact.shaderId;
|
||||||
|
layer.sourceFingerprint = layer.pendingPreparedPrograms.front().sourceFingerprint;
|
||||||
|
layer.pendingFingerprint.clear();
|
||||||
|
layer.pendingPreparedPrograms.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint RuntimeRenderScene::RenderLayer(
|
||||||
|
LayerProgram& layer,
|
||||||
|
uint64_t frameIndex,
|
||||||
|
unsigned width,
|
||||||
|
unsigned height,
|
||||||
|
GLuint layerInputTexture,
|
||||||
|
GLuint outputFramebuffer,
|
||||||
|
bool renderToOutput)
|
||||||
|
{
|
||||||
|
GLuint namedOutputs[2] = {};
|
||||||
|
std::string namedOutputNames[2];
|
||||||
|
std::size_t nextTargetIndex = 2;
|
||||||
|
GLuint lastOutputTexture = layerInputTexture;
|
||||||
|
|
||||||
|
for (LayerProgram::PassProgram& pass : layer.passes)
|
||||||
|
{
|
||||||
|
if (!pass.renderer || !pass.renderer->HasProgram())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
GLuint sourceTexture = layerInputTexture;
|
||||||
|
if (!pass.inputNames.empty())
|
||||||
|
{
|
||||||
|
const std::string& inputName = pass.inputNames.front();
|
||||||
|
if (inputName != "layerInput" && inputName != "videoInput")
|
||||||
|
{
|
||||||
|
for (std::size_t index = 0; index < 2; ++index)
|
||||||
|
{
|
||||||
|
if (namedOutputNames[index] == inputName)
|
||||||
|
{
|
||||||
|
sourceTexture = namedOutputs[index];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool writesLayerOutput = pass.outputName == "layerOutput";
|
||||||
|
if (writesLayerOutput && renderToOutput)
|
||||||
|
{
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, outputFramebuffer);
|
||||||
|
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, sourceTexture);
|
||||||
|
lastOutputTexture = 0;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layer->renderer)
|
if (!EnsureLayerTargets(width, height))
|
||||||
layer->renderer->ShutdownGl();
|
continue;
|
||||||
layer->renderer = std::move(nextRenderer);
|
|
||||||
layer->shaderId = preparedProgram.shaderId;
|
const std::size_t targetIndex = nextTargetIndex;
|
||||||
layer->sourceFingerprint = preparedProgram.sourceFingerprint;
|
nextTargetIndex = nextTargetIndex == 2 ? 3 : 2;
|
||||||
layer->pendingFingerprint.clear();
|
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[targetIndex]);
|
||||||
|
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, sourceTexture);
|
||||||
|
const std::size_t namedIndex = targetIndex - 2;
|
||||||
|
namedOutputs[namedIndex] = mLayerTextures[targetIndex];
|
||||||
|
namedOutputNames[namedIndex] = pass.outputName;
|
||||||
|
lastOutputTexture = mLayerTextures[targetIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return lastOutputTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
|
bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
|
||||||
{
|
{
|
||||||
if (width == 0 || height == 0)
|
if (width == 0 || height == 0)
|
||||||
return false;
|
return false;
|
||||||
if (mLayerFramebuffers[0] != 0 && mLayerFramebuffers[1] != 0 && mLayerTextures[0] != 0 && mLayerTextures[1] != 0
|
if (mLayerFramebuffers[0] != 0 && mLayerFramebuffers[1] != 0 && mLayerFramebuffers[2] != 0 && mLayerFramebuffers[3] != 0
|
||||||
|
&& mLayerTextures[0] != 0 && mLayerTextures[1] != 0 && mLayerTextures[2] != 0 && mLayerTextures[3] != 0
|
||||||
&& mLayerTargetWidth == width && mLayerTargetHeight == height)
|
&& mLayerTargetWidth == width && mLayerTargetHeight == height)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -201,9 +366,9 @@ bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
|
|||||||
mLayerTargetWidth = width;
|
mLayerTargetWidth = width;
|
||||||
mLayerTargetHeight = height;
|
mLayerTargetHeight = height;
|
||||||
|
|
||||||
glGenFramebuffers(2, mLayerFramebuffers);
|
glGenFramebuffers(4, mLayerFramebuffers);
|
||||||
glGenTextures(2, mLayerTextures);
|
glGenTextures(4, mLayerTextures);
|
||||||
for (int index = 0; index < 2; ++index)
|
for (int index = 0; index < 4; ++index)
|
||||||
{
|
{
|
||||||
glBindTexture(GL_TEXTURE_2D, mLayerTextures[index]);
|
glBindTexture(GL_TEXTURE_2D, mLayerTextures[index]);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
@@ -239,14 +404,15 @@ bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
|
|||||||
|
|
||||||
void RuntimeRenderScene::DestroyLayerTargets()
|
void RuntimeRenderScene::DestroyLayerTargets()
|
||||||
{
|
{
|
||||||
if (mLayerFramebuffers[0] != 0 || mLayerFramebuffers[1] != 0)
|
if (mLayerFramebuffers[0] != 0 || mLayerFramebuffers[1] != 0 || mLayerFramebuffers[2] != 0 || mLayerFramebuffers[3] != 0)
|
||||||
glDeleteFramebuffers(2, mLayerFramebuffers);
|
glDeleteFramebuffers(4, mLayerFramebuffers);
|
||||||
if (mLayerTextures[0] != 0 || mLayerTextures[1] != 0)
|
if (mLayerTextures[0] != 0 || mLayerTextures[1] != 0 || mLayerTextures[2] != 0 || mLayerTextures[3] != 0)
|
||||||
glDeleteTextures(2, mLayerTextures);
|
glDeleteTextures(4, mLayerTextures);
|
||||||
mLayerFramebuffers[0] = 0;
|
for (int index = 0; index < 4; ++index)
|
||||||
mLayerFramebuffers[1] = 0;
|
{
|
||||||
mLayerTextures[0] = 0;
|
mLayerFramebuffers[index] = 0;
|
||||||
mLayerTextures[1] = 0;
|
mLayerTextures[index] = 0;
|
||||||
|
}
|
||||||
mLayerTargetWidth = 0;
|
mLayerTargetWidth = 0;
|
||||||
mLayerTargetHeight = 0;
|
mLayerTargetHeight = 0;
|
||||||
}
|
}
|
||||||
@@ -271,8 +437,23 @@ const RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std:
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RuntimeRenderScene::LayerProgram::PassProgram* RuntimeRenderScene::FindPass(LayerProgram& layer, const std::string& passId)
|
||||||
|
{
|
||||||
|
for (LayerProgram::PassProgram& pass : layer.passes)
|
||||||
|
{
|
||||||
|
if (pass.passId == passId)
|
||||||
|
return &pass;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
std::string RuntimeRenderScene::Fingerprint(const RuntimeShaderArtifact& artifact)
|
std::string RuntimeRenderScene::Fingerprint(const RuntimeShaderArtifact& artifact)
|
||||||
{
|
{
|
||||||
const std::hash<std::string> hasher;
|
const std::hash<std::string> hasher;
|
||||||
return artifact.shaderId + ":" + std::to_string(artifact.fragmentShaderSource.size()) + ":" + std::to_string(hasher(artifact.fragmentShaderSource));
|
std::string source;
|
||||||
|
for (const RuntimeShaderPassArtifact& pass : artifact.passes)
|
||||||
|
source += pass.passId + ":" + pass.outputName + ":" + pass.fragmentShaderSource + "\n";
|
||||||
|
if (source.empty())
|
||||||
|
source = artifact.fragmentShaderSource;
|
||||||
|
return artifact.shaderId + ":" + std::to_string(source.size()) + ":" + std::to_string(hasher(source));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,21 +32,33 @@ private:
|
|||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
std::string sourceFingerprint;
|
std::string sourceFingerprint;
|
||||||
std::string pendingFingerprint;
|
std::string pendingFingerprint;
|
||||||
|
std::vector<RuntimePreparedShaderProgram> pendingPreparedPrograms;
|
||||||
|
struct PassProgram
|
||||||
|
{
|
||||||
|
std::string passId;
|
||||||
|
std::vector<std::string> inputNames;
|
||||||
|
std::string outputName;
|
||||||
std::unique_ptr<RuntimeShaderRenderer> renderer;
|
std::unique_ptr<RuntimeShaderRenderer> renderer;
|
||||||
};
|
};
|
||||||
|
std::vector<PassProgram> passes;
|
||||||
|
};
|
||||||
|
|
||||||
void ConsumePreparedPrograms();
|
void ConsumePreparedPrograms();
|
||||||
|
void ReleasePendingPrograms(LayerProgram& layer);
|
||||||
|
void TryCommitPendingPrograms(LayerProgram& layer);
|
||||||
bool EnsureLayerTargets(unsigned width, unsigned height);
|
bool EnsureLayerTargets(unsigned width, unsigned height);
|
||||||
void DestroyLayerTargets();
|
void DestroyLayerTargets();
|
||||||
|
GLuint RenderLayer(LayerProgram& layer, uint64_t frameIndex, unsigned width, unsigned height, GLuint layerInputTexture, GLuint outputFramebuffer, bool renderToOutput);
|
||||||
LayerProgram* FindLayer(const std::string& layerId);
|
LayerProgram* FindLayer(const std::string& layerId);
|
||||||
const LayerProgram* FindLayer(const std::string& layerId) const;
|
const LayerProgram* FindLayer(const std::string& layerId) const;
|
||||||
|
LayerProgram::PassProgram* FindPass(LayerProgram& layer, const std::string& passId);
|
||||||
static std::string Fingerprint(const RuntimeShaderArtifact& artifact);
|
static std::string Fingerprint(const RuntimeShaderArtifact& artifact);
|
||||||
|
|
||||||
RuntimeShaderPrepareWorker mPrepareWorker;
|
RuntimeShaderPrepareWorker mPrepareWorker;
|
||||||
std::vector<LayerProgram> mLayers;
|
std::vector<LayerProgram> mLayers;
|
||||||
std::vector<std::string> mLayerOrder;
|
std::vector<std::string> mLayerOrder;
|
||||||
GLuint mLayerFramebuffers[2] = {};
|
GLuint mLayerFramebuffers[4] = {};
|
||||||
GLuint mLayerTextures[2] = {};
|
GLuint mLayerTextures[4] = {};
|
||||||
unsigned mLayerTargetWidth = 0;
|
unsigned mLayerTargetWidth = 0;
|
||||||
unsigned mLayerTargetHeight = 0;
|
unsigned mLayerTargetHeight = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -77,21 +77,36 @@ void RuntimeShaderPrepareWorker::Submit(const std::vector<RenderCadenceComposito
|
|||||||
std::lock_guard<std::mutex> lock(mMutex);
|
std::lock_guard<std::mutex> lock(mMutex);
|
||||||
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
|
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
|
||||||
{
|
{
|
||||||
if (layer.artifact.fragmentShaderSource.empty())
|
if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
std::vector<RuntimeShaderPassArtifact> passes = layer.artifact.passes;
|
||||||
|
if (passes.empty())
|
||||||
|
{
|
||||||
|
RuntimeShaderPassArtifact pass;
|
||||||
|
pass.passId = "main";
|
||||||
|
pass.fragmentShaderSource = layer.artifact.fragmentShaderSource;
|
||||||
|
pass.outputName = "layerOutput";
|
||||||
|
passes.push_back(std::move(pass));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sameLayer = [&layer](const PrepareRequest& existing) {
|
||||||
|
return existing.layerId == layer.id;
|
||||||
|
};
|
||||||
|
mRequests.erase(std::remove_if(mRequests.begin(), mRequests.end(), sameLayer), mRequests.end());
|
||||||
|
|
||||||
|
for (const RuntimeShaderPassArtifact& pass : passes)
|
||||||
|
{
|
||||||
PrepareRequest request;
|
PrepareRequest request;
|
||||||
request.layerId = layer.id;
|
request.layerId = layer.id;
|
||||||
request.shaderId = layer.shaderId;
|
request.shaderId = layer.shaderId;
|
||||||
|
request.passId = pass.passId;
|
||||||
request.sourceFingerprint = Fingerprint(layer.artifact);
|
request.sourceFingerprint = Fingerprint(layer.artifact);
|
||||||
request.artifact = layer.artifact;
|
request.artifact = layer.artifact;
|
||||||
|
request.passArtifact = pass;
|
||||||
auto sameLayer = [&request](const PrepareRequest& existing) {
|
|
||||||
return existing.layerId == request.layerId;
|
|
||||||
};
|
|
||||||
mRequests.erase(std::remove_if(mRequests.begin(), mRequests.end(), sameLayer), mRequests.end());
|
|
||||||
mRequests.push_back(std::move(request));
|
mRequests.push_back(std::move(request));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
mCondition.notify_one();
|
mCondition.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,10 +152,11 @@ void RuntimeShaderPrepareWorker::ThreadMain()
|
|||||||
}
|
}
|
||||||
|
|
||||||
RuntimePreparedShaderProgram preparedProgram;
|
RuntimePreparedShaderProgram preparedProgram;
|
||||||
RuntimeShaderRenderer::BuildPreparedProgram(
|
RuntimeShaderRenderer::BuildPreparedPassProgram(
|
||||||
request.layerId,
|
request.layerId,
|
||||||
request.sourceFingerprint,
|
request.sourceFingerprint,
|
||||||
request.artifact,
|
request.artifact,
|
||||||
|
request.passArtifact,
|
||||||
preparedProgram);
|
preparedProgram);
|
||||||
glFlush();
|
glFlush();
|
||||||
|
|
||||||
@@ -154,5 +170,10 @@ void RuntimeShaderPrepareWorker::ThreadMain()
|
|||||||
std::string RuntimeShaderPrepareWorker::Fingerprint(const RuntimeShaderArtifact& artifact)
|
std::string RuntimeShaderPrepareWorker::Fingerprint(const RuntimeShaderArtifact& artifact)
|
||||||
{
|
{
|
||||||
const std::hash<std::string> hasher;
|
const std::hash<std::string> hasher;
|
||||||
return artifact.shaderId + ":" + std::to_string(artifact.fragmentShaderSource.size()) + ":" + std::to_string(hasher(artifact.fragmentShaderSource));
|
std::string source;
|
||||||
|
for (const RuntimeShaderPassArtifact& pass : artifact.passes)
|
||||||
|
source += pass.passId + ":" + pass.outputName + ":" + pass.fragmentShaderSource + "\n";
|
||||||
|
if (source.empty())
|
||||||
|
source = artifact.fragmentShaderSource;
|
||||||
|
return artifact.shaderId + ":" + std::to_string(source.size()) + ":" + std::to_string(hasher(source));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ private:
|
|||||||
{
|
{
|
||||||
std::string layerId;
|
std::string layerId;
|
||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
|
std::string passId;
|
||||||
std::string sourceFingerprint;
|
std::string sourceFingerprint;
|
||||||
RuntimeShaderArtifact artifact;
|
RuntimeShaderArtifact artifact;
|
||||||
|
RuntimeShaderPassArtifact passArtifact;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ThreadMain();
|
void ThreadMain();
|
||||||
|
|||||||
@@ -4,13 +4,18 @@
|
|||||||
#include "../../runtime/RuntimeShaderArtifact.h"
|
#include "../../runtime/RuntimeShaderArtifact.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
struct RuntimePreparedShaderProgram
|
struct RuntimePreparedShaderProgram
|
||||||
{
|
{
|
||||||
std::string layerId;
|
std::string layerId;
|
||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
|
std::string passId;
|
||||||
std::string sourceFingerprint;
|
std::string sourceFingerprint;
|
||||||
RuntimeShaderArtifact artifact;
|
RuntimeShaderArtifact artifact;
|
||||||
|
RuntimeShaderPassArtifact passArtifact;
|
||||||
|
std::vector<std::string> inputNames;
|
||||||
|
std::string outputName;
|
||||||
GLuint program = 0;
|
GLuint program = 0;
|
||||||
GLuint vertexShader = 0;
|
GLuint vertexShader = 0;
|
||||||
GLuint fragmentShader = 0;
|
GLuint fragmentShader = 0;
|
||||||
|
|||||||
@@ -99,21 +99,41 @@ bool RuntimeShaderRenderer::BuildPreparedProgram(
|
|||||||
const std::string& sourceFingerprint,
|
const std::string& sourceFingerprint,
|
||||||
const RuntimeShaderArtifact& artifact,
|
const RuntimeShaderArtifact& artifact,
|
||||||
RuntimePreparedShaderProgram& preparedProgram)
|
RuntimePreparedShaderProgram& preparedProgram)
|
||||||
|
{
|
||||||
|
RuntimeShaderPassArtifact passArtifact;
|
||||||
|
passArtifact.passId = "main";
|
||||||
|
passArtifact.fragmentShaderSource = artifact.fragmentShaderSource;
|
||||||
|
passArtifact.outputName = "layerOutput";
|
||||||
|
if (!artifact.passes.empty())
|
||||||
|
passArtifact = artifact.passes.front();
|
||||||
|
return BuildPreparedPassProgram(layerId, sourceFingerprint, artifact, passArtifact, preparedProgram);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RuntimeShaderRenderer::BuildPreparedPassProgram(
|
||||||
|
const std::string& layerId,
|
||||||
|
const std::string& sourceFingerprint,
|
||||||
|
const RuntimeShaderArtifact& artifact,
|
||||||
|
const RuntimeShaderPassArtifact& passArtifact,
|
||||||
|
RuntimePreparedShaderProgram& preparedProgram)
|
||||||
{
|
{
|
||||||
preparedProgram = RuntimePreparedShaderProgram();
|
preparedProgram = RuntimePreparedShaderProgram();
|
||||||
preparedProgram.layerId = layerId;
|
preparedProgram.layerId = layerId;
|
||||||
preparedProgram.shaderId = artifact.shaderId;
|
preparedProgram.shaderId = artifact.shaderId;
|
||||||
|
preparedProgram.passId = passArtifact.passId;
|
||||||
preparedProgram.sourceFingerprint = sourceFingerprint;
|
preparedProgram.sourceFingerprint = sourceFingerprint;
|
||||||
preparedProgram.artifact = artifact;
|
preparedProgram.artifact = artifact;
|
||||||
|
preparedProgram.passArtifact = passArtifact;
|
||||||
|
preparedProgram.inputNames = passArtifact.inputNames;
|
||||||
|
preparedProgram.outputName = passArtifact.outputName.empty() ? passArtifact.passId : passArtifact.outputName;
|
||||||
|
|
||||||
if (artifact.fragmentShaderSource.empty())
|
if (passArtifact.fragmentShaderSource.empty())
|
||||||
{
|
{
|
||||||
preparedProgram.error = "Cannot prepare an empty fragment shader.";
|
preparedProgram.error = "Cannot prepare an empty fragment shader.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!BuildProgram(
|
if (!BuildProgram(
|
||||||
artifact.fragmentShaderSource,
|
passArtifact.fragmentShaderSource,
|
||||||
preparedProgram.program,
|
preparedProgram.program,
|
||||||
preparedProgram.vertexShader,
|
preparedProgram.vertexShader,
|
||||||
preparedProgram.fragmentShader,
|
preparedProgram.fragmentShader,
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ public:
|
|||||||
const std::string& sourceFingerprint,
|
const std::string& sourceFingerprint,
|
||||||
const RuntimeShaderArtifact& artifact,
|
const RuntimeShaderArtifact& artifact,
|
||||||
RuntimePreparedShaderProgram& preparedProgram);
|
RuntimePreparedShaderProgram& preparedProgram);
|
||||||
|
static bool BuildPreparedPassProgram(
|
||||||
|
const std::string& layerId,
|
||||||
|
const std::string& sourceFingerprint,
|
||||||
|
const RuntimeShaderArtifact& artifact,
|
||||||
|
const RuntimeShaderPassArtifact& passArtifact,
|
||||||
|
RuntimePreparedShaderProgram& preparedProgram);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool EnsureStaticGlResources(std::string& error);
|
bool EnsureStaticGlResources(std::string& error);
|
||||||
|
|||||||
@@ -5,12 +5,21 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
struct RuntimeShaderPassArtifact
|
||||||
|
{
|
||||||
|
std::string passId;
|
||||||
|
std::string fragmentShaderSource;
|
||||||
|
std::vector<std::string> inputNames;
|
||||||
|
std::string outputName;
|
||||||
|
};
|
||||||
|
|
||||||
struct RuntimeShaderArtifact
|
struct RuntimeShaderArtifact
|
||||||
{
|
{
|
||||||
std::string layerId;
|
std::string layerId;
|
||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
std::string displayName;
|
std::string displayName;
|
||||||
std::string fragmentShaderSource;
|
std::string fragmentShaderSource;
|
||||||
|
std::vector<RuntimeShaderPassArtifact> passes;
|
||||||
std::string message;
|
std::string message;
|
||||||
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -112,8 +112,6 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildShader(const std::strin
|
|||||||
return build;
|
return build;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShaderPassDefinition& pass = shaderPackage.passes.front();
|
|
||||||
|
|
||||||
ShaderCompiler compiler(
|
ShaderCompiler compiler(
|
||||||
repoRoot,
|
repoRoot,
|
||||||
runtimeBuildDir / (shaderId + ".wrapper.slang"),
|
runtimeBuildDir / (shaderId + ".wrapper.slang"),
|
||||||
@@ -122,19 +120,32 @@ RuntimeSlangShaderBuild RuntimeSlangShaderCompiler::BuildShader(const std::strin
|
|||||||
0);
|
0);
|
||||||
|
|
||||||
const auto start = std::chrono::steady_clock::now();
|
const auto start = std::chrono::steady_clock::now();
|
||||||
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, build.artifact.fragmentShaderSource, error))
|
for (const ShaderPassDefinition& pass : shaderPackage.passes)
|
||||||
|
{
|
||||||
|
std::string fragmentShaderSource;
|
||||||
|
if (!compiler.BuildPassFragmentShaderSource(shaderPackage, pass, fragmentShaderSource, error))
|
||||||
{
|
{
|
||||||
build.succeeded = false;
|
build.succeeded = false;
|
||||||
build.message = error.empty() ? "Slang compile failed." : error;
|
build.message = error.empty() ? "Slang compile failed." : error;
|
||||||
return build;
|
return build;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RuntimeShaderPassArtifact passArtifact;
|
||||||
|
passArtifact.passId = pass.id;
|
||||||
|
passArtifact.fragmentShaderSource = std::move(fragmentShaderSource);
|
||||||
|
passArtifact.inputNames = pass.inputNames;
|
||||||
|
passArtifact.outputName = pass.outputName;
|
||||||
|
build.artifact.passes.push_back(std::move(passArtifact));
|
||||||
|
}
|
||||||
|
|
||||||
const auto end = std::chrono::steady_clock::now();
|
const auto end = std::chrono::steady_clock::now();
|
||||||
const double milliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - start).count();
|
const double milliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(end - start).count();
|
||||||
build.succeeded = true;
|
build.succeeded = true;
|
||||||
build.artifact.shaderId = shaderPackage.id;
|
build.artifact.shaderId = shaderPackage.id;
|
||||||
build.artifact.displayName = shaderPackage.displayName;
|
build.artifact.displayName = shaderPackage.displayName;
|
||||||
build.artifact.parameterDefinitions = shaderPackage.parameters;
|
build.artifact.parameterDefinitions = shaderPackage.parameters;
|
||||||
|
if (!build.artifact.passes.empty())
|
||||||
|
build.artifact.fragmentShaderSource = build.artifact.passes.front().fragmentShaderSource;
|
||||||
build.artifact.message = shaderPackage.displayName + " Slang compile completed in " + std::to_string(milliseconds) + " ms.";
|
build.artifact.message = shaderPackage.displayName + " Slang compile completed in " + std::to_string(milliseconds) + " ms.";
|
||||||
build.message = build.artifact.message;
|
build.message = build.artifact.message;
|
||||||
return build;
|
return build;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ namespace RenderCadenceCompositor
|
|||||||
{
|
{
|
||||||
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage)
|
ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& shaderPackage)
|
||||||
{
|
{
|
||||||
if (shaderPackage.passes.size() != 1)
|
if (shaderPackage.passes.empty())
|
||||||
return { false, "RenderCadenceCompositor currently supports only single-pass runtime shaders." };
|
return { false, "Shader package has no render passes." };
|
||||||
|
|
||||||
if (shaderPackage.temporal.enabled)
|
if (shaderPackage.temporal.enabled)
|
||||||
return { false, "RenderCadenceCompositor currently supports only stateless shaders; temporal history is not enabled in this app." };
|
return { false, "RenderCadenceCompositor currently supports only stateless shaders; temporal history is not enabled in this app." };
|
||||||
@@ -30,6 +30,35 @@ ShaderSupportResult CheckStatelessSinglePassShaderSupport(const ShaderPackage& s
|
|||||||
return { false, "RenderCadenceCompositor currently skips text parameters because they require per-shader text texture storage." };
|
return { false, "RenderCadenceCompositor currently skips text parameters because they require per-shader text texture storage." };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool writesLayerOutput = false;
|
||||||
|
for (const ShaderPassDefinition& pass : shaderPackage.passes)
|
||||||
|
{
|
||||||
|
if (pass.sourcePath.empty())
|
||||||
|
{
|
||||||
|
return { false, "Shader pass '" + pass.id + "' has no source." };
|
||||||
|
}
|
||||||
|
if (pass.outputName == "layerOutput")
|
||||||
|
writesLayerOutput = true;
|
||||||
|
for (const std::string& inputName : pass.inputNames)
|
||||||
|
{
|
||||||
|
if (inputName == "videoInput" || inputName == "layerInput")
|
||||||
|
continue;
|
||||||
|
bool matchesNamedOutput = false;
|
||||||
|
for (const ShaderPassDefinition& outputPass : shaderPackage.passes)
|
||||||
|
{
|
||||||
|
if (outputPass.outputName == inputName)
|
||||||
|
{
|
||||||
|
matchesNamedOutput = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matchesNamedOutput)
|
||||||
|
return { false, "Shader pass '" + pass.id + "' references unknown input '" + inputName + "'." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!writesLayerOutput)
|
||||||
|
return { false, "Shader package must write a pass output named 'layerOutput'." };
|
||||||
|
|
||||||
return { true, std::string() };
|
return { true, std::string() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ ShaderPackage MakeSinglePassPackage()
|
|||||||
ShaderPassDefinition pass;
|
ShaderPassDefinition pass;
|
||||||
pass.id = "main";
|
pass.id = "main";
|
||||||
pass.entryPoint = "mainImage";
|
pass.entryPoint = "mainImage";
|
||||||
|
pass.sourcePath = "shader.slang";
|
||||||
|
pass.outputName = "layerOutput";
|
||||||
shaderPackage.passes.push_back(pass);
|
shaderPackage.passes.push_back(pass);
|
||||||
return shaderPackage;
|
return shaderPackage;
|
||||||
}
|
}
|
||||||
@@ -37,19 +39,35 @@ void SupportsSinglePassStatelessPackage()
|
|||||||
Expect(result.reason.empty(), "supported packages should not report a rejection reason");
|
Expect(result.reason.empty(), "supported packages should not report a rejection reason");
|
||||||
}
|
}
|
||||||
|
|
||||||
void RejectsMultipassPackage()
|
void SupportsStatelessNamedPassPackage()
|
||||||
{
|
{
|
||||||
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||||
|
shaderPackage.passes.front().outputName = "generatedMask";
|
||||||
ShaderPassDefinition secondPass;
|
ShaderPassDefinition secondPass;
|
||||||
secondPass.id = "second";
|
secondPass.id = "second";
|
||||||
secondPass.entryPoint = "mainImage";
|
secondPass.entryPoint = "mainImage";
|
||||||
|
secondPass.sourcePath = "shader.slang";
|
||||||
|
secondPass.inputNames.push_back("generatedMask");
|
||||||
|
secondPass.outputName = "layerOutput";
|
||||||
shaderPackage.passes.push_back(secondPass);
|
shaderPackage.passes.push_back(secondPass);
|
||||||
|
|
||||||
const RenderCadenceCompositor::ShaderSupportResult result =
|
const RenderCadenceCompositor::ShaderSupportResult result =
|
||||||
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
|
||||||
Expect(!result.supported, "multipass packages should be rejected");
|
Expect(result.supported, "stateless named-pass packages should be supported");
|
||||||
Expect(result.reason.find("single-pass") != std::string::npos, "multipass rejection should explain the single-pass limit");
|
Expect(result.reason.empty(), "supported named-pass packages should not report a rejection reason");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RejectsUnknownPassInput()
|
||||||
|
{
|
||||||
|
ShaderPackage shaderPackage = MakeSinglePassPackage();
|
||||||
|
shaderPackage.passes.front().inputNames.push_back("missingIntermediate");
|
||||||
|
|
||||||
|
const RenderCadenceCompositor::ShaderSupportResult result =
|
||||||
|
RenderCadenceCompositor::CheckStatelessSinglePassShaderSupport(shaderPackage);
|
||||||
|
|
||||||
|
Expect(!result.supported, "packages with unknown pass inputs should be rejected");
|
||||||
|
Expect(result.reason.find("unknown input") != std::string::npos, "unknown input rejection should explain the missing named output");
|
||||||
}
|
}
|
||||||
|
|
||||||
void RejectsTemporalPackage()
|
void RejectsTemporalPackage()
|
||||||
@@ -97,7 +115,8 @@ void RejectsTextParameters()
|
|||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
SupportsSinglePassStatelessPackage();
|
SupportsSinglePassStatelessPackage();
|
||||||
RejectsMultipassPackage();
|
SupportsStatelessNamedPassPackage();
|
||||||
|
RejectsUnknownPassInput();
|
||||||
RejectsTemporalPackage();
|
RejectsTemporalPackage();
|
||||||
RejectsTextureAssets();
|
RejectsTextureAssets();
|
||||||
RejectsTextParameters();
|
RejectsTextParameters();
|
||||||
|
|||||||
Reference in New Issue
Block a user