re organisation
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
#include "GlShaderSources.h"
|
||||
|
||||
const char* kFullscreenTriangleVertexShaderSource =
|
||||
"#version 430 core\n"
|
||||
"out vec2 vTexCoord;\n"
|
||||
"void main()\n"
|
||||
"{\n"
|
||||
" vec2 positions[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n"
|
||||
" vec2 texCoords[3] = vec2[3](vec2(0.0, 0.0), vec2(2.0, 0.0), vec2(0.0, 2.0));\n"
|
||||
" gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);\n"
|
||||
" vTexCoord = texCoords[gl_VertexID];\n"
|
||||
"}\n";
|
||||
|
||||
const char* kDecodeFragmentShaderSource =
|
||||
"#version 430 core\n"
|
||||
"layout(binding = 2) uniform sampler2D uPackedVideoInput;\n"
|
||||
"uniform vec2 uPackedVideoResolution;\n"
|
||||
"uniform vec2 uDecodedVideoResolution;\n"
|
||||
"uniform int uInputPixelFormat;\n"
|
||||
"in vec2 vTexCoord;\n"
|
||||
"layout(location = 0) out vec4 fragColor;\n"
|
||||
"vec4 rec709YCbCr2rgba(float Y, float Cb, float Cr, float a)\n"
|
||||
"{\n"
|
||||
" Y = (Y * 256.0 - 16.0) / 219.0;\n"
|
||||
" Cb = (Cb * 256.0 - 16.0) / 224.0 - 0.5;\n"
|
||||
" Cr = (Cr * 256.0 - 16.0) / 224.0 - 0.5;\n"
|
||||
" return vec4(Y + 1.5748 * Cr, Y - 0.1873 * Cb - 0.4681 * Cr, Y + 1.8556 * Cb, a);\n"
|
||||
"}\n"
|
||||
"vec4 rec709YCbCr10_2rgba(float Y, float Cb, float Cr, float a)\n"
|
||||
"{\n"
|
||||
" Y = (Y - 64.0) / 876.0;\n"
|
||||
" Cb = (Cb - 64.0) / 896.0 - 0.5;\n"
|
||||
" Cr = (Cr - 64.0) / 896.0 - 0.5;\n"
|
||||
" return vec4(Y + 1.5748 * Cr, Y - 0.1873 * Cb - 0.4681 * Cr, Y + 1.8556 * Cb, a);\n"
|
||||
"}\n"
|
||||
"uint loadV210Word(ivec2 coord)\n"
|
||||
"{\n"
|
||||
" vec4 b = round(texelFetch(uPackedVideoInput, coord, 0) * 255.0);\n"
|
||||
" return uint(b.r) | (uint(b.g) << 8) | (uint(b.b) << 16) | (uint(b.a) << 24);\n"
|
||||
"}\n"
|
||||
"float v210Component(uint word, int index)\n"
|
||||
"{\n"
|
||||
" return float((word >> uint(index * 10)) & 1023u);\n"
|
||||
"}\n"
|
||||
"vec4 decodeUyvy8(ivec2 outputCoord, ivec2 packedSize)\n"
|
||||
"{\n"
|
||||
" ivec2 packedCoord = ivec2(clamp(outputCoord.x / 2, 0, packedSize.x - 1), clamp(outputCoord.y, 0, packedSize.y - 1));\n"
|
||||
" vec4 macroPixel = texelFetch(uPackedVideoInput, packedCoord, 0);\n"
|
||||
" float ySample = (outputCoord.x & 1) != 0 ? macroPixel.a : macroPixel.g;\n"
|
||||
" return rec709YCbCr2rgba(ySample, macroPixel.b, macroPixel.r, 1.0);\n"
|
||||
"}\n"
|
||||
"vec4 decodeV210(ivec2 outputCoord, ivec2 packedSize)\n"
|
||||
"{\n"
|
||||
" int group = outputCoord.x / 6;\n"
|
||||
" int pixel = outputCoord.x - group * 6;\n"
|
||||
" int wordBase = group * 4;\n"
|
||||
" ivec2 rowBase = ivec2(wordBase, clamp(outputCoord.y, 0, packedSize.y - 1));\n"
|
||||
" uint w0 = loadV210Word(ivec2(min(rowBase.x + 0, packedSize.x - 1), rowBase.y));\n"
|
||||
" uint w1 = loadV210Word(ivec2(min(rowBase.x + 1, packedSize.x - 1), rowBase.y));\n"
|
||||
" uint w2 = loadV210Word(ivec2(min(rowBase.x + 2, packedSize.x - 1), rowBase.y));\n"
|
||||
" uint w3 = loadV210Word(ivec2(min(rowBase.x + 3, packedSize.x - 1), rowBase.y));\n"
|
||||
" float y0 = v210Component(w0, 1);\n"
|
||||
" float y1 = v210Component(w1, 0);\n"
|
||||
" float y2 = v210Component(w1, 2);\n"
|
||||
" float y3 = v210Component(w2, 1);\n"
|
||||
" float y4 = v210Component(w3, 0);\n"
|
||||
" float y5 = v210Component(w3, 2);\n"
|
||||
" float cb0 = v210Component(w0, 0);\n"
|
||||
" float cr0 = v210Component(w0, 2);\n"
|
||||
" float cb2 = v210Component(w1, 1);\n"
|
||||
" float cr2 = v210Component(w2, 0);\n"
|
||||
" float cb4 = v210Component(w2, 2);\n"
|
||||
" float cr4 = v210Component(w3, 1);\n"
|
||||
" float ySample = pixel == 0 ? y0 : pixel == 1 ? y1 : pixel == 2 ? y2 : pixel == 3 ? y3 : pixel == 4 ? y4 : y5;\n"
|
||||
" float cbSample = pixel < 2 ? cb0 : pixel < 4 ? cb2 : cb4;\n"
|
||||
" float crSample = pixel < 2 ? cr0 : pixel < 4 ? cr2 : cr4;\n"
|
||||
" return rec709YCbCr10_2rgba(ySample, cbSample, crSample, 1.0);\n"
|
||||
"}\n"
|
||||
"void main()\n"
|
||||
"{\n"
|
||||
" vec2 correctedUv = vec2(vTexCoord.x, 1.0 - vTexCoord.y);\n"
|
||||
" ivec2 decodedSize = ivec2(max(uDecodedVideoResolution, vec2(1.0, 1.0)));\n"
|
||||
" ivec2 outputCoord = clamp(ivec2(correctedUv * vec2(decodedSize)), ivec2(0, 0), decodedSize - ivec2(1, 1));\n"
|
||||
" ivec2 packedSize = ivec2(max(uPackedVideoResolution, vec2(1.0, 1.0)));\n"
|
||||
" fragColor = uInputPixelFormat == 1 ? decodeV210(outputCoord, packedSize) : decodeUyvy8(outputCoord, packedSize);\n"
|
||||
"}\n";
|
||||
|
||||
const char* kOutputPackFragmentShaderSource =
|
||||
"#version 430 core\n"
|
||||
"layout(binding = 0) uniform sampler2D uOutputRgb;\n"
|
||||
"uniform vec2 uOutputVideoResolution;\n"
|
||||
"uniform float uActiveV210Words;\n"
|
||||
"in vec2 vTexCoord;\n"
|
||||
"layout(location = 0) out vec4 fragColor;\n"
|
||||
"vec3 rgbAt(int x, int y)\n"
|
||||
"{\n"
|
||||
" ivec2 size = ivec2(max(uOutputVideoResolution, vec2(1.0, 1.0)));\n"
|
||||
" return clamp(texelFetch(uOutputRgb, ivec2(clamp(x, 0, size.x - 1), clamp(y, 0, size.y - 1)), 0).rgb, vec3(0.0), vec3(1.0));\n"
|
||||
"}\n"
|
||||
"vec3 rgbToLegalYcbcr10(vec3 rgb)\n"
|
||||
"{\n"
|
||||
" float y = dot(rgb, vec3(0.2126, 0.7152, 0.0722));\n"
|
||||
" float cb = (rgb.b - y) / 1.8556 + 0.5;\n"
|
||||
" float cr = (rgb.r - y) / 1.5748 + 0.5;\n"
|
||||
" return vec3(clamp(round(64.0 + y * 876.0), 64.0, 940.0), clamp(round(64.0 + cb * 896.0), 64.0, 960.0), clamp(round(64.0 + cr * 896.0), 64.0, 960.0));\n"
|
||||
"}\n"
|
||||
"uint makeWord(float a, float b, float c)\n"
|
||||
"{\n"
|
||||
" return (uint(a) & 1023u) | ((uint(b) & 1023u) << 10) | ((uint(c) & 1023u) << 20);\n"
|
||||
"}\n"
|
||||
"vec4 wordToBytes(uint word)\n"
|
||||
"{\n"
|
||||
" return vec4(float(word & 255u), float((word >> 8) & 255u), float((word >> 16) & 255u), float((word >> 24) & 255u)) / 255.0;\n"
|
||||
"}\n"
|
||||
"void main()\n"
|
||||
"{\n"
|
||||
" ivec2 outCoord = ivec2(gl_FragCoord.xy);\n"
|
||||
" if (float(outCoord.x) >= uActiveV210Words)\n"
|
||||
" {\n"
|
||||
" fragColor = vec4(0.0);\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
" int group = outCoord.x / 4;\n"
|
||||
" int wordIndex = outCoord.x - group * 4;\n"
|
||||
" int pixelBase = group * 6;\n"
|
||||
" int y = outCoord.y;\n"
|
||||
" vec3 c0 = rgbToLegalYcbcr10(rgbAt(pixelBase + 0, y));\n"
|
||||
" vec3 c1 = rgbToLegalYcbcr10(rgbAt(pixelBase + 1, y));\n"
|
||||
" vec3 c2 = rgbToLegalYcbcr10(rgbAt(pixelBase + 2, y));\n"
|
||||
" vec3 c3 = rgbToLegalYcbcr10(rgbAt(pixelBase + 3, y));\n"
|
||||
" vec3 c4 = rgbToLegalYcbcr10(rgbAt(pixelBase + 4, y));\n"
|
||||
" vec3 c5 = rgbToLegalYcbcr10(rgbAt(pixelBase + 5, y));\n"
|
||||
" float cb0 = round((c0.y + c1.y) * 0.5);\n"
|
||||
" float cr0 = round((c0.z + c1.z) * 0.5);\n"
|
||||
" float cb2 = round((c2.y + c3.y) * 0.5);\n"
|
||||
" float cr2 = round((c2.z + c3.z) * 0.5);\n"
|
||||
" float cb4 = round((c4.y + c5.y) * 0.5);\n"
|
||||
" float cr4 = round((c4.z + c5.z) * 0.5);\n"
|
||||
" uint word = wordIndex == 0 ? makeWord(cb0, c0.x, cr0) : wordIndex == 1 ? makeWord(c1.x, cb2, c2.x) : wordIndex == 2 ? makeWord(cr2, c3.x, cb4) : makeWord(c4.x, cr4, c5.x);\n"
|
||||
" fragColor = wordToBytes(word);\n"
|
||||
"}\n";
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
extern const char* kFullscreenTriangleVertexShaderSource;
|
||||
extern const char* kDecodeFragmentShaderSource;
|
||||
extern const char* kOutputPackFragmentShaderSource;
|
||||
@@ -0,0 +1,102 @@
|
||||
#include "GlobalParamsBuffer.h"
|
||||
|
||||
#include "GlRenderConstants.h"
|
||||
#include "Std140Buffer.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
GlobalParamsBuffer::GlobalParamsBuffer(OpenGLRenderer& renderer) :
|
||||
mRenderer(renderer)
|
||||
{
|
||||
}
|
||||
|
||||
bool GlobalParamsBuffer::Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength)
|
||||
{
|
||||
std::vector<unsigned char> buffer;
|
||||
buffer.reserve(512);
|
||||
|
||||
AppendStd140Float(buffer, static_cast<float>(state.timeSeconds));
|
||||
AppendStd140Vec2(buffer, static_cast<float>(state.inputWidth), static_cast<float>(state.inputHeight));
|
||||
AppendStd140Vec2(buffer, static_cast<float>(state.outputWidth), static_cast<float>(state.outputHeight));
|
||||
AppendStd140Float(buffer, static_cast<float>(state.utcTimeSeconds));
|
||||
AppendStd140Float(buffer, static_cast<float>(state.utcOffsetSeconds));
|
||||
AppendStd140Float(buffer, static_cast<float>(state.startupRandom));
|
||||
AppendStd140Float(buffer, static_cast<float>(state.frameCount));
|
||||
AppendStd140Float(buffer, static_cast<float>(state.mixAmount));
|
||||
AppendStd140Float(buffer, static_cast<float>(state.bypass));
|
||||
const unsigned effectiveSourceHistoryLength = availableSourceHistoryLength < state.effectiveTemporalHistoryLength
|
||||
? availableSourceHistoryLength
|
||||
: state.effectiveTemporalHistoryLength;
|
||||
const unsigned effectiveTemporalHistoryLength = (state.temporalHistorySource == TemporalHistorySource::PreLayerInput)
|
||||
? (availableTemporalHistoryLength < state.effectiveTemporalHistoryLength ? availableTemporalHistoryLength : state.effectiveTemporalHistoryLength)
|
||||
: 0u;
|
||||
AppendStd140Int(buffer, static_cast<int>(effectiveSourceHistoryLength));
|
||||
AppendStd140Int(buffer, static_cast<int>(effectiveTemporalHistoryLength));
|
||||
|
||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
||||
{
|
||||
auto valueIt = state.parameterValues.find(definition.id);
|
||||
const ShaderParameterValue value = valueIt != state.parameterValues.end()
|
||||
? valueIt->second
|
||||
: ShaderParameterValue();
|
||||
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
AppendStd140Float(buffer, value.numberValues.empty() ? 0.0f : static_cast<float>(value.numberValues[0]));
|
||||
break;
|
||||
case ShaderParameterType::Vec2:
|
||||
AppendStd140Vec2(buffer,
|
||||
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 0.0f,
|
||||
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 0.0f);
|
||||
break;
|
||||
case ShaderParameterType::Color:
|
||||
AppendStd140Vec4(buffer,
|
||||
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 1.0f,
|
||||
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 1.0f,
|
||||
value.numberValues.size() > 2 ? static_cast<float>(value.numberValues[2]) : 1.0f,
|
||||
value.numberValues.size() > 3 ? static_cast<float>(value.numberValues[3]) : 1.0f);
|
||||
break;
|
||||
case ShaderParameterType::Boolean:
|
||||
AppendStd140Int(buffer, value.booleanValue ? 1 : 0);
|
||||
break;
|
||||
case ShaderParameterType::Enum:
|
||||
{
|
||||
int selectedIndex = 0;
|
||||
for (std::size_t optionIndex = 0; optionIndex < definition.enumOptions.size(); ++optionIndex)
|
||||
{
|
||||
if (definition.enumOptions[optionIndex].value == value.enumValue)
|
||||
{
|
||||
selectedIndex = static_cast<int>(optionIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
AppendStd140Int(buffer, selectedIndex);
|
||||
break;
|
||||
}
|
||||
case ShaderParameterType::Text:
|
||||
break;
|
||||
case ShaderParameterType::Trigger:
|
||||
AppendStd140Int(buffer, value.numberValues.empty() ? 0 : static_cast<int>(value.numberValues[0]));
|
||||
AppendStd140Float(buffer, value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : -1000000.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.resize(AlignStd140(buffer.size(), 16), 0);
|
||||
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, mRenderer.GlobalParamsUBO());
|
||||
if (mRenderer.GlobalParamsUBOSize() != static_cast<GLsizeiptr>(buffer.size()))
|
||||
{
|
||||
glBufferData(GL_UNIFORM_BUFFER, static_cast<GLsizeiptr>(buffer.size()), buffer.data(), GL_DYNAMIC_DRAW);
|
||||
mRenderer.SetGlobalParamsUBOSize(static_cast<GLsizeiptr>(buffer.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
glBufferSubData(GL_UNIFORM_BUFFER, 0, static_cast<GLsizeiptr>(buffer.size()), buffer.data());
|
||||
}
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mRenderer.GlobalParamsUBO());
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
class GlobalParamsBuffer
|
||||
{
|
||||
public:
|
||||
explicit GlobalParamsBuffer(OpenGLRenderer& renderer);
|
||||
|
||||
bool Update(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength);
|
||||
|
||||
private:
|
||||
OpenGLRenderer& mRenderer;
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
#include "OpenGLShaderPrograms.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
if (!errorMessage || errorMessageSize <= 0)
|
||||
return;
|
||||
|
||||
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
|
||||
}
|
||||
}
|
||||
|
||||
OpenGLShaderPrograms::OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost) :
|
||||
mRenderer(renderer),
|
||||
mRuntimeHost(runtimeHost),
|
||||
mGlobalParamsBuffer(renderer),
|
||||
mCompiler(renderer, runtimeHost, mTextureBindings)
|
||||
{
|
||||
}
|
||||
|
||||
bool OpenGLShaderPrograms::CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
const std::vector<RuntimeRenderState> layerStates = mRuntimeHost.GetLayerRenderStates(inputFrameWidth, inputFrameHeight);
|
||||
std::string temporalError;
|
||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(layerStates, historyCap, temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
if (!mRenderer.TemporalHistory().EnsureResources(layerStates, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<LayerProgram> newPrograms;
|
||||
newPrograms.reserve(layerStates.size());
|
||||
|
||||
for (const RuntimeRenderState& state : layerStates)
|
||||
{
|
||||
LayerProgram layerProgram;
|
||||
if (!mCompiler.CompileLayerProgram(state, layerProgram, errorMessageSize, errorMessage))
|
||||
{
|
||||
for (LayerProgram& program : newPrograms)
|
||||
DestroySingleLayerProgram(program);
|
||||
return false;
|
||||
}
|
||||
newPrograms.push_back(layerProgram);
|
||||
}
|
||||
|
||||
DestroyLayerPrograms();
|
||||
mRenderer.ReplaceLayerPrograms(newPrograms);
|
||||
mCommittedLayerStates = layerStates;
|
||||
|
||||
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
|
||||
mRuntimeHost.ClearReloadRequest();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLShaderPrograms::CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
if (!preparedBuild.succeeded)
|
||||
{
|
||||
CopyErrorMessage(preparedBuild.message, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string temporalError;
|
||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||
if (!mRenderer.TemporalHistory().ValidateTextureUnitBudget(preparedBuild.layerStates, historyCap, temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
if (!mRenderer.TemporalHistory().EnsureResources(preparedBuild.layerStates, historyCap, inputFrameWidth, inputFrameHeight, temporalError))
|
||||
{
|
||||
CopyErrorMessage(temporalError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<LayerProgram> newPrograms;
|
||||
newPrograms.reserve(preparedBuild.layers.size());
|
||||
|
||||
for (const PreparedLayerShader& preparedLayer : preparedBuild.layers)
|
||||
{
|
||||
LayerProgram layerProgram;
|
||||
if (!mCompiler.CompilePreparedLayerProgram(preparedLayer.state, preparedLayer.fragmentShaderSource, layerProgram, errorMessageSize, errorMessage))
|
||||
{
|
||||
for (LayerProgram& program : newPrograms)
|
||||
DestroySingleLayerProgram(program);
|
||||
return false;
|
||||
}
|
||||
newPrograms.push_back(layerProgram);
|
||||
}
|
||||
|
||||
DestroyLayerPrograms();
|
||||
mRenderer.ReplaceLayerPrograms(newPrograms);
|
||||
mCommittedLayerStates = preparedBuild.layerStates;
|
||||
|
||||
mRuntimeHost.SetCompileStatus(true, "Shader layers compiled successfully.");
|
||||
mRuntimeHost.ClearReloadRequest();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenGLShaderPrograms::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
return mCompiler.CompileDecodeShader(errorMessageSize, errorMessage);
|
||||
}
|
||||
|
||||
bool OpenGLShaderPrograms::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
return mCompiler.CompileOutputPackShader(errorMessageSize, errorMessage);
|
||||
}
|
||||
|
||||
void OpenGLShaderPrograms::DestroySingleLayerProgram(LayerProgram& layerProgram)
|
||||
{
|
||||
mRenderer.DestroySingleLayerProgram(layerProgram);
|
||||
}
|
||||
|
||||
void OpenGLShaderPrograms::DestroyLayerPrograms()
|
||||
{
|
||||
mRenderer.DestroyLayerPrograms();
|
||||
}
|
||||
|
||||
void OpenGLShaderPrograms::DestroyDecodeShaderProgram()
|
||||
{
|
||||
mRenderer.DestroyDecodeShaderProgram();
|
||||
}
|
||||
|
||||
void OpenGLShaderPrograms::ResetTemporalHistoryState()
|
||||
{
|
||||
mRenderer.TemporalHistory().ResetState();
|
||||
}
|
||||
|
||||
bool OpenGLShaderPrograms::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
|
||||
{
|
||||
return mTextureBindings.UpdateTextBindingTexture(state, textBinding, error);
|
||||
}
|
||||
|
||||
bool OpenGLShaderPrograms::UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength)
|
||||
{
|
||||
return mGlobalParamsBuffer.Update(state, availableSourceHistoryLength, availableTemporalHistoryLength);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "GlobalParamsBuffer.h"
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "ShaderBuildQueue.h"
|
||||
#include "ShaderTypes.h"
|
||||
#include "ShaderProgramCompiler.h"
|
||||
#include "ShaderTextureBindings.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class OpenGLShaderPrograms
|
||||
{
|
||||
public:
|
||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||
|
||||
OpenGLShaderPrograms(OpenGLRenderer& renderer, RuntimeHost& runtimeHost);
|
||||
|
||||
bool CompileLayerPrograms(unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
||||
bool CommitPreparedLayerPrograms(const PreparedShaderBuild& preparedBuild, unsigned inputFrameWidth, unsigned inputFrameHeight, int errorMessageSize, char* errorMessage);
|
||||
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
|
||||
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
|
||||
void DestroyLayerPrograms();
|
||||
void DestroySingleLayerProgram(LayerProgram& layerProgram);
|
||||
void DestroyDecodeShaderProgram();
|
||||
void ResetTemporalHistoryState();
|
||||
const std::vector<RuntimeRenderState>& CommittedLayerStates() const { return mCommittedLayerStates; }
|
||||
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
|
||||
bool UpdateGlobalParamsBuffer(const RuntimeRenderState& state, unsigned availableSourceHistoryLength, unsigned availableTemporalHistoryLength);
|
||||
|
||||
private:
|
||||
OpenGLRenderer& mRenderer;
|
||||
RuntimeHost& mRuntimeHost;
|
||||
ShaderTextureBindings mTextureBindings;
|
||||
GlobalParamsBuffer mGlobalParamsBuffer;
|
||||
ShaderProgramCompiler mCompiler;
|
||||
std::vector<RuntimeRenderState> mCommittedLayerStates;
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "ShaderBuildQueue.h"
|
||||
|
||||
#include "RuntimeHost.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto kShaderBuildDebounce = std::chrono::milliseconds(400);
|
||||
}
|
||||
|
||||
ShaderBuildQueue::ShaderBuildQueue(RuntimeHost& runtimeHost) :
|
||||
mRuntimeHost(runtimeHost),
|
||||
mWorkerThread([this]() { WorkerLoop(); })
|
||||
{
|
||||
}
|
||||
|
||||
ShaderBuildQueue::~ShaderBuildQueue()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
void ShaderBuildQueue::RequestBuild(unsigned outputWidth, unsigned outputHeight)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mHasRequest = true;
|
||||
++mRequestedGeneration;
|
||||
mRequestedOutputWidth = outputWidth;
|
||||
mRequestedOutputHeight = outputHeight;
|
||||
mHasReadyBuild = false;
|
||||
}
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
bool ShaderBuildQueue::TryConsumeReadyBuild(PreparedShaderBuild& build)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (!mHasReadyBuild)
|
||||
return false;
|
||||
|
||||
build = std::move(mReadyBuild);
|
||||
mReadyBuild = PreparedShaderBuild();
|
||||
mHasReadyBuild = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShaderBuildQueue::Stop()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mStopping)
|
||||
return;
|
||||
mStopping = true;
|
||||
}
|
||||
mCondition.notify_one();
|
||||
if (mWorkerThread.joinable())
|
||||
mWorkerThread.join();
|
||||
}
|
||||
|
||||
void ShaderBuildQueue::WorkerLoop()
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
uint64_t generation = 0;
|
||||
unsigned outputWidth = 0;
|
||||
unsigned outputHeight = 0;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mCondition.wait(lock, [this]() { return mStopping || mHasRequest; });
|
||||
if (mStopping)
|
||||
return;
|
||||
|
||||
generation = mRequestedGeneration;
|
||||
outputWidth = mRequestedOutputWidth;
|
||||
outputHeight = mRequestedOutputHeight;
|
||||
mHasRequest = false;
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (mCondition.wait_for(lock, kShaderBuildDebounce, [this, generation]() {
|
||||
return mStopping || (mHasRequest && mRequestedGeneration != generation);
|
||||
}))
|
||||
{
|
||||
if (mStopping)
|
||||
return;
|
||||
|
||||
generation = mRequestedGeneration;
|
||||
outputWidth = mRequestedOutputWidth;
|
||||
outputHeight = mRequestedOutputHeight;
|
||||
mHasRequest = false;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
PreparedShaderBuild build = Build(generation, outputWidth, outputHeight);
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mStopping)
|
||||
return;
|
||||
if (generation != mRequestedGeneration)
|
||||
continue;
|
||||
mReadyBuild = std::move(build);
|
||||
mHasReadyBuild = true;
|
||||
}
|
||||
}
|
||||
|
||||
PreparedShaderBuild ShaderBuildQueue::Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight)
|
||||
{
|
||||
PreparedShaderBuild build;
|
||||
build.generation = generation;
|
||||
build.layerStates = mRuntimeHost.GetLayerRenderStates(outputWidth, outputHeight);
|
||||
build.layers.reserve(build.layerStates.size());
|
||||
|
||||
for (const RuntimeRenderState& state : build.layerStates)
|
||||
{
|
||||
PreparedLayerShader layer;
|
||||
layer.state = state;
|
||||
if (!mRuntimeHost.BuildLayerFragmentShaderSource(state.layerId, layer.fragmentShaderSource, build.message))
|
||||
{
|
||||
build.succeeded = false;
|
||||
return build;
|
||||
}
|
||||
build.layers.push_back(std::move(layer));
|
||||
}
|
||||
|
||||
build.succeeded = true;
|
||||
build.message = "Shader layers prepared successfully.";
|
||||
return build;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeHost;
|
||||
|
||||
struct PreparedLayerShader
|
||||
{
|
||||
RuntimeRenderState state;
|
||||
std::string fragmentShaderSource;
|
||||
};
|
||||
|
||||
struct PreparedShaderBuild
|
||||
{
|
||||
uint64_t generation = 0;
|
||||
bool succeeded = false;
|
||||
std::string message;
|
||||
std::vector<RuntimeRenderState> layerStates;
|
||||
std::vector<PreparedLayerShader> layers;
|
||||
};
|
||||
|
||||
class ShaderBuildQueue
|
||||
{
|
||||
public:
|
||||
explicit ShaderBuildQueue(RuntimeHost& runtimeHost);
|
||||
~ShaderBuildQueue();
|
||||
|
||||
ShaderBuildQueue(const ShaderBuildQueue&) = delete;
|
||||
ShaderBuildQueue& operator=(const ShaderBuildQueue&) = delete;
|
||||
|
||||
void RequestBuild(unsigned outputWidth, unsigned outputHeight);
|
||||
bool TryConsumeReadyBuild(PreparedShaderBuild& build);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void WorkerLoop();
|
||||
PreparedShaderBuild Build(uint64_t generation, unsigned outputWidth, unsigned outputHeight);
|
||||
|
||||
RuntimeHost& mRuntimeHost;
|
||||
std::thread mWorkerThread;
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mCondition;
|
||||
bool mStopping = false;
|
||||
bool mHasRequest = false;
|
||||
uint64_t mRequestedGeneration = 0;
|
||||
unsigned mRequestedOutputWidth = 0;
|
||||
unsigned mRequestedOutputHeight = 0;
|
||||
bool mHasReadyBuild = false;
|
||||
PreparedShaderBuild mReadyBuild;
|
||||
};
|
||||
@@ -0,0 +1,244 @@
|
||||
#include "ShaderProgramCompiler.h"
|
||||
|
||||
#include "GlRenderConstants.h"
|
||||
#include "GlScopedObjects.h"
|
||||
#include "GlShaderSources.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
void CopyErrorMessage(const std::string& message, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
if (!errorMessage || errorMessageSize <= 0)
|
||||
return;
|
||||
|
||||
strncpy_s(errorMessage, errorMessageSize, message.c_str(), _TRUNCATE);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderProgramCompiler::ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings) :
|
||||
mRenderer(renderer),
|
||||
mRuntimeHost(runtimeHost),
|
||||
mTextureBindings(textureBindings)
|
||||
{
|
||||
}
|
||||
|
||||
bool ShaderProgramCompiler::CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
std::string fragmentShaderSource;
|
||||
std::string loadError;
|
||||
|
||||
if (!mRuntimeHost.BuildLayerFragmentShaderSource(state.layerId, fragmentShaderSource, loadError))
|
||||
{
|
||||
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return CompilePreparedLayerProgram(state, fragmentShaderSource, layerProgram, errorMessageSize, errorMessage);
|
||||
}
|
||||
|
||||
bool ShaderProgramCompiler::CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::string& fragmentShaderSource, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
GLsizei errorBufferSize = 0;
|
||||
GLint compileResult = GL_FALSE;
|
||||
GLint linkResult = GL_FALSE;
|
||||
std::string loadError;
|
||||
std::vector<LayerProgram::TextureBinding> textureBindings;
|
||||
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
||||
const char* fragmentSource = fragmentShaderSource.c_str();
|
||||
|
||||
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
||||
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
||||
glCompileShader(newVertexShader.get());
|
||||
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||
if (compileResult == GL_FALSE)
|
||||
{
|
||||
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
||||
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
||||
glCompileShader(newFragmentShader.get());
|
||||
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||
if (compileResult == GL_FALSE)
|
||||
{
|
||||
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGlProgram newProgram(glCreateProgram());
|
||||
glAttachShader(newProgram.get(), newVertexShader.get());
|
||||
glAttachShader(newProgram.get(), newFragmentShader.get());
|
||||
glLinkProgram(newProgram.get());
|
||||
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
||||
if (linkResult == GL_FALSE)
|
||||
{
|
||||
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const ShaderTextureAsset& textureAsset : state.textureAssets)
|
||||
{
|
||||
LayerProgram::TextureBinding textureBinding;
|
||||
textureBinding.samplerName = textureAsset.id;
|
||||
textureBinding.sourcePath = textureAsset.path;
|
||||
if (!mTextureBindings.LoadTextureAsset(textureAsset, textureBinding.texture, loadError))
|
||||
{
|
||||
for (LayerProgram::TextureBinding& loadedTexture : textureBindings)
|
||||
{
|
||||
if (loadedTexture.texture != 0)
|
||||
glDeleteTextures(1, &loadedTexture.texture);
|
||||
}
|
||||
CopyErrorMessage(loadError, errorMessageSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
textureBindings.push_back(textureBinding);
|
||||
}
|
||||
|
||||
std::vector<LayerProgram::TextBinding> textBindings;
|
||||
mTextureBindings.CreateTextBindings(state, textBindings);
|
||||
|
||||
const GLuint globalParamsIndex = glGetUniformBlockIndex(newProgram.get(), "GlobalParams");
|
||||
if (globalParamsIndex != GL_INVALID_INDEX)
|
||||
glUniformBlockBinding(newProgram.get(), globalParamsIndex, kGlobalParamsBindingPoint);
|
||||
|
||||
const unsigned historyCap = mRuntimeHost.GetMaxTemporalHistoryFrames();
|
||||
const GLuint shaderTextureBase = state.isTemporal ? kSourceHistoryTextureUnitBase + historyCap + historyCap : kSourceHistoryTextureUnitBase;
|
||||
glUseProgram(newProgram.get());
|
||||
const GLint videoInputLocation = glGetUniformLocation(newProgram.get(), "gVideoInput");
|
||||
if (videoInputLocation >= 0)
|
||||
glUniform1i(videoInputLocation, static_cast<GLint>(kDecodedVideoTextureUnit));
|
||||
for (unsigned index = 0; index < historyCap; ++index)
|
||||
{
|
||||
const std::string sourceSamplerName = "gSourceHistory" + std::to_string(index);
|
||||
const GLint sourceSamplerLocation = glGetUniformLocation(newProgram.get(), sourceSamplerName.c_str());
|
||||
if (sourceSamplerLocation >= 0)
|
||||
glUniform1i(sourceSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + index));
|
||||
|
||||
const std::string temporalSamplerName = "gTemporalHistory" + std::to_string(index);
|
||||
const GLint temporalSamplerLocation = glGetUniformLocation(newProgram.get(), temporalSamplerName.c_str());
|
||||
if (temporalSamplerLocation >= 0)
|
||||
glUniform1i(temporalSamplerLocation, static_cast<GLint>(kSourceHistoryTextureUnitBase + historyCap + index));
|
||||
}
|
||||
for (std::size_t index = 0; index < textureBindings.size(); ++index)
|
||||
{
|
||||
const GLint textureSamplerLocation = mTextureBindings.FindSamplerUniformLocation(newProgram.get(), textureBindings[index].samplerName);
|
||||
if (textureSamplerLocation >= 0)
|
||||
glUniform1i(textureSamplerLocation, static_cast<GLint>(shaderTextureBase + static_cast<GLuint>(index)));
|
||||
}
|
||||
const GLuint textTextureBase = shaderTextureBase + static_cast<GLuint>(textureBindings.size());
|
||||
for (std::size_t index = 0; index < textBindings.size(); ++index)
|
||||
{
|
||||
const GLint textSamplerLocation = mTextureBindings.FindSamplerUniformLocation(newProgram.get(), textBindings[index].samplerName);
|
||||
if (textSamplerLocation >= 0)
|
||||
glUniform1i(textSamplerLocation, static_cast<GLint>(textTextureBase + static_cast<GLuint>(index)));
|
||||
}
|
||||
glUseProgram(0);
|
||||
|
||||
layerProgram.layerId = state.layerId;
|
||||
layerProgram.shaderId = state.shaderId;
|
||||
layerProgram.shaderTextureBase = shaderTextureBase;
|
||||
layerProgram.program = newProgram.release();
|
||||
layerProgram.vertexShader = newVertexShader.release();
|
||||
layerProgram.fragmentShader = newFragmentShader.release();
|
||||
layerProgram.textureBindings.swap(textureBindings);
|
||||
layerProgram.textBindings.swap(textBindings);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderProgramCompiler::CompileDecodeShader(int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
GLsizei errorBufferSize = 0;
|
||||
GLint compileResult = GL_FALSE;
|
||||
GLint linkResult = GL_FALSE;
|
||||
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
||||
const char* fragmentSource = kDecodeFragmentShaderSource;
|
||||
|
||||
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
||||
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
||||
glCompileShader(newVertexShader.get());
|
||||
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||
if (compileResult == GL_FALSE)
|
||||
{
|
||||
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
||||
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
||||
glCompileShader(newFragmentShader.get());
|
||||
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||
if (compileResult == GL_FALSE)
|
||||
{
|
||||
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGlProgram newProgram(glCreateProgram());
|
||||
glAttachShader(newProgram.get(), newVertexShader.get());
|
||||
glAttachShader(newProgram.get(), newFragmentShader.get());
|
||||
glLinkProgram(newProgram.get());
|
||||
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
||||
if (linkResult == GL_FALSE)
|
||||
{
|
||||
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
mRenderer.DestroyDecodeShaderProgram();
|
||||
mRenderer.SetDecodeShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderProgramCompiler::CompileOutputPackShader(int errorMessageSize, char* errorMessage)
|
||||
{
|
||||
GLsizei errorBufferSize = 0;
|
||||
GLint compileResult = GL_FALSE;
|
||||
GLint linkResult = GL_FALSE;
|
||||
const char* vertexSource = kFullscreenTriangleVertexShaderSource;
|
||||
const char* fragmentSource = kOutputPackFragmentShaderSource;
|
||||
|
||||
ScopedGlShader newVertexShader(glCreateShader(GL_VERTEX_SHADER));
|
||||
glShaderSource(newVertexShader.get(), 1, (const GLchar**)&vertexSource, NULL);
|
||||
glCompileShader(newVertexShader.get());
|
||||
glGetShaderiv(newVertexShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||
if (compileResult == GL_FALSE)
|
||||
{
|
||||
glGetShaderInfoLog(newVertexShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGlShader newFragmentShader(glCreateShader(GL_FRAGMENT_SHADER));
|
||||
glShaderSource(newFragmentShader.get(), 1, (const GLchar**)&fragmentSource, NULL);
|
||||
glCompileShader(newFragmentShader.get());
|
||||
glGetShaderiv(newFragmentShader.get(), GL_COMPILE_STATUS, &compileResult);
|
||||
if (compileResult == GL_FALSE)
|
||||
{
|
||||
glGetShaderInfoLog(newFragmentShader.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedGlProgram newProgram(glCreateProgram());
|
||||
glAttachShader(newProgram.get(), newVertexShader.get());
|
||||
glAttachShader(newProgram.get(), newFragmentShader.get());
|
||||
glLinkProgram(newProgram.get());
|
||||
glGetProgramiv(newProgram.get(), GL_LINK_STATUS, &linkResult);
|
||||
if (linkResult == GL_FALSE)
|
||||
{
|
||||
glGetProgramInfoLog(newProgram.get(), errorMessageSize, &errorBufferSize, errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
glUseProgram(newProgram.get());
|
||||
const GLint outputSamplerLocation = glGetUniformLocation(newProgram.get(), "uOutputRgb");
|
||||
if (outputSamplerLocation >= 0)
|
||||
glUniform1i(outputSamplerLocation, 0);
|
||||
glUseProgram(0);
|
||||
|
||||
mRenderer.DestroyOutputPackShaderProgram();
|
||||
mRenderer.SetOutputPackShaderProgram(newProgram.release(), newVertexShader.release(), newFragmentShader.release());
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "RuntimeHost.h"
|
||||
#include "ShaderTextureBindings.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class ShaderProgramCompiler
|
||||
{
|
||||
public:
|
||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||
|
||||
ShaderProgramCompiler(OpenGLRenderer& renderer, RuntimeHost& runtimeHost, ShaderTextureBindings& textureBindings);
|
||||
|
||||
bool CompileLayerProgram(const RuntimeRenderState& state, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
||||
bool CompilePreparedLayerProgram(const RuntimeRenderState& state, const std::string& fragmentShaderSource, LayerProgram& layerProgram, int errorMessageSize, char* errorMessage);
|
||||
bool CompileDecodeShader(int errorMessageSize, char* errorMessage);
|
||||
bool CompileOutputPackShader(int errorMessageSize, char* errorMessage);
|
||||
|
||||
private:
|
||||
OpenGLRenderer& mRenderer;
|
||||
RuntimeHost& mRuntimeHost;
|
||||
ShaderTextureBindings& mTextureBindings;
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "ShaderTextureBindings.h"
|
||||
|
||||
#include "GlRenderConstants.h"
|
||||
#include "TextRasterizer.h"
|
||||
#include "TextureAssetLoader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string TextValueForBinding(const RuntimeRenderState& state, const std::string& parameterId)
|
||||
{
|
||||
auto valueIt = state.parameterValues.find(parameterId);
|
||||
return valueIt == state.parameterValues.end() ? std::string() : valueIt->second.textValue;
|
||||
}
|
||||
|
||||
const ShaderFontAsset* FindFontAssetForParameter(const RuntimeRenderState& state, const ShaderParameterDefinition& definition)
|
||||
{
|
||||
if (!definition.fontId.empty())
|
||||
{
|
||||
for (const ShaderFontAsset& fontAsset : state.fontAssets)
|
||||
{
|
||||
if (fontAsset.id == definition.fontId)
|
||||
return &fontAsset;
|
||||
}
|
||||
}
|
||||
return state.fontAssets.empty() ? nullptr : &state.fontAssets.front();
|
||||
}
|
||||
}
|
||||
|
||||
bool ShaderTextureBindings::LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
||||
{
|
||||
return ::LoadTextureAsset(textureAsset, textureId, error);
|
||||
}
|
||||
|
||||
void ShaderTextureBindings::CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings)
|
||||
{
|
||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
||||
{
|
||||
if (definition.type != ShaderParameterType::Text)
|
||||
continue;
|
||||
LayerProgram::TextBinding textBinding;
|
||||
textBinding.parameterId = definition.id;
|
||||
textBinding.samplerName = definition.id + "Texture";
|
||||
textBinding.fontId = definition.fontId;
|
||||
glGenTextures(1, &textBinding.texture);
|
||||
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
|
||||
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);
|
||||
std::vector<unsigned char> empty(static_cast<std::size_t>(kTextTextureWidth) * kTextTextureHeight * 4, 0);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextTextureWidth, kTextTextureHeight, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, empty.data());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
textBindings.push_back(textBinding);
|
||||
}
|
||||
}
|
||||
|
||||
bool ShaderTextureBindings::UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error)
|
||||
{
|
||||
const std::string text = TextValueForBinding(state, textBinding.parameterId);
|
||||
if (text == textBinding.renderedText && textBinding.renderedWidth == kTextTextureWidth && textBinding.renderedHeight == kTextTextureHeight)
|
||||
return true;
|
||||
|
||||
auto definitionIt = std::find_if(state.parameterDefinitions.begin(), state.parameterDefinitions.end(),
|
||||
[&textBinding](const ShaderParameterDefinition& definition) { return definition.id == textBinding.parameterId; });
|
||||
if (definitionIt == state.parameterDefinitions.end())
|
||||
return true;
|
||||
|
||||
const ShaderFontAsset* fontAsset = FindFontAssetForParameter(state, *definitionIt);
|
||||
std::filesystem::path fontPath;
|
||||
if (fontAsset)
|
||||
fontPath = fontAsset->path;
|
||||
|
||||
std::vector<unsigned char> sdf;
|
||||
if (!RasterizeTextSdf(text, fontPath, sdf, error))
|
||||
return false;
|
||||
|
||||
GLint previousActiveTexture = 0;
|
||||
GLint previousUnpackBuffer = 0;
|
||||
glGetIntegerv(GL_ACTIVE_TEXTURE, &previousActiveTexture);
|
||||
glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &previousUnpackBuffer);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, textBinding.texture);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTextTextureWidth, kTextTextureHeight, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, sdf.data());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast<GLuint>(previousUnpackBuffer));
|
||||
glActiveTexture(static_cast<GLenum>(previousActiveTexture));
|
||||
|
||||
textBinding.renderedText = text;
|
||||
textBinding.renderedWidth = kTextTextureWidth;
|
||||
textBinding.renderedHeight = kTextTextureHeight;
|
||||
return true;
|
||||
}
|
||||
|
||||
GLint ShaderTextureBindings::FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const
|
||||
{
|
||||
GLint location = glGetUniformLocation(program, samplerName.c_str());
|
||||
if (location >= 0)
|
||||
return location;
|
||||
return glGetUniformLocation(program, (samplerName + "_0").c_str());
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "OpenGLRenderer.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class ShaderTextureBindings
|
||||
{
|
||||
public:
|
||||
using LayerProgram = OpenGLRenderer::LayerProgram;
|
||||
|
||||
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);
|
||||
void CreateTextBindings(const RuntimeRenderState& state, std::vector<LayerProgram::TextBinding>& textBindings);
|
||||
bool UpdateTextBindingTexture(const RuntimeRenderState& state, LayerProgram::TextBinding& textBinding, std::string& error);
|
||||
GLint FindSamplerUniformLocation(GLuint program, const std::string& samplerName) const;
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
inline std::size_t AlignStd140(std::size_t offset, std::size_t alignment)
|
||||
{
|
||||
const std::size_t mask = alignment - 1;
|
||||
return (offset + mask) & ~mask;
|
||||
}
|
||||
|
||||
template <typename TValue>
|
||||
inline void AppendStd140Value(std::vector<unsigned char>& buffer, std::size_t alignment, const TValue& value)
|
||||
{
|
||||
const std::size_t offset = AlignStd140(buffer.size(), alignment);
|
||||
if (buffer.size() < offset + sizeof(TValue))
|
||||
buffer.resize(offset + sizeof(TValue), 0);
|
||||
std::memcpy(buffer.data() + offset, &value, sizeof(TValue));
|
||||
}
|
||||
|
||||
inline void AppendStd140Float(std::vector<unsigned char>& buffer, float value)
|
||||
{
|
||||
AppendStd140Value(buffer, 4, value);
|
||||
}
|
||||
|
||||
inline void AppendStd140Int(std::vector<unsigned char>& buffer, int value)
|
||||
{
|
||||
AppendStd140Value(buffer, 4, value);
|
||||
}
|
||||
|
||||
inline void AppendStd140Vec2(std::vector<unsigned char>& buffer, float x, float y)
|
||||
{
|
||||
const std::size_t offset = AlignStd140(buffer.size(), 8);
|
||||
if (buffer.size() < offset + sizeof(float) * 2)
|
||||
buffer.resize(offset + sizeof(float) * 2, 0);
|
||||
float values[2] = { x, y };
|
||||
std::memcpy(buffer.data() + offset, values, sizeof(values));
|
||||
}
|
||||
|
||||
inline void AppendStd140Vec4(std::vector<unsigned char>& buffer, float x, float y, float z, float w)
|
||||
{
|
||||
const std::size_t offset = AlignStd140(buffer.size(), 16);
|
||||
if (buffer.size() < offset + sizeof(float) * 4)
|
||||
buffer.resize(offset + sizeof(float) * 4, 0);
|
||||
float values[4] = { x, y, z, w };
|
||||
std::memcpy(buffer.data() + offset, values, sizeof(values));
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
#include "TextRasterizer.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <gdiplus.h>
|
||||
#include <memory>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int kTextSdfSpread = 20;
|
||||
constexpr float kTextFontPixelSize = 144.0f;
|
||||
constexpr float kTextLayoutPadding = 48.0f;
|
||||
constexpr float kSdfInfinity = 1.0e20f;
|
||||
|
||||
class GdiplusSession
|
||||
{
|
||||
public:
|
||||
GdiplusSession()
|
||||
{
|
||||
Gdiplus::GdiplusStartupInput startupInput;
|
||||
mStarted = Gdiplus::GdiplusStartup(&mToken, &startupInput, NULL) == Gdiplus::Ok;
|
||||
}
|
||||
|
||||
~GdiplusSession()
|
||||
{
|
||||
if (mStarted)
|
||||
Gdiplus::GdiplusShutdown(mToken);
|
||||
}
|
||||
|
||||
GdiplusSession(const GdiplusSession&) = delete;
|
||||
GdiplusSession& operator=(const GdiplusSession&) = delete;
|
||||
|
||||
bool started() const { return mStarted; }
|
||||
|
||||
private:
|
||||
ULONG_PTR mToken = 0;
|
||||
bool mStarted = false;
|
||||
};
|
||||
|
||||
std::wstring Utf8ToWide(const std::string& text)
|
||||
{
|
||||
if (text.empty())
|
||||
return std::wstring();
|
||||
const int required = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, NULL, 0);
|
||||
if (required <= 1)
|
||||
return std::wstring();
|
||||
std::wstring wide(static_cast<std::size_t>(required - 1), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, wide.data(), required);
|
||||
return wide;
|
||||
}
|
||||
|
||||
void DistanceTransform1D(const std::vector<float>& input, std::vector<float>& output, unsigned count)
|
||||
{
|
||||
std::vector<unsigned> locations(count, 0);
|
||||
std::vector<float> boundaries(static_cast<std::size_t>(count) + 1, 0.0f);
|
||||
|
||||
unsigned segment = 0;
|
||||
locations[0] = 0;
|
||||
boundaries[0] = -kSdfInfinity;
|
||||
boundaries[1] = kSdfInfinity;
|
||||
|
||||
for (unsigned q = 1; q < count; ++q)
|
||||
{
|
||||
float intersection = 0.0f;
|
||||
for (;;)
|
||||
{
|
||||
const unsigned location = locations[segment];
|
||||
intersection =
|
||||
((input[q] + static_cast<float>(q * q)) - (input[location] + static_cast<float>(location * location))) /
|
||||
(2.0f * static_cast<float>(q) - 2.0f * static_cast<float>(location));
|
||||
if (intersection > boundaries[segment] || segment == 0)
|
||||
break;
|
||||
--segment;
|
||||
}
|
||||
|
||||
++segment;
|
||||
locations[segment] = q;
|
||||
boundaries[segment] = intersection;
|
||||
boundaries[segment + 1] = kSdfInfinity;
|
||||
}
|
||||
|
||||
segment = 0;
|
||||
for (unsigned q = 0; q < count; ++q)
|
||||
{
|
||||
while (boundaries[segment + 1] < static_cast<float>(q))
|
||||
++segment;
|
||||
const unsigned location = locations[segment];
|
||||
const float delta = static_cast<float>(q) - static_cast<float>(location);
|
||||
output[q] = delta * delta + input[location];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<float> DistanceTransform2D(const std::vector<unsigned char>& targetMask, unsigned width, unsigned height)
|
||||
{
|
||||
std::vector<float> rowInput(width, 0.0f);
|
||||
std::vector<float> rowOutput(width, 0.0f);
|
||||
std::vector<float> columnInput(height, 0.0f);
|
||||
std::vector<float> columnOutput(height, 0.0f);
|
||||
std::vector<float> rowDistance(static_cast<std::size_t>(width) * height, 0.0f);
|
||||
std::vector<float> distance(static_cast<std::size_t>(width) * height, 0.0f);
|
||||
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
{
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
rowInput[x] = targetMask[static_cast<std::size_t>(y) * width + x] ? 0.0f : kSdfInfinity;
|
||||
DistanceTransform1D(rowInput, rowOutput, width);
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
rowDistance[static_cast<std::size_t>(y) * width + x] = rowOutput[x];
|
||||
}
|
||||
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
{
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
columnInput[y] = rowDistance[static_cast<std::size_t>(y) * width + x];
|
||||
DistanceTransform1D(columnInput, columnOutput, height);
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
distance[static_cast<std::size_t>(y) * width + x] = columnOutput[y];
|
||||
}
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> BuildTextSdfTexture(const std::vector<unsigned char>& alpha, unsigned width, unsigned height)
|
||||
{
|
||||
std::vector<unsigned char> insideMask(static_cast<std::size_t>(width) * height, 0);
|
||||
std::vector<unsigned char> outsideMask(static_cast<std::size_t>(width) * height, 0);
|
||||
for (std::size_t index = 0; index < alpha.size(); ++index)
|
||||
{
|
||||
const bool inside = alpha[index] > 127;
|
||||
insideMask[index] = inside ? 1 : 0;
|
||||
outsideMask[index] = inside ? 0 : 1;
|
||||
}
|
||||
|
||||
const std::vector<float> distanceToInside = DistanceTransform2D(insideMask, width, height);
|
||||
const std::vector<float> distanceToOutside = DistanceTransform2D(outsideMask, width, height);
|
||||
std::vector<unsigned char> sdf(static_cast<std::size_t>(width) * height * 4, 0);
|
||||
|
||||
for (unsigned y = 0; y < height; ++y)
|
||||
{
|
||||
const unsigned flippedY = height - 1 - y;
|
||||
for (unsigned x = 0; x < width; ++x)
|
||||
{
|
||||
const std::size_t source = static_cast<std::size_t>(y) * width + x;
|
||||
const float signedDistance = std::sqrt(distanceToOutside[source]) - std::sqrt(distanceToInside[source]);
|
||||
const float normalized = std::clamp(
|
||||
0.5f + signedDistance / static_cast<float>(kTextSdfSpread * 2),
|
||||
0.0f,
|
||||
1.0f);
|
||||
const unsigned char value = static_cast<unsigned char>(normalized * 255.0f + 0.5f);
|
||||
const std::size_t out = (static_cast<std::size_t>(flippedY) * width + x) * 4;
|
||||
sdf[out + 0] = value;
|
||||
sdf[out + 1] = value;
|
||||
sdf[out + 2] = value;
|
||||
sdf[out + 3] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return sdf;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error)
|
||||
{
|
||||
GdiplusSession gdiplus;
|
||||
if (!gdiplus.started())
|
||||
{
|
||||
error = "Could not start GDI+ for text rendering.";
|
||||
return false;
|
||||
}
|
||||
|
||||
Gdiplus::PrivateFontCollection fontCollection;
|
||||
Gdiplus::FontFamily fallbackFamily(L"Arial");
|
||||
Gdiplus::FontFamily* fontFamily = &fallbackFamily;
|
||||
std::unique_ptr<Gdiplus::FontFamily[]> families;
|
||||
const std::wstring wideFontPath = fontPath.empty() ? std::wstring() : fontPath.wstring();
|
||||
if (!wideFontPath.empty())
|
||||
{
|
||||
if (fontCollection.AddFontFile(wideFontPath.c_str()) != Gdiplus::Ok)
|
||||
{
|
||||
error = "Could not load packaged font file for text rendering: " + fontPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
const INT familyCount = fontCollection.GetFamilyCount();
|
||||
if (familyCount <= 0)
|
||||
{
|
||||
error = "Packaged font did not contain a usable font family: " + fontPath.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
families.reset(new Gdiplus::FontFamily[familyCount]);
|
||||
INT found = 0;
|
||||
if (fontCollection.GetFamilies(familyCount, families.get(), &found) != Gdiplus::Ok || found <= 0)
|
||||
{
|
||||
error = "Could not read the packaged font family: " + fontPath.string();
|
||||
return false;
|
||||
}
|
||||
fontFamily = &families[0];
|
||||
}
|
||||
|
||||
Gdiplus::Bitmap bitmap(kTextTextureWidth, kTextTextureHeight, PixelFormat32bppARGB);
|
||||
Gdiplus::Graphics graphics(&bitmap);
|
||||
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceCopy);
|
||||
graphics.Clear(Gdiplus::Color(255, 0, 0, 0));
|
||||
graphics.SetCompositingMode(Gdiplus::CompositingModeSourceOver);
|
||||
graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
|
||||
graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
|
||||
Gdiplus::Font font(fontFamily, kTextFontPixelSize, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
|
||||
Gdiplus::SolidBrush brush(Gdiplus::Color(255, 255, 255, 255));
|
||||
Gdiplus::StringFormat format;
|
||||
format.SetAlignment(Gdiplus::StringAlignmentNear);
|
||||
format.SetLineAlignment(Gdiplus::StringAlignmentCenter);
|
||||
format.SetFormatFlags(Gdiplus::StringFormatFlagsNoWrap | Gdiplus::StringFormatFlagsMeasureTrailingSpaces);
|
||||
const Gdiplus::RectF layout(
|
||||
kTextLayoutPadding,
|
||||
0.0f,
|
||||
static_cast<Gdiplus::REAL>(kTextTextureWidth) - (kTextLayoutPadding * 2.0f),
|
||||
static_cast<Gdiplus::REAL>(kTextTextureHeight));
|
||||
const std::wstring wideText = Utf8ToWide(text);
|
||||
graphics.DrawString(wideText.c_str(), -1, &font, layout, &format, &brush);
|
||||
|
||||
std::vector<unsigned char> alpha(static_cast<std::size_t>(kTextTextureWidth) * kTextTextureHeight, 0);
|
||||
for (unsigned y = 0; y < kTextTextureHeight; ++y)
|
||||
{
|
||||
for (unsigned x = 0; x < kTextTextureWidth; ++x)
|
||||
{
|
||||
Gdiplus::Color pixel;
|
||||
bitmap.GetPixel(x, y, &pixel);
|
||||
BYTE luminance = pixel.GetRed();
|
||||
if (pixel.GetGreen() > luminance)
|
||||
luminance = pixel.GetGreen();
|
||||
if (pixel.GetBlue() > luminance)
|
||||
luminance = pixel.GetBlue();
|
||||
alpha[static_cast<std::size_t>(y) * kTextTextureWidth + x] = static_cast<unsigned char>(luminance);
|
||||
}
|
||||
}
|
||||
sdf = BuildTextSdfTexture(alpha, kTextTextureWidth, kTextTextureHeight);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
constexpr unsigned kTextTextureWidth = 4096;
|
||||
constexpr unsigned kTextTextureHeight = 512;
|
||||
|
||||
bool RasterizeTextSdf(const std::string& text, const std::filesystem::path& fontPath, std::vector<unsigned char>& sdf, std::string& error);
|
||||
@@ -0,0 +1,222 @@
|
||||
#include "TextureAssetLoader.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <wincodec.h>
|
||||
|
||||
#include <atlbase.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef GL_RGBA32F
|
||||
#define GL_RGBA32F 0x8814
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string LowercaseExtension(const std::filesystem::path& path)
|
||||
{
|
||||
std::string extension = path.extension().string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(),
|
||||
[](unsigned char value) { return static_cast<char>(std::tolower(value)); });
|
||||
return extension;
|
||||
}
|
||||
|
||||
bool LoadCubeTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
||||
{
|
||||
std::ifstream file(textureAsset.path);
|
||||
if (!file)
|
||||
{
|
||||
error = "Could not open shader LUT asset: " + textureAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned lutSize = 0;
|
||||
std::vector<float> values;
|
||||
std::string line;
|
||||
while (std::getline(file, line))
|
||||
{
|
||||
const std::size_t commentStart = line.find('#');
|
||||
if (commentStart != std::string::npos)
|
||||
line.resize(commentStart);
|
||||
|
||||
std::istringstream stream(line);
|
||||
std::string firstToken;
|
||||
if (!(stream >> firstToken))
|
||||
continue;
|
||||
|
||||
if (firstToken == "TITLE" || firstToken == "DOMAIN_MIN" || firstToken == "DOMAIN_MAX")
|
||||
continue;
|
||||
if (firstToken == "LUT_3D_SIZE")
|
||||
{
|
||||
stream >> lutSize;
|
||||
continue;
|
||||
}
|
||||
if (firstToken == "LUT_1D_SIZE")
|
||||
{
|
||||
error = "Only 3D .cube LUT assets are supported: " + textureAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
float red = 0.0f;
|
||||
float green = 0.0f;
|
||||
float blue = 0.0f;
|
||||
try
|
||||
{
|
||||
red = std::stof(firstToken);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
error = "Unsupported .cube directive in shader LUT asset: " + firstToken;
|
||||
return false;
|
||||
}
|
||||
if (!(stream >> green >> blue))
|
||||
{
|
||||
error = "Malformed RGB entry in shader LUT asset: " + textureAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
values.push_back(red);
|
||||
values.push_back(green);
|
||||
values.push_back(blue);
|
||||
values.push_back(1.0f);
|
||||
}
|
||||
|
||||
if (lutSize == 0)
|
||||
{
|
||||
error = "Shader LUT asset is missing LUT_3D_SIZE: " + textureAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::size_t expectedFloats = static_cast<std::size_t>(lutSize) * lutSize * lutSize * 4;
|
||||
if (values.size() != expectedFloats)
|
||||
{
|
||||
error = "Shader LUT asset entry count does not match LUT_3D_SIZE: " + textureAsset.path.string();
|
||||
return false;
|
||||
}
|
||||
|
||||
const GLsizei atlasWidth = static_cast<GLsizei>(lutSize * lutSize);
|
||||
const GLsizei atlasHeight = static_cast<GLsizei>(lutSize);
|
||||
glGenTextures(1, &textureId);
|
||||
glBindTexture(GL_TEXTURE_2D, textureId);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, atlasWidth, atlasHeight, 0, GL_RGBA, GL_FLOAT, values.data());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error)
|
||||
{
|
||||
textureId = 0;
|
||||
if (LowercaseExtension(textureAsset.path) == ".cube")
|
||||
return LoadCubeTextureAsset(textureAsset, textureId, error);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "GLExtensions.h"
|
||||
#include "ShaderTypes.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <gl/gl.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
bool LoadTextureAsset(const ShaderTextureAsset& textureAsset, GLuint& textureId, std::string& error);
|
||||
Reference in New Issue
Block a user