/* * Copyright © 2014 Keith Packard * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that copyright * notice and this permission notice appear in supporting documentation, and * that the name of the copyright holders not be used in advertising or * publicity pertaining to distribution of the software without specific, * written prior permission. The copyright holders make no representations * about the suitability of this software for any purpose. It is provided "as * is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ #include "glamor_priv.h" #include "glamor_transfer.h" #include "glamor_prepare.h" #include "glamor_transform.h" struct copy_args { PixmapPtr src_pixmap; glamor_pixmap_fbo *src; uint32_t bitplane; int dx, dy; }; static Bool use_copyarea(PixmapPtr dst, GCPtr gc, glamor_program *prog, void *arg) { struct copy_args *args = arg; glamor_pixmap_fbo *src = args->src; glamor_bind_texture(glamor_get_screen_private(dst->drawable.pScreen), GL_TEXTURE0, src, TRUE); glUniform2f(prog->fill_offset_uniform, args->dx, args->dy); glUniform2f(prog->fill_size_inv_uniform, 1.0f/src->width, 1.0f/src->height); return TRUE; } static const glamor_facet glamor_facet_copyarea = { "copy_area", .vs_vars = "attribute vec2 primitive;\n", .vs_exec = (GLAMOR_POS(gl_Position, primitive.xy) " fill_pos = (fill_offset + primitive.xy) * fill_size_inv;\n"), .fs_exec = " gl_FragColor = texture2D(sampler, fill_pos);\n", .locations = glamor_program_location_fillsamp | glamor_program_location_fillpos, .use = use_copyarea, }; /* * Configure the copy plane program for the current operation */ static Bool use_copyplane(PixmapPtr dst, GCPtr gc, glamor_program *prog, void *arg) { struct copy_args *args = arg; glamor_pixmap_fbo *src = args->src; glamor_bind_texture(glamor_get_screen_private(dst->drawable.pScreen), GL_TEXTURE0, src, TRUE); glUniform2f(prog->fill_offset_uniform, args->dx, args->dy); glUniform2f(prog->fill_size_inv_uniform, 1.0f/src->width, 1.0f/src->height); glamor_set_color(dst, gc->fgPixel, prog->fg_uniform); glamor_set_color(dst, gc->bgPixel, prog->bg_uniform); /* XXX handle 2 10 10 10 and 1555 formats; presumably the pixmap private knows this? */ switch (args->src_pixmap->drawable.depth) { case 30: glUniform4ui(prog->bitplane_uniform, (args->bitplane >> 20) & 0x3ff, (args->bitplane >> 10) & 0x3ff, (args->bitplane ) & 0x3ff, 0); glUniform4f(prog->bitmul_uniform, 0x3ff, 0x3ff, 0x3ff, 0); break; case 24: glUniform4ui(prog->bitplane_uniform, (args->bitplane >> 16) & 0xff, (args->bitplane >> 8) & 0xff, (args->bitplane ) & 0xff, 0); glUniform4f(prog->bitmul_uniform, 0xff, 0xff, 0xff, 0); break; case 32: glUniform4ui(prog->bitplane_uniform, (args->bitplane >> 16) & 0xff, (args->bitplane >> 8) & 0xff, (args->bitplane ) & 0xff, (args->bitplane >> 24) & 0xff); glUniform4f(prog->bitmul_uniform, 0xff, 0xff, 0xff, 0xff); break; case 16: glUniform4ui(prog->bitplane_uniform, (args->bitplane >> 11) & 0x1f, (args->bitplane >> 5) & 0x3f, (args->bitplane ) & 0x1f, 0); glUniform4f(prog->bitmul_uniform, 0x1f, 0x3f, 0x1f, 0); break; case 15: glUniform4ui(prog->bitplane_uniform, (args->bitplane >> 10) & 0x1f, (args->bitplane >> 5) & 0x1f, (args->bitplane ) & 0x1f, 0); glUniform4f(prog->bitmul_uniform, 0x1f, 0x1f, 0x1f, 0); break; case 8: glUniform4ui(prog->bitplane_uniform, 0, 0, 0, args->bitplane); glUniform4f(prog->bitmul_uniform, 0, 0, 0, 0xff); break; case 1: glUniform4ui(prog->bitplane_uniform, 0, 0, 0, args->bitplane); glUniform4f(prog->bitmul_uniform, 0, 0, 0, 0xff); break; } return TRUE; } static const glamor_facet glamor_facet_copyplane = { "copy_plane", .version = 130, .vs_vars = "attribute vec2 primitive;\n", .vs_exec = (GLAMOR_POS(gl_Position, (primitive.xy)) " fill_pos = (fill_offset + primitive.xy) * fill_size_inv;\n"), .fs_exec = (" uvec4 bits = uvec4(round(texture2D(sampler, fill_pos) * bitmul));\n" " if ((bits & bitplane) != uvec4(0,0,0,0))\n" " gl_FragColor = fg;\n" " else\n" " gl_FragColor = bg;\n"), .locations = glamor_program_location_fillsamp|glamor_program_location_fillpos|glamor_program_location_fg|glamor_program_location_bg|glamor_program_location_bitplane, .use = use_copyplane, }; /* * When all else fails, pull the bits out of the GPU and do the * operation with fb */ static void glamor_copy_bail(DrawablePtr src, DrawablePtr dst, GCPtr gc, BoxPtr box, int nbox, int dx, int dy, Bool reverse, Bool upsidedown, Pixel bitplane, void *closure) { if (glamor_prepare_access(dst, GLAMOR_ACCESS_RW) && glamor_prepare_access(src, GLAMOR_ACCESS_RO)) { if (bitplane) { if (src->bitsPerPixel > 1) fbCopyNto1(src, dst, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); else fbCopy1toN(src, dst, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); } else { fbCopyNtoN(src, dst, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); } } glamor_finish_access(dst); glamor_finish_access(src); } /** * Implements CopyPlane and CopyArea from the CPU to the GPU by using * the source as a texture and painting that into the destination. * * This requires that source and dest are different textures, or that * (if the copy area doesn't overlap), GL_NV_texture_barrier is used * to ensure that the caches are flushed at the right times. */ static Bool glamor_copy_cpu_fbo(DrawablePtr src, DrawablePtr dst, GCPtr gc, BoxPtr box, int nbox, int dx, int dy, Bool reverse, Bool upsidedown, Pixel bitplane, void *closure) { ScreenPtr screen = dst->pScreen; glamor_screen_private *glamor_priv = glamor_get_screen_private(screen); PixmapPtr dst_pixmap = glamor_get_drawable_pixmap(dst); int dst_xoff, dst_yoff; if (gc && gc->alu != GXcopy) goto bail; if (gc && !glamor_pm_is_solid(gc->depth, gc->planemask)) goto bail; glamor_make_current(glamor_priv); glamor_prepare_access(src, GLAMOR_ACCESS_RO); glamor_get_drawable_deltas(dst, dst_pixmap, &dst_xoff, &dst_yoff); if (bitplane) { FbBits *tmp_bits; FbStride tmp_stride; int tmp_bpp; int tmp_xoff, tmp_yoff; PixmapPtr tmp_pix = fbCreatePixmap(screen, dst_pixmap->drawable.width, dst_pixmap->drawable.height, dst->depth, 0); if (!tmp_pix) { glamor_finish_access(src); goto bail; } tmp_pix->drawable.x = dst_xoff; tmp_pix->drawable.y = dst_yoff; fbGetDrawable(&tmp_pix->drawable, tmp_bits, tmp_stride, tmp_bpp, tmp_xoff, tmp_yoff); if (src->bitsPerPixel > 1) fbCopyNto1(src, &tmp_pix->drawable, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); else fbCopy1toN(src, &tmp_pix->drawable, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); glamor_upload_boxes(dst_pixmap, box, nbox, tmp_xoff, tmp_yoff, dst_xoff, dst_yoff, (uint8_t *) tmp_bits, tmp_stride * sizeof(FbBits)); fbDestroyPixmap(tmp_pix); } else { FbBits *src_bits; FbStride src_stride; int src_bpp; int src_xoff, src_yoff; fbGetDrawable(src, src_bits, src_stride, src_bpp, src_xoff, src_yoff); glamor_upload_boxes(dst_pixmap, box, nbox, src_xoff + dx, src_yoff + dy, dst_xoff, dst_yoff, (uint8_t *) src_bits, src_stride * sizeof (FbBits)); } glamor_finish_access(src); return TRUE; bail: return FALSE; } /** * Implements CopyArea from the GPU to the CPU using glReadPixels from the * source FBO. */ static Bool glamor_copy_fbo_cpu(DrawablePtr src, DrawablePtr dst, GCPtr gc, BoxPtr box, int nbox, int dx, int dy, Bool reverse, Bool upsidedown, Pixel bitplane, void *closure) { ScreenPtr screen = dst->pScreen; glamor_screen_private *glamor_priv = glamor_get_screen_private(screen); PixmapPtr src_pixmap = glamor_get_drawable_pixmap(src); FbBits *dst_bits; FbStride dst_stride; int dst_bpp; int src_xoff, src_yoff; int dst_xoff, dst_yoff; if (gc && gc->alu != GXcopy) goto bail; if (gc && !glamor_pm_is_solid(gc->depth, gc->planemask)) goto bail; glamor_make_current(glamor_priv); glamor_prepare_access(dst, GLAMOR_ACCESS_RW); glamor_get_drawable_deltas(src, src_pixmap, &src_xoff, &src_yoff); fbGetDrawable(dst, dst_bits, dst_stride, dst_bpp, dst_xoff, dst_yoff); glamor_download_boxes(src_pixmap, box, nbox, src_xoff + dx, src_yoff + dy, dst_xoff, dst_yoff, (uint8_t *) dst_bits, dst_stride * sizeof (FbBits)); glamor_finish_access(dst); return TRUE; bail: return FALSE; } /* Include the enums here for the moment, to keep from needing to bump epoxy. */ #ifndef GL_TILE_RASTER_ORDER_FIXED_MESA #define GL_TILE_RASTER_ORDER_FIXED_MESA 0x8BB8 #define GL_TILE_RASTER_ORDER_INCREASING_X_MESA 0x8BB9 #define GL_TILE_RASTER_ORDER_INCREASING_Y_MESA 0x8BBA #endif /* * Copy from GPU to GPU by using the source * as a texture and painting that into the destination */ static Bool glamor_copy_fbo_fbo_draw(DrawablePtr src, DrawablePtr dst, GCPtr gc, BoxPtr box, int nbox, int dx, int dy, Bool reverse, Bool upsidedown, Pixel bitplane, void *closure) { ScreenPtr screen = dst->pScreen; glamor_screen_private *glamor_priv = glamor_get_screen_private(screen); PixmapPtr src_pixmap = glamor_get_drawable_pixmap(src); PixmapPtr dst_pixmap = glamor_get_drawable_pixmap(dst); glamor_pixmap_private *src_priv = glamor_get_pixmap_private(src_pixmap); glamor_pixmap_private *dst_priv = glamor_get_pixmap_private(dst_pixmap); int src_box_index, dst_box_index; int dst_off_x, dst_off_y; int src_off_x, src_off_y; GLshort *v; char *vbo_offset; struct copy_args args; glamor_program *prog; const glamor_facet *copy_facet; int n; Bool ret = FALSE; BoxRec bounds = glamor_no_rendering_bounds(); glamor_make_current(glamor_priv); if (gc && !glamor_set_planemask(gc->depth, gc->planemask)) goto bail_ctx; if (!glamor_set_alu(screen, gc ? gc->alu : GXcopy)) goto bail_ctx; if (bitplane && !glamor_priv->can_copyplane) goto bail_ctx; if (bitplane) { prog = &glamor_priv->copy_plane_prog; copy_facet = &glamor_facet_copyplane; } else { prog = &glamor_priv->copy_area_prog; copy_facet = &glamor_facet_copyarea; } if (prog->failed) goto bail_ctx; if (!prog->prog) { if (!glamor_build_program(screen, prog, copy_facet, NULL, NULL, NULL)) goto bail_ctx; } args.src_pixmap = src_pixmap; args.bitplane = bitplane; /* Set up the vertex buffers for the points */ v = glamor_get_vbo_space(dst->pScreen, nbox * 8 * sizeof (int16_t), &vbo_offset); if (src_pixmap == dst_pixmap && glamor_priv->has_mesa_tile_raster_order) { glEnable(GL_TILE_RASTER_ORDER_FIXED_MESA); if (dx >= 0) glEnable(GL_TILE_RASTER_ORDER_INCREASING_X_MESA); else glDisable(GL_TILE_RASTER_ORDER_INCREASING_X_MESA); if (dy >= 0) glEnable(GL_TILE_RASTER_ORDER_INCREASING_Y_MESA); else glDisable(GL_TILE_RASTER_ORDER_INCREASING_Y_MESA); } glEnableVertexAttribArray(GLAMOR_VERTEX_POS); glVertexAttribPointer(GLAMOR_VERTEX_POS, 2, GL_SHORT, GL_FALSE, 2 * sizeof (GLshort), vbo_offset); if (nbox < 100) { bounds = glamor_start_rendering_bounds(); for (int i = 0; i < nbox; i++) glamor_bounds_union_box(&bounds, &box[i]); } for (n = 0; n < nbox; n++) { v[0] = box->x1; v[1] = box->y1; v[2] = box->x1; v[3] = box->y2; v[4] = box->x2; v[5] = box->y2; v[6] = box->x2; v[7] = box->y1; v += 8; box++; } glamor_put_vbo_space(screen); glamor_get_drawable_deltas(src, src_pixmap, &src_off_x, &src_off_y); glEnable(GL_SCISSOR_TEST); glamor_pixmap_loop(src_priv, src_box_index) { BoxPtr src_box = glamor_pixmap_box_at(src_priv, src_box_index); args.dx = dx + src_off_x - src_box->x1; args.dy = dy + src_off_y - src_box->y1; args.src = glamor_pixmap_fbo_at(src_priv, src_box_index); if (!glamor_use_program(dst_pixmap, gc, prog, &args)) goto bail_ctx; glamor_pixmap_loop(dst_priv, dst_box_index) { BoxRec scissor = { .x1 = max(-args.dx, bounds.x1), .y1 = max(-args.dy, bounds.y1), .x2 = min(-args.dx + src_box->x2 - src_box->x1, bounds.x2), .y2 = min(-args.dy + src_box->y2 - src_box->y1, bounds.y2), }; if (scissor.x1 >= scissor.x2 || scissor.y1 >= scissor.y2) continue; if (!glamor_set_destination_drawable(dst, dst_box_index, FALSE, FALSE, prog->matrix_uniform, &dst_off_x, &dst_off_y)) goto bail_ctx; glScissor(scissor.x1 + dst_off_x, scissor.y1 + dst_off_y, scissor.x2 - scissor.x1, scissor.y2 - scissor.y1); glamor_glDrawArrays_GL_QUADS(glamor_priv, nbox); } } ret = TRUE; bail_ctx: if (src_pixmap == dst_pixmap && glamor_priv->has_mesa_tile_raster_order) { glDisable(GL_TILE_RASTER_ORDER_FIXED_MESA); } glDisable(GL_SCISSOR_TEST); glDisableVertexAttribArray(GLAMOR_VERTEX_POS); return ret; } /** * Copies from the GPU to the GPU using a temporary pixmap in between, * to correctly handle overlapping copies. */ static Bool glamor_copy_fbo_fbo_temp(DrawablePtr src, DrawablePtr dst, GCPtr gc, BoxPtr box, int nbox, int dx, int dy, Bool reverse, Bool upsidedown, Pixel bitplane, void *closure) { ScreenPtr screen = dst->pScreen; glamor_screen_private *glamor_priv = glamor_get_screen_private(screen); PixmapPtr tmp_pixmap; BoxRec bounds; int n; BoxPtr tmp_box; if (nbox == 0) return TRUE; /* Sanity check state to avoid getting halfway through and bailing * at the last second. Might be nice to have checks that didn't * involve setting state. */ glamor_make_current(glamor_priv); if (gc && !glamor_set_planemask(gc->depth, gc->planemask)) goto bail_ctx; if (!glamor_set_alu(screen, gc ? gc->alu : GXcopy)) goto bail_ctx; /* Find the size of the area to copy */ bounds = box[0]; for (n = 1; n < nbox; n++) { bounds.x1 = min(bounds.x1, box[n].x1); bounds.x2 = max(bounds.x2, box[n].x2); bounds.y1 = min(bounds.y1, box[n].y1); bounds.y2 = max(bounds.y2, box[n].y2); } /* Allocate a suitable temporary pixmap */ tmp_pixmap = glamor_create_pixmap(screen, bounds.x2 - bounds.x1, bounds.y2 - bounds.y1, src->depth, 0); if (!tmp_pixmap) goto bail; tmp_box = calloc(nbox, sizeof (BoxRec)); if (!tmp_box) goto bail_pixmap; /* Convert destination boxes into tmp pixmap boxes */ for (n = 0; n < nbox; n++) { tmp_box[n].x1 = box[n].x1 - bounds.x1; tmp_box[n].x2 = box[n].x2 - bounds.x1; tmp_box[n].y1 = box[n].y1 - bounds.y1; tmp_box[n].y2 = box[n].y2 - bounds.y1; } if (!glamor_copy_fbo_fbo_draw(src, &tmp_pixmap->drawable, NULL, tmp_box, nbox, dx + bounds.x1, dy + bounds.y1, FALSE, FALSE, 0, NULL)) goto bail_box; if (!glamor_copy_fbo_fbo_draw(&tmp_pixmap->drawable, dst, gc, box, nbox, -bounds.x1, -bounds.y1, FALSE, FALSE, bitplane, closure)) goto bail_box; free(tmp_box); glamor_destroy_pixmap(tmp_pixmap); return TRUE; bail_box: free(tmp_box); bail_pixmap: glamor_destroy_pixmap(tmp_pixmap); bail: return FALSE; bail_ctx: return FALSE; } /** * Returns TRUE if the copy has to be implemented with * glamor_copy_fbo_fbo_temp() instead of glamor_copy_fbo_fbo(). * * If the src and dst are in the same pixmap, then glamor_copy_fbo_fbo()'s * sampling would give undefined results (since the same texture would be * bound as an FBO destination and as a texture source). However, if we * have GL_NV_texture_barrier, we can take advantage of the exception it * added: * * "- If a texel has been written, then in order to safely read the result * a texel fetch must be in a subsequent Draw separated by the command * * void TextureBarrierNV(void); * * TextureBarrierNV() will guarantee that writes have completed and caches * have been invalidated before subsequent Draws are executed." */ static Bool glamor_copy_needs_temp(DrawablePtr src, DrawablePtr dst, BoxPtr box, int nbox, int dx, int dy) { PixmapPtr src_pixmap = glamor_get_drawable_pixmap(src); PixmapPtr dst_pixmap = glamor_get_drawable_pixmap(dst); ScreenPtr screen = dst->pScreen; glamor_screen_private *glamor_priv = glamor_get_screen_private(screen); int n; int dst_off_x, dst_off_y; int src_off_x, src_off_y; BoxRec bounds; if (src_pixmap != dst_pixmap) return FALSE; if (nbox == 0) return FALSE; if (!glamor_priv->has_nv_texture_barrier) return TRUE; if (!glamor_priv->has_mesa_tile_raster_order) { glamor_get_drawable_deltas(src, src_pixmap, &src_off_x, &src_off_y); glamor_get_drawable_deltas(dst, dst_pixmap, &dst_off_x, &dst_off_y); bounds = box[0]; for (n = 1; n < nbox; n++) { bounds.x1 = min(bounds.x1, box[n].x1); bounds.y1 = min(bounds.y1, box[n].y1); bounds.x2 = max(bounds.x2, box[n].x2); bounds.y2 = max(bounds.y2, box[n].y2); } /* Check to see if the pixmap-relative boxes overlap in both X and Y, * in which case we can't rely on NV_texture_barrier and must * make a temporary copy * * dst.x1 < src.x2 && * src.x1 < dst.x2 && * * dst.y1 < src.y2 && * src.y1 < dst.y2 */ if (bounds.x1 + dst_off_x < bounds.x2 + dx + src_off_x && bounds.x1 + dx + src_off_x < bounds.x2 + dst_off_x && bounds.y1 + dst_off_y < bounds.y2 + dy + src_off_y && bounds.y1 + dy + src_off_y < bounds.y2 + dst_off_y) { return TRUE; } } glTextureBarrierNV(); return FALSE; } static Bool glamor_copy_gl(DrawablePtr src, DrawablePtr dst, GCPtr gc, BoxPtr box, int nbox, int dx, int dy, Bool reverse, Bool upsidedown, Pixel bitplane, void *closure) { PixmapPtr src_pixmap = glamor_get_drawable_pixmap(src); PixmapPtr dst_pixmap = glamor_get_drawable_pixmap(dst); glamor_pixmap_private *src_priv = glamor_get_pixmap_private(src_pixmap); glamor_pixmap_private *dst_priv = glamor_get_pixmap_private(dst_pixmap); if (GLAMOR_PIXMAP_PRIV_HAS_FBO(dst_priv)) { if (GLAMOR_PIXMAP_PRIV_HAS_FBO(src_priv)) { if (glamor_copy_needs_temp(src, dst, box, nbox, dx, dy)) return glamor_copy_fbo_fbo_temp(src, dst, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); else return glamor_copy_fbo_fbo_draw(src, dst, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); } return glamor_copy_cpu_fbo(src, dst, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); } else if (GLAMOR_PIXMAP_PRIV_HAS_FBO(src_priv) && dst_priv->type != GLAMOR_DRM_ONLY && bitplane == 0) { return glamor_copy_fbo_cpu(src, dst, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); } return FALSE; } void glamor_copy(DrawablePtr src, DrawablePtr dst, GCPtr gc, BoxPtr box, int nbox, int dx, int dy, Bool reverse, Bool upsidedown, Pixel bitplane, void *closure) { if (nbox == 0) return; if (glamor_copy_gl(src, dst, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure)) return; glamor_copy_bail(src, dst, gc, box, nbox, dx, dy, reverse, upsidedown, bitplane, closure); } RegionPtr glamor_copy_area(DrawablePtr src, DrawablePtr dst, GCPtr gc, int srcx, int srcy, int width, int height, int dstx, int dsty) { return miDoCopy(src, dst, gc, srcx, srcy, width, height, dstx, dsty, glamor_copy, 0, NULL); } RegionPtr glamor_copy_plane(DrawablePtr src, DrawablePtr dst, GCPtr gc, int srcx, int srcy, int width, int height, int dstx, int dsty, unsigned long bitplane) { if ((bitplane & FbFullMask(src->depth)) == 0) return miHandleExposures(src, dst, gc, srcx, srcy, width, height, dstx, dsty); return miDoCopy(src, dst, gc, srcx, srcy, width, height, dstx, dsty, glamor_copy, bitplane, NULL); } void glamor_copy_window(WindowPtr window, DDXPointRec old_origin, RegionPtr src_region) { PixmapPtr pixmap = glamor_get_drawable_pixmap(&window->drawable); DrawablePtr drawable = &pixmap->drawable; RegionRec dst_region; int dx, dy; dx = old_origin.x - window->drawable.x; dy = old_origin.y - window->drawable.y; RegionTranslate(src_region, -dx, -dy); RegionNull(&dst_region); RegionIntersect(&dst_region, &window->borderClip, src_region); #ifdef COMPOSITE if (pixmap->screen_x || pixmap->screen_y) RegionTranslate(&dst_region, -pixmap->screen_x, -pixmap->screen_y); #endif miCopyRegion(drawable, drawable, 0, &dst_region, dx, dy, glamor_copy, 0, 0); RegionUninit(&dst_region); }