#include "RuntimeShaderRenderer.h" #include "RuntimeShaderParams.h" #include #include #include 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(width), static_cast(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 log = {}; GLsizei length = 0; glGetProgramInfoLog(program, static_cast(log.size()), &length, log.data()); error = std::string(log.data(), static_cast(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(kVideoInputTextureUnit)); const GLint videoInputArrayLocation = glGetUniformLocation(program, "gVideoInput_0"); if (videoInputArrayLocation >= 0) glUniform1i(videoInputArrayLocation, static_cast(kVideoInputTextureUnit)); const GLint layerInputLocation = glGetUniformLocation(program, "gLayerInput"); if (layerInputLocation >= 0) glUniform1i(layerInputLocation, static_cast(kLayerInputTextureUnit)); const GLint layerInputArrayLocation = glGetUniformLocation(program, "gLayerInput_0"); if (layerInputArrayLocation >= 0) glUniform1i(layerInputArrayLocation, static_cast(kLayerInputTextureUnit)); glUseProgram(0); } void RuntimeShaderRenderer::UpdateGlobalParams(uint64_t frameIndex, unsigned width, unsigned height) { std::vector& buffer = mGlobalParamsScratch; buffer = BuildRuntimeShaderGlobalParamsStd140(mArtifact, frameIndex, width, height); glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsBuffer); const GLsizeiptr bufferSize = static_cast(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 log = {}; GLsizei length = 0; glGetShaderInfoLog(shader, static_cast(log.size()), &length, log.data()); error = std::string(log.data(), static_cast(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; }