Files
Aiden 5fb4607d8c
All checks were successful
CI / React UI Build (push) Successful in 10s
CI / Native Windows Build And Tests (push) Successful in 2m55s
CI / Windows Release Package (push) Has been skipped
Clean up
2026-05-12 18:03:54 +10:00

321 lines
10 KiB
C++

#include "RuntimeShaderRenderer.h"
#include "RuntimeShaderParams.h"
#include <array>
#include <cstring>
#include <string>
namespace
{
constexpr GLuint kGlobalParamsBindingPoint = 0;
constexpr GLuint kVideoInputTextureUnit = 0;
constexpr GLuint kLayerInputTextureUnit = 1;
const char* kVertexShaderSource = R"GLSL(
#version 430 core
out vec2 vTexCoord;
void main()
{
vec2 positions[3] = vec2[3](
vec2(-1.0, -1.0),
vec2( 3.0, -1.0),
vec2(-1.0, 3.0));
vec2 texCoords[3] = vec2[3](
vec2(0.0, 0.0),
vec2(2.0, 0.0),
vec2(0.0, 2.0));
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
vTexCoord = texCoords[gl_VertexID];
}
)GLSL";
}
RuntimeShaderRenderer::~RuntimeShaderRenderer()
{
ShutdownGl();
}
bool RuntimeShaderRenderer::CommitPreparedProgram(RuntimePreparedShaderProgram& preparedProgram, std::string& error)
{
if (!preparedProgram.succeeded || preparedProgram.program == 0)
{
error = preparedProgram.error.empty() ? "Prepared runtime shader program is not valid." : preparedProgram.error;
return false;
}
if (!EnsureStaticGlResources(error))
return false;
DestroyProgram();
mProgram = preparedProgram.program;
mVertexShader = preparedProgram.vertexShader;
mFragmentShader = preparedProgram.fragmentShader;
mArtifact = preparedProgram.artifact;
preparedProgram.program = 0;
preparedProgram.vertexShader = 0;
preparedProgram.fragmentShader = 0;
return true;
}
void RuntimeShaderRenderer::UpdateArtifactState(const RuntimeShaderArtifact& artifact)
{
mArtifact.parameterDefinitions = artifact.parameterDefinitions;
mArtifact.parameterValues = artifact.parameterValues;
mArtifact.message = artifact.message;
}
bool RuntimeShaderRenderer::BuildPreparedProgram(
const std::string& layerId,
const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact,
RuntimePreparedShaderProgram& preparedProgram)
{
RuntimeShaderPassArtifact passArtifact;
passArtifact.passId = "main";
passArtifact.fragmentShaderSource = artifact.fragmentShaderSource;
passArtifact.outputName = "layerOutput";
if (!artifact.passes.empty())
passArtifact = artifact.passes.front();
return BuildPreparedPassProgram(layerId, sourceFingerprint, artifact, passArtifact, preparedProgram);
}
bool RuntimeShaderRenderer::BuildPreparedPassProgram(
const std::string& layerId,
const std::string& sourceFingerprint,
const RuntimeShaderArtifact& artifact,
const RuntimeShaderPassArtifact& passArtifact,
RuntimePreparedShaderProgram& preparedProgram)
{
preparedProgram = RuntimePreparedShaderProgram();
preparedProgram.layerId = layerId;
preparedProgram.shaderId = artifact.shaderId;
preparedProgram.passId = passArtifact.passId;
preparedProgram.sourceFingerprint = sourceFingerprint;
preparedProgram.artifact = artifact;
preparedProgram.passArtifact = passArtifact;
preparedProgram.inputNames = passArtifact.inputNames;
preparedProgram.outputName = passArtifact.outputName.empty() ? passArtifact.passId : passArtifact.outputName;
if (passArtifact.fragmentShaderSource.empty())
{
preparedProgram.error = "Cannot prepare an empty fragment shader.";
return false;
}
if (!BuildProgram(
passArtifact.fragmentShaderSource,
preparedProgram.program,
preparedProgram.vertexShader,
preparedProgram.fragmentShader,
preparedProgram.error))
{
preparedProgram.ReleaseGl();
return false;
}
preparedProgram.succeeded = true;
AssignSamplerUniforms(preparedProgram.program);
return true;
}
void RuntimeShaderRenderer::RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint sourceTexture, GLuint layerInputTexture)
{
if (mProgram == 0)
return;
glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height));
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
UpdateGlobalParams(frameIndex, width, height);
BindRuntimeTextures(sourceTexture, layerInputTexture);
glBindVertexArray(mVertexArray);
glUseProgram(mProgram);
glDrawArrays(GL_TRIANGLES, 0, 3);
glUseProgram(0);
glBindVertexArray(0);
}
void RuntimeShaderRenderer::ShutdownGl()
{
DestroyProgram();
DestroyStaticGlResources();
}
bool RuntimeShaderRenderer::EnsureStaticGlResources(std::string& error)
{
if (mVertexArray == 0)
glGenVertexArrays(1, &mVertexArray);
if (mGlobalParamsBuffer == 0)
{
glGenBuffers(1, &mGlobalParamsBuffer);
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsBuffer);
glBufferData(GL_UNIFORM_BUFFER, 1024, nullptr, GL_DYNAMIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
if (mFallbackSourceTexture == 0)
{
const unsigned char pixels[] = {
0, 0, 0, 255,
96, 64, 32, 255,
64, 96, 160, 255,
255, 255, 255, 255
};
glGenTextures(1, &mFallbackSourceTexture);
glBindTexture(GL_TEXTURE_2D, mFallbackSourceTexture);
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, 2, 2, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
glBindTexture(GL_TEXTURE_2D, 0);
}
if (mVertexArray == 0 || mGlobalParamsBuffer == 0 || mFallbackSourceTexture == 0)
{
error = "Failed to create runtime shader GL resources.";
return false;
}
return true;
}
bool RuntimeShaderRenderer::BuildProgram(const std::string& fragmentShaderSource, GLuint& program, GLuint& vertexShader, GLuint& fragmentShader, std::string& error)
{
program = 0;
vertexShader = 0;
fragmentShader = 0;
if (!CompileShader(GL_VERTEX_SHADER, kVertexShaderSource, vertexShader, error))
return false;
if (!CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource.c_str(), fragmentShader, error))
{
glDeleteShader(vertexShader);
vertexShader = 0;
return false;
}
program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
GLint linkResult = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkResult);
if (linkResult == GL_FALSE)
{
std::array<char, 4096> log = {};
GLsizei length = 0;
glGetProgramInfoLog(program, static_cast<GLsizei>(log.size()), &length, log.data());
error = std::string(log.data(), static_cast<std::size_t>(length));
glDeleteProgram(program);
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
program = 0;
vertexShader = 0;
fragmentShader = 0;
return false;
}
const GLuint globalParamsIndex = glGetUniformBlockIndex(program, "GlobalParams");
if (globalParamsIndex != GL_INVALID_INDEX)
glUniformBlockBinding(program, globalParamsIndex, kGlobalParamsBindingPoint);
return true;
}
void RuntimeShaderRenderer::AssignSamplerUniforms(GLuint program)
{
glUseProgram(program);
const GLint videoInputLocation = glGetUniformLocation(program, "gVideoInput");
if (videoInputLocation >= 0)
glUniform1i(videoInputLocation, static_cast<GLint>(kVideoInputTextureUnit));
const GLint videoInputArrayLocation = glGetUniformLocation(program, "gVideoInput_0");
if (videoInputArrayLocation >= 0)
glUniform1i(videoInputArrayLocation, static_cast<GLint>(kVideoInputTextureUnit));
const GLint layerInputLocation = glGetUniformLocation(program, "gLayerInput");
if (layerInputLocation >= 0)
glUniform1i(layerInputLocation, static_cast<GLint>(kLayerInputTextureUnit));
const GLint layerInputArrayLocation = glGetUniformLocation(program, "gLayerInput_0");
if (layerInputArrayLocation >= 0)
glUniform1i(layerInputArrayLocation, static_cast<GLint>(kLayerInputTextureUnit));
glUseProgram(0);
}
void RuntimeShaderRenderer::UpdateGlobalParams(uint64_t frameIndex, unsigned width, unsigned height)
{
std::vector<unsigned char>& buffer = mGlobalParamsScratch;
buffer = BuildRuntimeShaderGlobalParamsStd140(mArtifact, frameIndex, width, height);
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsBuffer);
const GLsizeiptr bufferSize = static_cast<GLsizeiptr>(buffer.size());
if (mGlobalParamsBufferSize != bufferSize)
{
glBufferData(GL_UNIFORM_BUFFER, bufferSize, buffer.data(), GL_DYNAMIC_DRAW);
mGlobalParamsBufferSize = bufferSize;
}
else
{
glBufferSubData(GL_UNIFORM_BUFFER, 0, bufferSize, buffer.data());
}
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsBuffer);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
void RuntimeShaderRenderer::BindRuntimeTextures(GLuint sourceTexture, GLuint layerInputTexture)
{
const GLuint resolvedSourceTexture = sourceTexture != 0 ? sourceTexture : mFallbackSourceTexture;
const GLuint resolvedLayerInputTexture = layerInputTexture != 0 ? layerInputTexture : resolvedSourceTexture;
glActiveTexture(GL_TEXTURE0 + kVideoInputTextureUnit);
glBindTexture(GL_TEXTURE_2D, resolvedSourceTexture);
glActiveTexture(GL_TEXTURE0 + kLayerInputTextureUnit);
glBindTexture(GL_TEXTURE_2D, resolvedLayerInputTexture);
glActiveTexture(GL_TEXTURE0);
}
bool RuntimeShaderRenderer::CompileShader(GLenum shaderType, const char* source, GLuint& shader, std::string& error)
{
shader = glCreateShader(shaderType);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint compileResult = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
if (compileResult != GL_FALSE)
return true;
std::array<char, 4096> log = {};
GLsizei length = 0;
glGetShaderInfoLog(shader, static_cast<GLsizei>(log.size()), &length, log.data());
error = std::string(log.data(), static_cast<std::size_t>(length));
glDeleteShader(shader);
shader = 0;
return false;
}
void RuntimeShaderRenderer::DestroyProgram()
{
if (mProgram != 0)
glDeleteProgram(mProgram);
if (mVertexShader != 0)
glDeleteShader(mVertexShader);
if (mFragmentShader != 0)
glDeleteShader(mFragmentShader);
mProgram = 0;
mVertexShader = 0;
mFragmentShader = 0;
}
void RuntimeShaderRenderer::DestroyStaticGlResources()
{
if (mGlobalParamsBuffer != 0)
glDeleteBuffers(1, &mGlobalParamsBuffer);
if (mVertexArray != 0)
glDeleteVertexArrays(1, &mVertexArray);
if (mFallbackSourceTexture != 0)
glDeleteTextures(1, &mFallbackSourceTexture);
mGlobalParamsBuffer = 0;
mGlobalParamsBufferSize = 0;
mVertexArray = 0;
mFallbackSourceTexture = 0;
}