xserver-multidpi/glamor/glamor_copy.c
Keith Packard 181a4bd0cc glamor: Preserve GL_RED bits in R channel when destination is GL_RED [v2]
A1 and A8 pixmaps are usually stored in the Red channel to conform
with more recent GL versions. When using these pixmaps as mask values,
that works great. When using these pixmaps as source values, then the
value we want depends on what the destination looks like.

For RGBA or RGB destinations, then we want to use the Red channel
for A values and leave RGB all set to zero.

For A destinations, then we want to leave the R values in the Red
channel so that they end up in the Red channel of the output.

This patch adds a helper function, glamor_bind_texture, which performs
the glBindTexture call along with setting the swizzle parameter
correctly for the Red channel. The swizzle parameter for the Alpha
channel doesn't depend on the destination as it's safe to leave it
always swizzled from the Red channel.

This fixes incorrect rendering in firefox for this page:

	https://gfycat.com/HoarseCheapAmericankestrel

while not breaking rendering for this page:

	https://feedly.com

v2: Add change accidentally left in patch for missing
    glDisable(GL_COLOR_LOGIC_OP).
    Found by Emil Velikov <emil.l.velikov@gmail.com>

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=63397
Signed-off-by: Keith Packard <keithp@keithp.com>
Tested-by: Michel Dänzer <michel.daenzer@amd.com>
2016-05-26 09:17:59 -07:00

700 lines
22 KiB
C

/*
* 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 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 GPU 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);
FbBits *src_bits;
FbStride src_stride;
int src_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(src, GLAMOR_ACCESS_RO);
glamor_get_drawable_deltas(dst, dst_pixmap, &dst_xoff, &dst_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;
}
/*
* 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;
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) {
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);
glEnableVertexAttribArray(GLAMOR_VERTEX_POS);
glVertexAttribPointer(GLAMOR_VERTEX_POS, 2, GL_SHORT, GL_FALSE,
2 * sizeof (GLshort), vbo_offset);
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) {
glamor_set_destination_drawable(dst, dst_box_index, FALSE, FALSE,
prog->matrix_uniform,
&dst_off_x, &dst_off_y);
glScissor(dst_off_x - args.dx,
dst_off_y - args.dy,
src_box->x2 - src_box->x1,
src_box->y2 - src_box->y1);
glamor_glDrawArrays_GL_QUADS(glamor_priv, nbox);
}
}
glDisable(GL_SCISSOR_TEST);
glDisableVertexAttribArray(GLAMOR_VERTEX_POS);
return TRUE;
bail_ctx:
return FALSE;
}
/**
* 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;
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);
}
if (bitplane == 0)
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);
}