git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

scenefx/render/fx_renderer/fx_pass.c (59K)

   1 #include <math.h>
   2 #include <stdio.h>
   3 #include <stdlib.h>
   4 #include <assert.h>
   5 #include <pixman.h>
   6 #include <time.h>
   7 #include <unistd.h>
   8 #include <wlr/render/allocator.h>
   9 #include <wlr/render/drm_syncobj.h>
  10 #include <wlr/util/transform.h>
  11 #include <wlr/util/log.h>
  12 #include <wlr/util/region.h>
  13 
  14 #include "render/egl.h"
  15 #include "render/fx_renderer/fx_renderer.h"
  16 #include "render/fx_renderer/shaders.h"
  17 #include "render/pass.h"
  18 #include "render/tracy.h"
  19 #include "scenefx/render/fx_renderer/fx_offscreen_buffers.h"
  20 #include "scenefx/render/fx_renderer/fx_renderer.h"
  21 #include "scenefx/types/fx/blur_data.h"
  22 #include "util/env.h"
  23 #include "util/matrix.h"
  24 
  25 #define MAX_QUADS 86 // 4kb
  26 
  27 // The blur traces below sit in the per-node, per-frame render path: at ~10 blur
  28 // nodes and 60Hz that is ~600 synchronous formatted writes/second on the same
  29 // thread that composites and delivers frame callbacks. Gate them.
  30 static bool cce_blur_debug(void) {
  31 	static bool initialized = false;
  32 	static bool enabled = false;
  33 	if (!initialized) {
  34 		enabled = env_parse_bool("CCE_BLUR_DEBUG");
  35 		initialized = true;
  36 	}
  37 	return enabled;
  38 }
  39 
  40 static void read_to_buffer_offset(struct fx_gles_render_pass *pass,
  41 		pixman_region32_t *_region, struct fx_framebuffer *dst_buffer,
  42 		struct fx_framebuffer *src_buffer, int ox, int oy);
  43 
  44 struct fx_render_texture_options fx_render_texture_options_default(
  45 		const struct wlr_render_texture_options *base) {
  46 	struct fx_render_texture_options options = {
  47 		.corners = {0},
  48 		.discard_transparent = false,
  49 		.clip_box = NULL,
  50 		.clipped_region = {0},
  51 	};
  52 	memcpy(&options.base, base, sizeof(*base));
  53 	return options;
  54 }
  55 
  56 struct fx_render_rect_options fx_render_rect_options_default(
  57 		const struct wlr_render_rect_options *base) {
  58 	struct fx_render_rect_options options = {
  59 		.base = *base,
  60 		.clipped_region = {
  61 			.area = { .0, .0, .0, .0 },
  62 			.corners = {0},
  63 		},
  64 	};
  65 	return options;
  66 }
  67 
  68 bool fx_render_pass_init_offscreen_buffers(struct wlr_render_pass *render_pass,
  69 		struct wlr_output *output) {
  70 	struct fx_gles_render_pass *pass = fx_get_render_pass(render_pass);
  71 	if (output == NULL) {
  72 		pass->fx_offscreen_buffers = NULL;
  73 		return false;
  74 	}
  75 	if (pass->fx_offscreen_buffers != NULL) {
  76 		wlr_log(WLR_ERROR, "Extra buffers called twice. Ignoring...");
  77 		return true;
  78 	}
  79 
  80 	// For per output framebuffers
  81 	pass->fx_offscreen_buffers = fx_offscreen_buffers_try_get(output);
  82 	if (pass->fx_offscreen_buffers == NULL) {
  83 		wlr_log(WLR_ERROR, "Failed to get/create effect framebuffers for output: %s",
  84 				output->name);
  85 		return false;
  86 	}
  87 
  88 	// Update the buffers if needed
  89 	struct fx_renderer *renderer = pass->buffer->renderer;
  90 	const int width = pass->buffer->buffer->width;
  91 	const int height = pass->buffer->buffer->height;
  92 	bool failed = false;
  93 	fx_framebuffer_get_or_create_custom(renderer, output->allocator, width, height, false,
  94 			&pass->fx_offscreen_buffers->blur_saved_pixels_buffer, &failed);
  95 	fx_framebuffer_get_or_create_custom(renderer, output->allocator, width, height, true,
  96 			&pass->fx_offscreen_buffers->effects_buffer, &failed);
  97 	fx_framebuffer_get_or_create_custom(renderer, output->allocator, width, height, true,
  98 			&pass->fx_offscreen_buffers->effects_buffer_swapped, &failed);
  99 	// The optimized-blur cache carries a quarter-output margin on every
 100 	// side (see fx_offscreen_buffers.cache_margin_*).
 101 	const int margin_x = width / 4;
 102 	const int margin_y = height / 4;
 103 	pass->fx_offscreen_buffers->cache_margin_x = margin_x;
 104 	pass->fx_offscreen_buffers->cache_margin_y = margin_y;
 105 	fx_framebuffer_get_or_create_custom(renderer, output->allocator,
 106 			width + 2 * margin_x, height + 2 * margin_y, false,
 107 			&pass->fx_offscreen_buffers->optimized_blur_buffer, &failed);
 108 
 109 	// Bind back to the default buffer
 110 	fx_framebuffer_bind(pass->buffer);
 111 
 112 	if (failed) {
 113 		fx_offscreen_buffers_destroy(pass->fx_offscreen_buffers);
 114 		pass->fx_offscreen_buffers = NULL;
 115 		wlr_log(WLR_ERROR, "Failed to create effect framebuffers");
 116 		return false;
 117 	}
 118 	return true;
 119 }
 120 
 121 ///
 122 /// Base Wlroots pass functions
 123 ///
 124 
 125 static const struct wlr_render_pass_impl render_pass_impl;
 126 
 127 struct fx_gles_render_pass *fx_get_render_pass(struct wlr_render_pass *render_pass) {
 128 	assert(render_pass->impl == &render_pass_impl);
 129 	struct fx_gles_render_pass *pass = wl_container_of(render_pass, pass, base);
 130 	return pass;
 131 }
 132 
 133 static bool render_pass_submit(struct wlr_render_pass *wlr_pass) {
 134 	struct fx_gles_render_pass *pass = fx_get_render_pass(wlr_pass);
 135 	struct fx_renderer *renderer = pass->buffer->renderer;
 136 	struct fx_render_timer *timer = pass->timer;
 137 	bool ok = false;
 138 
 139 	TRACY_BOTH_ZONES_START(pass->buffer->renderer);
 140 	push_fx_debug(renderer);
 141 
 142 	if (timer) {
 143 		// clear disjoint flag
 144 		GLint64 disjoint;
 145 		renderer->procs.glGetInteger64vEXT(GL_GPU_DISJOINT_EXT, &disjoint);
 146 		// set up the query
 147 		renderer->procs.glQueryCounterEXT(timer->id, GL_TIMESTAMP_EXT);
 148 		// get end-of-CPU-work time in GL time domain
 149 		renderer->procs.glGetInteger64vEXT(GL_TIMESTAMP_EXT, &timer->gl_cpu_end);
 150 		// get end-of-CPU-work time in CPU time domain
 151 		clock_gettime(CLOCK_MONOTONIC, &timer->cpu_end);
 152 	}
 153 
 154 	if (pass->signal_timeline != NULL) {
 155 		EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, -1);
 156 		if (sync == EGL_NO_SYNC_KHR) {
 157 			goto out;
 158 		}
 159 
 160 		int sync_file_fd = wlr_egl_dup_fence_fd(renderer->egl, sync);
 161 		wlr_egl_destroy_sync(renderer->egl, sync);
 162 		if (sync_file_fd < 0) {
 163 			goto out;
 164 		}
 165 
 166 		ok = wlr_drm_syncobj_timeline_import_sync_file(pass->signal_timeline, pass->signal_point, sync_file_fd);
 167 		close(sync_file_fd);
 168 		if (!ok) {
 169 			goto out;
 170 		}
 171 	} else {
 172 		glFlush();
 173 	}
 174 
 175 	ok = true;
 176 
 177 out:
 178 	glBindFramebuffer(GL_FRAMEBUFFER, 0);
 179 
 180 	pop_fx_debug(renderer);
 181 	TRACY_BOTH_ZONES_END;
 182 	TRACY_GPU_ZONE_COLLECT(renderer);
 183 
 184 	wlr_egl_restore_context(&pass->prev_ctx);
 185 
 186 	wlr_drm_syncobj_timeline_unref(pass->signal_timeline);
 187 	wlr_buffer_unlock(pass->buffer->buffer);
 188 
 189 	pass->fx_offscreen_buffers = NULL;
 190 	pixman_region32_fini(&pass->blur_padding_region);
 191 
 192 	free(pass);
 193 
 194 	return ok;
 195 }
 196 
 197 static void render_pass_add_texture(struct wlr_render_pass *wlr_pass,
 198 		const struct wlr_render_texture_options *options) {
 199 	struct fx_gles_render_pass *pass = fx_get_render_pass(wlr_pass);
 200 	const struct fx_render_texture_options fx_options =
 201 		fx_render_texture_options_default(options);
 202 	// Re-use fx function but with default options
 203 	// TODO: Simplified version?
 204 	fx_render_pass_add_texture(pass, &fx_options);
 205 }
 206 
 207 static void render_pass_add_rect(struct wlr_render_pass *wlr_pass,
 208 		const struct wlr_render_rect_options *options) {
 209 	struct fx_gles_render_pass *pass = fx_get_render_pass(wlr_pass);
 210 	const struct fx_render_rect_options fx_options =
 211 		fx_render_rect_options_default(options);
 212 	// Re-use fx function but with default options
 213 	// TODO: Simplified version?
 214 	fx_render_pass_add_rect(pass, &fx_options);
 215 }
 216 
 217 static const struct wlr_render_pass_impl render_pass_impl = {
 218 	.submit = render_pass_submit,
 219 	.add_texture = render_pass_add_texture,
 220 	.add_rect = render_pass_add_rect,
 221 };
 222 
 223 ///
 224 /// FX pass functions
 225 ///
 226 
 227 // TODO: REMOVE STENCILING
 228 
 229 // Initialize the stenciling work
 230 static void stencil_mask_init(void) {
 231 	glClearStencil(0);
 232 	glClear(GL_STENCIL_BUFFER_BIT);
 233 	glEnable(GL_STENCIL_TEST);
 234 
 235 	glStencilFunc(GL_ALWAYS, 1, 0xFF);
 236 	glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
 237 	// Disable writing to color buffer
 238 	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
 239 }
 240 
 241 // Close the mask
 242 static void stencil_mask_close(bool draw_inside_mask) {
 243 	// Reenable writing to color buffer
 244 	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
 245 	if (draw_inside_mask) {
 246 		glStencilFunc(GL_EQUAL, 1, 0xFF);
 247 		glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
 248 		return;
 249 	}
 250 	glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
 251 	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
 252 }
 253 
 254 // Finish stenciling and clear the buffer
 255 static void stencil_mask_fini(void) {
 256 	glClearStencil(0);
 257 	glClear(GL_STENCIL_BUFFER_BIT);
 258 	glDisable(GL_STENCIL_TEST);
 259 }
 260 
 261 static void render(const struct wlr_box *box, const pixman_region32_t *clip, GLint attrib) {
 262 	pixman_region32_t region;
 263 	pixman_region32_init_rect(&region, box->x, box->y, box->width, box->height);
 264 
 265 	if (clip) {
 266 		pixman_region32_intersect(&region, &region, clip);
 267 	}
 268 
 269 	int rects_len;
 270 	const pixman_box32_t *rects = pixman_region32_rectangles(&region, &rects_len);
 271 	if (rects_len == 0) {
 272 		pixman_region32_fini(&region);
 273 		return;
 274 	}
 275 
 276 	glEnableVertexAttribArray(attrib);
 277 
 278 	for (int i = 0; i < rects_len;) {
 279 		int batch = rects_len - i < MAX_QUADS ? rects_len - i : MAX_QUADS;
 280 		int batch_end = batch + i;
 281 
 282 		size_t vert_index = 0;
 283 		GLfloat verts[MAX_QUADS * 6 * 2];
 284 		for (; i < batch_end; i++) {
 285 			const pixman_box32_t *rect = &rects[i];
 286 
 287 			verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width;
 288 			verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height;
 289 			verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width;
 290 			verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height;
 291 			verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width;
 292 			verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height;
 293 			verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width;
 294 			verts[vert_index++] = (GLfloat)(rect->y1 - box->y) / box->height;
 295 			verts[vert_index++] = (GLfloat)(rect->x2 - box->x) / box->width;
 296 			verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height;
 297 			verts[vert_index++] = (GLfloat)(rect->x1 - box->x) / box->width;
 298 			verts[vert_index++] = (GLfloat)(rect->y2 - box->y) / box->height;
 299 		}
 300 
 301 		glVertexAttribPointer(attrib, 2, GL_FLOAT, GL_FALSE, 0, verts);
 302 		glDrawArrays(GL_TRIANGLES, 0, batch * 6);
 303 	}
 304 
 305 	glDisableVertexAttribArray(attrib);
 306 
 307 	pixman_region32_fini(&region);
 308 }
 309 
 310 static void set_proj_matrix(GLint loc, float proj[9], const struct wlr_box *box) {
 311 	float gl_matrix[9];
 312 	wlr_matrix_identity(gl_matrix);
 313 	wlr_matrix_translate(gl_matrix, box->x, box->y);
 314 	wlr_matrix_scale(gl_matrix, box->width, box->height);
 315 	wlr_matrix_multiply(gl_matrix, proj, gl_matrix);
 316 	glUniformMatrix3fv(loc, 1, GL_FALSE, gl_matrix);
 317 }
 318 
 319 static void set_tex_matrix(GLint loc, enum wl_output_transform trans,
 320 		const struct wlr_fbox *box) {
 321 	float tex_matrix[9];
 322 	wlr_matrix_identity(tex_matrix);
 323 	wlr_matrix_translate(tex_matrix, box->x, box->y);
 324 	wlr_matrix_scale(tex_matrix, box->width, box->height);
 325 	wlr_matrix_translate(tex_matrix, .5, .5);
 326 
 327 	// since textures have a different origin point we have to transform
 328 	// differently if we are rotating
 329 	if (trans & WL_OUTPUT_TRANSFORM_90) {
 330 		wlr_matrix_transform(tex_matrix, wlr_output_transform_invert(trans));
 331 	} else {
 332 		wlr_matrix_transform(tex_matrix, trans);
 333 	}
 334 	wlr_matrix_translate(tex_matrix, -.5, -.5);
 335 
 336 	glUniformMatrix3fv(loc, 1, GL_FALSE, tex_matrix);
 337 }
 338 
 339 static void setup_blending(enum wlr_render_blend_mode mode) {
 340 	switch (mode) {
 341 	case WLR_RENDER_BLEND_MODE_PREMULTIPLIED:
 342 		glEnable(GL_BLEND);
 343 		break;
 344 	case WLR_RENDER_BLEND_MODE_NONE:
 345 		glDisable(GL_BLEND);
 346 		break;
 347 	}
 348 }
 349 
 350 static bool apply_clip_region(pixman_region32_t *clip_region,
 351 		const struct wlr_box *clipped_region_box, const struct fx_corner_fradii *corners) {
 352 	if (!wlr_box_empty(clipped_region_box)) {
 353 		float top = fmax(corners->top_left, corners->top_right);
 354 		float bottom = fmax(corners->bottom_left, corners->bottom_right);
 355 		float left = fmax(corners->top_left, corners->bottom_left);
 356 		float right = fmax(corners->top_right, corners->bottom_right);
 357 
 358 		pixman_region32_t user_clip_region;
 359 		pixman_region32_init_rect(
 360 			&user_clip_region,
 361 			clipped_region_box->x + (left * 0.3),
 362 			clipped_region_box->y + (top * 0.3),
 363 			fmax(clipped_region_box->width - (left + right) * 0.3, 0),
 364 			fmax(clipped_region_box->height - (top + bottom) * 0.3, 0)
 365 		);
 366 		pixman_region32_subtract(clip_region, clip_region, &user_clip_region);
 367 		pixman_region32_fini(&user_clip_region);
 368 		return true;
 369 	}
 370 
 371 	return false;
 372 }
 373 
 374 void fx_render_pass_add_texture(struct fx_gles_render_pass *pass,
 375 		const struct fx_render_texture_options *fx_options) {
 376 	const struct wlr_render_texture_options *options = &fx_options->base;
 377 	struct fx_renderer *renderer = pass->buffer->renderer;
 378 	struct fx_texture *texture = fx_get_texture(options->texture);
 379 
 380 	struct tex_shader *shader = NULL;
 381 
 382 	bool use_effects = !fx_corner_fradii_is_empty(&fx_options->corners)
 383 		|| clipped_fregion_is_valid(&fx_options->clipped_region);
 384 	switch (texture->target) {
 385 	case GL_TEXTURE_2D:
 386 		if (texture->has_alpha) {
 387 			shader = use_effects
 388 				? &renderer->shaders.tex_effects_rgba
 389 				: &renderer->shaders.tex_rgba;
 390 		} else {
 391 			shader = use_effects
 392 				? &renderer->shaders.tex_effects_rgbx
 393 				: &renderer->shaders.tex_rgbx;
 394 		}
 395 		break;
 396 	case GL_TEXTURE_EXTERNAL_OES:
 397 		// EGL_EXT_image_dma_buf_import_modifiers requires
 398 		// GL_OES_EGL_image_external
 399 		assert(renderer->exts.OES_egl_image_external);
 400 		shader = use_effects
 401 			? &renderer->shaders.tex_effects_ext
 402 			: &renderer->shaders.tex_ext;
 403 		break;
 404 	default:
 405 		abort();
 406 	}
 407 
 408 	struct wlr_box dst_box;
 409 	struct wlr_fbox src_fbox;
 410 	wlr_render_texture_options_get_src_box(options, &src_fbox);
 411 	wlr_render_texture_options_get_dst_box(options, &dst_box);
 412 	float alpha = wlr_render_texture_options_get_alpha(options);
 413 
 414 	const struct wlr_box *clip_box = &dst_box;
 415 	if (!wlr_box_empty(fx_options->clip_box)) {
 416 		clip_box = fx_options->clip_box;
 417 	}
 418 
 419 	src_fbox.x /= options->texture->width;
 420 	src_fbox.y /= options->texture->height;
 421 	src_fbox.width /= options->texture->width;
 422 	src_fbox.height /= options->texture->height;
 423 
 424 	TRACY_BOTH_ZONES_START(renderer);
 425 	TRACY_ZONE_TEXT_f("dst_box (WxH, X, Y): %dx%d, %d, %d",
 426 			dst_box.width, dst_box.height, dst_box.x, dst_box.y);
 427 	TRACY_ZONE_TEXT_f("clip_box (WxH, X, Y): %dx%d, %d, %d",
 428 			clip_box->width, clip_box->height, clip_box->x, clip_box->y);
 429 	TRACY_ZONE_TEXT_f("src_box (WxH, X, Y): %lfx%lf, %lf, %lf",
 430 			src_fbox.width, src_fbox.height, src_fbox.x, src_fbox.y);
 431 	TRACY_ZONE_TEXT_f("Shader Type: %s",
 432 			use_effects ? (
 433 			 shader == &renderer->shaders.tex_effects_rgba ? "Effects RGBA"
 434 			 : shader == &renderer->shaders.tex_effects_rgbx ? "Effects RGBX"
 435 			 : "Effects EXT"
 436 			) : (
 437 				shader == &renderer->shaders.tex_rgba ? "RGBA"
 438 				: shader == &renderer->shaders.tex_rgbx ? "RGBX"
 439 				: "EXT"
 440 			)
 441 		);
 442 	push_fx_debug(renderer);
 443 
 444 	if (options->wait_timeline != NULL) {
 445 		int sync_file_fd =
 446 			wlr_drm_syncobj_timeline_export_sync_file(options->wait_timeline, options->wait_point);
 447 		if (sync_file_fd < 0) {
 448 			TRACY_BOTH_ZONES_END_FAIL;
 449 			return;
 450 		}
 451 
 452 		EGLSyncKHR sync = wlr_egl_create_sync(renderer->egl, sync_file_fd);
 453 		close(sync_file_fd);
 454 		if (sync == EGL_NO_SYNC_KHR) {
 455 			TRACY_BOTH_ZONES_END_FAIL;
 456 			return;
 457 		}
 458 
 459 		bool ok = wlr_egl_wait_sync(renderer->egl, sync);
 460 		wlr_egl_destroy_sync(renderer->egl, sync);
 461 		if (!ok) {
 462 			TRACY_BOTH_ZONES_END_FAIL;
 463 			return;
 464 		}
 465 	}
 466 
 467 	bool has_alpha = texture->has_alpha || alpha < 1.0 || use_effects;
 468 	TRACY_ZONE_TEXT_f("Has Alpha: %d", has_alpha);
 469 	setup_blending(!has_alpha ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode);
 470 
 471 	pixman_region32_t clip_region;
 472 	if (options->clip) {
 473 		pixman_region32_init(&clip_region);
 474 		pixman_region32_copy(&clip_region, options->clip);
 475 	} else {
 476 		pixman_region32_init_rect(&clip_region, dst_box.x, dst_box.y, dst_box.width, dst_box.height);
 477 	}
 478 	const struct wlr_box clipped_region_box = fx_options->clipped_region.area;
 479 	struct fx_corner_fradii clipped_region_corners = fx_options->clipped_region.corners;
 480 	apply_clip_region(&clip_region, &clipped_region_box, &clipped_region_corners);
 481 
 482 	glUseProgram(shader->program);
 483 
 484 	glActiveTexture(GL_TEXTURE0);
 485 	glBindTexture(texture->target, texture->tex);
 486 
 487 	switch (options->filter_mode) {
 488 	case WLR_SCALE_FILTER_BILINEAR:
 489 		glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 490 		glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 491 		break;
 492 	case WLR_SCALE_FILTER_NEAREST:
 493 		glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
 494 		glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 495 		break;
 496 	}
 497 
 498 	glUniform1i(shader->tex, 0);
 499 	glUniform1f(shader->alpha, alpha);
 500 
 501 	glUniform1f(shader->discard_transparent, fx_options->discard_transparent);
 502 
 503 	if (use_effects) {
 504 		struct fx_corner_fradii corners = fx_options->corners;
 505 
 506 		glUniform2f(shader->effects.size, clip_box->width, clip_box->height);
 507 		glUniform2f(shader->effects.position, clip_box->x, clip_box->y);
 508 		uniform_corner_radii_set(&shader->effects.radius, &corners);
 509 
 510 		glUniform2f(shader->effects.clip_size, clipped_region_box.width, clipped_region_box.height);
 511 		glUniform2f(shader->effects.clip_position, clipped_region_box.x, clipped_region_box.y);
 512 		uniform_corner_radii_set(&shader->effects.clip_radius, &clipped_region_corners);
 513 	}
 514 
 515 	set_proj_matrix(shader->proj, pass->projection_matrix, &dst_box);
 516 	set_tex_matrix(shader->tex_proj, options->transform, &src_fbox);
 517 
 518 	render(&dst_box, &clip_region, shader->pos_attrib);
 519 	pixman_region32_fini(&clip_region);
 520 
 521 	glBindTexture(texture->target, 0);
 522 
 523 	pop_fx_debug(renderer);
 524 	TRACY_BOTH_ZONES_END;
 525 }
 526 
 527 void fx_render_pass_add_rect(struct fx_gles_render_pass *pass,
 528 		const struct fx_render_rect_options *fx_options) {
 529 	const struct wlr_render_rect_options *options = &fx_options->base;
 530 
 531 	struct fx_renderer *renderer = pass->buffer->renderer;
 532 
 533 	const struct wlr_render_color *color = &options->color;
 534 	struct wlr_box box;
 535 	struct wlr_buffer *wlr_buffer = pass->buffer->buffer;
 536 	wlr_render_rect_options_get_box(options, wlr_buffer, &box);
 537 
 538 	const bool should_clip = clipped_fregion_is_valid(&fx_options->clipped_region);
 539 
 540 	enum wlr_render_blend_mode blend_mode =
 541 		(color->a == 1.0 && !should_clip) ? WLR_RENDER_BLEND_MODE_NONE : options->blend_mode;
 542 	const bool use_fast_clear =
 543 		blend_mode == WLR_RENDER_BLEND_MODE_NONE && // includes check for `should_clip`
 544 		options->clip == NULL &&
 545 		box.x == 0 && box.y == 0 &&
 546 		box.width == wlr_buffer->width &&
 547 		box.height == wlr_buffer->height;
 548 
 549 	TRACY_BOTH_ZONES_START(renderer);
 550 	TRACY_ZONE_TEXT_f("Box (WxH, X, Y): %dx%d, %d, %d", box.width, box.height, box.x, box.y);
 551 	TRACY_ZONE_TEXT_f("Color RGBA: %f, %f, %f, %f", color->r, color->g, color->b, color->a);
 552 	TRACY_ZONE_TEXT_f("Use fast clear optimization: %d", use_fast_clear);
 553 
 554 	push_fx_debug(renderer);
 555 	if (use_fast_clear) {
 556 		glClearColor(color->r, color->g, color->b, color->a);
 557 		glClear(GL_COLOR_BUFFER_BIT);
 558 	} else {
 559 		const struct wlr_box *clipped_region_box = &fx_options->clipped_region.area;
 560 		const struct fx_corner_fradii *clipped_region_corners = &fx_options->clipped_region.corners;
 561 
 562 		TRACY_ZONE_TEXT_f("Clip Box (WxH, X, Y): %dx%d, %d, %d",
 563 				clipped_region_box->width, clipped_region_box->height,
 564 				clipped_region_box->x, clipped_region_box->y);
 565 		TRACY_ZONE_TEXT_f("Clip Box Corners (TL, TR, BL, BR): %f, %f, %f, %f",
 566 				clipped_region_corners->top_left,
 567 				clipped_region_corners->top_right,
 568 				clipped_region_corners->bottom_left,
 569 				clipped_region_corners->bottom_right);
 570 
 571 		pixman_region32_t clip_region;
 572 		if (options->clip) {
 573 			pixman_region32_init(&clip_region);
 574 			pixman_region32_copy(&clip_region, options->clip);
 575 		} else {
 576 			pixman_region32_init_rect(&clip_region, box.x, box.y, box.width, box.height);
 577 		}
 578 
 579 		apply_clip_region(&clip_region, clipped_region_box, clipped_region_corners);
 580 
 581 		setup_blending(blend_mode);
 582 		struct quad_shader *shader = should_clip
 583 			? &renderer->shaders.quad_clip
 584 			: &renderer->shaders.quad;
 585 		glUseProgram(shader->program);
 586 		set_proj_matrix(shader->proj, pass->projection_matrix, &box);
 587 		glUniform4f(shader->color, color->r, color->g, color->b, color->a);
 588 		if (should_clip) {
 589 			glUniform2f(shader->effects.clip_size, clipped_region_box->width, clipped_region_box->height);
 590 			glUniform2f(shader->effects.clip_position, clipped_region_box->x, clipped_region_box->y);
 591 			uniform_corner_radii_set(&shader->effects.clip_radius, clipped_region_corners);
 592 		}
 593 		render(&box, &clip_region, shader->pos_attrib);
 594 
 595 		pixman_region32_fini(&clip_region);
 596 	}
 597 
 598 	pop_fx_debug(renderer);
 599 	TRACY_BOTH_ZONES_END;
 600 }
 601 
 602 void fx_render_pass_add_rect_grad(struct fx_gles_render_pass *pass,
 603 		const struct fx_render_rect_grad_options *fx_options) {
 604 	const struct wlr_render_rect_options *options = &fx_options->base;
 605 
 606 	struct fx_renderer *renderer = pass->buffer->renderer;
 607 
 608 	if (renderer->shaders.quad_grad.max_len <= fx_options->gradient.count) {
 609 		glDeleteProgram(renderer->shaders.quad_grad.program);
 610 		if (!link_quad_grad_program(&renderer->shaders.quad_grad, fx_options->gradient.count + 1)) {
 611 			wlr_log(WLR_ERROR, "Could not link quad shader after updating max_len to %d. Aborting renderer", fx_options->gradient.count + 1);
 612 			abort();
 613 		}
 614 	}
 615 
 616 	struct wlr_box box;
 617 	struct wlr_buffer *wlr_buffer = pass->buffer->buffer;
 618 	wlr_render_rect_options_get_box(options, wlr_buffer, &box);
 619 
 620 	TRACY_BOTH_ZONES_START(renderer);
 621 	TRACY_ZONE_TEXT_f("Box (WxH, X, Y): %dx%d, %d, %d", box.width, box.height, box.x, box.y);
 622 	TRACY_ZONE_TEXT_f("Gradient:");
 623 	TRACY_ZONE_TEXT_f("\tNum Colors: %d", fx_options->gradient.count);
 624 	TRACY_ZONE_TEXT_f("\tBlend: %d", fx_options->gradient.blend);
 625 	TRACY_ZONE_TEXT_f("\tDegree: %f", fx_options->gradient.degree);
 626 	TRACY_ZONE_TEXT_f("\tType: %s",
 627 			fx_options->gradient.linear == 1 ? "Linear"
 628 			: fx_options->gradient.linear == 2 ? "Conic"
 629 			: "Unknown");
 630 	TRACY_ZONE_TEXT_f("\tOrigin: %fx%f",
 631 			fx_options->gradient.origin[0], fx_options->gradient.origin[1]);
 632 	TRACY_ZONE_TEXT_f("\tRange (WxH, X, Y): %dx%d, %d, %d",
 633 			fx_options->gradient.range.width, fx_options->gradient.range.height,
 634 			fx_options->gradient.range.x, fx_options->gradient.range.y);
 635 	// TODO: Display Colors (not really sure how it works without a scene example...)
 636 	push_fx_debug(renderer);
 637 
 638 	setup_blending(options->blend_mode);
 639 
 640 	struct quad_grad_shader shader = renderer->shaders.quad_grad;
 641 	glUseProgram(shader.program);
 642 
 643 	set_proj_matrix(shader.proj, pass->projection_matrix, &box);
 644 	glUniform4fv(shader.colors, fx_options->gradient.count, (GLfloat*)fx_options->gradient.colors);
 645 	glUniform1i(shader.count, fx_options->gradient.count);
 646 	glUniform2f(shader.size, fx_options->gradient.range.width, fx_options->gradient.range.height);
 647 	glUniform1f(shader.degree, fx_options->gradient.degree);
 648 	glUniform1f(shader.linear, fx_options->gradient.linear);
 649 	glUniform1f(shader.blend, fx_options->gradient.blend);
 650 	glUniform2f(shader.grad_box, fx_options->gradient.range.x, fx_options->gradient.range.y);
 651 	glUniform2f(shader.origin, fx_options->gradient.origin[0], fx_options->gradient.origin[1]);
 652 
 653 	render(&box, options->clip, shader.pos_attrib);
 654 
 655 	pop_fx_debug(renderer);
 656 	TRACY_BOTH_ZONES_END;
 657 }
 658 
 659 void fx_render_pass_add_rounded_rect(struct fx_gles_render_pass *pass,
 660 		const struct fx_render_rounded_rect_options *fx_options) {
 661 	const struct wlr_render_rect_options *options = &fx_options->base;
 662 
 663 	struct fx_renderer *renderer = pass->buffer->renderer;
 664 
 665 	const struct wlr_render_color *color = &options->color;
 666 	struct wlr_box box;
 667 	struct wlr_buffer *wlr_buffer = pass->buffer->buffer;
 668 	wlr_render_rect_options_get_box(options, wlr_buffer, &box);
 669 
 670 	pixman_region32_t clip_region;
 671 	if (options->clip) {
 672 		pixman_region32_init(&clip_region);
 673 		pixman_region32_copy(&clip_region, options->clip);
 674 	} else {
 675 		pixman_region32_init_rect(&clip_region, box.x, box.y, box.width, box.height);
 676 	}
 677 	const struct wlr_box *clipped_region_box = &fx_options->clipped_region.area;
 678 	const struct fx_corner_fradii *clipped_region_corners = &fx_options->clipped_region.corners;
 679 	apply_clip_region(&clip_region, clipped_region_box, clipped_region_corners);
 680 
 681 	TRACY_BOTH_ZONES_START(renderer);
 682 	TRACY_ZONE_TEXT_f("Box (WxH, X, Y): %dx%d, %d, %d", box.width, box.height, box.x, box.y);
 683 	TRACY_ZONE_TEXT_f("Clip Box (WxH, X, Y): %dx%d, %d, %d",
 684 			clipped_region_box->width, clipped_region_box->height,
 685 			clipped_region_box->x, clipped_region_box->y);
 686 	TRACY_ZONE_TEXT_f("Clip Box Corners (TL, TR, BL, BR): %f, %f, %f, %f",
 687 			clipped_region_corners->top_left,
 688 			clipped_region_corners->top_right,
 689 			clipped_region_corners->bottom_left,
 690 			clipped_region_corners->bottom_right);
 691 	TRACY_ZONE_TEXT_f("Color RGBA: %f, %f, %f, %f", color->r, color->g, color->b, color->a);
 692 	TRACY_ZONE_TEXT_f("Corners (TL, TR, BL, BR): %f, %f, %f, %f",
 693 			clipped_region_corners->top_left,
 694 			clipped_region_corners->top_right,
 695 			clipped_region_corners->bottom_left,
 696 			clipped_region_corners->bottom_right);
 697 	push_fx_debug(renderer);
 698 
 699 	setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED);
 700 
 701 	struct quad_round_shader shader = renderer->shaders.quad_round;
 702 
 703 	glUseProgram(shader.program);
 704 
 705 	set_proj_matrix(shader.proj, pass->projection_matrix, &box);
 706 	glUniform4f(shader.color, color->r, color->g, color->b, color->a);
 707 
 708 	glUniform2f(shader.size, box.width, box.height);
 709 	glUniform2f(shader.position, box.x, box.y);
 710 	glUniform2f(shader.clip_size, clipped_region_box->width, clipped_region_box->height);
 711 	glUniform2f(shader.clip_position, clipped_region_box->x, clipped_region_box->y);
 712 	uniform_corner_radii_set(&shader.clip_radius, clipped_region_corners);
 713 
 714 	struct fx_corner_fradii corners = fx_options->corners;
 715 	uniform_corner_radii_set(&shader.radius, &corners);
 716 	int fade_mode = fx_options->fade_inset % 10;
 717 	float fade_inset = (float)(fx_options->fade_inset / 10) / 100.0f;
 718 	glUniform1f(shader.fade_inset, fade_inset);
 719 	glUniform1i(shader.fade_mode, fade_mode);
 720 
 721 	render(&box, &clip_region, renderer->shaders.quad_round.pos_attrib);
 722 	pixman_region32_fini(&clip_region);
 723 
 724 	pop_fx_debug(renderer);
 725 	TRACY_BOTH_ZONES_END;
 726 }
 727 
 728 void fx_render_pass_add_rounded_rect_grad(struct fx_gles_render_pass *pass,
 729 		const struct fx_render_rounded_rect_grad_options *fx_options) {
 730 	const struct wlr_render_rect_options *options = &fx_options->base;
 731 
 732 	struct fx_renderer *renderer = pass->buffer->renderer;
 733 
 734 	if (renderer->shaders.quad_grad_round.max_len <= fx_options->gradient.count) {
 735 		glDeleteProgram(renderer->shaders.quad_grad_round.program);
 736 		if (!link_quad_grad_round_program(&renderer->shaders.quad_grad_round, fx_options->gradient.count + 1)) {
 737 			wlr_log(WLR_ERROR, "Could not link quad shader after updating max_len to %d. Aborting renderer", fx_options->gradient.count + 1);
 738 			abort();
 739 		}
 740 	}
 741 
 742 	struct wlr_box box;
 743 	struct wlr_buffer *wlr_buffer = pass->buffer->buffer;
 744 	wlr_render_rect_options_get_box(options, wlr_buffer, &box);
 745 
 746 	TRACY_BOTH_ZONES_START(renderer);
 747 	TRACY_ZONE_TEXT_f("Box (WxH, X, Y): %dx%d, %d, %d", box.width, box.height, box.x, box.y);
 748 	TRACY_ZONE_TEXT_f("Corners (TL, TR, BL, BR): %f, %f, %f, %f",
 749 			fx_options->corners.top_left,
 750 			fx_options->corners.top_right,
 751 			fx_options->corners.bottom_left,
 752 			fx_options->corners.bottom_right);
 753 	TRACY_ZONE_TEXT_f("Gradient:");
 754 	TRACY_ZONE_TEXT_f("\tNum Colors: %d", fx_options->gradient.count);
 755 	TRACY_ZONE_TEXT_f("\tBlend: %d", fx_options->gradient.blend);
 756 	TRACY_ZONE_TEXT_f("\tDegree: %f", fx_options->gradient.degree);
 757 	TRACY_ZONE_TEXT_f("\tType: %s",
 758 			fx_options->gradient.linear == 1 ? "Linear"
 759 			: fx_options->gradient.linear == 2 ? "Conic"
 760 			: "Unknown");
 761 	TRACY_ZONE_TEXT_f("\tOrigin: %fx%f",
 762 			fx_options->gradient.origin[0], fx_options->gradient.origin[1]);
 763 	TRACY_ZONE_TEXT_f("\tRange (WxH, X, Y): %dx%d, %d, %d",
 764 			fx_options->gradient.range.width, fx_options->gradient.range.height,
 765 			fx_options->gradient.range.x, fx_options->gradient.range.y);
 766 	// TODO: Display Colors (not really sure how it works without a scene example...)
 767 	push_fx_debug(renderer);
 768 
 769 	setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED);
 770 
 771 	struct quad_grad_round_shader shader = renderer->shaders.quad_grad_round;
 772 	glUseProgram(shader.program);
 773 
 774 	set_proj_matrix(shader.proj, pass->projection_matrix, &box);
 775 
 776 	glUniform2f(shader.size, box.width, box.height);
 777 	glUniform2f(shader.position, box.x, box.y);
 778 
 779 	glUniform4fv(shader.colors, fx_options->gradient.count, (GLfloat*)fx_options->gradient.colors);
 780 	glUniform1i(shader.count, fx_options->gradient.count);
 781 	glUniform2f(shader.grad_size, fx_options->gradient.range.width, fx_options->gradient.range.height);
 782 	glUniform1f(shader.degree, fx_options->gradient.degree);
 783 	glUniform1f(shader.linear, fx_options->gradient.linear);
 784 	glUniform1f(shader.blend, fx_options->gradient.blend);
 785 	glUniform2f(shader.grad_box, fx_options->gradient.range.x, fx_options->gradient.range.y);
 786 	glUniform2f(shader.origin, fx_options->gradient.origin[0], fx_options->gradient.origin[1]);
 787 
 788 	struct fx_corner_fradii corners = fx_options->corners;
 789 	uniform_corner_radii_set(&shader.radius, &corners);
 790 
 791 	render(&box, options->clip, shader.pos_attrib);
 792 
 793 	pop_fx_debug(renderer);
 794 	TRACY_BOTH_ZONES_END;
 795 }
 796 
 797 void fx_render_pass_add_box_shadow(struct fx_gles_render_pass *pass,
 798 		const struct fx_render_box_shadow_options *options) {
 799 	struct fx_renderer *renderer = pass->buffer->renderer;
 800 
 801 	struct wlr_box box = options->box;
 802 	assert(box.width > 0 && box.height > 0);
 803 
 804 	pixman_region32_t clip_region;
 805 	if (options->clip) {
 806 		pixman_region32_init(&clip_region);
 807 		pixman_region32_copy(&clip_region, options->clip);
 808 	} else {
 809 		pixman_region32_init_rect(&clip_region, box.x, box.y, box.width, box.height);
 810 	}
 811 	const struct wlr_box clipped_region_box = options->clipped_region.area;
 812 	struct fx_corner_fradii clipped_region_corners = options->clipped_region.corners;
 813 	apply_clip_region(&clip_region, &clipped_region_box, &clipped_region_corners);
 814 
 815 	TRACY_BOTH_ZONES_START(renderer);
 816 	TRACY_ZONE_TEXT_f("Box (WxH, X, Y): %dx%d, %d, %d", box.width, box.height, box.x, box.y);
 817 	TRACY_ZONE_TEXT_f("Clip Box (WxH, X, Y): %dx%d, %d, %d",
 818 			clipped_region_box.width, clipped_region_box.height,
 819 			clipped_region_box.x, clipped_region_box.y);
 820 	TRACY_ZONE_TEXT_f("Clip Box Corners (TL, TR, BL, BR): %f, %f, %f, %f",
 821 			clipped_region_corners.top_left,
 822 			clipped_region_corners.top_right,
 823 			clipped_region_corners.bottom_left,
 824 			clipped_region_corners.bottom_right);
 825 	TRACY_ZONE_TEXT_f("Shadow Options:");
 826 	TRACY_ZONE_TEXT_f("\tColor RGBA: %f, %f, %f, %f",
 827 			options->color.r, options->color.g, options->color.b, options->color.a);
 828 	TRACY_ZONE_TEXT_f("\tBlur Sigma: %f", options->blur_sigma);
 829 	push_fx_debug(renderer);
 830 
 831 	// blending will practically always be needed (unless we have a madman
 832 	// who uses opaque shadows with zero sigma), so just enable it
 833 	setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED);
 834 	glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
 835 
 836 	glUseProgram(renderer->shaders.box_shadow.program);
 837 
 838 	const struct wlr_render_color *color = &options->color;
 839 	set_proj_matrix(renderer->shaders.box_shadow.proj, pass->projection_matrix, &box);
 840 	glUniform4f(renderer->shaders.box_shadow.color, color->r, color->g, color->b, color->a);
 841 	glUniform1f(renderer->shaders.box_shadow.blur_sigma, options->blur_sigma);
 842 	glUniform2f(renderer->shaders.box_shadow.size, box.width, box.height);
 843 	glUniform2f(renderer->shaders.box_shadow.position, box.x, box.y);
 844 	glUniform1f(renderer->shaders.box_shadow.corner_radius, options->corner_radius);
 845 
 846 	uniform_corner_radii_set(&renderer->shaders.box_shadow.clip_radius, &clipped_region_corners);
 847 
 848 	glUniform2f(renderer->shaders.box_shadow.clip_position, clipped_region_box.x, clipped_region_box.y);
 849 	glUniform2f(renderer->shaders.box_shadow.clip_size, clipped_region_box.width, clipped_region_box.height);
 850 
 851 	render(&box, &clip_region, renderer->shaders.box_shadow.pos_attrib);
 852 	pixman_region32_fini(&clip_region);
 853 
 854 	glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
 855 
 856 	pop_fx_debug(renderer);
 857 	TRACY_BOTH_ZONES_END;
 858 }
 859 
 860 // Renders the blur for each damaged rect and swaps the buffer
 861 static void render_blur_segments(struct fx_gles_render_pass *pass,
 862 		struct fx_render_blur_pass_options *fx_options, struct blur_shader* shader) {
 863 	struct fx_render_texture_options *tex_options = &fx_options->tex_options;
 864 	struct wlr_render_texture_options *options = &tex_options->base;
 865 	struct fx_renderer *renderer = pass->buffer->renderer;
 866 	struct blur_data *blur_data = fx_options->blur_data;
 867 
 868 	TRACY_BOTH_ZONES_START(renderer);
 869 	push_fx_debug(renderer);
 870 
 871 	// Swap fbo
 872 	if (fx_options->current_buffer == pass->fx_offscreen_buffers->effects_buffer) {
 873 		fx_framebuffer_bind(pass->fx_offscreen_buffers->effects_buffer_swapped);
 874 	} else {
 875 		fx_framebuffer_bind(pass->fx_offscreen_buffers->effects_buffer);
 876 	}
 877 
 878 	options->texture = fx_texture_from_buffer(&renderer->wlr_renderer,
 879 			fx_options->current_buffer->buffer);
 880 	struct fx_texture *texture = fx_get_texture(options->texture);
 881 
 882 	/*
 883 	 * Render
 884 	 */
 885 
 886 	struct wlr_box dst_box;
 887 	struct wlr_fbox src_fbox;
 888 	wlr_render_texture_options_get_src_box(options, &src_fbox);
 889 	wlr_render_texture_options_get_dst_box(options, &dst_box);
 890 	src_fbox.x /= options->texture->width;
 891 	src_fbox.y /= options->texture->height;
 892 	src_fbox.width /= options->texture->width;
 893 	src_fbox.height /= options->texture->height;
 894 
 895 	glDisable(GL_BLEND);
 896 	glDisable(GL_STENCIL_TEST);
 897 
 898 	glUseProgram(shader->program);
 899 
 900 	glActiveTexture(GL_TEXTURE0);
 901 	glBindTexture(texture->target, texture->tex);
 902 
 903 	switch (options->filter_mode) {
 904 	case WLR_SCALE_FILTER_BILINEAR:
 905 		glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 906 		glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 907 		break;
 908 	case WLR_SCALE_FILTER_NEAREST:
 909 		abort();
 910 	}
 911 
 912 	glUniform1i(shader->tex, 0);
 913 	glUniform1f(shader->radius, blur_data->radius);
 914 
 915 	if (shader == &renderer->shaders.blur1) {
 916 		glUniform2f(shader->halfpixel,
 917 				0.5f / (options->texture->width / 2.0f),
 918 				0.5f / (options->texture->height / 2.0f));
 919 	} else {
 920 		glUniform2f(shader->halfpixel,
 921 				0.5f / (options->texture->width * 2.0f),
 922 				0.5f / (options->texture->height * 2.0f));
 923 	}
 924 
 925 	set_proj_matrix(shader->proj, pass->projection_matrix, &dst_box);
 926 	set_tex_matrix(shader->tex_proj, options->transform, &src_fbox);
 927 
 928 	render(&dst_box, options->clip, shader->pos_attrib);
 929 
 930 	glBindTexture(texture->target, 0);
 931 	pop_fx_debug(renderer);
 932 	TRACY_BOTH_ZONES_END;
 933 
 934 	wlr_texture_destroy(options->texture);
 935 
 936 	// Swap buffer. We don't want to draw to the same buffer
 937 	if (fx_options->current_buffer != pass->fx_offscreen_buffers->effects_buffer) {
 938 		fx_options->current_buffer = pass->fx_offscreen_buffers->effects_buffer;
 939 	} else {
 940 		fx_options->current_buffer = pass->fx_offscreen_buffers->effects_buffer_swapped;
 941 	}
 942 }
 943 
 944 static void render_blur_effects(struct fx_gles_render_pass *pass,
 945 		struct fx_render_blur_pass_options *fx_options) {
 946 	struct fx_render_texture_options *tex_options = &fx_options->tex_options;
 947 	struct wlr_render_texture_options *options = &tex_options->base;
 948 	struct fx_renderer *renderer = pass->buffer->renderer;
 949 	struct blur_data *blur_data = fx_options->blur_data;
 950 	struct fx_texture *texture = fx_get_texture(options->texture);
 951 
 952 	struct blur_effects_shader shader = renderer->shaders.blur_effects;
 953 
 954 	struct wlr_box dst_box;
 955 	struct wlr_fbox src_fbox;
 956 	wlr_render_texture_options_get_src_box(options, &src_fbox);
 957 	wlr_render_texture_options_get_dst_box(options, &dst_box);
 958 
 959 	src_fbox.x /= options->texture->width;
 960 	src_fbox.y /= options->texture->height;
 961 	src_fbox.width /= options->texture->width;
 962 	src_fbox.height /= options->texture->height;
 963 
 964 	glDisable(GL_BLEND);
 965 	glDisable(GL_STENCIL_TEST);
 966 
 967 	TRACY_BOTH_ZONES_START(renderer);
 968 	push_fx_debug(renderer);
 969 
 970 	glUseProgram(shader.program);
 971 
 972 	glActiveTexture(GL_TEXTURE0);
 973 	glBindTexture(texture->target, texture->tex);
 974 
 975 	switch (options->filter_mode) {
 976 	case WLR_SCALE_FILTER_BILINEAR:
 977 		glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
 978 		glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 979 		break;
 980 	case WLR_SCALE_FILTER_NEAREST:
 981 		abort();
 982 	}
 983 
 984 	glUniform1i(shader.tex, 0);
 985 	glUniform1f(shader.noise, blur_data->noise);
 986 	glUniform1f(shader.brightness, blur_data->brightness);
 987 	glUniform1f(shader.contrast, blur_data->contrast);
 988 	glUniform1f(shader.saturation, blur_data->saturation);
 989 
 990 	set_proj_matrix(shader.proj, pass->projection_matrix, &dst_box);
 991 	set_tex_matrix(shader.tex_proj, options->transform, &src_fbox);
 992 
 993 	render(&dst_box, options->clip, shader.pos_attrib);
 994 
 995 	glBindTexture(texture->target, 0);
 996 
 997 	pop_fx_debug(renderer);
 998 	TRACY_BOTH_ZONES_END;
 999 
1000 	wlr_texture_destroy(options->texture);
1001 }
1002 
1003 // Blurs the fx_options current_buffer content and returns the blurred framebuffer.
1004 // Returns NULL when the blur parameters reach 0.
1005 static struct fx_framebuffer *get_main_buffer_blur(struct fx_gles_render_pass *pass,
1006 		struct fx_render_blur_pass_options *fx_options) {
1007 	if (pass->fx_offscreen_buffers == NULL) {
1008 		wlr_log(WLR_ERROR, "FX Pass offscreen buffers not initialized. Skipping getting blur...");
1009 		return NULL;
1010 	}
1011 
1012 	struct fx_renderer *renderer = pass->buffer->renderer;
1013 	struct wlr_box buffer_bounds = {
1014 		0, 0,
1015 		fx_options->current_buffer->buffer->width, fx_options->current_buffer->buffer->height
1016 	};
1017 
1018 	// We don't want to affect the reference blur_data
1019 	struct blur_data blur_data = blur_data_apply_strength(fx_options->blur_data, fx_options->blur_strength);
1020 	if (fx_options->blur_strength <= 0 || !is_scene_blur_enabled(&blur_data)) {
1021 		return NULL;
1022 	}
1023 	fx_options->blur_data = &blur_data;
1024 
1025 	pixman_region32_t damage;
1026 	pixman_region32_init(&damage);
1027 	pixman_region32_copy(&damage, fx_options->tex_options.base.clip);
1028 	wlr_region_transform(&damage, &damage, fx_options->tex_options.base.transform,
1029 			buffer_bounds.width, buffer_bounds.height);
1030 
1031 	wlr_region_expand(&damage, &damage, blur_data_calc_size(&blur_data));
1032 	// Make sure that the region doesn't expand past the buffer bounds
1033 	pixman_region32_intersect_rect(&damage, &damage,
1034 			0, 0, buffer_bounds.width, buffer_bounds.height);
1035 
1036 	// damage region will be scaled, make a temp
1037 	pixman_region32_t scaled_damage;
1038 	pixman_region32_init(&scaled_damage);
1039 
1040 	fx_options->tex_options.base.src_box = (struct wlr_fbox) {
1041 		0, 0,
1042 		buffer_bounds.width, buffer_bounds.height,
1043 	};
1044 	fx_options->tex_options.base.dst_box = buffer_bounds;
1045 	// Clip the blur to the damage
1046 	fx_options->tex_options.base.clip = &scaled_damage;
1047 	// Artifacts with NEAREST filter
1048 	fx_options->tex_options.base.filter_mode = WLR_SCALE_FILTER_BILINEAR;
1049 
1050 	TRACY_BOTH_ZONES_START(renderer);
1051 	TRACY_ZONE_TEXT_f("dst_box (WxH, X, Y): %dx%d, %d, %d",
1052 			fx_options->tex_options.base.dst_box.width,
1053 			fx_options->tex_options.base.dst_box.height,
1054 			fx_options->tex_options.base.dst_box.x,
1055 			fx_options->tex_options.base.dst_box.y);
1056 	TRACY_ZONE_TEXT_f("clip_box (WxH, X, Y): %dx%d, %d, %d",
1057 			fx_options->tex_options.clip_box->width,
1058 			fx_options->tex_options.clip_box->height,
1059 			fx_options->tex_options.clip_box->x,
1060 			fx_options->tex_options.clip_box->y);
1061 	TRACY_ZONE_TEXT_f("Corners (TL, TR, BL, BR): %f, %f, %f, %f",
1062 			fx_options->corners.top_left,
1063 			fx_options->corners.top_right,
1064 			fx_options->corners.bottom_left,
1065 			fx_options->corners.bottom_right);
1066 	TRACY_ZONE_TEXT_f("src_box (WxH, X, Y): %lfx%lf, %lf, %lf",
1067 			fx_options->tex_options.base.src_box.width,
1068 			fx_options->tex_options.base.src_box.height,
1069 			fx_options->tex_options.base.src_box.x,
1070 			fx_options->tex_options.base.src_box.y);
1071 	TRACY_ZONE_TEXT_f("Ignore Transparent: %d", fx_options->ignore_transparent);
1072 	TRACY_ZONE_TEXT_f("Discard Transparent: %d", fx_options->tex_options.discard_transparent);
1073 	TRACY_ZONE_TEXT_f("Use Optimized Blur: %d", fx_options->use_optimized_blur);
1074 	TRACY_ZONE_TEXT_f("Blur Options:");
1075 	TRACY_ZONE_TEXT_f("\tNum Blur Passes: %d", fx_options->blur_data->num_passes);
1076 	TRACY_ZONE_TEXT_f("\tBlur Radius: %f", fx_options->blur_data->radius);
1077 	TRACY_ZONE_TEXT_f("\tBrightness: %f", fx_options->blur_data->brightness);
1078 	TRACY_ZONE_TEXT_f("\tContrast: %f", fx_options->blur_data->contrast);
1079 	TRACY_ZONE_TEXT_f("\tNoise: %f", fx_options->blur_data->noise);
1080 	TRACY_ZONE_TEXT_f("\tSaturation: %f", fx_options->blur_data->saturation);
1081 	push_fx_debug(renderer);
1082 
1083 	// Downscale
1084 	for (int i = 0; i < blur_data.num_passes; ++i) {
1085 		wlr_region_scale(&scaled_damage, &damage, 1.0f / (1 << (i + 1)));
1086 		render_blur_segments(pass, fx_options, &renderer->shaders.blur1);
1087 	}
1088 
1089 	// Upscale
1090 	for (int i = blur_data.num_passes - 1; i >= 0; --i) {
1091 		// when upsampling we make the region twice as big
1092 		wlr_region_scale(&scaled_damage, &damage, 1.0f / (1 << i));
1093 		render_blur_segments(pass, fx_options, &renderer->shaders.blur2);
1094 	}
1095 
1096 	pixman_region32_fini(&scaled_damage);
1097 
1098 	// Render additional blur effects like saturation, noise, contrast, etc...
1099 	if (blur_data_should_parameters_blur_effects(&blur_data)
1100 			&& pixman_region32_not_empty(&damage)) {
1101 		if (fx_options->current_buffer == pass->fx_offscreen_buffers->effects_buffer) {
1102 			fx_framebuffer_bind(pass->fx_offscreen_buffers->effects_buffer_swapped);
1103 		} else {
1104 			fx_framebuffer_bind(pass->fx_offscreen_buffers->effects_buffer);
1105 		}
1106 		fx_options->tex_options.base.clip = &damage;
1107 		fx_options->tex_options.base.texture = fx_texture_from_buffer(
1108 				&renderer->wlr_renderer, fx_options->current_buffer->buffer);
1109 		render_blur_effects(pass, fx_options);
1110 		if (fx_options->current_buffer != pass->fx_offscreen_buffers->effects_buffer) {
1111 			fx_options->current_buffer = pass->fx_offscreen_buffers->effects_buffer;
1112 		} else {
1113 			fx_options->current_buffer = pass->fx_offscreen_buffers->effects_buffer_swapped;
1114 		}
1115 	}
1116 
1117 	pixman_region32_fini(&damage);
1118 
1119 	// Bind back to the default buffer
1120 	fx_framebuffer_bind(pass->buffer);
1121 
1122 	pop_fx_debug(renderer);
1123 	TRACY_BOTH_ZONES_END;
1124 
1125 	return fx_options->current_buffer;
1126 }
1127 
1128 void fx_render_pass_add_frame(struct fx_gles_render_pass *pass,
1129 		const struct fx_render_frame_options *options) {
1130 	struct fx_renderer *renderer = pass->buffer->renderer;
1131 
1132 	struct wlr_box box = options->box;
1133 	assert(box.width > 0 && box.height > 0);
1134 
1135 	pixman_region32_t clip_region;
1136 	if (options->clip) {
1137 		pixman_region32_init(&clip_region);
1138 		pixman_region32_copy(&clip_region, options->clip);
1139 	} else {
1140 		pixman_region32_init_rect(&clip_region, box.x, box.y, box.width, box.height);
1141 	}
1142 
1143 	push_fx_debug(renderer);
1144 
1145 	// Premultiplied source-over: the shader emits colour already scaled by
1146 	// its coverage, the same convention the bevel pass settled on.
1147 	setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED);
1148 	glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
1149 
1150 	glUseProgram(renderer->shaders.frame.program);
1151 
1152 	const struct wlr_render_color *color = &options->color;
1153 	set_proj_matrix(renderer->shaders.frame.proj, pass->projection_matrix, &box);
1154 	glUniform4f(renderer->shaders.frame.color, color->r, color->g, color->b, color->a);
1155 	glUniform2f(renderer->shaders.frame.size, box.width, box.height);
1156 	glUniform2f(renderer->shaders.frame.position, box.x, box.y);
1157 	glUniform1f(renderer->shaders.frame.corner_radius, options->corner_radius);
1158 	glUniform1f(renderer->shaders.frame.corner_shape, fx_corner_shape());
1159 	glUniform1f(renderer->shaders.frame.band, options->band);
1160 	glUniform1f(renderer->shaders.frame.band_min, options->band_min);
1161 	glUniform1f(renderer->shaders.frame.corner_len, options->corner_len);
1162 	glUniform1f(renderer->shaders.frame.gap, options->gap);
1163 	glUniform1f(renderer->shaders.frame.hovered, options->hovered);
1164 	glUniform1f(renderer->shaders.frame.swell_curve, options->swell_curve);
1165 	glUniform1f(renderer->shaders.frame.bulge, options->bulge);
1166 	glUniform4f(renderer->shaders.frame.exclusion, options->exclusion[0],
1167 			options->exclusion[1], options->exclusion[2], options->exclusion[3]);
1168 	glUniform4f(renderer->shaders.frame.hover_color,
1169 			options->hover_color[0], options->hover_color[1],
1170 			options->hover_color[2], options->hover_color[3]);
1171 
1172 	render(&box, &clip_region, renderer->shaders.frame.pos_attrib);
1173 	pixman_region32_fini(&clip_region);
1174 
1175 	pop_fx_debug(renderer);
1176 }
1177 
1178 void fx_render_pass_add_bevel(struct fx_gles_render_pass *pass,
1179 		const struct fx_render_bevel_options *options) {
1180 	struct fx_renderer *renderer = pass->buffer->renderer;
1181 
1182 	struct wlr_box box = options->box;
1183 	assert(box.width > 0 && box.height > 0);
1184 
1185 	pixman_region32_t clip_region;
1186 	if (options->clip) {
1187 		pixman_region32_init(&clip_region);
1188 		pixman_region32_copy(&clip_region, options->clip);
1189 	} else {
1190 		pixman_region32_init_rect(&clip_region, box.x, box.y, box.width, box.height);
1191 	}
1192 
1193 	push_fx_debug(renderer);
1194 
1195 	// The highlight adds light and the shade subtracts it, both premultiplied
1196 	// into the same draw — ordinary source-over blending. The src factor is
1197 	// GL_ONE because the shader emits premultiplied colour: with GL_SRC_ALPHA
1198 	// the blend multiplied by alpha a SECOND time, squaring the rim's
1199 	// intensity, and left no way for a branch to composite additively (alpha
1200 	// 0 zeroed the whole contribution instead of adding it).
1201 	setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED);
1202 	glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
1203 
1204 	glUseProgram(renderer->shaders.bevel.program);
1205 
1206 	const struct wlr_render_color *color = &options->color;
1207 	set_proj_matrix(renderer->shaders.bevel.proj, pass->projection_matrix, &box);
1208 	glUniform4f(renderer->shaders.bevel.color, color->r, color->g, color->b, color->a);
1209 	glUniform2f(renderer->shaders.bevel.size, box.width, box.height);
1210 	glUniform2f(renderer->shaders.bevel.position, box.x, box.y);
1211 	glUniform1f(renderer->shaders.bevel.corner_radius, options->corner_radius);
1212 	// The radius the compositor sends is span-widened for this exponent, so
1213 	// the rim has to read it as the same superellipse the other cuts do.
1214 	glUniform1f(renderer->shaders.bevel.corner_shape, fx_corner_shape());
1215 	glUniform1f(renderer->shaders.bevel.thickness, options->thickness);
1216 	glUniform2f(renderer->shaders.bevel.light_dir,
1217 			options->light_dir[0], options->light_dir[1]);
1218 	glUniform1f(renderer->shaders.bevel.light_intensity, options->light_intensity);
1219 	glUniform1f(renderer->shaders.bevel.shade_intensity, options->shade_intensity);
1220 	glUniform1f(renderer->shaders.bevel.shoulder, options->shoulder);
1221 	glUniform1f(renderer->shaders.bevel.focus, options->focus);
1222 	glUniform3f(renderer->shaders.bevel.focus_color,
1223 			options->focus_color[0], options->focus_color[1], options->focus_color[2]);
1224 	glUniform1f(renderer->shaders.bevel.focus_sharpness, options->focus_sharpness);
1225 
1226 	render(&box, &clip_region, renderer->shaders.bevel.pos_attrib);
1227 	pixman_region32_fini(&clip_region);
1228 
1229 	glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
1230 
1231 	pop_fx_debug(renderer);
1232 }
1233 
1234 void fx_render_pass_add_droplet(struct fx_gles_render_pass *pass,
1235 		const struct fx_render_droplet_options *options) {
1236 	struct fx_renderer *renderer = pass->buffer->renderer;
1237 
1238 	struct wlr_box box = options->box;
1239 	assert(box.width > 0 && box.height > 0);
1240 
1241 	// The lens samples the FRAME-SO-FAR — everything already rendered below
1242 	// this node (desktop, windows, all of it), copied 1:1 into the saved-
1243 	// pixels buffer just before the draw, because GL cannot sample the
1244 	// framebuffer it is rendering into. The optimized-blur bake is NOT used:
1245 	// it captures bottom layers only (that's the DE's frost design), so a
1246 	// drop over a window would refract the desktop hiding behind that
1247 	// window instead of the window itself. Without offscreen buffers there
1248 	// is nothing to refract; the client's own translucent drop still
1249 	// renders, so skipping is a graceful degrade.
1250 	if (pass->fx_offscreen_buffers == NULL) {
1251 		return;
1252 	}
1253 	struct fx_framebuffer *snap = pass->fx_offscreen_buffers->blur_saved_pixels_buffer;
1254 	if (snap == NULL || snap->buffer == NULL) {
1255 		return;
1256 	}
1257 
1258 	// Copy the drop's neighborhood: the box grown by the refraction reach
1259 	// and the inverted ghost's sampling span (0.35 x the box about its
1260 	// center), clamped to the buffer.
1261 	int margin = (int)(options->refr + 1.0f
1262 			+ 0.35f * (float)(box.width > box.height ? box.width : box.height));
1263 	pixman_region32_t copy_region;
1264 	pixman_region32_init_rect(&copy_region,
1265 			box.x - margin, box.y - margin,
1266 			box.width + 2 * margin, box.height + 2 * margin);
1267 	pixman_region32_intersect_rect(&copy_region, &copy_region,
1268 			0, 0, pass->buffer->buffer->width, pass->buffer->buffer->height);
1269 	fx_render_pass_read_to_buffer(pass, &copy_region, snap, pass->buffer);
1270 	pixman_region32_fini(&copy_region);
1271 
1272 	struct wlr_texture *wlr_texture =
1273 		fx_texture_from_buffer(&renderer->wlr_renderer, snap->buffer);
1274 	if (wlr_texture == NULL) {
1275 		return;
1276 	}
1277 	struct fx_texture *texture = fx_get_texture(wlr_texture);
1278 
1279 	pixman_region32_t clip_region;
1280 	if (options->clip) {
1281 		pixman_region32_init(&clip_region);
1282 		pixman_region32_copy(&clip_region, options->clip);
1283 	} else {
1284 		pixman_region32_init_rect(&clip_region, box.x, box.y, box.width, box.height);
1285 	}
1286 
1287 	push_fx_debug(renderer);
1288 
1289 	// The refracted backdrop is opaque inside the silhouette (it REPLACES
1290 	// what the drop covers); the feathered rim blends source-over.
1291 	setup_blending(WLR_RENDER_BLEND_MODE_PREMULTIPLIED);
1292 
1293 	struct droplet_shader *shader = &renderer->shaders.droplet;
1294 	glUseProgram(shader->program);
1295 
1296 	glActiveTexture(GL_TEXTURE0);
1297 	glBindTexture(texture->target, texture->tex);
1298 	glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1299 	glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1300 
1301 	set_proj_matrix(shader->proj, pass->projection_matrix, &box);
1302 	glUniform1i(shader->tex, 0);
1303 	glUniform2f(shader->tex_size, snap->buffer->width, snap->buffer->height);
1304 	glUniform2f(shader->position, box.x, box.y);
1305 	glUniform2f(shader->size, box.width, box.height);
1306 	glUniform1f(shader->attach_r, options->attach_r);
1307 	glUniform1f(shader->sheet_r, options->sheet_r);
1308 	glUniform1f(shader->bow_rise, options->bow_rise);
1309 	glUniform1f(shader->blend_k, options->blend_k);
1310 	glUniform1f(shader->curve, options->curve);
1311 	glUniform1f(shader->band_px, options->band_px);
1312 	glUniform1f(shader->refr, options->refr);
1313 	glUniform1f(shader->ghost, options->ghost);
1314 
1315 	render(&box, &clip_region, shader->pos_attrib);
1316 
1317 	glBindTexture(texture->target, 0);
1318 	pixman_region32_fini(&clip_region);
1319 	wlr_texture_destroy(wlr_texture);
1320 
1321 	pop_fx_debug(renderer);
1322 }
1323 
1324 void fx_render_pass_add_blur(struct fx_gles_render_pass *pass,
1325 		struct fx_render_blur_pass_options *fx_options) {
1326 	if (pass->fx_offscreen_buffers == NULL) {
1327 		wlr_log(WLR_ERROR, "FX Pass offscreen buffers not initialized. Skipping blur...");
1328 		return;
1329 	}
1330 
1331 	struct fx_renderer *renderer = pass->buffer->renderer;
1332 	struct fx_render_texture_options *tex_options = &fx_options->tex_options;
1333 
1334 	TRACY_BOTH_ZONES_START(renderer);
1335 	push_fx_debug(renderer);
1336 
1337 	// NOTE: `optimized` here is the flag saying this node SAMPLES the cached
1338 	// buffer — it is not a re-bake. The bake is add_optimized_blur, below.
1339 	if (cce_blur_debug()) {
1340 		wlr_log(WLR_INFO, "[scenefx] add_blur dst_box: %dx%d at (%d, %d), optimized: %d strength: %f",
1341 				tex_options->base.dst_box.width, tex_options->base.dst_box.height,
1342 				tex_options->base.dst_box.x, tex_options->base.dst_box.y,
1343 				fx_options->use_optimized_blur, fx_options->blur_strength);
1344 	}
1345 
1346 	const bool has_strength = fx_options->blur_strength < 1.0;
1347 	struct fx_framebuffer *buffer = pass->fx_offscreen_buffers->optimized_blur_buffer;
1348 	TRACY_ZONE_TEXT_f("Use Optimized Blur: %d", fx_options->use_optimized_blur);
1349 	TRACY_ZONE_TEXT_f("Optimized Blur Successfully Used: %d",
1350 			buffer && fx_options->use_optimized_blur);
1351 	if (!fx_options->use_optimized_blur || has_strength) {
1352 		// Render the blur into its own buffer (a reduced-strength blur of
1353 		// an optimized node too: the cache holds only the full-strength
1354 		// bake, at its node's anchor).
1355 		struct fx_render_blur_pass_options blur_options = *fx_options;
1356 		blur_options.current_buffer = pass->buffer;
1357 		buffer = get_main_buffer_blur(pass, &blur_options);
1358 	}
1359 	if (!buffer) {
1360 		goto finish;
1361 	}
1362 	struct wlr_texture *wlr_texture =
1363 		fx_texture_from_buffer(&renderer->wlr_renderer, buffer->buffer);
1364 	struct fx_texture *blur_texture = fx_get_texture(wlr_texture);
1365 
1366 	// Get a stencil of the window ignoring transparent regions
1367 	if (fx_options->ignore_transparent && fx_options->tex_options.base.texture) {
1368 		stencil_mask_init();
1369 
1370 		struct fx_render_texture_options tex_options = fx_options->tex_options;
1371 		tex_options.discard_transparent = true;
1372 		tex_options.clipped_region = fx_options->clipped_region;
1373 		fx_render_pass_add_texture(pass, &tex_options);
1374 
1375 		stencil_mask_close(true);
1376 	}
1377 
1378 	// Draw the blurred texture. The shared cache (optimized, full strength)
1379 	// is drawn so that screen pixel s reads cache pixel s + shift — the
1380 	// node's bake at its anchor, inside the margin; a freshly blurred
1381 	// buffer is output-sized and in place.
1382 	const bool direct_cache = fx_options->use_optimized_blur && !has_strength;
1383 	tex_options->base.dst_box = (struct wlr_box) {
1384 		.x = direct_cache ? -fx_options->cache_shift_x : 0,
1385 		.y = direct_cache ? -fx_options->cache_shift_y : 0,
1386 		.width = buffer->buffer->width,
1387 		.height = buffer->buffer->height,
1388 	};
1389 	tex_options->base.src_box = (struct wlr_fbox) {
1390 		.x = 0,
1391 		.y = 0,
1392 		.width = buffer->buffer->width,
1393 		.height = buffer->buffer->height,
1394 	};
1395 	tex_options->base.texture = &blur_texture->wlr_texture;
1396 	// since we're capturing from the fbo, transform will always be normal
1397 	tex_options->base.transform = WL_OUTPUT_TRANSFORM_NORMAL;
1398 	tex_options->clipped_region = fx_options->clipped_region;
1399 	fx_render_pass_add_texture(pass, tex_options);
1400 
1401 	wlr_texture_destroy(&blur_texture->wlr_texture);
1402 
1403 	// Finish stenciling
1404 	if (fx_options->ignore_transparent && fx_options->tex_options.base.texture) {
1405 		stencil_mask_fini();
1406 	}
1407 
1408 finish:
1409 	pop_fx_debug(renderer);
1410 	TRACY_BOTH_ZONES_END;
1411 }
1412 
1413 bool fx_render_pass_add_optimized_blur(struct fx_gles_render_pass *pass,
1414 		struct fx_render_blur_pass_options *fx_options) {
1415 	if (pass->fx_offscreen_buffers == NULL) {
1416 		wlr_log(WLR_ERROR, "FX Pass offscreen buffers not initialized. Skipping optimized blur...");
1417 		return false;
1418 	}
1419 	struct fx_renderer *renderer = pass->buffer->renderer;
1420 	struct wlr_box dst_box = fx_options->tex_options.base.dst_box;
1421 
1422 	TRACY_BOTH_ZONES_START(renderer);
1423 	TRACY_ZONE_TEXT_f("dst_box (WxH, X, Y): %dx%d, %d, %d",
1424 			fx_options->tex_options.base.dst_box.width,
1425 			fx_options->tex_options.base.dst_box.height,
1426 			fx_options->tex_options.base.dst_box.x,
1427 			fx_options->tex_options.base.dst_box.y);
1428 	TRACY_ZONE_TEXT_f("clip_box (WxH, X, Y): %dx%d, %d, %d",
1429 			fx_options->tex_options.clip_box->width,
1430 			fx_options->tex_options.clip_box->height,
1431 			fx_options->tex_options.clip_box->x,
1432 			fx_options->tex_options.clip_box->y);
1433 	TRACY_ZONE_TEXT_f("src_box (WxH, X, Y): %lfx%lf, %lf, %lf",
1434 			fx_options->tex_options.base.src_box.width,
1435 			fx_options->tex_options.base.src_box.height,
1436 			fx_options->tex_options.base.src_box.x,
1437 			fx_options->tex_options.base.src_box.y);
1438 	TRACY_ZONE_TEXT_f("Ignore Transparent: %d", fx_options->ignore_transparent);
1439 	TRACY_ZONE_TEXT_f("Discard Transparent: %d", fx_options->tex_options.discard_transparent);
1440 	TRACY_ZONE_TEXT_f("Use Optimized Blur: %d", fx_options->use_optimized_blur);
1441 	TRACY_ZONE_TEXT_f("Blur Options:");
1442 	TRACY_ZONE_TEXT_f("\tNum Blur Passes: %d", fx_options->blur_data->num_passes);
1443 	TRACY_ZONE_TEXT_f("\tBlur Radius: %f", fx_options->blur_data->radius);
1444 	TRACY_ZONE_TEXT_f("\tBrightness: %f", fx_options->blur_data->brightness);
1445 	TRACY_ZONE_TEXT_f("\tContrast: %f", fx_options->blur_data->contrast);
1446 	TRACY_ZONE_TEXT_f("\tNoise: %f", fx_options->blur_data->noise);
1447 	TRACY_ZONE_TEXT_f("\tSaturation: %f", fx_options->blur_data->saturation);
1448 	push_fx_debug(renderer);
1449 
1450 	// The actual cache bake (a whole node, or a strip it newly exposes).
1451 	if (cce_blur_debug()) {
1452 		wlr_log(WLR_INFO, "[scenefx] add_optimized_blur dst_box: %dx%d at (%d, %d) shift (%d, %d)",
1453 				dst_box.width, dst_box.height, dst_box.x, dst_box.y,
1454 				fx_options->cache_shift_x, fx_options->cache_shift_y);
1455 	}
1456 
1457 	pixman_region32_t clip;
1458 	pixman_region32_init_rect(&clip,
1459 			dst_box.x, dst_box.y, dst_box.width, dst_box.height);
1460 
1461 	// Render the blur into its own buffer
1462 	struct fx_render_blur_pass_options blur_options = *fx_options;
1463 	blur_options.current_buffer = pass->buffer;
1464 	blur_options.tex_options.base.clip = &clip;
1465 	struct fx_framebuffer *fx_buffer = get_main_buffer_blur(pass, &blur_options);
1466 	if (fx_buffer != NULL) {
1467 		// Land the newly blurred content in the cache at the node's anchor.
1468 		read_to_buffer_offset(pass, &clip,
1469 				pass->fx_offscreen_buffers->optimized_blur_buffer, fx_buffer,
1470 				fx_options->cache_shift_x, fx_options->cache_shift_y);
1471 	}
1472 
1473 	pixman_region32_fini(&clip);
1474 
1475 	pop_fx_debug(renderer);
1476 	TRACY_BOTH_ZONES_END;
1477 	return fx_buffer != NULL;
1478 }
1479 
1480 /* Copy `_region` of src_buffer into dst_buffer, landing at (+ox, +oy). */
1481 static void read_to_buffer_offset(struct fx_gles_render_pass *pass,
1482 		pixman_region32_t *_region, struct fx_framebuffer *dst_buffer,
1483 		struct fx_framebuffer *src_buffer, int ox, int oy) {
1484 	if (!_region || !pixman_region32_not_empty(_region)) {
1485 		return;
1486 	}
1487 	TRACY_BOTH_ZONES_START(pass->buffer->renderer);
1488 
1489 	pixman_region32_t region;
1490 	pixman_region32_init(&region);
1491 	pixman_region32_copy(&region, _region);
1492 	pixman_region32_translate(&region, ox, oy);
1493 
1494 	struct wlr_texture *src_tex =
1495 		fx_texture_from_buffer(&pass->buffer->renderer->wlr_renderer, src_buffer->buffer);
1496 	if (src_tex == NULL) {
1497 		goto done;
1498 	}
1499 
1500 	// Draw onto the dst_buffer. The pass's projection and viewport are the
1501 	// output's; a larger destination (the margin cache) needs its own for
1502 	// the duration of the copy, or everything past the output's extent is
1503 	// clipped away.
1504 	const int dst_w = dst_buffer->buffer->width;
1505 	const int dst_h = dst_buffer->buffer->height;
1506 	const int pass_w = pass->buffer->buffer->width;
1507 	const int pass_h = pass->buffer->buffer->height;
1508 	const bool resize = dst_w != pass_w || dst_h != pass_h;
1509 	float saved_proj[9];
1510 	if (resize) {
1511 		memcpy(saved_proj, pass->projection_matrix, sizeof(saved_proj));
1512 		matrix_projection(pass->projection_matrix, dst_w, dst_h, WL_OUTPUT_TRANSFORM_FLIPPED_180);
1513 	}
1514 	fx_framebuffer_bind(dst_buffer);
1515 	if (resize) {
1516 		glViewport(0, 0, dst_w, dst_h);
1517 	}
1518 	wlr_render_pass_add_texture(&pass->base, &(struct wlr_render_texture_options) {
1519 		.texture = src_tex,
1520 		.clip = &region,
1521 		.transform = WL_OUTPUT_TRANSFORM_NORMAL,
1522 		.blend_mode = WLR_RENDER_BLEND_MODE_NONE,
1523 		.dst_box = (struct wlr_box){
1524 			.x = ox,
1525 			.y = oy,
1526 			.width = src_buffer->buffer->width,
1527 			.height = src_buffer->buffer->height,
1528 		},
1529 		.src_box = (struct wlr_fbox){
1530 			.x = 0,
1531 			.y = 0,
1532 			.width = src_buffer->buffer->width,
1533 			.height = src_buffer->buffer->height,
1534 		},
1535 	});
1536 	wlr_texture_destroy(src_tex);
1537 
1538 	// Bind back to the main WLR buffer
1539 	fx_framebuffer_bind(pass->buffer);
1540 	if (resize) {
1541 		memcpy(pass->projection_matrix, saved_proj, sizeof(saved_proj));
1542 		glViewport(0, 0, pass_w, pass_h);
1543 	}
1544 
1545 done:
1546 	TRACY_BOTH_ZONES_END;
1547 
1548 	pixman_region32_fini(&region);
1549 }
1550 
1551 void fx_render_pass_read_to_buffer(struct fx_gles_render_pass *pass,
1552 		pixman_region32_t *_region, struct fx_framebuffer *dst_buffer,
1553 		struct fx_framebuffer *src_buffer) {
1554 	read_to_buffer_offset(pass, _region, dst_buffer, src_buffer, 0, 0);
1555 }
1556 
1557 static const char *reset_status_str(GLenum status) {
1558 	switch (status) {
1559 	case GL_GUILTY_CONTEXT_RESET_KHR:
1560 		return "guilty";
1561 	case GL_INNOCENT_CONTEXT_RESET_KHR:
1562 		return "innocent";
1563 	case GL_UNKNOWN_CONTEXT_RESET_KHR:
1564 		return "unknown";
1565 	default:
1566 		return "<invalid>";
1567 	}
1568 }
1569 
1570 struct fx_gles_render_pass *fx_begin_buffer_pass(struct fx_framebuffer *buffer,
1571 		struct wlr_egl_context *prev_ctx, struct fx_render_timer *timer,
1572 		struct wlr_drm_syncobj_timeline *signal_timeline, uint64_t signal_point) {
1573 	struct fx_renderer *renderer = buffer->renderer;
1574 	struct wlr_buffer *wlr_buffer = buffer->buffer;
1575 
1576 	if (renderer->procs.glGetGraphicsResetStatusKHR) {
1577 		GLenum status = renderer->procs.glGetGraphicsResetStatusKHR();
1578 		if (status != GL_NO_ERROR) {
1579 			wlr_log(WLR_ERROR, "GPU reset (%s)", reset_status_str(status));
1580 			wl_signal_emit_mutable(&renderer->wlr_renderer.events.lost, NULL);
1581 			return NULL;
1582 		}
1583 	}
1584 
1585 	GLint fbo = fx_framebuffer_get_fbo(buffer);
1586 	if (!fbo) {
1587 		return NULL;
1588 	}
1589 
1590 	struct fx_gles_render_pass *pass = calloc(1, sizeof(*pass));
1591 	if (pass == NULL) {
1592 		return NULL;
1593 	}
1594 
1595 	wlr_render_pass_init(&pass->base, &render_pass_impl);
1596 	wlr_buffer_lock(wlr_buffer);
1597 	pass->buffer = buffer;
1598 	pass->timer = timer;
1599 	pass->prev_ctx = *prev_ctx;
1600 	if (signal_timeline != NULL) {
1601 		pass->signal_timeline = wlr_drm_syncobj_timeline_ref(signal_timeline);
1602 		pass->signal_point = signal_point;
1603 	}
1604 
1605 	pass->fx_offscreen_buffers = NULL;
1606 	pixman_region32_init(&pass->blur_padding_region);
1607 	pass->has_blur = false;
1608 
1609 	matrix_projection(pass->projection_matrix, wlr_buffer->width, wlr_buffer->height,
1610 		WL_OUTPUT_TRANSFORM_FLIPPED_180);
1611 
1612 	push_fx_debug(renderer);
1613 	glBindFramebuffer(GL_FRAMEBUFFER, fbo);
1614 
1615 	glViewport(0, 0, wlr_buffer->width, wlr_buffer->height);
1616 	glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
1617 	glDisable(GL_SCISSOR_TEST);
1618 
1619 	pop_fx_debug(renderer);
1620 	return pass;
1621 }