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

@@ -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;