diff --git a/SHADER_CONTRACT.md b/SHADER_CONTRACT.md index c69594f..32c096a 100644 --- a/SHADER_CONTRACT.md +++ b/SHADER_CONTRACT.md @@ -15,6 +15,7 @@ Each shader package lives under `shaders//` 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` globals using the texture `id`, for example: + +```slang +float4 logo = logoTexture.Sample(uv); +``` diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp index cab147b..041071b 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -1064,6 +1065,7 @@ bool OpenGLComposite::compileSingleLayerProgram(const RuntimeRenderState& state, GLint linkResult = GL_FALSE; std::string fragmentShaderSource; std::string loadError; + std::vector 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(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(shaderTextureBase + static_cast(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 layerStates = mRuntimeHost ? mRuntimeHost->GetLayerRenderStates(mFrameWidth, mFrameHeight) : std::vector(); 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 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 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 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 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 pixels(static_cast(stride) * static_cast(height)); + result = formatConverter->CopyPixels(NULL, stride, static_cast(pixels.size()), pixels.data()); + if (FAILED(result)) + { + if (shouldUninitializeCom) + CoUninitialize(); + error = "Could not read shader texture pixels: " + textureAsset.path.string(); + return false; + } + + std::vector flippedPixels(pixels.size()); + for (UINT row = 0; row < height; ++row) + { + const std::size_t srcOffset = static_cast(row) * stride; + const std::size_t dstOffset = static_cast(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(width), static_cast(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(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& 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(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(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(index)); + glBindTexture(GL_TEXTURE_2D, 0); + } glActiveTexture(GL_TEXTURE0 + kDecodedVideoTextureUnit); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); diff --git a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h index 1281fe2..f800149 100644 --- a/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h +++ b/apps/LoopThroughWithOpenGLCompositing/OpenGLComposite.h @@ -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 textureBindings; }; std::vector 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& layerStates, std::string& error) const; bool ensureTemporalHistoryResources(const std::vector& layerStates, std::string& error); bool createHistoryRing(HistoryRing& ring, unsigned effectiveLength, TemporalHistorySource historySource, std::string& error); void destroyHistoryRing(HistoryRing& ring); diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp index 4a36101..01df860 100644 --- a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp +++ b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.cpp @@ -159,6 +159,24 @@ bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& } return false; } + +bool TextureAssetsEqual(const std::vector& left, const std::vector& 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 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 gSourceHistory" << index << ";\n"; for (unsigned index = 0; index < mConfig.maxTemporalHistoryFrames; ++index) source << "Sampler2D gTemporalHistory" << index << ";\n"; + for (const ShaderTextureAsset& textureAsset : shaderPackage.textureAssets) + source << "Sampler2D " << textureAsset.id << ";\n"; source << "\n"; source << "float4 sampleVideo(float2 tc)\n"; source << "{\n"; diff --git a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h index 15041af..5dfa229 100644 --- a/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h +++ b/apps/LoopThroughWithOpenGLCompositing/RuntimeHost.h @@ -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 parameters; + std::vector 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 parameterDefinitions; std::map parameterValues; + std::vector textureAssets; double timeSeconds = 0.0; double frameCount = 0.0; double mixAmount = 1.0; diff --git a/shaders/dvd-bounce/shader.json b/shaders/dvd-bounce/shader.json index a4f3ba8..15d6f20 100644 --- a/shaders/dvd-bounce/shader.json +++ b/shaders/dvd-bounce/shader.json @@ -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", diff --git a/shaders/dvd-bounce/shader.slang b/shaders/dvd-bounce/shader.slang index 3cb10f8..f2e87b7 100644 --- a/shaders/dvd-bounce/shader.slang +++ b/shaders/dvd-bounce/shader.slang @@ -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); }