#include "effect.h" #include #include #include #include #include #include "globals.h" #include "texture.h" #include "gstreamer_texture.h" GLuint Effect::g_dummyVAO = 0; static std::string shaderHeader = R"(#version 460 core out vec4 FragColor; in vec2 vUV; in vec2 vPosition; uniform float uTime; uniform vec4 uMouse; uniform vec2 uDesktopSize; uniform vec4 uDisplay[16]; // support up to 16 displays, more can be added if needed uniform vec4 uDisplayNorm[16]; // pre-normalized display rects for convenience uniform int uNumDisplays; #define MAX_DISPLAYS 16 // Background modes #define BG_FIT_WIDTH 0 #define BG_FIT_HEIGHT 1 #define BG_COVER 2 #define BG_STRETCH 3 #define BG_CONTAIN 4 #define BG_TILE 5 vec2 GetDisplayUV(sampler2D tex, int displayIndex, int mode) { vec2 inUV = vec2(0.0); if (displayIndex < 0 || displayIndex >= uNumDisplays) { // Entire desktop as fallback inUV = vUV; } else { // Map global UV to display-local [0,1] inUV = (vUV - uDisplayNorm[displayIndex].xy) / uDisplayNorm[displayIndex].zw; } vec2 uv = inUV; if (mode == BG_STRETCH) { // No adjustment needed return uv; } vec4 display = uDisplay[displayIndex]; float displayRatio = display.z / display.w; vec2 texSize = vec2(textureSize(tex, 0)); float textureRatio = texSize.x / texSize.y; if (mode == BG_COVER) { if (displayRatio > textureRatio) { // Display wider: crop top/bottom float scale = textureRatio / displayRatio; uv.y = uv.y * scale + (1.0 - scale) * 0.5; } else { // Display taller: crop left/right float scale = displayRatio / textureRatio; uv.x = uv.x * scale + (1.0 - scale) * 0.5; } } else if (mode == BG_CONTAIN) { if (displayRatio > textureRatio) { // Display wider: pillarbox left/right float scale = displayRatio / textureRatio; uv.x = uv.x * scale + (1.0 - scale) * 0.5; } else { // Display taller: letterbox top/bottom float scale = textureRatio / displayRatio; uv.y = uv.y * scale + (1.0 - scale) * 0.5; } } else if (mode == BG_FIT_WIDTH) { // Scale to match display width, adjust Y float scale = textureRatio / displayRatio; uv.y = uv.y * scale + (1.0 - scale) * 0.5; } else if (mode == BG_FIT_HEIGHT) { // Scale to match display height, adjust X float scale = displayRatio / textureRatio; uv.x = uv.x * scale + (1.0 - scale) * 0.5; } else if (mode == BG_TILE) { // Tile at native texture resolution uv = uv * display.zw / texSize; } return uv; } // Default mode = BG_COVER vec2 GetDisplayUV(sampler2D tex, int displayIndex) { return GetDisplayUV(tex, displayIndex, BG_COVER); } vec2 GetFullUV(sampler2D tex) { return GetDisplayUV(tex, -1, BG_COVER); } vec2 GetFullUV(sampler2D tex, int mode) { return GetDisplayUV(tex, -1, mode); } #line 1 )"; static std::vector SplitLines(const std::string& str) { std::vector lines; size_t start = 0, end = 0; while ((end = str.find_first_of("\r\n", start)) != std::string::npos) { lines.push_back(str.substr(start, end - start)); start = end + 1; } if (start < str.size()) { lines.push_back(str.substr(start)); } return lines; } Effect::Effect(const std::string& fragmentShaderSource) { const std::string vertexShaderSource = #include "fx_vertex.glsl.h" ; GLuint vertexShader = CreateShader(vertexShaderSource, GL_VERTEX_SHADER); if (!vertexShader) { throw std::runtime_error("Failed to compile vertex shader"); return; } GLuint fragmentShader = CreateShader(shaderHeader + fragmentShaderSource, GL_FRAGMENT_SHADER); if (!fragmentShader) { glDeleteShader(vertexShader); throw std::runtime_error("Failed to compile fragment shader"); return; } m_program = glCreateProgram(); glAttachShader(m_program, vertexShader); glAttachShader(m_program, fragmentShader); glLinkProgram(m_program); GLint success; glGetProgramiv(m_program, GL_LINK_STATUS, &success); if (!success) { GLint logLength; glGetProgramiv(m_program, GL_INFO_LOG_LENGTH, &logLength); std::string log(logLength, '\0'); glGetProgramInfoLog(m_program, logLength, nullptr, log.data()); throw std::runtime_error("Failed to link shader program: " + log); } glDeleteShader(vertexShader); glDeleteShader(fragmentShader); // Create a dummy VAO to allow rendering without a real VAO bound if (!g_dummyVAO) { glGenVertexArrays(1, &g_dummyVAO); } } Effect::~Effect() { if (m_program) { glDeleteProgram(m_program); m_program = 0; } } void Effect::Use() const { glUseProgram(m_program); } GLuint Effect::GetUniformLocation(const std::string& name) { auto it = m_uniformLocations.find(name); if (it != m_uniformLocations.end()) { return it->second; } GLuint location = glGetUniformLocation(m_program, name.c_str()); m_uniformLocations[name] = location; return location; } void Effect::SetTexture(const std::string &name, const Texture& texture, size_t textureUnit) { glActiveTexture(GL_TEXTURE0 + static_cast(textureUnit)); texture.Bind(); GStreamerTexture* gstTexture = dynamic_cast(const_cast(&texture)); if (gstTexture) { // For GStreamerTexture, we need to call Update() to upload the latest frame data gstTexture->Update(); } glUniform1i(GetUniformLocation(name), static_cast(textureUnit)); } void Effect::SetInt(const std::string &name, int value) { glUniform1i(GetUniformLocation(name), value); } void Effect::SetFloat(const std::string &name, float value) { glUniform1f(GetUniformLocation(name), value); } void Effect::SetVector2(const std::string &name, const Vector2 &value) { glUniform2f(GetUniformLocation(name), value.x, value.y); } void Effect::SetVector3(const std::string &name, const Vector3 &value) { glUniform3f(GetUniformLocation(name), value.x, value.y, value.z); } void Effect::SetVector4(const std::string &name, const Vector4 &value) { glUniform4f(GetUniformLocation(name), value.x, value.y, value.z, value.w); } void Effect::SetMatrix4(const std::string &name, const Matrix4 &value) { glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, value.data()); } void Effect::Render() { Use(); // Set default uniforms SetFloat("uTime", g_Time); SetVector4("uMouse", g_Mouse); SetVector2("uDesktopSize", g_DesktopSize); for (size_t i = 0; i < g_Displays.size(); i++) { SetVector4("uDisplay[" + std::to_string(i) + "]", g_Displays[i]); SetVector4("uDisplayNorm[" + std::to_string(i) + "]", g_Displays[i] / Vector4(g_DesktopSize, g_DesktopSize)); } SetInt("uNumDisplays", static_cast(g_Displays.size())); glBindVertexArray(g_dummyVAO); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } GLuint Effect::CreateShader(const std::string &source, GLenum shaderType) { GLuint shader = glCreateShader(shaderType); const char* src = source.c_str(); glShaderSource(shader, 1, &src, nullptr); glCompileShader(shader); GLint success; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { std::vector sourceLines = SplitLines(source); GLint logLength; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); std::string log(logLength, '\0'); glGetShaderInfoLog(shader, logLength, nullptr, log.data()); std::string logText = ""; std::string shaderTypeText = "??"; switch (shaderType) { case GL_VERTEX_SHADER: shaderTypeText = "Vertex Shader"; break; case GL_FRAGMENT_SHADER: shaderTypeText = "Fragment Shader"; break; default: shaderTypeText = "Shader"; break; } std::smatch match; int lineNumber = 0; int column = -1; std::string errorMessage; // Mesa/Intel: "0:15(10): error: msg" or "ERROR: 0:15: msg" std::regex mesaRegex(R"((?:ERROR: )?\d+:(\d+)(?:\((\d+)\))?:\s*(.*))"); // NVIDIA: "0(11) : error C7530: msg" std::regex nvidiaRegex(R"(\d+\((\d+)\)\s*:\s*(.*))"); if (std::regex_search(log, match, mesaRegex)) { lineNumber = std::stoi(match[1].str()); column = match[2].matched ? std::stoi(match[2].str()) : -1; errorMessage = match[3].str(); } else if (std::regex_search(log, match, nvidiaRegex)) { lineNumber = std::stoi(match[1].str()); errorMessage = match[2].str(); } if (lineNumber > 0) { logText = std::format("{} error at line {}: {}\nOffending code:\n", shaderTypeText, lineNumber, errorMessage); if (lineNumber <= static_cast(sourceLines.size())) { std::string line = sourceLines[lineNumber - 1]; // add a ^ at column in a new line std::string arrow(line.size(), ' '); if (column >= 0 && column < static_cast(arrow.size())) { arrow[column] = '^'; } if (lineNumber - 2 >= 0) { logText += std::format("{}\n", sourceLines[lineNumber - 2]); } logText += std::format("{}\n{}", line, arrow); if (lineNumber < static_cast(sourceLines.size())) { logText += std::format("\n{}", sourceLines[lineNumber]); } } } std::cout << logText << std::endl; glDeleteShader(shader); return 0; } return shader; }