Added textures
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user