Files
live-wallpaper/src/effect.cpp

334 lines
9.8 KiB
C++

#include "effect.h"
#include <vector>
#include <regex>
#include <format>
#include <iostream>
#include <stdexcept>
#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<std::string> SplitLines(const std::string& str) {
std::vector<std::string> 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<GLuint>(textureUnit));
texture.Bind();
GStreamerTexture* gstTexture = dynamic_cast<GStreamerTexture*>(const_cast<Texture*>(&texture));
if (gstTexture) {
// For GStreamerTexture, we need to call Update() to upload the latest frame data
gstTexture->Update();
}
glUniform1i(GetUniformLocation(name), static_cast<GLint>(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<int>(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<std::string> 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<int>(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<int>(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<int>(sourceLines.size())) {
logText += std::format("\n{}", sourceLines[lineNumber]);
}
}
}
std::cout << logText << std::endl;
glDeleteShader(shader);
return 0;
}
return shader;
}