Added textures
This commit is contained in:
@@ -15,6 +15,7 @@ Each shader package lives under `shaders/<id>/` and includes:
|
|||||||
- `category`
|
- `category`
|
||||||
- `entryPoint`
|
- `entryPoint`
|
||||||
- `parameters`
|
- `parameters`
|
||||||
|
- optional `textures`
|
||||||
- optional `temporal`
|
- optional `temporal`
|
||||||
|
|
||||||
Supported parameter types:
|
Supported parameter types:
|
||||||
@@ -25,6 +26,25 @@ Supported parameter types:
|
|||||||
- `bool`
|
- `bool`
|
||||||
- `enum`
|
- `enum`
|
||||||
|
|
||||||
|
## Texture assets
|
||||||
|
|
||||||
|
Shaders can optionally declare texture assets:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"id": "logoTexture",
|
||||||
|
"path": "logo.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `id` becomes a shader-visible sampler name
|
||||||
|
- `path` is resolved relative to the shader package directory
|
||||||
|
- texture asset changes trigger shader reload just like shader and manifest edits
|
||||||
|
|
||||||
## Temporal manifests
|
## Temporal manifests
|
||||||
|
|
||||||
Shaders can optionally declare temporal history needs:
|
Shaders can optionally declare temporal history needs:
|
||||||
@@ -86,3 +106,9 @@ Helper function:
|
|||||||
- `sampleVideo(float2 uv)` returns decoded full-resolution RGBA video from the live DeckLink input.
|
- `sampleVideo(float2 uv)` returns decoded full-resolution RGBA video from the live DeckLink input.
|
||||||
- `sampleSourceHistory(int framesAgo, float2 uv)` samples the most recent available source history frame, clamping to the oldest available frame if needed.
|
- `sampleSourceHistory(int framesAgo, float2 uv)` samples the most recent available source history frame, clamping to the oldest available frame if needed.
|
||||||
- `sampleTemporalHistory(int framesAgo, float2 uv)` samples the most recent available pre-layer history frame for temporal shaders, clamping to the oldest available frame if needed.
|
- `sampleTemporalHistory(int framesAgo, float2 uv)` samples the most recent available pre-layer history frame for temporal shaders, clamping to the oldest available frame if needed.
|
||||||
|
|
||||||
|
Declared texture assets are exposed as `Sampler2D<float4>` globals using the texture `id`, for example:
|
||||||
|
|
||||||
|
```slang
|
||||||
|
float4 logo = logoTexture.Sample(uv);
|
||||||
|
```
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <wincodec.h>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -1064,6 +1065,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
|
|||||||
GLint linkResult = GL_FALSE;
|
GLint linkResult = GL_FALSE;
|
||||||
std::string fragmentShaderSource;
|
std::string fragmentShaderSource;
|
||||||
std::string loadError;
|
std::string loadError;
|
||||||
|
std::vector<LayerProgram::TextureBinding> textureBindings;
|
||||||
const char* vertexSource = kVertexShaderSource;
|
const char* vertexSource = kVertexShaderSource;
|
||||||
|
|
||||||
if (!mRuntimeHost->BuildLayerFragmentShaderSource(state.layerId, fragmentShaderSource, loadError))
|
if (!mRuntimeHost->BuildLayerFragmentShaderSource(state.layerId, fragmentShaderSource, loadError))
|
||||||
@@ -1111,11 +1113,33 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const ShaderTextureAsset& textureAsset : state.textureAssets)
|
||||||
|
{
|
||||||
|
LayerProgram::TextureBinding textureBinding;
|
||||||
|
textureBinding.samplerName = textureAsset.id;
|
||||||
|
textureBinding.sourcePath = textureAsset.path;
|
||||||
|
if (!loadTextureAsset(textureAsset, textureBinding.texture, loadError))
|
||||||
|
{
|
||||||
|
for (LayerProgram::TextureBinding& loadedTexture : textureBindings)
|
||||||
|
{
|
||||||
|
if (loadedTexture.texture != 0)
|
||||||
|
glDeleteTextures(1, &loadedTexture.texture);
|
||||||
|
}
|
||||||
|
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
||||||
|
glDeleteProgram(newProgram);
|
||||||
|
glDeleteShader(newVertexShader);
|
||||||
|
glDeleteShader(newFragmentShader);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
textureBindings.push_back(textureBinding);
|
||||||
|
}
|
||||||
|
|
||||||
const GLuint globalParamsIndex = glGetUniformBlockIndex(newProgram, "GlobalParams");
|
const GLuint globalParamsIndex = glGetUniformBlockIndex(newProgram, "GlobalParams");
|
||||||
if (globalParamsIndex != GL_INVALID_INDEX)
|
if (globalParamsIndex != GL_INVALID_INDEX)
|
||||||
glUniformBlockBinding(newProgram, globalParamsIndex, kGlobalParamsBindingPoint);
|
glUniformBlockBinding(newProgram, globalParamsIndex, kGlobalParamsBindingPoint);
|
||||||
|
|
||||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||||
|
const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap;
|
||||||
glUseProgram(newProgram);
|
glUseProgram(newProgram);
|
||||||
const GLint videoInputLocation = glGetUniformLocation(newProgram, "gVideoInput");
|
const GLint videoInputLocation = glGetUniformLocation(newProgram, "gVideoInput");
|
||||||
if (videoInputLocation >= 0)
|
if (videoInputLocation >= 0)
|
||||||
@@ -1132,6 +1156,12 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
|
|||||||
if (temporalSamplerLocation >= 0)
|
if (temporalSamplerLocation >= 0)
|
||||||
glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index));
|
glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index));
|
||||||
}
|
}
|
||||||
|
for (std::size_t index = 0; index < textureBindings.size(); ++index)
|
||||||
|
{
|
||||||
|
const GLint textureSamplerLocation = glGetUniformLocation(newProgram, textureBindings[index].samplerName.c_str());
|
||||||
|
if (textureSamplerLocation >= 0)
|
||||||
|
glUniform1i(textureSamplerLocation, static_cast<GLint>(shaderTextureBase + static_cast<GLuint>(index)));
|
||||||
|
}
|
||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
|
|
||||||
layerProgram.layerId = state.layerId;
|
layerProgram.layerId = state.layerId;
|
||||||
@@ -1139,6 +1169,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
|
|||||||
layerProgram.program = newProgram;
|
layerProgram.program = newProgram;
|
||||||
layerProgram.vertexShader = newVertexShader;
|
layerProgram.vertexShader = newVertexShader;
|
||||||
layerProgram.fragmentShader = newFragmentShader;
|
layerProgram.fragmentShader = newFragmentShader;
|
||||||
|
layerProgram.textureBindings.swap(textureBindings);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1146,7 +1177,7 @@ bool OpenGLComposite::compileLayerPrograms(int errorMessageSize, char* errorMess
|
|||||||
{
|
{
|
||||||
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mFrameWidth, mFrameHeight) : std::vector<RuntimeRenderState>();
|
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mFrameWidth, mFrameHeight) : std::vector<RuntimeRenderState>();
|
||||||
std::string temporalError;
|
std::string temporalError;
|
||||||
if (!validateTemporalTextureUnitBudget(temporalError))
|
if (!validateTemporalTextureUnitBudget(layerStates, temporalError))
|
||||||
{
|
{
|
||||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||||
return false;
|
return false;
|
||||||
@@ -1237,6 +1268,16 @@ bool OpenGLComposite::compileDecodeShader(int errorMessageSize, char* errorMessa
|
|||||||
|
|
||||||
void OpenGLComposite::destroySingleLayerProgram(LayerProgram& layerProgram)
|
void OpenGLComposite::destroySingleLayerProgram(LayerProgram& layerProgram)
|
||||||
{
|
{
|
||||||
|
for (LayerProgram::TextureBinding& textureBinding : layerProgram.textureBindings)
|
||||||
|
{
|
||||||
|
if (textureBinding.texture != 0)
|
||||||
|
{
|
||||||
|
glDeleteTextures(1, &textureBinding.texture);
|
||||||
|
textureBinding.texture = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layerProgram.textureBindings.clear();
|
||||||
|
|
||||||
if (layerProgram.program != 0)
|
if (layerProgram.program != 0)
|
||||||
{
|
{
|
||||||
glDeleteProgram(layerProgram.program);
|
glDeleteProgram(layerProgram.program);
|
||||||
@@ -1263,6 +1304,124 @@ void OpenGLComposite::destroyLayerPrograms()
|
|||||||
mLayerPrograms.clear();
|
mLayerPrograms.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OpenGLComposite::loadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
||||||
|
{
|
||||||
|
textureId = 0;
|
||||||
|
|
||||||
|
HRESULT comInitResult = CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||||
|
const bool shouldUninitializeCom = (comInitResult == S_OK || comInitResult == S_FALSE);
|
||||||
|
if (FAILED(comInitResult) && comInitResult != RPC_E_CHANGED_MODE)
|
||||||
|
{
|
||||||
|
error = "Could not initialize COM to load shader texture assets.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IWICImagingFactory> imagingFactory;
|
||||||
|
HRESULT result = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&imagingFactory));
|
||||||
|
if (FAILED(result) || !imagingFactory)
|
||||||
|
{
|
||||||
|
if (shouldUninitializeCom)
|
||||||
|
CoUninitialize();
|
||||||
|
error = "Could not create a WIC imaging factory to load shader texture assets.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IWICBitmapDecoder> bitmapDecoder;
|
||||||
|
result = imagingFactory->CreateDecoderFromFilename(textureAsset.path.wstring().c_str(), NULL, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &bitmapDecoder);
|
||||||
|
if (FAILED(result) || !bitmapDecoder)
|
||||||
|
{
|
||||||
|
if (shouldUninitializeCom)
|
||||||
|
CoUninitialize();
|
||||||
|
error = "Could not open shader texture asset: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IWICBitmapFrameDecode> bitmapFrame;
|
||||||
|
result = bitmapDecoder->GetFrame(0, &bitmapFrame);
|
||||||
|
if (FAILED(result) || !bitmapFrame)
|
||||||
|
{
|
||||||
|
if (shouldUninitializeCom)
|
||||||
|
CoUninitialize();
|
||||||
|
error = "Could not decode the first frame of shader texture asset: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CComPtr<IWICFormatConverter> formatConverter;
|
||||||
|
result = imagingFactory->CreateFormatConverter(&formatConverter);
|
||||||
|
if (FAILED(result) || !formatConverter)
|
||||||
|
{
|
||||||
|
if (shouldUninitializeCom)
|
||||||
|
CoUninitialize();
|
||||||
|
error = "Could not create a WIC format converter for shader texture asset: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = formatConverter->Initialize(bitmapFrame, GUID_WICPixelFormat32bppBGRA, WICBitmapDitherTypeNone, NULL, 0.0, WICBitmapPaletteTypeCustom);
|
||||||
|
if (FAILED(result))
|
||||||
|
{
|
||||||
|
if (shouldUninitializeCom)
|
||||||
|
CoUninitialize();
|
||||||
|
error = "Could not convert shader texture asset to BGRA: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT width = 0;
|
||||||
|
UINT height = 0;
|
||||||
|
result = formatConverter->GetSize(&width, &height);
|
||||||
|
if (FAILED(result) || width == 0 || height == 0)
|
||||||
|
{
|
||||||
|
if (shouldUninitializeCom)
|
||||||
|
CoUninitialize();
|
||||||
|
error = "Shader texture asset has an invalid size: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UINT stride = width * 4;
|
||||||
|
std::vector<unsigned char> pixels(static_cast<std::size_t>(stride) * static_cast<std::size_t>(height));
|
||||||
|
result = formatConverter->CopyPixels(NULL, stride, static_cast<UINT>(pixels.size()), pixels.data());
|
||||||
|
if (FAILED(result))
|
||||||
|
{
|
||||||
|
if (shouldUninitializeCom)
|
||||||
|
CoUninitialize();
|
||||||
|
error = "Could not read shader texture pixels: " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<unsigned char> flippedPixels(pixels.size());
|
||||||
|
for (UINT row = 0; row < height; ++row)
|
||||||
|
{
|
||||||
|
const std::size_t srcOffset = static_cast<std::size_t>(row) * stride;
|
||||||
|
const std::size_t dstOffset = static_cast<std::size_t>(height - 1 - row) * stride;
|
||||||
|
std::memcpy(flippedPixels.data() + dstOffset, pixels.data() + srcOffset, stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
glGenTextures(1, &textureId);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, static_cast<GLsizei>(width), static_cast<GLsizei>(height), 0, GL_BGRA, GL_UNSIGNED_BYTE, flippedPixels.data());
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
if (shouldUninitializeCom)
|
||||||
|
CoUninitialize();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenGLComposite::bindLayerTextureAssets(const LayerProgram& layerProgram)
|
||||||
|
{
|
||||||
|
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||||
|
const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap;
|
||||||
|
for (std::size_t index = 0; index < layerProgram.textureBindings.size(); ++index)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));
|
||||||
|
glBindTexture(GL_TEXTURE_2D, layerProgram.textureBindings[index].texture);
|
||||||
|
}
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
}
|
||||||
|
|
||||||
void OpenGLComposite::destroyDecodeShaderProgram()
|
void OpenGLComposite::destroyDecodeShaderProgram()
|
||||||
{
|
{
|
||||||
if (mDecodeProgram != 0)
|
if (mDecodeProgram != 0)
|
||||||
@@ -1284,17 +1443,23 @@ void OpenGLComposite::destroyDecodeShaderProgram()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenGLComposite::validateTemporalTextureUnitBudget(std::string& error) const
|
bool OpenGLComposite::validateTemporalTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, std::string& error) const
|
||||||
{
|
{
|
||||||
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
|
||||||
|
unsigned maxAssetTextures = 0;
|
||||||
|
for (const RuntimeRenderState& state : layerStates)
|
||||||
|
{
|
||||||
|
if (state.textureAssets.size() > maxAssetTextures)
|
||||||
|
maxAssetTextures = static_cast<unsigned>(state.textureAssets.size());
|
||||||
|
}
|
||||||
GLint maxTextureUnits = 0;
|
GLint maxTextureUnits = 0;
|
||||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
|
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits);
|
||||||
const unsigned requiredUnits = kSourceHistoryTextureUnitBase + historyCap + historyCap;
|
const unsigned requiredUnits = kSourceHistoryTextureUnitBase + historyCap + historyCap + maxAssetTextures;
|
||||||
const unsigned availableUnits = maxTextureUnits > 0 ? static_cast<unsigned>(maxTextureUnits) : 0u;
|
const unsigned availableUnits = maxTextureUnits > 0 ? static_cast<unsigned>(maxTextureUnits) : 0u;
|
||||||
if (requiredUnits > availableUnits)
|
if (requiredUnits > availableUnits)
|
||||||
{
|
{
|
||||||
std::ostringstream message;
|
std::ostringstream message;
|
||||||
message << "Temporal history cap requires " << requiredUnits
|
message << "The current history and shader texture asset configuration requires " << requiredUnits
|
||||||
<< " fragment texture units, but only " << maxTextureUnits << " are available.";
|
<< " fragment texture units, but only " << maxTextureUnits << " are available.";
|
||||||
error = message.str();
|
error = message.str();
|
||||||
return false;
|
return false;
|
||||||
@@ -1548,6 +1713,7 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati
|
|||||||
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
||||||
glBindTexture(GL_TEXTURE_2D, sourceTexture);
|
glBindTexture(GL_TEXTURE_2D, sourceTexture);
|
||||||
bindHistorySamplers(state, sourceTexture);
|
bindHistorySamplers(state, sourceTexture);
|
||||||
|
bindLayerTextureAssets(layerProgram);
|
||||||
glBindVertexArray(mFullscreenVAO);
|
glBindVertexArray(mFullscreenVAO);
|
||||||
glUseProgram(layerProgram.program);
|
glUseProgram(layerProgram.program);
|
||||||
updateGlobalParamsBuffer(state, sourceHistoryAvailableCount(), temporalHistoryAvailableCountForLayer(state.layerId));
|
updateGlobalParamsBuffer(state, sourceHistoryAvailableCount(), temporalHistoryAvailableCountForLayer(state.layerId));
|
||||||
@@ -1562,6 +1728,12 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati
|
|||||||
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index);
|
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap;
|
||||||
|
for (std::size_t index = 0; index < layerProgram.textureBindings.size(); ++index)
|
||||||
|
{
|
||||||
|
glActiveTexture(GL_TEXTURE0 + shaderTextureBase + static_cast<GLuint>(index));
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
|||||||
@@ -151,11 +151,19 @@ private:
|
|||||||
|
|
||||||
struct LayerProgram
|
struct LayerProgram
|
||||||
{
|
{
|
||||||
|
struct TextureBinding
|
||||||
|
{
|
||||||
|
std::string samplerName;
|
||||||
|
std::filesystem::path sourcePath;
|
||||||
|
GLuint texture = 0;
|
||||||
|
};
|
||||||
|
|
||||||
std::string layerId;
|
std::string layerId;
|
||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
GLuint program = 0;
|
GLuint program = 0;
|
||||||
GLuint vertexShader = 0;
|
GLuint vertexShader = 0;
|
||||||
GLuint fragmentShader = 0;
|
GLuint fragmentShader = 0;
|
||||||
|
std::vector<TextureBinding> textureBindings;
|
||||||
};
|
};
|
||||||
std::vector<LayerProgram> mLayerPrograms;
|
std::vector<LayerProgram> mLayerPrograms;
|
||||||
|
|
||||||
@@ -187,11 +195,13 @@ private:
|
|||||||
void destroyDecodeShaderProgram();
|
void destroyDecodeShaderProgram();
|
||||||
void renderDecodePass();
|
void renderDecodePass();
|
||||||
void renderShaderProgram(GLuint sourceTexture, GLuint destinationFrameBuffer, const LayerProgram& layerProgram, const RuntimeRenderState& state);
|
void renderShaderProgram(GLuint sourceTexture, GLuint destinationFrameBuffer, const LayerProgram& layerProgram, const RuntimeRenderState& state);
|
||||||
|
bool loadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);
|
||||||
|
void bindLayerTextureAssets(const LayerProgram& layerProgram);
|
||||||
void renderEffect();
|
void renderEffect();
|
||||||
bool PollRuntimeChanges();
|
bool PollRuntimeChanges();
|
||||||
void broadcastRuntimeState();
|
void broadcastRuntimeState();
|
||||||
bool updateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength);
|
bool updateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength);
|
||||||
bool validateTemporalTextureUnitBudget(std::string& error) const;
|
bool validateTemporalTextureUnitBudget(const std::vector<RuntimeRenderState>& layerStates, std::string& error) const;
|
||||||
bool ensureTemporalHistoryResources(const std::vector<RuntimeRenderState>& layerStates, std::string& error);
|
bool ensureTemporalHistoryResources(const std::vector<RuntimeRenderState>& layerStates, std::string& error);
|
||||||
bool createHistoryRing(HistoryRing& ring, unsigned effectiveLength, TemporalHistorySource historySource, std::string& error);
|
bool createHistoryRing(HistoryRing& ring, unsigned effectiveLength, TemporalHistorySource historySource, std::string& error);
|
||||||
void destroyHistoryRing(HistoryRing& ring);
|
void destroyHistoryRing(HistoryRing& ring);
|
||||||
|
|||||||
@@ -159,6 +159,24 @@ bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType&
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TextureAssetsEqual(const std::vector<ShaderTextureAsset>& left, const std::vector<ShaderTextureAsset>& right)
|
||||||
|
{
|
||||||
|
if (left.size() != right.size())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < left.size(); ++index)
|
||||||
|
{
|
||||||
|
if (left[index].id != right[index].id ||
|
||||||
|
left[index].path != right[index].path ||
|
||||||
|
left[index].writeTime != right[index].writeTime)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeHost::RuntimeHost()
|
RuntimeHost::RuntimeHost()
|
||||||
@@ -283,7 +301,8 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (previous->second.shaderWriteTime != item.second.shaderWriteTime ||
|
if (previous->second.shaderWriteTime != item.second.shaderWriteTime ||
|
||||||
previous->second.manifestWriteTime != item.second.manifestWriteTime)
|
previous->second.manifestWriteTime != item.second.manifestWriteTime ||
|
||||||
|
!TextureAssetsEqual(previous->second.textureAssets, item.second.textureAssets))
|
||||||
{
|
{
|
||||||
registryChanged = true;
|
registryChanged = true;
|
||||||
break;
|
break;
|
||||||
@@ -300,8 +319,11 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested,
|
|||||||
EnsureLayerDefaultsLocked(layer, active->second);
|
EnsureLayerDefaultsLocked(layer, active->second);
|
||||||
if (previous != previousLayerShaderTimes.end())
|
if (previous != previousLayerShaderTimes.end())
|
||||||
{
|
{
|
||||||
|
auto previousPackage = previousPackages.find(layer.shaderId);
|
||||||
if (previous->second.first != active->second.shaderWriteTime ||
|
if (previous->second.first != active->second.shaderWriteTime ||
|
||||||
previous->second.second != active->second.manifestWriteTime)
|
previous->second.second != active->second.manifestWriteTime ||
|
||||||
|
(previousPackage != previousPackages.end() &&
|
||||||
|
!TextureAssetsEqual(previousPackage->second.textureAssets, active->second.textureAssets)))
|
||||||
{
|
{
|
||||||
mReloadRequested = true;
|
mReloadRequested = true;
|
||||||
}
|
}
|
||||||
@@ -699,6 +721,7 @@ std::vector<RuntimeRenderState> RuntimeHost::GetLayerRenderStates(unsigned outpu
|
|||||||
state.outputWidth = outputWidth;
|
state.outputWidth = outputWidth;
|
||||||
state.outputHeight = outputHeight;
|
state.outputHeight = outputHeight;
|
||||||
state.parameterDefinitions = shaderIt->second.parameters;
|
state.parameterDefinitions = shaderIt->second.parameters;
|
||||||
|
state.textureAssets = shaderIt->second.textureAssets;
|
||||||
state.isTemporal = shaderIt->second.temporal.enabled;
|
state.isTemporal = shaderIt->second.temporal.enabled;
|
||||||
state.temporalHistorySource = shaderIt->second.temporal.historySource;
|
state.temporalHistorySource = shaderIt->second.temporal.historySource;
|
||||||
state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength;
|
state.requestedTemporalHistoryLength = shaderIt->second.temporal.requestedHistoryLength;
|
||||||
@@ -981,6 +1004,45 @@ bool RuntimeHost::ParseShaderManifest(const std::filesystem::path& manifestPath,
|
|||||||
shaderPackage.shaderWriteTime = std::filesystem::last_write_time(shaderPackage.shaderPath);
|
shaderPackage.shaderWriteTime = std::filesystem::last_write_time(shaderPackage.shaderPath);
|
||||||
shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);
|
shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);
|
||||||
|
|
||||||
|
const JsonValue* texturesValue = manifestJson.find("textures");
|
||||||
|
if (texturesValue)
|
||||||
|
{
|
||||||
|
if (!texturesValue->isArray())
|
||||||
|
{
|
||||||
|
error = "Shader manifest 'textures' field must be an array in: " + manifestPath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const JsonValue& textureJson : texturesValue->asArray())
|
||||||
|
{
|
||||||
|
if (!textureJson.isObject())
|
||||||
|
{
|
||||||
|
error = "Shader texture entry must be an object in: " + manifestPath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonValue* textureIdValue = textureJson.find("id");
|
||||||
|
const JsonValue* texturePathValue = textureJson.find("path");
|
||||||
|
if (!textureIdValue || Trim(textureIdValue->asString()).empty() || !texturePathValue || Trim(texturePathValue->asString()).empty())
|
||||||
|
{
|
||||||
|
error = "Shader texture is missing required 'id' or 'path' in: " + manifestPath.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderTextureAsset textureAsset;
|
||||||
|
textureAsset.id = textureIdValue->asString();
|
||||||
|
textureAsset.path = shaderPackage.directoryPath / texturePathValue->asString();
|
||||||
|
if (!std::filesystem::exists(textureAsset.path))
|
||||||
|
{
|
||||||
|
error = "Shader texture asset not found for package " + shaderPackage.id + ": " + textureAsset.path.string();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
textureAsset.writeTime = std::filesystem::last_write_time(textureAsset.path);
|
||||||
|
shaderPackage.textureAssets.push_back(textureAsset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (const JsonValue* temporalValue = manifestJson.find("temporal"))
|
if (const JsonValue* temporalValue = manifestJson.find("temporal"))
|
||||||
{
|
{
|
||||||
if (!temporalValue->isObject())
|
if (!temporalValue->isObject())
|
||||||
@@ -1287,6 +1349,8 @@ std::string RuntimeHost::BuildWrapperSlangSource(const ShaderPackage& shaderPack
|
|||||||
source << "Sampler2D<float4> gSourceHistory" << index << ";\n";
|
source << "Sampler2D<float4> gSourceHistory" << index << ";\n";
|
||||||
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
|
||||||
source << "Sampler2D<float4> gTemporalHistory" << index << ";\n";
|
source << "Sampler2D<float4> gTemporalHistory" << index << ";\n";
|
||||||
|
for (const ShaderTextureAsset& textureAsset : shaderPackage.textureAssets)
|
||||||
|
source << "Sampler2D<float4> " << textureAsset.id << ";\n";
|
||||||
source << "\n";
|
source << "\n";
|
||||||
source << "float4 sampleVideo(float2 tc)\n";
|
source << "float4 sampleVideo(float2 tc)\n";
|
||||||
source << "{\n";
|
source << "{\n";
|
||||||
|
|||||||
@@ -60,6 +60,13 @@ struct TemporalSettings
|
|||||||
unsigned effectiveHistoryLength = 0;
|
unsigned effectiveHistoryLength = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ShaderTextureAsset
|
||||||
|
{
|
||||||
|
std::string id;
|
||||||
|
std::filesystem::path path;
|
||||||
|
std::filesystem::file_time_type writeTime;
|
||||||
|
};
|
||||||
|
|
||||||
struct ShaderPackage
|
struct ShaderPackage
|
||||||
{
|
{
|
||||||
std::string id;
|
std::string id;
|
||||||
@@ -71,6 +78,7 @@ struct ShaderPackage
|
|||||||
std::filesystem::path shaderPath;
|
std::filesystem::path shaderPath;
|
||||||
std::filesystem::path manifestPath;
|
std::filesystem::path manifestPath;
|
||||||
std::vector<ShaderParameterDefinition> parameters;
|
std::vector<ShaderParameterDefinition> parameters;
|
||||||
|
std::vector<ShaderTextureAsset> textureAssets;
|
||||||
TemporalSettings temporal;
|
TemporalSettings temporal;
|
||||||
std::filesystem::file_time_type shaderWriteTime;
|
std::filesystem::file_time_type shaderWriteTime;
|
||||||
std::filesystem::file_time_type manifestWriteTime;
|
std::filesystem::file_time_type manifestWriteTime;
|
||||||
@@ -82,6 +90,7 @@ struct RuntimeRenderState
|
|||||||
std::string shaderId;
|
std::string shaderId;
|
||||||
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
std::vector<ShaderParameterDefinition> parameterDefinitions;
|
||||||
std::map<std::string, ShaderParameterValue> parameterValues;
|
std::map<std::string, ShaderParameterValue> parameterValues;
|
||||||
|
std::vector<ShaderTextureAsset> textureAssets;
|
||||||
double timeSeconds = 0.0;
|
double timeSeconds = 0.0;
|
||||||
double frameCount = 0.0;
|
double frameCount = 0.0;
|
||||||
double mixAmount = 1.0;
|
double mixAmount = 1.0;
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
{
|
{
|
||||||
"id": "dvd-bounce",
|
"id": "dvd-bounce",
|
||||||
"name": "DVD Bounce",
|
"name": "DVD Bounce",
|
||||||
"description": "A transparent DVD-style logo that bounces endlessly and changes color on each screen hit.",
|
"description": "A transparent bouncing DVD logo sprite that changes color on each screen hit.",
|
||||||
"category": "Built-in",
|
"category": "Built-in",
|
||||||
"entryPoint": "shadeVideo",
|
"entryPoint": "shadeVideo",
|
||||||
|
"textures": [
|
||||||
|
{
|
||||||
|
"id": "dvdLogoTexture",
|
||||||
|
"path": "DVD_Logo.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"id": "logoScale",
|
"id": "logoScale",
|
||||||
|
|||||||
@@ -1,22 +1,3 @@
|
|||||||
float sdBox(float2 p, float2 b)
|
|
||||||
{
|
|
||||||
float2 d = abs(p) - b;
|
|
||||||
return length(max(d, float2(0.0, 0.0))) + min(max(d.x, d.y), 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float sdRoundedBox(float2 p, float2 b, float r)
|
|
||||||
{
|
|
||||||
return sdBox(p, b - r) - r;
|
|
||||||
}
|
|
||||||
|
|
||||||
float sdSegment(float2 p, float2 a, float2 b)
|
|
||||||
{
|
|
||||||
float2 pa = p - a;
|
|
||||||
float2 ba = b - a;
|
|
||||||
float h = saturate(dot(pa, ba) / max(dot(ba, ba), 0.0001));
|
|
||||||
return length(pa - ba * h);
|
|
||||||
}
|
|
||||||
|
|
||||||
float pingPong(float x, float lengthValue)
|
float pingPong(float x, float lengthValue)
|
||||||
{
|
{
|
||||||
float safeLength = max(lengthValue, 0.0001);
|
float safeLength = max(lengthValue, 0.0001);
|
||||||
@@ -32,38 +13,14 @@ float3 hsvToRgb(float3 hsv)
|
|||||||
return hsv.z * lerp(float3(1.0, 1.0, 1.0), rgb, hsv.y);
|
return hsv.z * lerp(float3(1.0, 1.0, 1.0), rgb, hsv.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
float letterD(float2 p, float scale)
|
|
||||||
{
|
|
||||||
float2 offset = p;
|
|
||||||
float stem = sdBox(offset + float2(scale * 0.18, 0.0), float2(scale * 0.065, scale * 0.47));
|
|
||||||
float outer = sdRoundedBox(offset + float2(scale * 0.03, 0.0), float2(scale * 0.30, scale * 0.47), scale * 0.23);
|
|
||||||
float inner = sdRoundedBox(offset + float2(scale * 0.11, 0.0), float2(scale * 0.15, scale * 0.24), scale * 0.12);
|
|
||||||
float bowl = max(outer, -inner);
|
|
||||||
return min(stem, bowl);
|
|
||||||
}
|
|
||||||
|
|
||||||
float letterV(float2 p, float scale)
|
|
||||||
{
|
|
||||||
float leftStroke = sdSegment(p, float2(-scale * 0.30, -scale * 0.48), float2(0.0, scale * 0.48)) - scale * 0.085;
|
|
||||||
float rightStroke = sdSegment(p, float2(scale * 0.30, -scale * 0.48), float2(0.0, scale * 0.48)) - scale * 0.085;
|
|
||||||
return min(leftStroke, rightStroke);
|
|
||||||
}
|
|
||||||
|
|
||||||
float logoLetters(float2 p, float scale)
|
|
||||||
{
|
|
||||||
float left = letterD(p + float2(scale * 0.92, 0.0), scale);
|
|
||||||
float middle = letterV(p, scale);
|
|
||||||
float right = letterD(p - float2(scale * 0.92, 0.0), scale);
|
|
||||||
return min(left, min(middle, right));
|
|
||||||
}
|
|
||||||
|
|
||||||
float4 shadeVideo(ShaderContext context)
|
float4 shadeVideo(ShaderContext context)
|
||||||
{
|
{
|
||||||
float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
|
float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
|
||||||
float minDimension = min(resolution.x, resolution.y);
|
float minDimension = min(resolution.x, resolution.y);
|
||||||
|
float logoAspect = 2.0;
|
||||||
|
|
||||||
float safeScale = max(logoScale, 0.05);
|
float safeScale = max(logoScale, 0.05);
|
||||||
float2 logoHalfSize = float2(minDimension * safeScale * 0.82, minDimension * safeScale * 0.28);
|
float2 logoHalfSize = float2(minDimension * safeScale, minDimension * safeScale / logoAspect);
|
||||||
float2 paddingPx = float2(minDimension * max(edgePadding, 0.0), minDimension * max(edgePadding, 0.0));
|
float2 paddingPx = float2(minDimension * max(edgePadding, 0.0), minDimension * max(edgePadding, 0.0));
|
||||||
float2 minCenterPx = logoHalfSize + paddingPx;
|
float2 minCenterPx = logoHalfSize + paddingPx;
|
||||||
float2 maxCenterPx = resolution - logoHalfSize - paddingPx;
|
float2 maxCenterPx = resolution - logoHalfSize - paddingPx;
|
||||||
@@ -86,31 +43,30 @@ float4 shadeVideo(ShaderContext context)
|
|||||||
float3 glowColor = hsvToRgb(float3(frac(hue + 0.06), 0.72, 1.0));
|
float3 glowColor = hsvToRgb(float3(frac(hue + 0.06), 0.72, 1.0));
|
||||||
|
|
||||||
float2 fragPx = context.uv * resolution;
|
float2 fragPx = context.uv * resolution;
|
||||||
float2 p = fragPx - centerPx;
|
float2 logoMin = centerPx - logoHalfSize;
|
||||||
|
float2 logoSize = logoHalfSize * 2.0;
|
||||||
|
float2 logoUv = (fragPx - logoMin) / max(logoSize, float2(1.0, 1.0));
|
||||||
|
float insideX = step(0.0, logoUv.x) * step(logoUv.x, 1.0);
|
||||||
|
float insideY = step(0.0, logoUv.y) * step(logoUv.y, 1.0);
|
||||||
|
float inside = insideX * insideY;
|
||||||
|
|
||||||
float badgeDist = sdRoundedBox(p, logoHalfSize, logoHalfSize.y * 0.42);
|
float4 logoSample = dvdLogoTexture.Sample(logoUv);
|
||||||
float innerBadgeDist = sdRoundedBox(p, logoHalfSize - float2(minDimension * 0.012, minDimension * 0.012), logoHalfSize.y * 0.34);
|
logoSample *= inside;
|
||||||
|
|
||||||
float letterScale = logoHalfSize.y * 0.88;
|
float logoAlpha = logoSample.a;
|
||||||
float letterDist = logoLetters(p, letterScale);
|
float logoLuma = dot(logoSample.rgb, float3(0.299, 0.587, 0.114));
|
||||||
|
float3 tintedLogo = lerp(logoSample.rgb, badgeColor * max(logoLuma, 0.35), 0.88);
|
||||||
|
|
||||||
float aa = 1.5;
|
float2 edgeDistancePx = min(logoUv, 1.0 - logoUv) * logoSize;
|
||||||
float badgeMask = 1.0 - smoothstep(0.0, aa, badgeDist);
|
float innerEdgePx = min(edgeDistancePx.x, edgeDistancePx.y);
|
||||||
float rimMask = 1.0 - smoothstep(0.0, aa, abs(badgeDist + minDimension * 0.01));
|
float rim = (1.0 - smoothstep(0.0, 5.0, innerEdgePx)) * logoAlpha;
|
||||||
float innerShade = 1.0 - smoothstep(0.0, aa, innerBadgeDist);
|
float glow = (1.0 - smoothstep(0.0, minDimension * 0.06, innerEdgePx + minDimension * 0.025)) * glowAmount * logoAlpha;
|
||||||
float lettersMask = 1.0 - smoothstep(0.0, aa, letterDist);
|
|
||||||
float glowMask = (1.0 - smoothstep(0.0, minDimension * 0.04, badgeDist)) * glowAmount;
|
|
||||||
|
|
||||||
float3 color = float3(0.0, 0.0, 0.0);
|
float3 color = tintedLogo;
|
||||||
color += glowColor * glowMask * 0.55;
|
color += glowColor * glow * 0.6;
|
||||||
color = lerp(color, badgeColor * 0.95, badgeMask);
|
color = lerp(color, float3(1.0, 1.0, 1.0), rim * 0.32);
|
||||||
color = lerp(color, badgeColor * 0.55 + float3(0.08, 0.08, 0.1), innerShade * 0.35);
|
|
||||||
color = lerp(color, float3(1.0, 1.0, 1.0), rimMask * 0.8);
|
|
||||||
color = lerp(color, float3(0.98, 0.99, 1.0), lettersMask);
|
|
||||||
|
|
||||||
float alpha = max(badgeMask, glowMask * 0.45);
|
float alpha = saturate(max(logoAlpha, glow * 0.5) * baseAlpha);
|
||||||
alpha = max(alpha, lettersMask);
|
float3 premultipliedColor = saturate(color) * alpha;
|
||||||
alpha *= saturate(baseAlpha);
|
return float4(premultipliedColor, alpha);
|
||||||
|
|
||||||
return float4(saturate(color), saturate(alpha));
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user