Added textures

This commit is contained in:
2026-05-02 19:46:48 +10:00
parent 8ce556330f
commit 4308e0b6f6
7 changed files with 318 additions and 75 deletions

View File

@@ -15,6 +15,7 @@ Each shader package lives under `shaders/<id>/` and includes:
- `category`
- `entryPoint`
- `parameters`
- optional `textures`
- optional `temporal`
Supported parameter types:
@@ -25,6 +26,25 @@ Supported parameter types:
- `bool`
- `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
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.
- `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.
Declared texture assets are exposed as `Sampler2D<float4>` globals using the texture `id`, for example:
```slang
float4 logo = logoTexture.Sample(uv);
```

View File

@@ -45,6 +45,7 @@
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <wincodec.h>
#include <set>
#include <sstream>
#include <string>
@@ -1064,6 +1065,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
GLint linkResult = GL_FALSE;
std::string fragmentShaderSource;
std::string loadError;
std::vector<LayerProgram::TextureBinding> textureBindings;
const char* vertexSource = kVertexShaderSource;
if (!mRuntimeHost->BuildLayerFragmentShaderSource(state.layerId, fragmentShaderSource, loadError))
@@ -1111,11 +1113,33 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
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");
if (globalParamsIndex != GL_INVALID_INDEX)
glUniformBlockBinding(newProgram, globalParamsIndex, kGlobalParamsBindingPoint);
const unsigned historyCap = mRuntimeHost ? mRuntimeHost->GetMaxTemporalHistoryFrames() : 0;
const GLuint shaderTextureBase = kSourceHistoryTextureUnitBase + historyCap + historyCap;
glUseProgram(newProgram);
const GLint videoInputLocation = glGetUniformLocation(newProgram, "gVideoInput");
if (videoInputLocation >= 0)
@@ -1132,6 +1156,12 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
if (temporalSamplerLocation >= 0)
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);
layerProgram.layerId = state.layerId;
@@ -1139,6 +1169,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state,
layerProgram.program = newProgram;
layerProgram.vertexShader = newVertexShader;
layerProgram.fragmentShader = newFragmentShader;
layerProgram.textureBindings.swap(textureBindings);
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>();
std::string temporalError;
if (!validateTemporalTextureUnitBudget(temporalError))
if (!validateTemporalTextureUnitBudget(layerStates, temporalError))
{
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
return false;
@@ -1237,6 +1268,16 @@ bool OpenGLComposite::compileDecodeShader(int errorMessageSize, char* errorMessa
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)
{
glDeleteProgram(layerProgram.program);
@@ -1263,6 +1304,124 @@ void OpenGLComposite::destroyLayerPrograms()
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()
{
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;
unsigned maxAssetTextures = 0;
for (const RuntimeRenderState& state : layerStates)
{
if (state.textureAssets.size() > maxAssetTextures)
maxAssetTextures = static_cast<unsigned>(state.textureAssets.size());
}
GLint maxTextureUnits = 0;
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;
if (requiredUnits > availableUnits)
{
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.";
error = message.str();
return false;
@@ -1548,6 +1713,7 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati
glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit);
glBindTexture(GL_TEXTURE_2D, sourceTexture);
bindHistorySamplers(state, sourceTexture);
bindLayerTextureAssets(layerProgram);
glBindVertexArray(mFullscreenVAO);
glUseProgram(layerProgram.program);
updateGlobalParamsBuffer(state, sourceHistoryAvailableCount(), temporalHistoryAvailableCountForLayer(state.layerId));
@@ -1562,6 +1728,12 @@ void OpenGLComposite::renderShaderProgram(GLuint sourceTexture, GLuint destinati
glActiveTexture(GL_TEXTURE0 + kSourceHistoryTextureUnitBase + historyCap + index);
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);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);

View File

@@ -151,11 +151,19 @@ private:
struct LayerProgram
{
struct TextureBinding
{
std::string samplerName;
std::filesystem::path sourcePath;
GLuint texture = 0;
};
std::string layerId;
std::string shaderId;
GLuint program = 0;
GLuint vertexShader = 0;
GLuint fragmentShader = 0;
std::vector<TextureBinding> textureBindings;
};
std::vector<LayerProgram> mLayerPrograms;
@@ -187,11 +195,13 @@ private:
void destroyDecodeShaderProgram();
void renderDecodePass();
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();
bool PollRuntimeChanges();
void broadcastRuntimeState();
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 createHistoryRing(HistoryRing& ring, unsigned effectiveLength, TemporalHistorySource historySource, std::string& error);
void destroyHistoryRing(HistoryRing& ring);

View File

@@ -159,6 +159,24 @@ bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType&
}
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()
@@ -283,7 +301,8 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested,
break;
}
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;
break;
@@ -300,8 +319,11 @@ bool RuntimeHost::PollFileChanges(bool& registryChanged, bool& reloadRequested,
EnsureLayerDefaultsLocked(layer, active->second);
if (previous != previousLayerShaderTimes.end())
{
auto previousPackage = previousPackages.find(layer.shaderId);
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;
}
@@ -699,6 +721,7 @@ std::vector<RuntimeRenderState> RuntimeHost::GetLayerRenderStates(unsigned outpu
state.outputWidth = outputWidth;
state.outputHeight = outputHeight;
state.parameterDefinitions = shaderIt->second.parameters;
state.textureAssets = shaderIt->second.textureAssets;
state.isTemporal = shaderIt->second.temporal.enabled;
state.temporalHistorySource = shaderIt->second.temporal.historySource;
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.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 (!temporalValue->isObject())
@@ -1287,6 +1349,8 @@ std::string RuntimeHost::BuildWrapperSlangSource(const ShaderPackage& shaderPack
source << "Sampler2D<float4> gSourceHistory" << index << ";\n";
for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index)
source << "Sampler2D<float4> gTemporalHistory" << index << ";\n";
for (const ShaderTextureAsset& textureAsset : shaderPackage.textureAssets)
source << "Sampler2D<float4> " << textureAsset.id << ";\n";
source << "\n";
source << "float4 sampleVideo(float2 tc)\n";
source << "{\n";

View File

@@ -60,6 +60,13 @@ struct TemporalSettings
unsigned effectiveHistoryLength = 0;
};
struct ShaderTextureAsset
{
std::string id;
std::filesystem::path path;
std::filesystem::file_time_type writeTime;
};
struct ShaderPackage
{
std::string id;
@@ -71,6 +78,7 @@ struct ShaderPackage
std::filesystem::path shaderPath;
std::filesystem::path manifestPath;
std::vector<ShaderParameterDefinition> parameters;
std::vector<ShaderTextureAsset> textureAssets;
TemporalSettings temporal;
std::filesystem::file_time_type shaderWriteTime;
std::filesystem::file_time_type manifestWriteTime;
@@ -82,6 +90,7 @@ struct RuntimeRenderState
std::string shaderId;
std::vector<ShaderParameterDefinition> parameterDefinitions;
std::map<std::string, ShaderParameterValue> parameterValues;
std::vector<ShaderTextureAsset> textureAssets;
double timeSeconds = 0.0;
double frameCount = 0.0;
double mixAmount = 1.0;

View File

@@ -1,9 +1,15 @@
{
"id": "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",
"entryPoint": "shadeVideo",
"textures": [
{
"id": "dvdLogoTexture",
"path": "DVD_Logo.png"
}
],
"parameters": [
{
"id": "logoScale",

View File

@@ -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 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);
}
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)
{
float2 resolution = max(context.outputResolution, float2(1.0, 1.0));
float minDimension = min(resolution.x, resolution.y);
float logoAspect = 2.0;
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 minCenterPx = 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));
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);
float innerBadgeDist = sdRoundedBox(p, logoHalfSize - float2(minDimension * 0.012, minDimension * 0.012), logoHalfSize.y * 0.34);
float4 logoSample = dvdLogoTexture.Sample(logoUv);
logoSample *= inside;
float letterScale = logoHalfSize.y * 0.88;
float letterDist = logoLetters(p, letterScale);
float logoAlpha = logoSample.a;
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;
float badgeMask = 1.0 - smoothstep(0.0, aa, badgeDist);
float rimMask = 1.0 - smoothstep(0.0, aa, abs(badgeDist + minDimension * 0.01));
float innerShade = 1.0 - smoothstep(0.0, aa, innerBadgeDist);
float lettersMask = 1.0 - smoothstep(0.0, aa, letterDist);
float glowMask = (1.0 - smoothstep(0.0, minDimension * 0.04, badgeDist)) * glowAmount;
float2 edgeDistancePx = min(logoUv, 1.0 - logoUv) * logoSize;
float innerEdgePx = min(edgeDistancePx.x, edgeDistancePx.y);
float rim = (1.0 - smoothstep(0.0, 5.0, innerEdgePx)) * logoAlpha;
float glow = (1.0 - smoothstep(0.0, minDimension * 0.06, innerEdgePx + minDimension * 0.025)) * glowAmount * logoAlpha;
float3 color = float3(0.0, 0.0, 0.0);
color += glowColor * glowMask * 0.55;
color = lerp(color, badgeColor * 0.95, badgeMask);
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);
float3 color = tintedLogo;
color += glowColor * glow * 0.6;
color = lerp(color, float3(1.0, 1.0, 1.0), rim * 0.32);
float alpha = max(badgeMask, glowMask * 0.45);
alpha = max(alpha, lettersMask);
alpha *= saturate(baseAlpha);
return float4(saturate(color), saturate(alpha));
float alpha = saturate(max(logoAlpha, glow * 0.5) * baseAlpha);
float3 premultipliedColor = saturate(color) * alpha;
return float4(premultipliedColor, alpha);
}