diff --git a/src/backend/backend.h b/src/backend/backend.h index 53ff831ee9..139a306766 100644 --- a/src/backend/backend.h +++ b/src/backend/backend.h @@ -128,13 +128,13 @@ struct backend_blit_args { /// Scale factor for the horizontal and vertical direction (X for horizontal, /// Y for vertical). vec2 scale; - /// Corner radius of the source image. The corners of + /// Corner radius of the source image BEFORE scaling. The corners of /// the source image will be rounded. double corner_radius; - /// Effective size of the source image, set where the corners + /// Effective size of the source image BEFORE scaling, set where the corners /// of the image are. ivec2 effective_size; - /// Border width of the source image. This is used with + /// Border width of the source image BEFORE scaling. This is used with /// `corner_radius` to create a border for the rounded corners. /// Setting this has no effect if `corner_radius` is 0. int border_width; diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index ca1883edb1..4096923d20 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -302,8 +302,8 @@ static bool xrender_blit(struct backend_base *base, ivec2 origin, bool has_alpha = inner->has_alpha || args->opacity != 1; auto const tmpw = to_u16_checked(inner->size.width); auto const tmph = to_u16_checked(inner->size.height); - auto const tmpew = to_u16_checked(args->effective_size.width); - auto const tmpeh = to_u16_checked(args->effective_size.height); + auto const tmpew = to_u16_saturated(args->effective_size.width * args->scale.x); + auto const tmpeh = to_u16_saturated(args->effective_size.height * args->scale.y); const xcb_render_color_t dim_color = { .red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * args->dim)}; @@ -346,8 +346,25 @@ static bool xrender_blit(struct backend_base *base, ivec2 origin, auto tmp_pict = x_create_picture_with_pictfmt( xd->base.c, inner->size.width, inner->size.height, pictfmt, depth, 0, NULL); - x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-origin.x), - to_i16_checked(-origin.y), args->target_mask); + vec2 inverse_scale = (vec2){ + .x = 1.0 / args->scale.x, + .y = 1.0 / args->scale.y, + }; + if (vec2_eq(args->scale, SCALE_IDENTITY)) { + x_set_picture_clip_region( + xd->base.c, tmp_pict, to_i16_checked(-origin.x), + to_i16_checked(-origin.y), args->target_mask); + } else { + // We need to scale the target_mask back so it's in the source's + // coordinate space. + scoped_region_t source_mask_region; + pixman_region32_init(&source_mask_region); + pixman_region32_copy(&source_mask_region, args->target_mask); + region_scale_ceil(&source_mask_region, origin, inverse_scale); + x_set_picture_clip_region( + xd->base.c, tmp_pict, to_i16_checked(-origin.x), + to_i16_checked(-origin.y), &source_mask_region); + } // Copy source -> tmp xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); @@ -398,6 +415,10 @@ static bool xrender_blit(struct backend_base *base, ivec2 origin, } set_picture_scale(xd->base.c, tmp_pict, args->scale); + // Transformations don't affect the picture's clip region, so we need to + // set it again + x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-origin.x), + to_i16_checked(-origin.y), args->target_mask); xcb_render_composite(xd->base.c->c, XCB_RENDER_PICT_OP_OVER, tmp_pict, mask_pict, target->pict, 0, 0, mask_pict_dst_x, diff --git a/src/renderer/command_builder.c b/src/renderer/command_builder.c index 9db53a57df..945b99b52b 100644 --- a/src/renderer/command_builder.c +++ b/src/renderer/command_builder.c @@ -96,6 +96,8 @@ command_for_shadow(struct layer *layer, struct backend_command *cmd, const struct win_option *wintype_options, const struct x_monitors *monitors, const struct backend_command *end) { auto w = layer->win; + auto shadow_size_scaled = vec2_as( + vec2_floor(vec2_scale(ivec2_as(layer->shadow.size), layer->shadow_scale))); if (!w->shadow) { return 0; } @@ -105,8 +107,8 @@ command_for_shadow(struct layer *layer, struct backend_command *cmd, pixman_region32_clear(&cmd->target_mask); pixman_region32_union_rect(&cmd->target_mask, &cmd->target_mask, layer->shadow.origin.x, layer->shadow.origin.y, - (unsigned)layer->shadow.size.width, - (unsigned)layer->shadow.size.height); + (unsigned)shadow_size_scaled.width, + (unsigned)shadow_size_scaled.height); log_trace("Calculate shadow for %#010x (%s)", w->base.id, w->name); log_region(TRACE, &cmd->target_mask); if (!wintype_options[w->window_type].full_shadow) { diff --git a/src/renderer/layout.c b/src/renderer/layout.c index 1083935b46..fed6e8560e 100644 --- a/src/renderer/layout.c +++ b/src/renderer/layout.c @@ -49,8 +49,7 @@ static bool layer_from_window(struct layer *out_layer, struct managed_win *w, iv out_layer->window.origin = vec2_as((vec2){.x = w->g.x + win_animatable_get(w, WIN_SCRIPT_OFFSET_X), .y = w->g.y + win_animatable_get(w, WIN_SCRIPT_OFFSET_Y)}); - out_layer->window.size = vec2_as((vec2){.width = w->widthb * out_layer->scale.x, - .height = w->heightb * out_layer->scale.y}); + out_layer->window.size = vec2_as((vec2){.width = w->widthb, .height = w->heightb}); out_layer->crop.origin = vec2_as((vec2){ .x = win_animatable_get(w, WIN_SCRIPT_CROP_X), .y = win_animatable_get(w, WIN_SCRIPT_CROP_Y), @@ -70,16 +69,19 @@ static bool layer_from_window(struct layer *out_layer, struct managed_win *w, iv .y = w->g.y + w->shadow_dy + win_animatable_get(w, WIN_SCRIPT_SHADOW_OFFSET_Y)}); out_layer->shadow.size = - vec2_as((vec2){.width = w->shadow_width * out_layer->shadow_scale.x, - .height = w->shadow_height * out_layer->shadow_scale.y}); + vec2_as((vec2){.width = w->shadow_width, .height = w->shadow_height}); } else { out_layer->shadow.origin = (ivec2){}; out_layer->shadow.size = (ivec2){}; out_layer->shadow_scale = SCALE_IDENTITY; } + struct ibox window_scaled = { + .origin = out_layer->window.origin, + .size = ivec2_scale_floor(out_layer->window.size, out_layer->scale), + }; struct ibox screen = {.origin = {0, 0}, .size = size}; - if (!ibox_overlap(out_layer->window, screen) || !ibox_overlap(out_layer->crop, screen)) { + if (!ibox_overlap(window_scaled, screen) || !ibox_overlap(out_layer->crop, screen)) { goto out; } diff --git a/src/renderer/layout.h b/src/renderer/layout.h index c0295cd679..00fdaa15f9 100644 --- a/src/renderer/layout.h +++ b/src/renderer/layout.h @@ -29,9 +29,9 @@ struct layer { struct managed_win *win; /// Damaged region of this layer, in screen coordinates region_t damaged; - /// Window rectangle in screen coordinates. + /// Window rectangle in screen coordinates, before it's scaled. struct ibox window; - /// Shadow rectangle in screen coordinates. + /// Shadow rectangle in screen coordinates, before it's scaled. struct ibox shadow; /// Scale of the window. The origin of scaling is the top left corner of the /// window. diff --git a/src/types.h b/src/types.h index 3e8f783c4f..1566aa8ea8 100644 --- a/src/types.h +++ b/src/types.h @@ -62,6 +62,13 @@ struct ibox { static const vec2 SCALE_IDENTITY = {1.0, 1.0}; +static inline vec2 ivec2_as(ivec2 a) { + return (vec2){ + .x = a.x, + .y = a.y, + }; +} + static inline ivec2 ivec2_add(ivec2 a, ivec2 b) { return (ivec2){ .x = a.x + b.x, @@ -95,6 +102,27 @@ static inline ivec2 vec2_as(vec2 a) { }; } +static inline vec2 vec2_add(vec2 a, vec2 b) { + return (vec2){ + .x = a.x + b.x, + .y = a.y + b.y, + }; +} + +static inline vec2 vec2_ceil(vec2 a) { + return (vec2){ + .x = ceil(a.x), + .y = ceil(a.y), + }; +} + +static inline vec2 vec2_floor(vec2 a) { + return (vec2){ + .x = floor(a.x), + .y = floor(a.y), + }; +} + static inline bool vec2_eq(vec2 a, vec2 b) { return a.x == b.x && a.y == b.y; } @@ -128,5 +156,15 @@ static inline bool ibox_eq(struct ibox a, struct ibox b) { return ivec2_eq(a.origin, b.origin) && ivec2_eq(a.size, b.size); } +static inline ivec2 ivec2_scale_ceil(ivec2 a, vec2 scale) { + vec2 scaled = vec2_scale(ivec2_as(a), scale); + return vec2_as(vec2_ceil(scaled)); +} + +static inline ivec2 ivec2_scale_floor(ivec2 a, vec2 scale) { + vec2 scaled = vec2_scale(ivec2_as(a), scale); + return vec2_as(vec2_floor(scaled)); +} + #define MARGIN_INIT \ { 0, 0, 0, 0 } diff --git a/src/utils.h b/src/utils.h index 3aef29310e..ba18886d04 100644 --- a/src/utils.h +++ b/src/utils.h @@ -140,6 +140,39 @@ safe_isinf(double a) { (uint32_t) __to_tmp; \ }) +static inline uint16_t u64_to_u16_saturated(uint64_t val) { + if (val > UINT16_MAX) { + return UINT16_MAX; + } + return (uint16_t)val; +} +static inline uint16_t double_to_u16_saturated(double val) { + BUG_ON(safe_isnan(val)); + if (val < 0) { + return 0; + } + if (val > UINT16_MAX) { + return UINT16_MAX; + } + return (uint16_t)val; +} +static inline uint16_t i64_to_u16_saturated(int64_t val) { + if (val < 0) { + return 0; + } + if (val > UINT16_MAX) { + return UINT16_MAX; + } + return (uint16_t)val; +} + +#define to_u16_saturated(val) \ + _Generic((val), \ + double: double_to_u16_saturated(val), \ + float: double_to_u16_saturated((double)(val)), \ + uint64_t: u64_to_u16_saturated(val), \ + default: i64_to_u16_saturated((int64_t)(val))) + static inline int32_t double_to_i32_saturated(double val) { BUG_ON(safe_isnan(val)); if (val < INT32_MIN) {