picom/src/backend/gl/gl_common.c

1335 lines
43 KiB
C

// SPDX-License-Identifier: MPL-2.0
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
#include <GL/gl.h>
#include <GL/glext.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <xcb/render.h> // for xcb_render_fixed_t, XXX
#include "backend/backend.h"
#include "common.h"
#include "compiler.h"
#include "config.h"
#include "kernel.h"
#include "log.h"
#include "region.h"
#include "string_utils.h"
#include "types.h"
#include "utils.h"
#include "backend/backend_common.h"
#include "backend/gl/gl_common.h"
GLuint gl_create_shader(GLenum shader_type, const char *shader_str) {
log_trace("===\n%s\n===", shader_str);
bool success = false;
GLuint shader = glCreateShader(shader_type);
if (!shader) {
log_error("Failed to create shader with type %#x.", shader_type);
goto end;
}
glShaderSource(shader, 1, &shader_str, NULL);
glCompileShader(shader);
// Get shader status
{
GLint status = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
if (status == GL_FALSE) {
GLint log_len = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
if (log_len) {
char log[log_len + 1];
glGetShaderInfoLog(shader, log_len, NULL, log);
log_error("Failed to compile shader with type %d: %s",
shader_type, log);
}
goto end;
}
}
success = true;
end:
if (shader && !success) {
glDeleteShader(shader);
shader = 0;
}
gl_check_err();
return shader;
}
GLuint gl_create_program(const GLuint *const shaders, int nshaders) {
bool success = false;
GLuint program = glCreateProgram();
if (!program) {
log_error("Failed to create program.");
goto end;
}
for (int i = 0; i < nshaders; ++i) {
glAttachShader(program, shaders[i]);
}
glLinkProgram(program);
// Get program status
{
GLint status = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &status);
if (status == GL_FALSE) {
GLint log_len = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_len);
if (log_len) {
char log[log_len + 1];
glGetProgramInfoLog(program, log_len, NULL, log);
log_error("Failed to link program: %s", log);
}
goto end;
}
}
success = true;
end:
if (program) {
for (int i = 0; i < nshaders; ++i) {
glDetachShader(program, shaders[i]);
}
}
if (program && !success) {
glDeleteProgram(program);
program = 0;
}
gl_check_err();
return program;
}
/**
* @brief Create a program from NULL-terminated arrays of vertex and fragment shader
* strings.
*/
GLuint gl_create_program_from_strv(const char **vert_shaders, const char **frag_shaders) {
int vert_count, frag_count;
for (vert_count = 0; vert_shaders && vert_shaders[vert_count]; ++vert_count) {
}
for (frag_count = 0; frag_shaders && frag_shaders[frag_count]; ++frag_count) {
}
GLuint prog = 0;
auto shaders = (GLuint *)ccalloc(vert_count + frag_count, GLuint);
for (int i = 0; i < vert_count; ++i) {
shaders[i] = gl_create_shader(GL_VERTEX_SHADER, vert_shaders[i]);
if (shaders[i] == 0) {
goto out;
}
}
for (int i = 0; i < frag_count; ++i) {
shaders[vert_count + i] =
gl_create_shader(GL_FRAGMENT_SHADER, frag_shaders[i]);
if (shaders[vert_count + i] == 0) {
goto out;
}
}
prog = gl_create_program(shaders, vert_count + frag_count);
out:
for (int i = 0; i < vert_count + frag_count; ++i) {
if (shaders[i] != 0) {
glDeleteShader(shaders[i]);
}
}
free(shaders);
gl_check_err();
return prog;
}
/**
* @brief Create a program from vertex and fragment shader strings.
*/
GLuint gl_create_program_from_str(const char *vert_shader_str, const char *frag_shader_str) {
const char *vert_shaders[2] = {vert_shader_str, NULL};
const char *frag_shaders[2] = {frag_shader_str, NULL};
return gl_create_program_from_strv(vert_shaders, frag_shaders);
}
void gl_destroy_window_shader(backend_t *backend_data attr_unused, void *shader) {
if (!shader) {
return;
}
auto pprogram = (gl_win_shader_t *)shader;
if (pprogram->prog) {
glDeleteProgram(pprogram->prog);
pprogram->prog = 0;
}
gl_check_err();
free(shader);
}
/*
* @brief Implements recursive part of gl_average_texture_color.
*
* @note In order to reduce number of textures which needs to be
* allocated and deleted during this recursive render
* we reuse the same two textures for render source and
* destination simply by alterating between them.
* Unfortunately on first iteration source_texture might
* be read-only. In this case we will select auxiliary_texture as
* destination_texture in order not to touch that read-only source
* texture in following render iteration.
* Otherwise we simply will switch source and destination textures
* between each other on each render iteration.
*/
static GLuint
_gl_average_texture_color(backend_t *base, GLuint source_texture, GLuint destination_texture,
GLuint auxiliary_texture, GLuint fbo, int width, int height) {
const int max_width = 1;
const int max_height = 1;
const int from_width = next_power_of_two(width);
const int from_height = next_power_of_two(height);
const int to_width = from_width > max_width ? from_width / 2 : from_width;
const int to_height = from_height > max_height ? from_height / 2 : from_height;
// Prepare coordinates
GLint coord[] = {
// top left
0, 0, // vertex coord
0, 0, // texture coord
// top right
to_width, 0, // vertex coord
width, 0, // texture coord
// bottom right
to_width, to_height, // vertex coord
width, height, // texture coord
// bottom left
0, to_height, // vertex coord
0, height, // texture coord
};
glBufferSubData(GL_ARRAY_BUFFER, 0, (long)sizeof(*coord) * 16, coord);
// Prepare framebuffer for new render iteration
glBindTexture(GL_TEXTURE_2D, destination_texture);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
destination_texture, 0);
gl_check_fb_complete(GL_FRAMEBUFFER);
// Bind source texture as downscaling shader uniform input
glBindTexture(GL_TEXTURE_2D, source_texture);
// Render into framebuffer
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
// Have we downscaled enough?
GLuint result;
if (to_width > max_width || to_height > max_height) {
GLuint new_source_texture = destination_texture;
GLuint new_destination_texture =
auxiliary_texture != 0 ? auxiliary_texture : source_texture;
result = _gl_average_texture_color(base, new_source_texture,
new_destination_texture, 0, fbo,
to_width, to_height);
} else {
result = destination_texture;
}
return result;
}
/*
* @brief Builds a 1x1 texture which has color corresponding to the average of all
* pixels of img by recursively rendering into texture of quorter the size (half
* width and half height).
* Returned texture must not be deleted, since it's owned by the gl_image. It will be
* deleted when the gl_image is released.
*/
static GLuint gl_average_texture_color(backend_t *base, struct backend_image *img) {
auto gd = (struct gl_data *)base;
auto inner = (struct gl_texture *)img->inner;
// Prepare textures which will be used for destination and source of rendering
// during downscaling.
const int texture_count = ARR_SIZE(inner->auxiliary_texture);
if (!inner->auxiliary_texture[0]) {
assert(!inner->auxiliary_texture[1]);
glGenTextures(texture_count, inner->auxiliary_texture);
glActiveTexture(GL_TEXTURE0);
for (int i = 0; i < texture_count; i++) {
glBindTexture(GL_TEXTURE_2D, inner->auxiliary_texture[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR,
(GLint[]){0, 0, 0, 0});
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, inner->width,
inner->height, 0, GL_BGR, GL_UNSIGNED_BYTE, NULL);
}
}
// Prepare framebuffer used for rendering and bind it
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
// Enable shaders
glUseProgram(gd->brightness_shader.prog);
glUniform2f(glGetUniformLocationChecked(gd->brightness_shader.prog, "texsize"),
(GLfloat)inner->width, (GLfloat)inner->height);
// Prepare vertex attributes
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint bo[2];
glGenBuffers(2, bo);
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
glEnableVertexAttribArray(vert_coord_loc);
glEnableVertexAttribArray(vert_in_texcoord_loc);
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
// Allocate buffers for render input
GLint coord[16] = {0};
GLuint indices[] = {0, 1, 2, 2, 3, 0};
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_DYNAMIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
GL_STATIC_DRAW);
// Do actual recursive render to 1x1 texture
GLuint result_texture = _gl_average_texture_color(
base, inner->texture, inner->auxiliary_texture[0],
inner->auxiliary_texture[1], fbo, inner->width, inner->height);
// Cleanup vertex attributes
glDisableVertexAttribArray(vert_coord_loc);
glDisableVertexAttribArray(vert_in_texcoord_loc);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDeleteBuffers(2, bo);
glBindVertexArray(0);
glDeleteVertexArrays(1, &vao);
// Cleanup shaders
glUseProgram(0);
// Cleanup framebuffers
glDeleteFramebuffers(1, &fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
// Cleanup render textures
glBindTexture(GL_TEXTURE_2D, 0);
gl_check_err();
return result_texture;
}
/**
* Render a region with texture data.
*
* @param ptex the texture
* @param target the framebuffer to render into
* @param dst_x,dst_y the top left corner of region where this texture
* should go. In OpenGL coordinate system (important!).
* @param reg_tgt the clip region, in Xorg coordinate system
* @param reg_visible ignored
*/
static void _gl_compose(backend_t *base, struct backend_image *img, GLuint target,
struct backend_image *mask, coord_t mask_offset, GLint *coord,
GLuint *indices, int nrects) {
// FIXME(yshui) breaks when `mask` and `img` doesn't have the same y_inverted
// value. but we don't ever hit this problem because all of our
// images and masks are y_inverted.
auto gd = (struct gl_data *)base;
auto inner = (struct gl_texture *)img->inner;
auto mask_texture =
mask ? ((struct gl_texture *)mask->inner)->texture : gd->default_mask_texture;
if (!img || !inner->texture) {
log_error("Missing texture.");
return;
}
GLuint brightness = 0;
if (img->max_brightness < 1.0) {
brightness = gl_average_texture_color(base, img);
}
auto win_shader = inner->shader;
if (!win_shader) {
win_shader = gd->default_shader;
}
assert(win_shader);
assert(win_shader->prog);
glUseProgram(win_shader->prog);
if (win_shader->uniform_opacity >= 0) {
glUniform1f(win_shader->uniform_opacity, (float)img->opacity);
}
if (win_shader->uniform_invert_color >= 0) {
glUniform1i(win_shader->uniform_invert_color, img->color_inverted);
}
if (win_shader->uniform_tex >= 0) {
glUniform1i(win_shader->uniform_tex, 0);
}
if (win_shader->uniform_dim >= 0) {
glUniform1f(win_shader->uniform_dim, (float)img->dim);
}
if (win_shader->uniform_brightness >= 0) {
glUniform1i(win_shader->uniform_brightness, 1);
}
if (win_shader->uniform_max_brightness >= 0) {
glUniform1f(win_shader->uniform_max_brightness, (float)img->max_brightness);
}
if (win_shader->uniform_corner_radius >= 0) {
glUniform1f(win_shader->uniform_corner_radius, (float)img->corner_radius);
}
if (win_shader->uniform_border_width >= 0) {
auto border_width = img->border_width;
if (border_width > img->corner_radius) {
border_width = 0;
}
glUniform1f(win_shader->uniform_border_width, (float)border_width);
}
if (win_shader->uniform_time >= 0) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
glUniform1f(win_shader->uniform_time,
(float)ts.tv_sec * 1000.0F + (float)ts.tv_nsec / 1.0e6F);
}
glUniform1i(win_shader->uniform_mask_tex, 2);
glUniform2f(win_shader->uniform_mask_offset, (float)mask_offset.x,
(float)mask_offset.y);
if (mask != NULL) {
glUniform1i(win_shader->uniform_mask_inverted, mask->color_inverted);
glUniform1f(win_shader->uniform_mask_corner_radius,
(GLfloat)mask->corner_radius);
} else {
glUniform1i(win_shader->uniform_mask_inverted, 0);
glUniform1f(win_shader->uniform_mask_corner_radius, 0);
}
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, mask_texture);
// log_trace("Draw: %d, %d, %d, %d -> %d, %d (%d, %d) z %d\n",
// x, y, width, height, dx, dy, ptex->width, ptex->height, z);
// Bind texture
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, brightness);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, inner->texture);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint bo[2];
glGenBuffers(2, bo);
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * nrects * 16, coord, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * nrects * 6,
indices, GL_STATIC_DRAW);
glEnableVertexAttribArray(vert_coord_loc);
glEnableVertexAttribArray(vert_in_texcoord_loc);
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target);
glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL);
glDisableVertexAttribArray(vert_coord_loc);
glDisableVertexAttribArray(vert_in_texcoord_loc);
glBindVertexArray(0);
glDeleteVertexArrays(1, &vao);
// Cleanup
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDrawBuffer(GL_BACK);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDeleteBuffers(2, bo);
glUseProgram(0);
gl_check_err();
return;
}
/// Convert rectangles in X coordinates to OpenGL vertex and texture coordinates
/// @param[in] nrects, rects rectangles
/// @param[in] image_dst origin of the OpenGL texture, affect the calculated texture
/// coordinates
/// @param[in] extend_height height of the drawing extent
/// @param[in] texture_height height of the OpenGL texture
/// @param[in] root_height height of the back buffer
/// @param[in] y_inverted whether the texture is y inverted
/// @param[out] coord, indices output
void x_rect_to_coords(int nrects, const rect_t *rects, coord_t image_dst,
int extent_height, int texture_height, int root_height,
bool y_inverted, GLint *coord, GLuint *indices) {
image_dst.y = root_height - image_dst.y;
image_dst.y -= extent_height;
for (int i = 0; i < nrects; i++) {
// Y-flip. Note after this, crect.y1 > crect.y2
rect_t crect = rects[i];
crect.y1 = root_height - crect.y1;
crect.y2 = root_height - crect.y2;
// Calculate texture coordinates
// (texture_x1, texture_y1), texture coord for the _bottom left_ corner
GLint texture_x1 = crect.x1 - image_dst.x,
texture_y1 = crect.y2 - image_dst.y,
texture_x2 = texture_x1 + (crect.x2 - crect.x1),
texture_y2 = texture_y1 + (crect.y1 - crect.y2);
// X pixmaps might be Y inverted, invert the texture coordinates
if (y_inverted) {
texture_y1 = texture_height - texture_y1;
texture_y2 = texture_height - texture_y2;
}
// Vertex coordinates
auto vx1 = crect.x1;
auto vy1 = crect.y2;
auto vx2 = crect.x2;
auto vy2 = crect.y1;
// log_trace("Rect %d: %f, %f, %f, %f -> %d, %d, %d, %d",
// ri, rx, ry, rxe, rye, rdx, rdy, rdxe, rdye);
memcpy(&coord[i * 16],
((GLint[][2]){
{vx1, vy1},
{texture_x1, texture_y1},
{vx2, vy1},
{texture_x2, texture_y1},
{vx2, vy2},
{texture_x2, texture_y2},
{vx1, vy2},
{texture_x1, texture_y2},
}),
sizeof(GLint[2]) * 8);
GLuint u = (GLuint)(i * 4);
memcpy(&indices[i * 6],
((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}),
sizeof(GLuint) * 6);
}
}
// TODO(yshui) make use of reg_visible
void gl_compose(backend_t *base, void *image_data, coord_t image_dst, void *mask,
coord_t mask_dst, const region_t *reg_tgt,
const region_t *reg_visible attr_unused) {
auto gd = (struct gl_data *)base;
struct backend_image *img = image_data;
auto inner = (struct gl_texture *)img->inner;
// Painting
int nrects;
const rect_t *rects;
rects = pixman_region32_rectangles((region_t *)reg_tgt, &nrects);
if (!nrects) {
// Nothing to paint
return;
}
// Until we start to use glClipControl, reg_tgt, dst_x and dst_y and
// in a different coordinate system than the one OpenGL uses.
// OpenGL window coordinate (or NDC) has the origin at the lower left of the
// screen, with y axis pointing up; Xorg has the origin at the upper left of the
// screen, with y axis pointing down. We have to do some coordinate conversion in
// this function
auto coord = ccalloc(nrects * 16, GLint);
auto indices = ccalloc(nrects * 6, GLuint);
coord_t mask_offset = {.x = mask_dst.x - image_dst.x, .y = mask_dst.y - image_dst.y};
x_rect_to_coords(nrects, rects, image_dst, inner->height, inner->height,
gd->height, inner->y_inverted, coord, indices);
_gl_compose(base, img, gd->back_fbo, mask, mask_offset, coord, indices, nrects);
free(indices);
free(coord);
}
/**
* Load a GLSL main program from shader strings.
*/
static bool gl_win_shader_from_stringv(const char **vshader_strv,
const char **fshader_strv, gl_win_shader_t *ret) {
// Build program
ret->prog = gl_create_program_from_strv(vshader_strv, fshader_strv);
if (!ret->prog) {
log_error("Failed to create GLSL program.");
gl_check_err();
return false;
}
// Get uniform addresses
bind_uniform(ret, opacity);
bind_uniform(ret, invert_color);
bind_uniform(ret, tex);
bind_uniform(ret, dim);
bind_uniform(ret, brightness);
bind_uniform(ret, max_brightness);
bind_uniform(ret, corner_radius);
bind_uniform(ret, border_width);
bind_uniform(ret, time);
bind_uniform(ret, mask_tex);
bind_uniform(ret, mask_offset);
bind_uniform(ret, mask_inverted);
bind_uniform(ret, mask_corner_radius);
gl_check_err();
return true;
}
/**
* Callback to run on root window size change.
*/
void gl_resize(struct gl_data *gd, int width, int height) {
GLint viewport_dimensions[2];
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
gd->height = height;
gd->width = width;
assert(viewport_dimensions[0] >= gd->width);
assert(viewport_dimensions[1] >= gd->height);
glBindTexture(GL_TEXTURE_2D, gd->back_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_BGR,
GL_UNSIGNED_BYTE, NULL);
gl_check_err();
}
/// Fill a given region in bound framebuffer.
/// @param[in] y_inverted whether the y coordinates in `clip` should be inverted
static void _gl_fill(backend_t *base, struct color c, const region_t *clip, GLuint target,
int height, bool y_inverted) {
static const GLuint fill_vert_in_coord_loc = 0;
int nrects;
const rect_t *rect = pixman_region32_rectangles((region_t *)clip, &nrects);
auto gd = (struct gl_data *)base;
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint bo[2];
glGenBuffers(2, bo);
glUseProgram(gd->fill_shader.prog);
glUniform4f(gd->fill_shader.color_loc, (GLfloat)c.red, (GLfloat)c.green,
(GLfloat)c.blue, (GLfloat)c.alpha);
glEnableVertexAttribArray(fill_vert_in_coord_loc);
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
auto coord = ccalloc(nrects * 8, GLint);
auto indices = ccalloc(nrects * 6, GLuint);
for (int i = 0; i < nrects; i++) {
GLint y1 = y_inverted ? height - rect[i].y2 : rect[i].y1,
y2 = y_inverted ? height - rect[i].y1 : rect[i].y2;
// clang-format off
memcpy(&coord[i * 8],
((GLint[][2]){
{rect[i].x1, y1}, {rect[i].x2, y1},
{rect[i].x2, y2}, {rect[i].x1, y2}}),
sizeof(GLint[2]) * 4);
// clang-format on
indices[i * 6 + 0] = (GLuint)i * 4 + 0;
indices[i * 6 + 1] = (GLuint)i * 4 + 1;
indices[i * 6 + 2] = (GLuint)i * 4 + 2;
indices[i * 6 + 3] = (GLuint)i * 4 + 2;
indices[i * 6 + 4] = (GLuint)i * 4 + 3;
indices[i * 6 + 5] = (GLuint)i * 4 + 0;
}
glBufferData(GL_ARRAY_BUFFER, nrects * 8 * (long)sizeof(*coord), coord, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, nrects * 6 * (long)sizeof(*indices),
indices, GL_STREAM_DRAW);
glVertexAttribPointer(fill_vert_in_coord_loc, 2, GL_INT, GL_FALSE,
sizeof(*coord) * 2, (void *)0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target);
glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(fill_vert_in_coord_loc);
glBindVertexArray(0);
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(2, bo);
free(indices);
free(coord);
gl_check_err();
}
void gl_fill(backend_t *base, struct color c, const region_t *clip) {
auto gd = (struct gl_data *)base;
return _gl_fill(base, c, clip, gd->back_fbo, gd->height, true);
}
void *gl_make_mask(backend_t *base, geometry_t size, const region_t *reg) {
auto tex = ccalloc(1, struct gl_texture);
auto img = default_new_backend_image(size.width, size.height);
tex->width = size.width;
tex->height = size.height;
tex->texture = gl_new_texture(GL_TEXTURE_2D);
tex->has_alpha = false;
tex->y_inverted = true;
img->inner = (struct backend_image_inner_base *)tex;
img->inner->refcount = 1;
glBindTexture(GL_TEXTURE_2D, tex->texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, size.width, size.height, 0, GL_RED,
GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
GLuint fbo;
glBlendFunc(GL_ONE, GL_ZERO);
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
tex->texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
_gl_fill(base, (struct color){1, 1, 1, 1}, reg, fbo, size.height, false);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
return img;
}
static void gl_release_image_inner(backend_t *base, struct gl_texture *inner) {
auto gd = (struct gl_data *)base;
if (inner->user_data) {
gd->release_user_data(base, inner);
}
assert(inner->user_data == NULL);
glDeleteTextures(1, &inner->texture);
glDeleteTextures(2, inner->auxiliary_texture);
free(inner);
gl_check_err();
}
void gl_release_image(backend_t *base, void *image_data) {
struct backend_image *wd = image_data;
auto inner = (struct gl_texture *)wd->inner;
inner->refcount--;
assert(inner->refcount >= 0);
if (inner->refcount == 0) {
gl_release_image_inner(base, inner);
}
free(wd);
}
void *gl_create_window_shader(backend_t *backend_data attr_unused, const char *source) {
auto win_shader = (gl_win_shader_t *)ccalloc(1, gl_win_shader_t);
const char *vert_shaders[2] = {vertex_shader, NULL};
const char *frag_shaders[4] = {win_shader_glsl, masking_glsl, source, NULL};
if (!gl_win_shader_from_stringv(vert_shaders, frag_shaders, win_shader)) {
free(win_shader);
return NULL;
}
GLint viewport_dimensions[2];
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
// Set projection matrix to gl viewport dimensions so we can use screen
// coordinates for all vertices
// Note: OpenGL matrices are column major
GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
{0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
{0, 0, 0, 0},
{-1, -1, 0, 1}};
int pml = glGetUniformLocationChecked(win_shader->prog, "projection");
glUseProgram(win_shader->prog);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
return win_shader;
}
uint64_t gl_get_shader_attributes(backend_t *backend_data attr_unused, void *shader) {
auto win_shader = (gl_win_shader_t *)shader;
uint64_t ret = 0;
if (glGetUniformLocation(win_shader->prog, "time") >= 0) {
ret |= SHADER_ATTRIBUTE_ANIMATED;
}
return ret;
}
bool gl_init(struct gl_data *gd, session_t *ps) {
// Initialize GLX data structure
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
// X pixmap is in premultiplied alpha, so we might just as well use it too.
// Thanks to derhass for help.
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// Initialize stencil buffer
glDisable(GL_STENCIL_TEST);
glStencilMask(0x1);
glStencilFunc(GL_EQUAL, 0x1, 0x1);
// Set gl viewport to the maximum supported size so we won't have to worry about
// it later on when the screen is resized. The corresponding projection matrix can
// be set now and won't have to be updated. Since fragments outside the target
// buffer are skipped anyways, this should have no impact on performance.
GLint viewport_dimensions[2];
glGetIntegerv(GL_MAX_VIEWPORT_DIMS, viewport_dimensions);
glViewport(0, 0, viewport_dimensions[0], viewport_dimensions[1]);
// Clear screen
glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glGenFramebuffers(1, &gd->back_fbo);
glGenTextures(1, &gd->back_texture);
if (!gd->back_fbo || !gd->back_texture) {
log_error("Failed to generate a framebuffer object");
return false;
}
glBindTexture(GL_TEXTURE_2D, gd->back_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);
glBindTexture(GL_TEXTURE_2D, 0);
gd->default_mask_texture = gl_new_texture(GL_TEXTURE_2D);
if (!gd->default_mask_texture) {
log_error("Failed to generate a default mask texture");
return false;
}
glBindTexture(GL_TEXTURE_2D, gd->default_mask_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE,
(GLbyte[]){'\xff'});
glBindTexture(GL_TEXTURE_2D, 0);
// Initialize shaders
gd->default_shader = gl_create_window_shader(NULL, win_shader_default);
if (!gd->default_shader) {
log_error("Failed to create window shaders");
return false;
}
// Set projection matrix to gl viewport dimensions so we can use screen
// coordinates for all vertices
// Note: OpenGL matrices are column major
GLfloat projection_matrix[4][4] = {{2.0F / (GLfloat)viewport_dimensions[0], 0, 0, 0},
{0, 2.0F / (GLfloat)viewport_dimensions[1], 0, 0},
{0, 0, 0, 0},
{-1, -1, 0, 1}};
gd->fill_shader.prog = gl_create_program_from_str(fill_vert, fill_frag);
gd->fill_shader.color_loc = glGetUniformLocation(gd->fill_shader.prog, "color");
int pml = glGetUniformLocationChecked(gd->fill_shader.prog, "projection");
glUseProgram(gd->fill_shader.prog);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
gd->present_prog = gl_create_program_from_str(present_vertex_shader, dummy_frag);
if (!gd->present_prog) {
log_error("Failed to create the present shader");
return false;
}
pml = glGetUniformLocationChecked(gd->present_prog, "projection");
glUseProgram(gd->present_prog);
glUniform1i(glGetUniformLocationChecked(gd->present_prog, "tex"), 0);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
gd->shadow_shader.prog =
gl_create_program_from_str(present_vertex_shader, shadow_colorization_frag);
gd->shadow_shader.uniform_color =
glGetUniformLocationChecked(gd->shadow_shader.prog, "color");
pml = glGetUniformLocationChecked(gd->shadow_shader.prog, "projection");
glUseProgram(gd->shadow_shader.prog);
glUniform1i(glGetUniformLocationChecked(gd->shadow_shader.prog, "tex"), 0);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
glBindFragDataLocation(gd->shadow_shader.prog, 0, "out_color");
gd->brightness_shader.prog =
gl_create_program_from_str(interpolating_vert, interpolating_frag);
if (!gd->brightness_shader.prog) {
log_error("Failed to create the brightness shader");
return false;
}
pml = glGetUniformLocationChecked(gd->brightness_shader.prog, "projection");
glUseProgram(gd->brightness_shader.prog);
glUniform1i(glGetUniformLocationChecked(gd->brightness_shader.prog, "tex"), 0);
glUniformMatrix4fv(pml, 1, false, projection_matrix[0]);
glUseProgram(0);
// Set up the size of the back texture
gl_resize(gd, ps->root_width, ps->root_height);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gd->back_fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
gd->back_texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
if (!gl_check_fb_complete(GL_FRAMEBUFFER)) {
return false;
}
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
gd->logger = gl_string_marker_logger_new();
if (gd->logger) {
log_add_target_tls(gd->logger);
}
const char *vendor = (const char *)glGetString(GL_VENDOR);
log_debug("GL_VENDOR = %s", vendor);
if (strcmp(vendor, "NVIDIA Corporation") == 0) {
log_info("GL vendor is NVIDIA, don't use glFinish");
gd->is_nvidia = true;
} else {
gd->is_nvidia = false;
}
gd->has_robustness = gl_has_extension("GL_ARB_robustness");
gl_check_err();
return true;
}
void gl_deinit(struct gl_data *gd) {
if (gd->logger) {
log_remove_target_tls(gd->logger);
gd->logger = NULL;
}
if (gd->default_shader) {
gl_destroy_window_shader(&gd->base, gd->default_shader);
gd->default_shader = NULL;
}
gl_check_err();
}
GLuint gl_new_texture(GLenum target) {
GLuint texture;
glGenTextures(1, &texture);
if (!texture) {
log_error("Failed to generate texture");
return 0;
}
glBindTexture(target, texture);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT);
glBindTexture(target, 0);
return texture;
}
/// Actually duplicate a texture into a new one, if this texture is shared
static inline void gl_image_decouple(backend_t *base, struct backend_image *img) {
if (img->inner->refcount == 1) {
return;
}
auto gd = (struct gl_data *)base;
auto inner = (struct gl_texture *)img->inner;
auto new_tex = ccalloc(1, struct gl_texture);
new_tex->texture = gl_new_texture(GL_TEXTURE_2D);
new_tex->y_inverted = true;
new_tex->height = inner->height;
new_tex->width = inner->width;
new_tex->refcount = 1;
new_tex->user_data = gd->decouple_texture_user_data(base, inner->user_data);
glBindTexture(GL_TEXTURE_2D, new_tex->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, new_tex->width, new_tex->height, 0,
GL_BGRA, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
assert(gd->present_prog);
glUseProgram(gd->present_prog);
glBindTexture(GL_TEXTURE_2D, inner->texture);
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
new_tex->texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
gl_check_fb_complete(GL_DRAW_FRAMEBUFFER);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
// clang-format off
GLint coord[] = {
// top left
0, 0, // vertex coord
0, 0, // texture coord
// top right
new_tex->width, 0, // vertex coord
new_tex->width, 0, // texture coord
// bottom right
new_tex->width, new_tex->height,
new_tex->width, new_tex->height,
// bottom left
0, new_tex->height,
0, new_tex->height,
};
// clang-format on
GLuint indices[] = {0, 1, 2, 2, 3, 0};
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint bo[2];
glGenBuffers(2, bo);
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 16, coord, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
GL_STATIC_DRAW);
glEnableVertexAttribArray(vert_coord_loc);
glEnableVertexAttribArray(vert_in_texcoord_loc);
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 4, NULL);
glVertexAttribPointer(vert_in_texcoord_loc, 2, GL_INT, GL_FALSE,
sizeof(GLint) * 4, (void *)(sizeof(GLint) * 2));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
glDisableVertexAttribArray(vert_coord_loc);
glDisableVertexAttribArray(vert_in_texcoord_loc);
glBindVertexArray(0);
glDeleteVertexArrays(1, &vao);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDeleteBuffers(2, bo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(0);
gl_check_err();
img->inner = (struct backend_image_inner_base *)new_tex;
inner->refcount--;
}
static void gl_image_apply_alpha(backend_t *base, struct backend_image *img,
const region_t *reg_op, double alpha) {
// Result color = 0 (GL_ZERO) + alpha (GL_CONSTANT_ALPHA) * original color
auto inner = (struct gl_texture *)img->inner;
glBlendFunc(GL_ZERO, GL_CONSTANT_ALPHA);
glBlendColor(0, 0, 0, (GLclampf)alpha);
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
inner->texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
_gl_fill(base, (struct color){0, 0, 0, 0}, reg_op, fbo, inner->height,
!inner->y_inverted);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
}
void gl_present(backend_t *base, const region_t *region) {
auto gd = (struct gl_data *)base;
int nrects;
const rect_t *rect = pixman_region32_rectangles((region_t *)region, &nrects);
auto coord = ccalloc(nrects * 8, GLint);
auto indices = ccalloc(nrects * 6, GLuint);
for (int i = 0; i < nrects; i++) {
// clang-format off
memcpy(&coord[i * 8],
((GLint[]){rect[i].x1, gd->height - rect[i].y2,
rect[i].x2, gd->height - rect[i].y2,
rect[i].x2, gd->height - rect[i].y1,
rect[i].x1, gd->height - rect[i].y1}),
sizeof(GLint) * 8);
// clang-format on
GLuint u = (GLuint)(i * 4);
memcpy(&indices[i * 6],
((GLuint[]){u + 0, u + 1, u + 2, u + 2, u + 3, u + 0}),
sizeof(GLuint) * 6);
}
glUseProgram(gd->present_prog);
glBindTexture(GL_TEXTURE_2D, gd->back_texture);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint bo[2];
glGenBuffers(2, bo);
glEnableVertexAttribArray(vert_coord_loc);
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(GLint) * nrects * 8, coord, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(GLuint) * nrects * 6, indices,
GL_STREAM_DRAW);
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL);
glDrawElements(GL_TRIANGLES, nrects * 6, GL_UNSIGNED_INT, NULL);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glDeleteBuffers(2, bo);
glDeleteVertexArrays(1, &vao);
free(coord);
free(indices);
}
bool gl_image_op(backend_t *base, enum image_operations op, void *image_data,
const region_t *reg_op, const region_t *reg_visible attr_unused, void *arg) {
struct backend_image *tex = image_data;
switch (op) {
case IMAGE_OP_APPLY_ALPHA:
gl_image_decouple(base, tex);
assert(tex->inner->refcount == 1);
gl_image_apply_alpha(base, tex, reg_op, *(double *)arg);
break;
}
return true;
}
bool gl_set_image_property(backend_t *backend_data, enum image_properties prop,
void *image_data, void *args) {
if (prop != IMAGE_PROPERTY_CUSTOM_SHADER) {
return default_set_image_property(backend_data, prop, image_data, args);
}
struct backend_image *img = image_data;
auto inner = (struct gl_texture *)img->inner;
inner->shader = args;
return true;
}
struct gl_shadow_context {
double radius;
void *blur_context;
};
struct backend_shadow_context *gl_create_shadow_context(backend_t *base, double radius) {
auto ctx = ccalloc(1, struct gl_shadow_context);
ctx->radius = radius;
struct gaussian_blur_args args = {
.size = (int)radius,
.deviation = gaussian_kernel_std_for_size(radius, 0.5 / 256.0),
};
ctx->blur_context = gl_create_blur_context(base, BLUR_METHOD_GAUSSIAN, &args);
return (struct backend_shadow_context *)ctx;
}
void gl_destroy_shadow_context(backend_t *base attr_unused, struct backend_shadow_context *ctx) {
auto ctx_ = (struct gl_shadow_context *)ctx;
gl_destroy_blur_context(base, (struct backend_blur_context *)ctx_->blur_context);
free(ctx_);
}
void *gl_shadow_from_mask(backend_t *base, void *mask,
struct backend_shadow_context *sctx, struct color color) {
log_debug("Create shadow from mask");
auto gd = (struct gl_data *)base;
auto img = (struct backend_image *)mask;
auto inner = (struct gl_texture *)img->inner;
auto gsctx = (struct gl_shadow_context *)sctx;
int radius = (int)gsctx->radius;
auto new_inner = ccalloc(1, struct gl_texture);
new_inner->width = inner->width + radius * 2;
new_inner->height = inner->height + radius * 2;
new_inner->texture = gl_new_texture(GL_TEXTURE_2D);
new_inner->has_alpha = inner->has_alpha;
new_inner->y_inverted = true;
auto new_img = default_new_backend_image(new_inner->width, new_inner->height);
new_img->inner = (struct backend_image_inner_base *)new_inner;
new_img->inner->refcount = 1;
// Render the mask to a texture, so inversion and corner radius can be
// applied.
auto source_texture = gl_new_texture(GL_TEXTURE_2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, source_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0,
GL_RED, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
GLuint fbo;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
source_texture, 0);
glDrawBuffer(GL_COLOR_ATTACHMENT0);
if (img->color_inverted) {
// If the mask is inverted, clear the source_texture to white, so the
// "outside" of the mask would be correct
glClearColor(1, 1, 1, 1);
} else {
glClearColor(0, 0, 0, 1);
}
glClear(GL_COLOR_BUFFER_BIT);
{
// clang-format off
// interleaved vertex coordinates and texture coordinates
GLint coords[] = {radius , radius , 0 , 0,
radius + inner->width, radius , inner->width, 0,
radius + inner->width, radius + inner->height, inner->width, inner->height,
radius , radius + inner->height, 0 , inner->height,};
// clang-format on
GLuint indices[] = {0, 1, 2, 2, 3, 0};
_gl_compose(base, mask, fbo, NULL, (coord_t){0}, coords, indices, 1);
}
gl_check_err();
glActiveTexture(GL_TEXTURE0);
auto tmp_texture = gl_new_texture(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, tmp_texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, new_inner->width, new_inner->height, 0,
GL_RED, GL_UNSIGNED_BYTE, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
tmp_texture, 0);
region_t reg_blur;
pixman_region32_init_rect(&reg_blur, 0, 0, (unsigned int)new_inner->width,
(unsigned int)new_inner->height);
// gl_blur expects reg_blur to be in X coordinate system (i.e. y flipped), but we
// are covering the whole texture so we don't need to worry about that.
gl_blur_impl(1.0, gsctx->blur_context, NULL, (coord_t){0}, &reg_blur, NULL,
source_texture,
(geometry_t){.width = new_inner->width, .height = new_inner->height},
fbo, gd->default_mask_texture);
pixman_region32_fini(&reg_blur);
// Colorize the shadow with color.
log_debug("Colorize shadow");
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, new_inner->texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, new_inner->width, new_inner->height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
new_inner->texture, 0);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, tmp_texture);
glUseProgram(gd->shadow_shader.prog);
glUniform4f(gd->shadow_shader.uniform_color, (GLfloat)color.red,
(GLfloat)color.green, (GLfloat)color.blue, (GLfloat)color.alpha);
// clang-format off
GLuint indices[] = {0, 1, 2, 2, 3, 0};
GLint coord[] = {0 , 0 ,
new_inner->width , 0 ,
new_inner->width , new_inner->height,
0 , new_inner->height,};
// clang-format on
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint bo[2];
glGenBuffers(2, bo);
glBindBuffer(GL_ARRAY_BUFFER, bo[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bo[1]);
glBufferData(GL_ARRAY_BUFFER, (long)sizeof(*coord) * 8, coord, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (long)sizeof(*indices) * 6, indices,
GL_STATIC_DRAW);
glEnableVertexAttribArray(vert_coord_loc);
glVertexAttribPointer(vert_coord_loc, 2, GL_INT, GL_FALSE, sizeof(GLint) * 2, NULL);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
glDisableVertexAttribArray(vert_coord_loc);
glBindVertexArray(0);
glDeleteVertexArrays(1, &vao);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDeleteBuffers(2, bo);
glDeleteTextures(1, (GLuint[]){source_texture, tmp_texture});
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fbo);
gl_check_err();
return new_img;
}
enum device_status gl_device_status(backend_t *base) {
auto gd = (struct gl_data *)base;
if (!gd->has_robustness) {
return DEVICE_STATUS_NORMAL;
}
if (glGetGraphicsResetStatusARB() == GL_NO_ERROR) {
return DEVICE_STATUS_NORMAL;
}
return DEVICE_STATUS_RESETTING;
}