diff --git a/12to11.c b/12to11.c
index 28240fb..f944a9a 100644
--- a/12to11.c
+++ b/12to11.c
@@ -122,6 +122,7 @@ XLMain (int argc, char **argv)
XLInitIconSurfaces ();
XLInitPrimarySelection ();
XLInitExplicitSynchronization ();
+ XLInitWpViewporter ();
/* This has to come after the rest of the initialization. */
DetermineServerTime ();
XLRunCompositor ();
diff --git a/Imakefile b/Imakefile
index c557feb..2c79375 100644
--- a/Imakefile
+++ b/Imakefile
@@ -8,8 +8,6 @@ SYS_LIBRARIES = MathLibrary ThreadsLibraries
DEPLIBS = $(DEPXLIB) $(DEPEXTENSIONLIB) $(DEPXRANDRLIB) $(DEPXRENDERLIB) \
$(DEPXFIXESLIB) $(DEPXILIB) $(DEPXKBFILELIB)
-ETAGS = etags
-
LOCAL_LIBRARIES = $(XLIB) $(EXTENSIONLIB) $(XCBLIB) $(XCB) $(XCB_SHM) \
$(XRANDRLIB) $(PIXMAN) $(XRENDERLIB) $(XILIB) $(XKBFILELIB) $(XFIXESLIB) \
$(XCB_DRI3) $(XCB_SHAPE) $(WAYLAND_SERVER)
@@ -22,7 +20,8 @@ SRCS = 12to11.c run.c alloc.c fns.c output.c compositor.c \
ewmh.c timer.c subsurface.c seat.c data_device.c xdg_popup.c \
dmabuf.c buffer.c select.c xdata.c xsettings.c dnd.c \
icon_surface.c primary_selection.c renderer.c \
- picture_renderer.c explicit_synchronization.c
+ picture_renderer.c explicit_synchronization.c transform.c \
+ wp_viewporter.c
OBJS = 12to11.o run.o alloc.o fns.o output.o compositor.o \
surface.o region.o shm.o atoms.o subcompositor.o positioner.o \
@@ -30,11 +29,10 @@ OBJS = 12to11.o run.o alloc.o fns.o output.o compositor.o \
ewmh.o timer.o subsurface.o seat.o data_device.o xdg_popup.o \
dmabuf.o buffer.o select.o xdata.o xsettings.o dnd.o \
icon_surface.o primary_selection.o renderer.o \
- picture_renderer.o explicit_synchronization.o
+ picture_renderer.o explicit_synchronization.o transform.o \
+ wp_viewporter.o
-GENHEADERS = transfer_atoms.h primary-selection-unstable-v1.h \
- linux-dmabuf-unstable-v1.h xdg-shell.h \
- linux-explicit-synchronization-unstable-v1.h
+GENHEADERS = transfer_atoms.h
#ifdef HaveEglSupport
@@ -111,6 +109,7 @@ ScannerTarget(linux-dmabuf-unstable-v1)
ScannerTarget(xdg-shell)
ScannerTarget(primary-selection-unstable-v1)
ScannerTarget(linux-explicit-synchronization-unstable-v1)
+ScannerTarget(viewporter)
/* Make OBJS depend on scanner headers, and depend on both them and SRCS. */
$(OBJS): $(GENHEADERS)
diff --git a/README b/README
index e391716..7a79c25 100644
--- a/README
+++ b/README
@@ -3,7 +3,7 @@ preferably with a compositing manager running.
It is not yet complete. What is not yet implemented includes support
for touchscreens, input methods, device switching in dmabuf feedback,
-and the viewporter protocol extension.
+and the single-pixel buffer protocol extension.
There are also problems with output reporting in subsurfaces.
@@ -64,6 +64,7 @@ complete degree:
'wl_data_device_manager', version: 3
'zwp_linux_dmabuf_v1', version: 4
'zwp_primary_selection_device_manager_v1', version: 1
+ 'wp_viewporter', version: 1
When built with EGL, the following Wayland protocol is also supported:
diff --git a/compositor.h b/compositor.h
index 0172072..3066ab8 100644
--- a/compositor.h
+++ b/compositor.h
@@ -1,4 +1,4 @@
-/* Wayland compositor running on top of an X serer.
+/* Wayland compositor running on top of an X server.
Copyright (C) 2022 to various contributors.
@@ -90,6 +90,10 @@ typedef struct _Seat Seat;
typedef struct _PDataSource PDataSource;
+/* Forward declarations from wp_viewporter.c. */
+
+typedef struct _ViewportExt ViewportExt;
+
/* Defined in 12to11.c. */
extern Compositor compositor;
@@ -125,7 +129,13 @@ enum _Operation
enum
{
/* Scale has been set. */
- ScaleSet = 1,
+ ScaleSet = 1,
+
+ /* Offset has been set. */
+ OffsetSet = (1 << 2),
+
+ /* Stretch has been set. */
+ StretchSet = (1 << 3),
};
struct _DrawParams
@@ -135,6 +145,15 @@ struct _DrawParams
/* A scale factor to apply to the buffer. */
double scale;
+
+ /* Offset from which to start sampling from the buffer, after
+ scaling. */
+ double off_x, off_y;
+
+ /* The crop, width and height of the buffer. The buffer will be
+ stretched or shrunk to this size, after being cropped to
+ crop and being offset by -off_x, -off_y. */
+ double crop_width, crop_height, stretch_width, stretch_height;
};
struct _SharedMemoryAttributes
@@ -261,7 +280,10 @@ struct _RenderFuncs
/* Composite width, height, from the given buffer onto the given
target, at x, y. The arguments are: buffer, target, operation,
source_x, source_y, x, y, width, height, params. params
- describes how to transform the given buffer. */
+ describes how to transform the given buffer.
+
+ Reads outside the texture contents should result in transparency
+ being composited. */
void (*composite) (RenderBuffer, RenderTarget, Operation, int, int,
int, int, int, int, DrawParams *);
@@ -360,10 +382,11 @@ struct _BufferFuncs
/* Notice that the given buffer has been damaged. May be NULL. If
the given NULL damage, assume that the entire buffer has been
damaged. Must be called at least once before any rendering can
- be performed on the buffer. 3rd arg is the scale by which to
- divide the buffer. */
+ be performed on the buffer. 3rd arg is a DrawParams struct
+ describing a transform to inversely apply to the damage
+ region. */
void (*update_buffer_for_damage) (RenderBuffer, pixman_region32_t *,
- float);
+ DrawParams *);
/* Return whether or not the buffer contents can be released early,
by being copied to an offscreen buffer. */
@@ -410,7 +433,7 @@ extern Bool RenderValidateShmParams (uint32_t, uint32_t, uint32_t, int32_t,
extern void RenderFreeShmBuffer (RenderBuffer);
extern void RenderFreeDmabufBuffer (RenderBuffer);
extern void RenderUpdateBufferForDamage (RenderBuffer, pixman_region32_t *,
- float);
+ DrawParams *);
extern Bool RenderCanReleaseNow (RenderBuffer);
/* Defined in run.c. */
@@ -503,6 +526,8 @@ extern int XLOpenShm (void);
extern void XLScaleRegion (pixman_region32_t *, pixman_region32_t *,
float, float);
+extern void XLExtendRegion (pixman_region32_t *, pixman_region32_t *,
+ int, int);
extern Time XLGetServerTimeRoundtrip (void);
extern RootWindowSelection *XLSelectInputFromRootWindow (unsigned long);
@@ -625,6 +650,11 @@ extern void ViewMove (View *, int, int);
extern void ViewDetach (View *);
extern void ViewMap (View *);
extern void ViewUnmap (View *);
+extern void ViewMoveFractional (View *, double, double);
+
+extern void ViewSetViewport (View *, double, double, double, double,
+ double, double);
+extern void ViewClearViewport (View *);
extern void ViewSetData (View *, void *);
extern void *ViewGetData (View *);
@@ -673,6 +703,8 @@ enum
PendingFrameCallbacks = (1 << 6),
PendingBufferScale = (1 << 7),
PendingAttachments = (1 << 8),
+ PendingViewportSrc = (1 << 9),
+ PendingViewportDest = (1 << 10),
/* Flags here are stored in `pending' of the current state for
space reasons. */
@@ -717,6 +749,14 @@ struct _State
/* Attachment position. */
int x, y;
+
+ /* Viewport destination width and height. All viewport coordinates
+ and dimensions are specified in terms of the surface coordinate
+ system. */
+ int dest_width, dest_height;
+
+ /* Viewport source rectangle. */
+ double src_x, src_y, src_width, src_height;
};
typedef enum _ClientDataType ClientDataType;
@@ -848,6 +888,13 @@ struct _Surface
/* The scale factor used to convert from surface coordinates to
window coordinates. */
double factor;
+
+ /* Any associated viewport resource. */
+ ViewportExt *viewport;
+
+ /* Any associated input delta. This is used to compensate
+ for fractional subsurface placement while handling input. */
+ double input_delta_x, input_delta_y;
};
struct _RoleFuncs
@@ -917,6 +964,16 @@ extern void XLSurfaceMoveBy (Surface *, int, int);
extern Window XLWindowFromSurface (Surface *);
extern void XLUpdateSurfaceOutputs (Surface *, int, int, int, int);
+extern void SurfaceToWindow (Surface *, double, double, double *, double *);
+extern void ScaleToWindow (Surface *, double, double, double *, double *);
+extern void WindowToSurface (Surface *, double, double, double *, double *);
+extern void ScaleToSurface (Surface *, double, double, double *, double *);
+
+extern void TruncateSurfaceToWindow (Surface *, int, int, int *, int *);
+extern void TruncateScaleToWindow (Surface *, int, int, int *, int *);
+extern void TruncateWindowToSurface (Surface *, int, int, int *, int *);
+extern void TruncateScaleToSurface (Surface *, int, int, int *, int *);
+
/* Defined in output.c. */
extern int global_scale_factor;
@@ -1016,6 +1073,7 @@ struct _XdgRoleImplementationFuncs
void (*handle_geometry_change) (Role *, XdgRoleImplementation *);
void (*post_resize) (Role *, XdgRoleImplementation *, int, int, int, int);
void (*commit_inside_frame) (Role *, XdgRoleImplementation *);
+ Bool (*is_window_mapped) (Role *, XdgRoleImplementation *);
};
struct _XdgRoleImplementation
@@ -1349,6 +1407,23 @@ extern void XLSyncRelease (SyncRelease *);
extern void XLWaitFence (Surface *);
extern void XLInitExplicitSynchronization (void);
+/* Defined in transform.c. */
+
+/* N.B. the data is stored in column-major order. */
+typedef float Matrix[9];
+
+extern void MatrixMultiply (Matrix, Matrix, Matrix *);
+extern void MatrixIdentity (Matrix *);
+extern void MatrixTranslate (Matrix *, float, float);
+extern void MatrixScale (Matrix *, float, float);
+extern void MatrixExport (Matrix *, XTransform *);
+
+/* Defined in wp_viewporter.c. */
+
+extern void XLInitWpViewporter (void);
+extern void XLWpViewportReportBadSize (ViewportExt *);
+extern void XLWpViewportReportOutOfBuffer (ViewportExt *);
+
/* Utility functions that don't belong in a specific file. */
#define ArrayElements(arr) (sizeof (arr) / sizeof (arr)[0])
diff --git a/dmabuf.c b/dmabuf.c
index ce5a3d1..f1ad8d5 100644
--- a/dmabuf.c
+++ b/dmabuf.c
@@ -1068,9 +1068,6 @@ XLInitDmabuf (void)
/* And try to create the format table. */
size = WriteFormatTable ();
- /* Create an unmapped, InputOnly window, that is used to receive
- roundtrip events. */
-
global_dmabuf = wl_global_create (compositor.wl_display,
&zwp_linux_dmabuf_v1_interface,
/* If writing the format table
diff --git a/dnd.c b/dnd.c
index 7d8f844..14305d5 100644
--- a/dnd.c
+++ b/dnd.c
@@ -897,12 +897,10 @@ HandleMotion (Surface *toplevel, int x, int y, uint32_t action,
/* Compute the surface-relative coordinates and scale them. */
if (child)
- {
- /* x_out and y_out are only used if dnd_state.child ends up
- non-NULL. */
- *x_out = (x - x_off) / child->factor;
- *y_out = (y - y_off) / child->factor;
- }
+ /* x_out and y_out are only used if dnd_state.child ends up
+ non-NULL. */
+ TruncateWindowToSurface (child, x - x_off, y - y_off,
+ x_out, y_out);
if (dnd_state.child == child)
/* If nothing changed, don't do anything. */
diff --git a/egl.c b/egl.c
index 65b8c13..6cb4f00 100644
--- a/egl.c
+++ b/egl.c
@@ -123,6 +123,11 @@ struct _EglBuffer
/* The texture name of any generated texture. */
GLuint texture;
+ /* 3x3 matrix that is used to transform texcoord into actual texture
+ coordinates. Note that GLfloat[9] should be the same type as
+ Matrix. */
+ GLfloat matrix[9];
+
/* The width and height of the buffer. */
int width, height;
@@ -180,13 +185,17 @@ struct _CompositeProgram
/* The index of the texture uniform. */
GLuint texture;
- /* The index of the scale uniform. */
- GLuint scale;
+ /* The index of the source uniform. */
+ GLuint source;
/* The index of the invert_y uniform. */
GLuint invert_y;
};
+/* This macro makes column major order easier to reason about for C
+ folks. */
+#define Index(matrix, row, column) ((matrix)[(column) * 3 + (row)])
+
/* All known SHM formats. */
static FormatInfo known_shm_formats[] =
{
@@ -531,6 +540,134 @@ EglInitGlFuncs (void)
IWaitSync = NULL;
}
+static Visual *
+PickBetterVisual (Visual *visual, int *depth)
+{
+ XRenderPictFormat target_format, *format, *found;
+ int i, num_x_formats, bpp, n_visuals, j;
+ EGLint alpha_size;
+ XPixmapFormatValues *formats;
+ XVisualInfo empty_template, *visuals;
+
+ /* First, see if there is already an alpha channel. */
+ format = XRenderFindVisualFormat (compositor.display, visual);
+
+ if (!format)
+ return visual;
+
+ if (format->type != PictTypeDirect)
+ /* Can this actually happen? */
+ return visual;
+
+ if (format->direct.alphaMask)
+ return visual;
+
+ /* Next, build the target format from the visual format. */
+ target_format.type = PictTypeDirect;
+ target_format.direct = format->direct;
+
+ /* Obtain the size of the alpha mask in the EGL config. */
+ if (!eglGetConfigAttrib (egl_display, egl_config,
+ EGL_ALPHA_SIZE, &alpha_size))
+ return visual;
+
+ if (alpha_size > 16)
+ /* If the alpha mask is too big, then use the chosen visual. */
+ return visual;
+
+ /* Add the alpha mask. */
+ for (i = 0; i < alpha_size; ++i)
+ target_format.direct.alphaMask |= 1 << i;
+
+ /* Look for matching picture formats with the same bpp and a larger
+ depth. */
+ formats = XListPixmapFormats (compositor.display, &num_x_formats);
+
+ if (!formats)
+ return visual;
+
+ /* Obtain the number of bits per pixel for the given depth. */
+ bpp = 0;
+
+ for (i = 0; i < num_x_formats; ++i)
+ {
+ if (formats[i].depth == format->depth)
+ bpp = formats[i].bits_per_pixel;
+ }
+
+ if (!bpp)
+ {
+ XFree (formats);
+ return visual;
+ }
+
+ /* Get a list of all visuals. */
+ empty_template.screen = DefaultScreen (compositor.display);
+ visuals = XGetVisualInfo (compositor.display, VisualScreenMask,
+ &empty_template, &n_visuals);
+
+ if (!visuals)
+ {
+ XFree (formats);
+ return visual;
+ }
+
+ /* Now, loop through each depth. */
+
+ for (i = 0; i < num_x_formats; ++i)
+ {
+ if (formats[i].depth > format->depth
+ && formats[i].bits_per_pixel == bpp)
+ {
+ /* Try to find a matching picture format. */
+ target_format.depth = formats[i].depth;
+
+ found = XRenderFindFormat (compositor.display,
+ PictFormatType
+ | PictFormatDepth
+ | PictFormatRed
+ | PictFormatGreen
+ | PictFormatBlue
+ | PictFormatRedMask
+ | PictFormatBlueMask
+ | PictFormatGreenMask
+ | PictFormatAlphaMask,
+ &target_format, 0);
+
+ if (found)
+ {
+ /* Now try to find the corresponding visual. */
+
+ for (j = 0; j < n_visuals; ++j)
+ {
+ if (visuals[j].depth != formats[i].depth)
+ continue;
+
+ if (XRenderFindVisualFormat (compositor.display,
+ visuals[j].visual) == found)
+ {
+ /* We got a usable visual with an alpha channel
+ otherwise matching the characteristics of the
+ visual specified by EGL. Return. */
+ *depth = formats[i].depth;
+ visual = visuals[j].visual;
+
+ XFree (visuals);
+ XFree (formats);
+ return visual;
+ }
+ }
+ }
+ }
+ }
+
+ /* Otherwise, nothing was found. Return the original visual
+ untouched, but free visuals. */
+ XFree (visuals);
+ XFree (formats);
+ return visual;
+}
+
static Visual *
FindVisual (VisualID visual, int *depth)
{
@@ -568,6 +705,12 @@ FindVisual (VisualID visual, int *depth)
them. */
value = visuals->visual;
+
+ /* EGL does not know how to find visuals with an alpha channel, even
+ if we specify one in the framebuffer configuration. Detect when
+ that is the case, and pick a better visual. */
+ value = PickBetterVisual (value, &visuals->depth);
+
*depth = visuals->depth;
XLFree (visuals);
@@ -735,8 +878,8 @@ EglCompileCompositeProgram (CompositeProgram *program,
"pos");
program->texture = glGetUniformLocation (program->program,
"texture");
- program->scale = glGetUniformLocation (program->program,
- "scale");
+ program->source = glGetUniformLocation (program->program,
+ "source");
program->invert_y = glGetUniformLocation (program->program,
"invert_y");
@@ -1162,6 +1305,39 @@ GetTextureTarget (EglBuffer *buffer)
abort ();
}
+static void
+ComputeTransformMatrix (EglBuffer *buffer, DrawParams *params)
+{
+ /* Update the transformation matrix of BUFFER. This is a 3x3
+ transformation matrix that maps from texcoords to actual
+ coordinates in the buffer. */
+
+ /* Copy over the identity transform. */
+ MatrixIdentity (&buffer->matrix);
+
+ /* Set the X and Y scales. */
+ if (params->flags & ScaleSet)
+ {
+ Index (buffer->matrix, 0, 0)
+ = (float) (1.0 / params->scale);
+ Index (buffer->matrix, 1, 1)
+ = (float) (1.0 / params->scale);
+ }
+
+ /* Set the offsets. */
+ if (params->flags & OffsetSet)
+ MatrixTranslate (&buffer->matrix,
+ (float) (params->off_x / buffer->width),
+ (float) (params->off_y / buffer->height));
+
+ /* Set the stretch. */
+ if (params->flags & StretchSet)
+ /* Scale the buffer down by this much. */
+ MatrixScale (&buffer->matrix,
+ (float) (params->crop_width / params->stretch_width),
+ (float) (params->crop_height / params->stretch_height));
+}
+
static void
Composite (RenderBuffer buffer, RenderTarget target,
Operation op, int src_x, int src_y, int x, int y,
@@ -1173,7 +1349,6 @@ Composite (RenderBuffer buffer, RenderTarget target,
EglBuffer *egl_buffer;
CompositeProgram *program;
GLenum tex_target;
- GLfloat scale;
egl_target = target.pointer;
egl_buffer = buffer.pointer;
@@ -1188,6 +1363,10 @@ Composite (RenderBuffer buffer, RenderTarget target,
/* Get the texturing target. */
tex_target = GetTextureTarget (egl_buffer);
+ /* Compute the transformation matrix to use to draw the given
+ buffer. */
+ ComputeTransformMatrix (egl_buffer, params);
+
/* dest rectangle on target. */
x1 = x;
y1 = y;
@@ -1233,18 +1412,17 @@ Composite (RenderBuffer buffer, RenderTarget target,
else
glDisable (GL_BLEND);
- if (params->flags & ScaleSet)
- scale = params->scale;
- else
- scale = 1.0f;
-
glActiveTexture (GL_TEXTURE0);
glBindTexture (tex_target, egl_buffer->texture);
- glTexParameteri (tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri (tex_target, GL_TEXTURE_MIN_FILTER,
+ GL_NEAREST);
+ glTexParameteri (tex_target, GL_TEXTURE_MAG_FILTER,
+ GL_NEAREST);
glUseProgram (program->program);
glUniform1i (program->texture, 0);
- glUniform1f (program->scale, scale);
+ glUniformMatrix3fv (program->source, 1, GL_FALSE,
+ egl_buffer->matrix);
glUniform1i (program->invert_y, egl_buffer->flags & InvertY);
glVertexAttribPointer (program->position, 2, GL_FLOAT,
GL_FALSE, 0, verts);
@@ -1501,6 +1679,10 @@ BufferFromDmaBuf (DmaBufAttributes *attributes, Bool *error)
buffer->width = attributes->width;
buffer->height = attributes->height;
buffer->u.type = DmaBufBuffer;
+
+ /* Copy over the identity transform. */
+ MatrixIdentity (&buffer->matrix);
+
i = 0;
/* Find the DRM format in question so we can determine the right
@@ -1651,6 +1833,9 @@ BufferFromShm (SharedMemoryAttributes *attributes, Bool *error)
buffer->height = attributes->height;
buffer->u.type = ShmBuffer;
+ /* Copy over the identity transform. */
+ MatrixIdentity (&buffer->matrix);
+
/* Record the buffer data. */
buffer->u.shm.format = FindFormatInfo (attributes->format);
@@ -1990,10 +2175,52 @@ UpdateTexture (EglBuffer *buffer)
}
static void
-UpdateShmBufferIncrementally (EglBuffer *buffer, pixman_region32_t *damage)
+ReverseTransformToBox (DrawParams *params, pixman_box32_t *box)
+{
+ double x_factor, y_factor;
+
+ if (!params)
+ return;
+
+ /* Apply the inverse of PARAMS to BOX, for use in damage
+ tracking. */
+
+ if (params->flags & ScaleSet)
+ {
+ box->x1 = floor (box->x1 / params->scale);
+ box->y1 = floor (box->y1 / params->scale);
+ box->x2 = ceil (box->x2 / params->scale);
+ box->y2 = ceil (box->y2 / params->scale);
+ }
+
+ if (params->flags & OffsetSet)
+ {
+ /* Since the offset can be a fractional value, also try to
+ include as much as possible in the box. */
+ box->x1 = floor (box->x1 + params->off_x);
+ box->y1 = floor (box->y1 + params->off_y);
+ box->x2 = ceil (box->x2 + params->off_x);
+ box->y2 = ceil (box->y2 + params->off_y);
+ }
+
+ if (params->flags & StretchSet)
+ {
+ x_factor = params->crop_width / params->stretch_width;
+ y_factor = params->crop_height / params->stretch_height;
+
+ box->x1 = floor (box->x1 * x_factor);
+ box->y1 = floor (box->y1 * y_factor);
+ box->x2 = ceil (box->x2 * x_factor);
+ box->y2 = ceil (box->y2 * y_factor);
+ }
+}
+
+static void
+UpdateShmBufferIncrementally (EglBuffer *buffer, pixman_region32_t *damage,
+ DrawParams *params)
{
GLenum target;
- pixman_box32_t *boxes;
+ pixman_box32_t *boxes, box;
int nboxes, i, width, height;
void *data_ptr;
size_t expected_data_size;
@@ -2018,34 +2245,39 @@ UpdateShmBufferIncrementally (EglBuffer *buffer, pixman_region32_t *damage)
the damage. */
for (i = 0; i < nboxes; ++i)
{
- if (boxes[i].x1 >= buffer->width
- || boxes[i].y1 >= buffer->height)
- /* This box isn't contained in the buffer. */
- continue;
+ /* Get a copy of the box. */
+ box = boxes[i];
+
+ /* Transform the box according to any transforms. */
+ ReverseTransformToBox (params, &box);
+
+ /* Clip the box X and Y to 0, 0. */
+ box.x1 = MIN (box.y1, 0);
+ box.y1 = MIN (box.y1, 0);
/* These computations are correct, since box->x2/box->y2 are
actually 1 pixel outside the last pixel in the box. */
- width = MIN (boxes[i].x2, buffer->width) - boxes[i].x1;
- height = MIN (boxes[i].y2, buffer->height) - boxes[i].y1;
+ width = MIN (box.x2, buffer->width) - box.x1;
+ height = MIN (box.y2, buffer->height) - box.y1;
if (width <= 0 || height <= 0)
/* The box is effectively empty because it straddles one of
- the corners of the buffer. */
+ the corners of the buffer, or is outside of the buffer. */
continue;
/* First, set the length of a single row. */
glPixelStorei (GL_UNPACK_ROW_LENGTH_EXT,
buffer->u.shm.stride / (buffer->u.shm.format->bpp / 8));
- /* Next, skip boxes[i].x1 pixels of each row. */
- glPixelStorei (GL_UNPACK_SKIP_PIXELS_EXT, boxes[i].x1);
+ /* Next, skip box.x1 pixels of each row. */
+ glPixelStorei (GL_UNPACK_SKIP_PIXELS_EXT, box.x1);
- /* And boxes[i].y1 rows. */
- glPixelStorei (GL_UNPACK_SKIP_ROWS_EXT, boxes[i].y1);
+ /* And box.y1 rows. */
+ glPixelStorei (GL_UNPACK_SKIP_ROWS_EXT, box.y1);
/* Copy the image into the sub-texture. */
- glTexSubImage2D (target, 0, boxes[i].x1, boxes[i].y1,
- width, height, buffer->u.shm.format->gl_format,
+ glTexSubImage2D (target, 0, box.x1, box.y1, width, height,
+ buffer->u.shm.format->gl_format,
buffer->u.shm.format->gl_type, data_ptr);
}
@@ -2080,7 +2312,8 @@ EnsureTexture (EglBuffer *buffer)
}
static void
-UpdateBuffer (RenderBuffer buffer, pixman_region32_t *damage)
+UpdateBuffer (RenderBuffer buffer, pixman_region32_t *damage,
+ DrawParams *params)
{
EglBuffer *egl_buffer;
@@ -2106,8 +2339,9 @@ UpdateBuffer (RenderBuffer buffer, pixman_region32_t *damage)
{
case ShmBuffer:
/* Update the shared memory buffer incrementally, taking
- into account the damaged area. */
- UpdateShmBufferIncrementally (egl_buffer, damage);
+ into account the damaged area and transform. */
+ UpdateShmBufferIncrementally (egl_buffer, damage,
+ params);
break;
default:
@@ -2119,23 +2353,9 @@ UpdateBuffer (RenderBuffer buffer, pixman_region32_t *damage)
static void
UpdateBufferForDamage (RenderBuffer buffer, pixman_region32_t *damage,
- float scale)
+ DrawParams *params)
{
- pixman_region32_t region;
-
- if (scale != 1.0f && damage)
- {
- /* Scale the damage, specified in scaled coordinates, down to
- texture coordinates. */
-
- pixman_region32_init (®ion);
- XLScaleRegion (®ion, damage, 1.0f / scale,
- 1.0f / scale);
- UpdateBuffer (buffer, ®ion);
- pixman_region32_fini (®ion);
- }
- else
- UpdateBuffer (buffer, damage);
+ UpdateBuffer (buffer, damage, params);
}
static Bool
diff --git a/fns.c b/fns.c
index d105750..79ad7fe 100644
--- a/fns.c
+++ b/fns.c
@@ -380,6 +380,36 @@ XLScaleRegion (pixman_region32_t *dst, pixman_region32_t *src,
XLFree (dst_rects);
}
+void
+XLExtendRegion (pixman_region32_t *dst, pixman_region32_t *src,
+ int extend_x, int extend_y)
+{
+ int nrects, i;
+ pixman_box32_t *src_rects;
+ pixman_box32_t *dst_rects;
+
+ src_rects = pixman_region32_rectangles (src, &nrects);
+
+ if (nrects < 128)
+ dst_rects = alloca (nrects * sizeof *dst_rects);
+ else
+ dst_rects = XLMalloc (nrects * sizeof *dst_rects);
+
+ for (i = 0; i < nrects; ++i)
+ {
+ dst_rects[i].x1 = src_rects[i].x1;
+ dst_rects[i].x2 = src_rects[i].x2 + extend_x;
+ dst_rects[i].y1 = src_rects[i].y1;
+ dst_rects[i].y2 = src_rects[i].y2 + extend_y;
+ }
+
+ pixman_region32_fini (dst);
+ pixman_region32_init_rects (dst, dst_rects, nrects);
+
+ if (nrects >= 128)
+ XLFree (dst_rects);
+}
+
int
XLOpenShm (void)
{
diff --git a/frame_clock.c b/frame_clock.c
index b55653f..4f13811 100644
--- a/frame_clock.c
+++ b/frame_clock.c
@@ -144,15 +144,45 @@ SetSyncCounter (XSyncCounter counter, uint64_t value)
}
static uint64_t
-CurrentHighPrecisionTimestamp (void)
+HighPrecisionTimestamp (struct timespec *clock)
{
- struct timespec clock;
uint64_t timestamp;
- clock_gettime (CLOCK_MONOTONIC, &clock);
+ if (IntMultiplyWrapv (clock->tv_sec, 1000000, ×tamp)
+ || IntAddWrapv (timestamp, clock->tv_nsec / 1000, ×tamp))
+ /* Overflow. */
+ return 0;
- if (IntMultiplyWrapv (clock.tv_sec, 1000000, ×tamp)
- || IntAddWrapv (timestamp, clock.tv_nsec / 1000, ×tamp))
+ return timestamp;
+}
+
+static uint64_t
+HighPrecisionTimestamp32 (struct timespec *clock)
+{
+ uint64_t timestamp, milliseconds;
+
+ /* This function is like CurrentHighPrecisionTimestamp, but the X
+ server time portion is limited to 32 bits. First, the seconds
+ are converted to milliseconds. */
+ if (IntMultiplyWrapv (clock->tv_sec, 1000, &milliseconds))
+ return 0;
+
+ /* Next, the nanosecond portion is also converted to
+ milliseconds. */
+ if (IntAddWrapv (milliseconds, clock->tv_nsec / 1000000,
+ &milliseconds))
+ return 0;
+
+ /* Then, the milliseconds are truncated to 32 bits. */
+ milliseconds &= 0xffffffff;
+
+ /* Finally, add the milliseconds to the timestamp. */
+ if (IntMultiplyWrapv (milliseconds, 1000, ×tamp))
+ return 0;
+
+ /* And add the remaining nsec portion. */
+ if (IntAddWrapv (timestamp, (clock->tv_nsec % 1000000) / 1000,
+ ×tamp))
/* Overflow. */
return 0;
@@ -197,8 +227,8 @@ HandleEndFrame (Timer *timer, void *data, struct timespec time)
static void
PostEndFrame (FrameClock *clock)
{
- uint64_t target, now;
- struct timespec timespec;
+ uint64_t target, fallback, now, additional;
+ struct timespec timespec, current_time;
XLAssert (clock->end_frame_timer == NULL);
@@ -206,11 +236,27 @@ PostEndFrame (FrameClock *clock)
|| !clock->presentation_time)
return;
+ /* Obtain the monotonic clock time. */
+ clock_gettime (CLOCK_MONOTONIC, ¤t_time);
+
/* Calculate the time by which the next frame must be drawn. It is
a multiple of the refresh rate with the vertical blanking
period added. */
target = clock->last_frame_time + clock->presentation_time;
- now = CurrentHighPrecisionTimestamp ();
+ now = HighPrecisionTimestamp (¤t_time);
+ additional = 0;
+
+ /* If now is more than UINT32_MAX * 1000, then this timestamp may
+ overflow the 32-bit X server time, depending on how the X
+ compositing manager implements timestamp generation. Generate a
+ fallback timestamp to use in that situation.
+
+ Use now << 10 instead of now / 1000; the difference is too small
+ to be noticeable. */
+ if (now << 10 > UINT32_MAX)
+ fallback = HighPrecisionTimestamp32 (¤t_time);
+ else
+ fallback = 0;
if (!now)
return;
@@ -218,7 +264,22 @@ PostEndFrame (FrameClock *clock)
/* If the last time the frame time was obtained was that long ago,
return immediately. */
if (now - clock->last_frame_time >= MaxPresentationAge)
- return;
+ {
+ if ((fallback - clock->last_frame_time) <= MaxPresentationAge)
+ {
+ /* Some compositors wrap around once the X server time
+ overflows the 32-bit Time type. If now happens to be
+ within the limit after its millisecond portion is
+ truncated to 32 bits, continue, after setting the
+ additional value the difference between the truncated
+ value and the actual time. */
+
+ additional = now - fallback;
+ now = fallback;
+ }
+ else
+ return;
+ }
while (target < now)
{
@@ -226,15 +287,19 @@ PostEndFrame (FrameClock *clock)
return;
}
- /* Convert the high precision timestamp to a timespec. */
- if (!HighPrecisionTimestampToTimespec (target, ×pec))
- return;
-
/* Use 3/4ths of the presentation time. Any more and we risk the
counter value change signalling the end of the frame arriving
after the presentation deadline. */
target = target - (clock->presentation_time / 4 * 3);
+ /* Add the remainder of now if it was probably truncated by the
+ compositor. */
+ target += additional;
+
+ /* Convert the high precision timestamp to a timespec. */
+ if (!HighPrecisionTimestampToTimespec (target, ×pec))
+ return;
+
/* Schedule the timer marking the end of this frame for the target
time. */
clock->end_frame_timer = AddTimerWithBaseTime (HandleEndFrame,
@@ -435,7 +500,7 @@ XLFrameClockFreeze (FrameClock *clock)
else
{
RemoveTimer (clock->end_frame_timer);
- clock->end_frame_timer = NULL;
+ clock->end_frame_timer = NULL;
}
/* Don't unfreeze until the next EndFrame. */
diff --git a/picture_renderer.c b/picture_renderer.c
index 7342495..ac63fd6 100644
--- a/picture_renderer.c
+++ b/picture_renderer.c
@@ -325,16 +325,54 @@ GetScale (DrawParams *params)
return 1.0;
}
+static double
+GetSourceX (DrawParams *params)
+{
+ if (params->flags & OffsetSet)
+ return params->off_x;
+
+ return 0.0;
+}
+
+static double
+GetSourceY (DrawParams *params)
+{
+ if (params->flags & OffsetSet)
+ return params->off_y;
+
+ return 0.0;
+}
+
+static Bool
+CompareStretch (DrawParams *params, DrawParams *other)
+{
+ if ((params->flags & StretchSet)
+ != (other->flags & StretchSet))
+ return False;
+
+ if (params->flags & StretchSet)
+ return (other->crop_width == params->crop_width
+ && other->crop_height == params->crop_height
+ && other->stretch_width == params->stretch_width
+ && other->stretch_height == params->stretch_height);
+
+ return True;
+}
+
static void
MaybeApplyTransform (PictureBuffer *buffer, DrawParams *params)
{
XTransform transform;
+ Matrix ftransform;
- if (GetScale (params) == GetScale (&buffer->params))
+ if (GetScale (params) == GetScale (&buffer->params)
+ && GetSourceX (params) == GetSourceX (&buffer->params)
+ && GetSourceY (params) == GetSourceY (&buffer->params)
+ && CompareStretch (params, &buffer->params))
/* Nothing changed. */
return;
- /* Otherwise, compute and apply the new transform. */
+ /* Otherwise, compute and apply the new transform. */
if (!params->flags)
/* No transform of any kind is set, use the identity matrix. */
XRenderSetPictureTransform (compositor.display,
@@ -342,14 +380,29 @@ MaybeApplyTransform (PictureBuffer *buffer, DrawParams *params)
&identity_transform);
else
{
- memset (&transform, 0, sizeof transform);
+ MatrixIdentity (&ftransform);
- transform.matrix[0][0] = XDoubleToFixed (1.0 / params->scale);
- transform.matrix[1][1] = XDoubleToFixed (1.0 / params->scale);
- transform.matrix[2][2] = XDoubleToFixed (1);
+ /* Note that these must be applied in the right order. First,
+ the scale is applied. Then, the offset, and finally the
+ stretch. */
+ if (params->flags & ScaleSet)
+ MatrixScale (&ftransform, 1.0 / GetScale (params),
+ 1.0 / GetScale (params));
- XRenderSetPictureTransform (compositor.display,
- buffer->picture,
+ if (params->flags & OffsetSet)
+ MatrixTranslate (&ftransform, params->off_x, params->off_y);
+
+ if (params->flags & StretchSet)
+ MatrixScale (&ftransform,
+ params->crop_width / params->stretch_width,
+ params->crop_height / params->stretch_height);
+
+ /* Export the matrix to an XTransform. */
+ MatrixExport (&ftransform, &transform);
+
+ /* Set the transform. The transform maps from dest coords to
+ picture coords, so that [X Y 1] = TRANSFORM * [DX DY 1]. */
+ XRenderSetPictureTransform (compositor.display, buffer->picture,
&transform);
}
diff --git a/positioner.c b/positioner.c
index 1f171da..ecbb4c3 100644
--- a/positioner.c
+++ b/positioner.c
@@ -60,7 +60,8 @@ struct _Positioner
int refcount;
};
-/* Scale factor used during constraint adjustment calculation. */
+/* Surface used to handle scaling during constraint adjustment
+ calculation. */
static double scale_adjustment_factor;
static void
@@ -673,8 +674,12 @@ GetAdjustmentOffset (Role *parent, int *off_x, int *off_y)
&parent_gy, NULL, NULL);
XLXdgRoleCurrentRootPosition (parent, &root_x, &root_y);
- *off_x = root_x + parent_gx * parent->surface->factor;
- *off_y = root_y + parent_gy * parent->surface->factor;
+ /* Convert the gx and gy to the window coordinate system. */
+ TruncateSurfaceToWindow (parent->surface, parent_gx, parent_gy,
+ &parent_gx, &parent_gy);
+
+ *off_x = root_x + parent_gx;
+ *off_y = root_y + parent_gy;
}
static void
@@ -684,18 +689,19 @@ ApplyConstraintAdjustment (Positioner *positioner, Role *parent, int x,
{
int width, height, cx, cy, cwidth, cheight, off_x, off_y;
- width = positioner->width * scale_adjustment_factor;
- height = positioner->height * scale_adjustment_factor;
+ width = positioner->width;
+ height = positioner->height;
+
+ /* Constraint calculations are simplest if we use scaled
+ coordinates, and then unscale them later. */
+ TruncateSurfaceToWindow (parent->surface, x, y, &x, &y);
+ TruncateScaleToWindow (parent->surface, width, height, &width,
+ &height);
/* Set the factor describing how to convert surface coordinates to
window ones. */
scale_adjustment_factor = parent->surface->factor;
- /* Constraint calculations are simplest if we use scaled
- coordinates, and then unscale them later. */
- x *= scale_adjustment_factor;
- y *= scale_adjustment_factor;
-
if (positioner->constraint_adjustment
== XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE)
/* There is no constraint adjustment. */
@@ -740,10 +746,14 @@ ApplyConstraintAdjustment (Positioner *positioner, Role *parent, int x,
off_y, &y, &height);
finish:
- *x_out = x / scale_adjustment_factor;
- *y_out = y / scale_adjustment_factor;
- *width_out = width / scale_adjustment_factor;
- *height_out = height / scale_adjustment_factor;
+ /* Now, scale the coordinates back. */
+ TruncateWindowToSurface (parent->surface, x, y, &x, &y);
+ TruncateScaleToSurface (parent->surface, width, height, &width, &height);
+
+ *x_out = x;
+ *y_out = y;
+ *width_out = width;
+ *height_out = height;
}
void
@@ -754,8 +764,12 @@ XLPositionerCalculateGeometry (Positioner *positioner, Role *parent,
int x, y, width, height;
CalculatePosition (positioner, &x, &y);
- ApplyConstraintAdjustment (positioner, parent, x, y,
- &x, &y, &width, &height);
+
+ if (parent->surface)
+ ApplyConstraintAdjustment (positioner, parent, x, y,
+ &x, &y, &width, &height);
+ else
+ width = positioner->width, height = positioner->height;
*x_out = x;
*y_out = y;
diff --git a/renderer.c b/renderer.c
index 3336944..7e466bf 100644
--- a/renderer.c
+++ b/renderer.c
@@ -241,12 +241,12 @@ RenderFreeDmabufBuffer (RenderBuffer buffer)
void
RenderUpdateBufferForDamage (RenderBuffer buffer, pixman_region32_t *damage,
- float scale)
+ DrawParams *params)
{
if (!buffer_funcs.update_buffer_for_damage)
return;
- buffer_funcs.update_buffer_for_damage (buffer, damage, scale);
+ buffer_funcs.update_buffer_for_damage (buffer, damage, params);
}
Bool
diff --git a/shaders.txt b/shaders.txt
index 2b4249d..9163601 100644
--- a/shaders.txt
+++ b/shaders.txt
@@ -31,19 +31,19 @@ main (void)
attribute vec2 pos;
attribute vec2 texcoord;
varying vec2 v_texcoord;
+uniform mat3 source;
void
main (void)
{
gl_Position = vec4 (pos.x, pos.y, 1.0, 1.0);
- v_texcoord = texcoord;
+ v_texcoord = (source * vec3 (texcoord, 1.0)).xy;
}
//==
//== Composite Rectangle Fragment Shader RGBA
precision mediump float;
uniform sampler2D texture;
-uniform float scale;
uniform bool invert_y;
varying vec2 v_texcoord;
@@ -52,7 +52,7 @@ main (void)
{
vec2 texcoord;
- texcoord = v_texcoord / scale;
+ texcoord = v_texcoord;
if (invert_y)
texcoord = vec2 (texcoord.x, 1.0 - texcoord.y);
@@ -64,7 +64,7 @@ main (void)
//== Composite Rectangle Fragment Shader RGBX
precision mediump float;
uniform sampler2D texture;
-uniform float scale;
+uniform mat3 source;
uniform bool invert_y;
varying vec2 v_texcoord;
@@ -73,14 +73,12 @@ main (void)
{
vec2 texcoord;
- texcoord = v_texcoord / scale;
+ texcoord = v_texcoord;
if (invert_y)
texcoord = vec2 (texcoord.x, 1.0 - texcoord.y);
- gl_FragColor = vec4 (texture2D (texture,
- texcoord).rgb,
- 1.0);
+ gl_FragColor = vec4 (texture2D (texture, texcoord).rgb, 1.0);
}
//==
@@ -89,7 +87,7 @@ main (void)
precision mediump float;
uniform samplerExternalOES texture;
-uniform float scale;
+uniform mat3 source;
uniform bool invert_y;
varying vec2 v_texcoord;
@@ -98,7 +96,7 @@ main (void)
{
vec2 texcoord;
- texcoord = v_texcoord / scale;
+ texcoord = v_texcoord;
if (invert_y)
texcoord = vec2 (texcoord.x, 1.0 - texcoord.y);
diff --git a/subcompositor.c b/subcompositor.c
index d5cf26b..4ab9bf6 100644
--- a/subcompositor.c
+++ b/subcompositor.c
@@ -192,7 +192,11 @@ enum
{
/* This means that the view and all its inferiors should be
skipped in bounds computation, input tracking, et cetera. */
- ViewIsUnmapped = 1,
+ ViewIsUnmapped = 1,
+ /* This means that the view has a viewport specifying its size,
+ effectively decoupling its relation to the buffer width and
+ height. */
+ ViewIsViewported = 1 << 2,
};
#define IsViewUnmapped(view) \
@@ -202,6 +206,13 @@ enum
#define ClearUnmapped(view) \
((view)->flags &= ~ViewIsUnmapped)
+#define IsViewported(view) \
+ ((view)->flags & ViewIsViewported)
+#define SetViewported(view) \
+ ((view)->flags |= ViewIsViewported)
+#define ClearViewported(view) \
+ ((view)->flags &= ~ViewIsViewported)
+
#endif
struct _List
@@ -273,6 +284,13 @@ struct _View
/* Flags; whether or not this view is unmapped, etc. */
int flags;
+
+ /* The viewport data. */
+ double src_x, src_y, crop_width, crop_height, dest_width, dest_height;
+
+ /* Fractional offset applied to the view contents and damage during
+ compositing. */
+ double fract_x, fract_y;
#else
/* Label used during tests. */
const char *label;
@@ -749,7 +767,6 @@ ViewUpdateBoundsForInsert (View *view)
view);
}
-
#endif
TEST_STATIC void
@@ -1214,6 +1231,10 @@ main (int argc, char **argv)
#ifndef TEST
+/* Notice that VIEW's size has changed, while VIEW itself has not
+ moved. Recompute the max_x, min_x, min_y, and max_y of its
+ subcompositor. */
+
static void
ViewAfterSizeUpdate (View *view)
{
@@ -1272,7 +1293,11 @@ ViewAttachBuffer (View *view, ExtBuffer *buffer)
&& (XLBufferWidth (buffer) != XLBufferWidth (old)
|| XLBufferHeight (buffer) != XLBufferHeight (old))))
{
- if (view->subcompositor)
+ if (view->subcompositor
+ /* If a viewport is specified, then the view width and
+ height are determined independently from the buffer
+ size. */
+ && !IsViewported (view))
{
/* A new buffer was attached, so garbage the subcompositor
as well. */
@@ -1398,6 +1423,20 @@ ViewMove (View *view, int x, int y)
}
}
+void
+ViewMoveFractional (View *view, double x, double y)
+{
+ XLAssert (x < 1.0 && y < 1.0);
+
+ /* This does not necessitate adjustments to the view size, but does
+ require that the subcompositor be garbaged. */
+ view->fract_x = x;
+ view->fract_y = y;
+
+ if (view->subcompositor)
+ SetGarbaged (view->subcompositor);
+}
+
void
ViewDetach (View *view)
{
@@ -1477,8 +1516,9 @@ ViewFree (View *view)
void
ViewDamage (View *view, pixman_region32_t *damage)
{
- pixman_region32_union (&view->damage,
- &view->damage,
+ /* This damage must be transformed by the viewport and scale, but
+ must NOT be transformed by the subpixel (fractional) offset. */
+ pixman_region32_union (&view->damage, &view->damage,
damage);
}
@@ -1509,6 +1549,15 @@ ViewGetSubcompositor (View *view)
return view->subcompositor;
}
+static double
+GetContentScale (int scale)
+{
+ if (scale > 0)
+ return 1.0 / (scale + 1);
+
+ return -scale + 1;
+}
+
int
ViewWidth (View *view)
{
@@ -1517,12 +1566,20 @@ ViewWidth (View *view)
if (!view->buffer)
return 0;
+ if (IsViewported (view))
+ /* The view has a viewport specified. view->dest_width and
+ view->dest_height can be fractional values. When that happens,
+ we simply use the ceiling and rely on the renderer to DTRT with
+ scaling. */
+ return ceil (view->dest_width
+ * GetContentScale (view->scale));
+
width = XLBufferWidth (view->buffer);
if (view->scale < 0)
- return width * (abs (view->scale) + 1);
+ return ceil (width * (abs (view->scale) + 1));
else
- return width / (view->scale + 1);
+ return ceil (width / (view->scale + 1));
}
int
@@ -1533,68 +1590,82 @@ ViewHeight (View *view)
if (!view->buffer)
return 0;
+ if (IsViewported (view))
+ /* The view has a viewport specified. view->dest_width and
+ view->dest_height can be fractional values. When that happens,
+ we simply use the ceiling and rely on the renderer to DTRT with
+ scaling. */
+ return ceil (view->dest_height
+ * GetContentScale (view->scale));
+
height = XLBufferHeight (view->buffer);
if (view->scale < 0)
- return height * (abs (view->scale) + 1);
+ return ceil (height * (abs (view->scale) + 1));
else
- return height / (view->scale + 1);
+ return ceil (height / (view->scale + 1));
}
void
ViewSetScale (View *view, int scale)
{
- int doflags;
-
- /* First, assume we will have to compute both max_x and max_y. */
- doflags = DoMaxX | DoMaxY;
-
if (view->scale == scale)
return;
view->scale = scale;
- if (view->subcompositor)
- {
- /* If the view is now wider than max_x and/or max_y, update those
- now. */
-
- if (view->subcompositor->max_x < ViewMaxX (view))
- {
- view->subcompositor->max_x = ViewMaxX (view);
-
- /* We don't have to update max_x anymore. */
- doflags &= ~DoMaxX;
- }
-
- if (view->subcompositor->max_y < ViewMaxY (view))
- {
- view->subcompositor->max_y = ViewMaxY (view);
-
- /* We don't have to update max_x anymore. */
- doflags &= ~DoMaxY;
- }
-
- /* Finally, update the bounds. */
- SubcompositorUpdateBounds (view->subcompositor,
- doflags);
- }
+ /* Recompute subcompositor bounds; they could've changed. */
+ ViewAfterSizeUpdate (view);
}
-static double
-GetContentScale (int scale)
-{
- if (scale > 0)
- return 1.0 / (scale + 1);
- return -scale + 1;
+void
+ViewSetViewport (View *view, double src_x, double src_y,
+ double crop_width, double crop_height,
+ double dest_width, double dest_height)
+{
+ SetViewported (view);
+
+ view->src_x = src_x;
+ view->src_y = src_y;
+ view->crop_width = crop_width;
+ view->crop_height = crop_height;
+ view->dest_width = dest_width;
+ view->dest_height = dest_height;
+
+ /* Update min_x and min_y. */
+ ViewAfterSizeUpdate (view);
+
+ /* Garbage the subcompositor as damage can no longer be trusted. */
+ if (view->subcompositor)
+ SubcompositorGarbage (view->subcompositor);
+}
+
+void
+ViewClearViewport (View *view)
+{
+ ClearViewported (view);
+
+ /* Update min_x and min_y. */
+ ViewAfterSizeUpdate (view);
+
+ /* Garbage the subcompositor as damage can no longer be trusted. */
+ if (view->subcompositor)
+ SubcompositorGarbage (view->subcompositor);
}
static void
-ViewComputeTransform (View *view, DrawParams *params)
+ViewComputeTransform (View *view, DrawParams *params, Bool draw)
{
+ /* Compute the effective transform of VIEW, then put it in PARAMS.
+ DRAW means whether or not the transform is intended for drawing;
+ when not set, the parameters are being used for damage tracking
+ instead. */
+
/* First, there is no transform. */
params->flags = 0;
+ params->off_x = 0.0;
+ params->off_y = 0.0;
if (view->scale)
{
@@ -1602,6 +1673,45 @@ ViewComputeTransform (View *view, DrawParams *params)
params->flags |= ScaleSet;
params->scale = GetContentScale (view->scale);
}
+
+ if (IsViewported (view))
+ {
+ /* Set the viewport (a.k.a "stretch" and "offset" in the
+ rendering code). */
+
+ params->flags |= StretchSet;
+ params->flags |= OffsetSet;
+
+ params->off_x = view->src_x;
+ params->off_y = view->src_y;
+ params->crop_width = view->crop_width;
+ params->stretch_width = view->dest_width;
+ params->crop_height = view->crop_height;
+ params->stretch_height = view->dest_height;
+
+ /* If the crop width/height were not specified, use the current
+ buffer width/height. */
+
+ if (params->crop_width == -1)
+ {
+ params->crop_width = (XLBufferWidth (view->buffer)
+ * GetContentScale (view->scale));
+ params->crop_height = (XLBufferHeight (view->buffer)
+ * GetContentScale (view->scale));
+ }
+ }
+
+ if ((view->fract_x != 0.0 || view->fract_y != 0.0)
+ && draw)
+ {
+ params->flags |= OffsetSet;
+
+ /* This is not entirely right. When applying a negative offset,
+ contents to the left of where the picture actually is can
+ appear to "shine through". */
+ params->off_x -= view->fract_x;
+ params->off_y -= view->fract_y;
+ }
}
void
@@ -1740,7 +1850,7 @@ IntersectBoxes (pixman_box32_t *in, pixman_box32_t *other,
out->y2 = MIN (a.y2, b.y2);
/* If the intersection is empty, return False. */
- if (out->x2 - out->x1 <= 0 || out->y2 - out->y1 <= 0)
+ if (out->x2 - out->x1 < 0 || out->y2 - out->y1 < 0)
return False;
return True;
@@ -1754,7 +1864,7 @@ SubcompositorUpdate (Subcompositor *subcompositor)
View *start, *original_start, *view, *first;
List *list;
pixman_box32_t *boxes, *extents, temp_boxes;
- int nboxes, i, tx, ty;
+ int nboxes, i, tx, ty, view_width, view_height;
Operation op;
RenderBuffer buffer;
int min_x, min_y;
@@ -1862,6 +1972,10 @@ SubcompositorUpdate (Subcompositor *subcompositor)
if (!view->buffer)
goto next;
+ /* Obtain the view width and height here. */
+ view_width = ViewWidth (view);
+ view_height = ViewHeight (view);
+
if (!start)
{
start = view;
@@ -1882,8 +1996,7 @@ SubcompositorUpdate (Subcompositor *subcompositor)
pixman_region32_intersect_rect (&temp, &view->opaque,
view->abs_x, view->abs_y,
- ViewWidth (view),
- ViewHeight (view));
+ view_width, view_height);
if (IsOpaqueDirty (subcompositor))
pixman_region32_union (&total_opaque, &total_opaque, &temp);
@@ -1922,8 +2035,7 @@ SubcompositorUpdate (Subcompositor *subcompositor)
pixman_region32_intersect_rect (&temp, &view->input,
view->abs_x, view->abs_y,
- ViewWidth (view),
- ViewHeight (view));
+ view_width, view_height);
pixman_region32_union (&total_input, &total_input, &temp);
@@ -1942,9 +2054,13 @@ SubcompositorUpdate (Subcompositor *subcompositor)
However, the function does not perform partial updates
when the damage region is empty. */
+ /* Compute the transform and put it in draw_params, so TRT
+ can be done in the rendering backend. */
+ ViewComputeTransform (view, &draw_params, False);
+
buffer = XLRenderBufferFromBuffer (view->buffer);
RenderUpdateBufferForDamage (buffer, &list->view->damage,
- GetContentScale (list->view->scale));
+ &draw_params);
if (pixman_region32_not_empty (&list->view->damage))
{
@@ -1958,9 +2074,21 @@ SubcompositorUpdate (Subcompositor *subcompositor)
clipping. */
pixman_region32_intersect_rect (&temp, &list->view->damage,
view->abs_x, view->abs_y,
- ViewWidth (view),
- ViewHeight (view));
+ view_width, view_height);
+ /* If a fractional offset is set, extend the damage by 1
+ pixel to cover the offset. */
+ if (view->fract_x != 0.0 && view->fract_y != 0.0)
+ {
+ XLExtendRegion (&temp, &temp, 1, 1);
+
+ /* Intersect the region again. */
+ pixman_region32_intersect_rect (&temp, &temp, view->abs_x,
+ view->abs_y, view_width,
+ view_height);
+ }
+
+ /* Union the region with the update region. */
pixman_region32_union (&update_region, &temp, &update_region);
/* If the damage extends outside the area known to be
@@ -2100,6 +2228,10 @@ SubcompositorUpdate (Subcompositor *subcompositor)
if (!view->buffer)
goto next_1;
+ /* Get the view width and height here. */
+ view_width = ViewWidth (view);
+ view_height = ViewHeight (view);
+
buffer = XLRenderBufferFromBuffer (view->buffer);
if (IsGarbaged (subcompositor))
@@ -2113,12 +2245,18 @@ SubcompositorUpdate (Subcompositor *subcompositor)
Note that if the subcompositor is not garbaged, then this
has already been done. */
- RenderUpdateBufferForDamage (buffer, NULL, 0.0f);
- else if (age < 0 || age > 3)
- /* The target contents are too old, but the damage can be
- trusted. */
- RenderUpdateBufferForDamage (buffer, &view->damage,
- GetContentScale (list->view->scale));
+ RenderUpdateBufferForDamage (buffer, NULL, NULL);
+ else if (age < 0 || age >= 3)
+ {
+ /* Compute the transform and put it in draw_params, so TRT
+ can be done in the rendering backend. */
+ ViewComputeTransform (view, &draw_params, False);
+
+ /* The target contents are too old, but the damage can be
+ trusted. */
+ RenderUpdateBufferForDamage (buffer, &view->damage,
+ &draw_params);
+ }
if (!first)
{
@@ -2126,7 +2264,7 @@ SubcompositorUpdate (Subcompositor *subcompositor)
with PictOpSrc so that transparency is applied correctly,
if it contains the entire update region. */
- if (IsGarbaged (subcompositor) || age < 0 || age > 3)
+ if (IsGarbaged (subcompositor) || age < 0 || age >= 3)
{
extents = &temp_boxes;
@@ -2149,7 +2287,7 @@ SubcompositorUpdate (Subcompositor *subcompositor)
/* Otherwise, fill the whole update region with
transparency. */
- if (IsGarbaged (subcompositor) || age < 0 || age > 3)
+ if (IsGarbaged (subcompositor) || age < 0 || age >= 3)
{
/* Use the entire subcompositor bounds if
garbaged. */
@@ -2174,7 +2312,7 @@ SubcompositorUpdate (Subcompositor *subcompositor)
first = view;
/* Compute the transform and put it in draw_params. */
- ViewComputeTransform (view, &draw_params);
+ ViewComputeTransform (view, &draw_params, True);
if (!IsGarbaged (subcompositor) && (age >= 0 && age < 3))
{
@@ -2190,8 +2328,8 @@ SubcompositorUpdate (Subcompositor *subcompositor)
temp_boxes.x1 = view->abs_x;
temp_boxes.y1 = view->abs_y;
- temp_boxes.x2 = view->abs_x + ViewWidth (view);
- temp_boxes.y2 = view->abs_y + ViewHeight (view);
+ temp_boxes.x2 = view->abs_x + view_width;
+ temp_boxes.y2 = view->abs_y + view_height;
if (IntersectBoxes (&boxes[i], &temp_boxes, &temp_boxes))
RenderComposite (buffer, subcompositor->target, op,
@@ -2226,9 +2364,9 @@ SubcompositorUpdate (Subcompositor *subcompositor)
/* dst-y. */
view->abs_y - min_y + ty,
/* width. */
- ViewWidth (view),
+ view_width,
/* height, draw-params. */
- ViewHeight (view), &draw_params);
+ view_height, &draw_params);
/* Also adjust the opaque and input regions here. */
@@ -2248,8 +2386,7 @@ SubcompositorUpdate (Subcompositor *subcompositor)
pixman_region32_intersect_rect (&temp, &view->opaque,
view->abs_x, view->abs_y,
- ViewWidth (view),
- ViewHeight (view));
+ view_width, view_height);
pixman_region32_union (&total_opaque, &temp, &total_opaque);
/* Translate it back. */
@@ -2270,8 +2407,7 @@ SubcompositorUpdate (Subcompositor *subcompositor)
list->view->abs_y);
pixman_region32_intersect_rect (&temp, &view->input,
view->abs_x, view->abs_y,
- ViewWidth (view),
- ViewHeight (view));
+ view_width, view_height);
pixman_region32_union (&total_input, &temp, &total_input);
/* Translate it back. */
@@ -2292,7 +2428,7 @@ SubcompositorUpdate (Subcompositor *subcompositor)
complete:
if (IsGarbaged (subcompositor)
- || ((age < 0 || age > 3)
+ || ((age < 0 || age >= 3)
&& (IsInputDirty (subcompositor)
|| IsOpaqueDirty (subcompositor))))
{
@@ -2436,11 +2572,17 @@ SubcompositorExpose (Subcompositor *subcompositor, XEvent *event)
boxes = pixman_region32_rectangles (&temp, &nboxes);
/* Compute the transform. */
- ViewComputeTransform (view, &draw_params);
+ ViewComputeTransform (view, &draw_params, False);
/* Update the attached buffer from any damage. */
RenderUpdateBufferForDamage (buffer, &list->view->damage,
- GetContentScale (list->view->scale));
+ &draw_params);
+
+ /* If a fractional offset is set, recompute the transform again,
+ this time for drawing. */
+ if (list->view->fract_x != 0.0
+ || list->view->fract_y != 0.0)
+ ViewComputeTransform (view, &draw_params, True);
for (i = 0; i < nboxes; ++i)
RenderComposite (buffer, subcompositor->target, op,
diff --git a/subsurface.c b/subsurface.c
index b0d236c..3996f53 100644
--- a/subsurface.c
+++ b/subsurface.c
@@ -493,8 +493,10 @@ MaybeUpdateOutputs (Subsurface *subsurface)
return;
/* Compute the positions relative to the parent. */
- x = subsurface->current_substate.x * subsurface->parent->factor;
- y = subsurface->current_substate.y * subsurface->parent->factor;
+ x = floor (subsurface->current_substate.x
+ * subsurface->parent->factor);
+ y = floor (subsurface->current_substate.y
+ * subsurface->parent->factor);
/* And the base X and Y. */
base_x = subsurface->role.surface->output_x;
@@ -525,6 +527,38 @@ MaybeUpdateOutputs (Subsurface *subsurface)
width, height);
}
+static void
+MoveFractional (Subsurface *subsurface)
+{
+ double x, y;
+ int x_int, y_int;
+
+ /* Move the surface to a fractional window (subcompositor)
+ coordinate relative to the parent. This is done by placing the
+ surface at the floor of the coordinates, and then offsetting the
+ image and input by the remainder during rendering. */
+ SurfaceToWindow (subsurface->parent, subsurface->current_substate.x,
+ subsurface->current_substate.y, &x, &y);
+
+ x_int = floor (x);
+ y_int = floor (y);
+
+ /* Move the subsurface to x_int, y_int. */
+ ViewMove (subsurface->role.surface->view, x_int, y_int);
+ ViewMove (subsurface->role.surface->under, x_int, y_int);
+
+ /* Apply the fractional offset. */
+ ViewMoveFractional (subsurface->role.surface->view,
+ x - x_int, y - y_int);
+ ViewMoveFractional (subsurface->role.surface->under,
+ x - x_int, y - y_int);
+
+ /* And set the fractional offset on the surface for input handling
+ purposes. */
+ subsurface->role.surface->input_delta_x = x - x_int;
+ subsurface->role.surface->input_delta_y = y - y_int;
+}
+
static void
AfterParentCommit (Surface *surface, void *data)
{
@@ -540,17 +574,14 @@ AfterParentCommit (Surface *surface, void *data)
if (subsurface->pending_substate.flags & PendingPosition)
{
+ /* Apply the new position. */
subsurface->current_substate.x
= subsurface->pending_substate.x;
subsurface->current_substate.y
= subsurface->pending_substate.y;
- /* The X and Y coordinates here are also parent-local and must
- be scaled by the global scale factor. */
-
- ViewMove (subsurface->role.surface->view,
- subsurface->current_substate.x * subsurface->parent->factor,
- subsurface->current_substate.y * subsurface->parent->factor);
+ /* And move the views. */
+ MoveFractional (subsurface);
}
/* And any cached surface state too. */
@@ -680,6 +711,10 @@ Setup (Surface *surface, Role *role)
ViewGetSubcompositor (parent_view));
ViewInsert (parent_view, surface->view);
+ /* Now move the subsurface to its initial location (0, 0) */
+ if (subsurface->parent)
+ MoveFractional (subsurface);
+
/* Now add the subsurface to the parent's list of subsurfaces. */
subsurface->parent->subsurfaces
= XLListPrepend (subsurface->parent->subsurfaces,
@@ -698,9 +733,7 @@ Rescale (Surface *surface, Role *role)
/* The scale factor changed; move the subsurface to the new correct
position. */
- ViewMove (surface->view,
- subsurface->current_substate.x * subsurface->parent->factor,
- subsurface->current_substate.y * subsurface->parent->factor);
+ MoveFractional (subsurface);
}
static void
diff --git a/surface.c b/surface.c
index 1e066f7..b4800b0 100644
--- a/surface.c
+++ b/surface.c
@@ -22,6 +22,7 @@ along with 12to11. If not, see . */
#include
#include
+#include
#include "compositor.h"
@@ -558,9 +559,7 @@ ApplyScale (Surface *surface)
D = C / L
D = (A * E) / (A / B)
- D = B * E
-
- Phew. */
+ D = B * E. */
b = scale;
g = global_scale_factor;
@@ -637,6 +636,161 @@ ApplyInputRegion (Surface *surface)
}
}
+static void
+ApplyViewport (Surface *surface)
+{
+ State *state;
+ int dest_width, dest_height;
+ double crop_width, crop_height, src_x, src_y;
+ double max_width, max_height;
+
+ state = &surface->current_state;
+
+ /* If no values are specified, return and clear the viewport. */
+ if (state->src_x == -1 && state->dest_width == -1)
+ {
+ ViewClearViewport (surface->view);
+ return;
+ }
+
+ /* Calculate the viewport. crop_width and crop_height describe the
+ amount by which to crop the surface contents, after conversion to
+ window geometry. dest_width and dest_height then describe how
+ large the surface should be. src_x and src_y describe the
+ origin at which to start sampling from the buffer. */
+
+ if (state->buffer)
+ {
+ max_width = XLBufferWidth (state->buffer);
+ max_height = XLBufferHeight (state->buffer);
+ }
+ else
+ {
+ /* If state->buffer is not set then the source rectangle does
+ not have to be validated now. It will be validated later
+ once the buffer is attached. */
+ max_width = DBL_MAX;
+ max_height = DBL_MAX;
+ }
+
+ if (state->src_x != -1.0)
+ {
+ /* This means a source rectangle has been specified. Set src_x
+ and src_y. */
+ src_x = state->src_x;
+ src_y = state->src_y;
+
+ /* Also set crop_width and crop_height. */
+ crop_width = state->src_width;
+ crop_height = state->src_height;
+ }
+ else
+ {
+ /* Set crop_width and crop_height to the default values, which
+ are the width and height of the buffer divided by the buffer
+ scale. */
+ src_x = 0;
+ src_y = 0;
+
+ crop_width = -1;
+ crop_height = -1;
+ }
+
+ /* Now, either dest_width/dest_height are specified, or dest_width
+ and dest_height should be crop_width and crop_height. If the
+ latter, then crop_width and crop_height must be integer
+ values. */
+
+ if (state->dest_width != -1)
+ {
+ /* This means dest_width and dest_height have been explicitly
+ specified. */
+ dest_width = state->dest_width;
+ dest_height = state->dest_height;
+ }
+ else
+ {
+ if ((rint (crop_width) != crop_width
+ || rint (crop_height) != crop_height)
+ /* If the src_width and src_height were not specified
+ manually but were computed from the buffer scale, don't
+ complain that they are not integer values. The
+ underlying viewport code satisfactorily handles
+ fractional width and height anyway. */
+ && state->src_x != 1.0)
+ goto bad_size;
+
+ dest_width = state->src_width;
+ dest_height = state->src_height;
+ }
+
+ /* Now all of the fields above must be set. Verify that none of
+ them lie outside the buffer. */
+ if (state->src_x != -1
+ && (src_x + crop_width - 1 >= max_width / state->buffer_scale
+ || src_y + crop_height - 1 >= max_height / state->buffer_scale))
+ goto out_of_buffer;
+
+ /* Finally, set the viewport. Convert the values to window
+ coordinates. */
+ src_x *= surface->factor;
+ src_y *= surface->factor;
+
+ if (crop_width != -1)
+ {
+ crop_width *= surface->factor;
+ crop_height *= surface->factor;
+ }
+
+ dest_width *= surface->factor;
+ dest_height *= surface->factor;
+
+ /* And really set the viewport. */
+ ViewSetViewport (surface->view, src_x, src_y, crop_width,
+ crop_height, dest_width, dest_height);
+
+ return;
+
+ bad_size:
+ /* By this point, surface->viewport should be non-NULL; however, if
+ a synchronous subsurface applies invalid viewporter state,
+ commits it, destroys the wp_viewport resource, and the parent
+ commits, then the cached state applied due to the parent commit
+ will be invalid, but the viewport resource will no longer be
+ associated with the surface. I don't know what to do in that
+ case, so leave the behavior undefined. */
+ if (surface->viewport)
+ XLWpViewportReportBadSize (surface->viewport);
+ return;
+
+ out_of_buffer:
+ if (surface->viewport)
+ XLWpViewportReportOutOfBuffer (surface->viewport);
+}
+
+static void
+CheckViewportValues (Surface *surface)
+{
+ State *state;
+ int width, height;
+
+ state = &surface->current_state;
+
+ if (!surface->viewport || state->src_x == -1.0
+ || !state->buffer)
+ return;
+
+ /* A buffer is attached and a viewport source rectangle is set;
+ check that it remains in bounds. */
+
+ width = XLBufferWidth (state->buffer);
+ height = XLBufferHeight (state->buffer);
+
+ if (state->src_x + state->src_width - 1 >= width / state->buffer_scale
+ || state->src_y + state->src_height - 1 >= height / state->buffer_scale)
+ XLWpViewportReportBadSize (surface->viewport);
+}
+
static void
HandleScaleChanged (void *data, int new_scale)
{
@@ -650,6 +804,7 @@ HandleScaleChanged (void *data, int new_scale)
ApplyScale (surface);
ApplyInputRegion (surface);
ApplyOpaqueRegion (surface);
+ ApplyViewport (surface);
/* Next, call any role-specific hooks. */
if (surface->role && surface->role->funcs.rescale)
@@ -680,21 +835,44 @@ ApplyDamage (Surface *surface)
{
pixman_region32_t temp;
int scale;
+ float x_factor, y_factor;
scale = GetEffectiveScale (surface->current_state.buffer_scale);
/* N.B. that this must come after the scale is applied. */
- if (scale)
+ if (scale || surface->current_state.src_x != -1
+ || surface->current_state.dest_width != -1)
{
pixman_region32_init (&temp);
+ if (!scale)
+ x_factor = y_factor = 1.0;
if (scale > 0)
- XLScaleRegion (&temp, &surface->current_state.damage,
- 1.0 / (scale + 1), 1.0 / (scale + 1));
+ x_factor = y_factor = 1.0 / (scale + 1);
else
+ x_factor = y_factor = abs (scale) + 1;
+
+ /* If a viewport dest size is set, add that to the scale as
+ well. */
+ if (surface->current_state.src_width != -1)
+ {
+ x_factor += (float) (surface->current_state.src_width
+ / surface->current_state.dest_width);
+ y_factor += (float) (surface->current_state.src_height
+ / surface->current_state.dest_height);
+ }
+
+ if (x_factor != 1.0f && y_factor != 1.0f)
XLScaleRegion (&temp, &surface->current_state.damage,
- abs (scale) + 1, abs (scale) + 1);
+ x_factor, y_factor);
+
+ /* If a viewport is set, translate the damage region by the
+ src_x and src_y. This is lossy. */
+ if (surface->current_state.src_x != -1.0)
+ pixman_region32_translate (&temp,
+ floor (surface->current_state.src_x),
+ floor (surface->current_state.src_y));
ViewDamage (surface->view, &temp);
@@ -767,6 +945,25 @@ SavePendingState (Surface *surface)
surface->cached_state.buffer_scale
= surface->pending_state.buffer_scale;
+ if (surface->pending_state.pending & PendingViewportDest)
+ {
+ surface->cached_state.dest_width
+ = surface->pending_state.dest_width;
+ surface->cached_state.dest_height
+ = surface->pending_state.dest_height;
+ }
+
+ if (surface->pending_state.pending & PendingViewportSrc)
+ {
+ surface->cached_state.src_x = surface->pending_state.src_x;
+ surface->cached_state.src_y = surface->pending_state.src_y;
+
+ surface->cached_state.src_width
+ = surface->pending_state.src_width;
+ surface->cached_state.src_height
+ = surface->pending_state.src_height;
+ }
+
if (surface->pending_state.pending & PendingAttachments)
{
surface->cached_state.x = surface->pending_state.x;
@@ -860,6 +1057,11 @@ InternalCommit (Surface *surface, State *pending)
pending->buffer);
ApplyBuffer (surface);
ClearBuffer (pending);
+
+ /* Check that any applied viewport source rectangles remain
+ valid. */
+ if (!(pending->pending & PendingViewportSrc))
+ CheckViewportValues (surface);
}
else
{
@@ -889,6 +1091,29 @@ InternalCommit (Surface *surface, State *pending)
ApplyOpaqueRegion (surface);
}
+ if (pending->pending & PendingViewportSrc
+ || pending->pending & PendingViewportDest)
+ {
+ /* Copy the viewport data over to the current state. */
+
+ if (pending->pending & PendingViewportDest)
+ {
+ surface->current_state.dest_width = pending->dest_width;
+ surface->current_state.dest_height = pending->dest_height;
+ }
+
+ if (pending->pending & PendingViewportSrc)
+ {
+ surface->current_state.src_x = pending->src_x;
+ surface->current_state.src_y = pending->src_y;
+ surface->current_state.src_width = pending->src_width;
+ surface->current_state.src_height = pending->src_height;
+ }
+
+ /* And apply the viewport now. */
+ ApplyViewport (surface);
+ }
+
if (pending->pending & PendingAttachments)
{
surface->current_state.x = pending->x;
@@ -1077,6 +1302,14 @@ InitState (State *state)
state->frame_callbacks.next = &state->frame_callbacks;
state->frame_callbacks.last = &state->frame_callbacks;
state->frame_callbacks.resource = NULL;
+
+ /* Initialize the viewport to the default undefined values. */
+ state->dest_width = -1;
+ state->dest_height = -1;
+ state->src_x = -1.0;
+ state->src_y = -1.0;
+ state->src_width = -1.0;
+ state->src_height = -1.0;
}
static void
@@ -1321,16 +1554,17 @@ XLStateDetachBuffer (State *state)
void
XLSurfaceRunFrameCallbacks (Surface *surface, struct timespec time)
{
- uint32_t ms_time;
+ uint64_t ms_time;
XLList *list;
- /* I don't know what else is reasonable in case of overflow. */
+ /* If ms_time is too large to fit in uint32_t, take the lower 32
+ bits. */
if (IntMultiplyWrapv (time.tv_sec, 1000, &ms_time))
- ms_time = UINT32_MAX;
+ ms_time = UINT64_MAX;
else if (IntAddWrapv (ms_time, time.tv_nsec / 1000000,
&ms_time))
- ms_time = UINT32_MAX;
+ ms_time = UINT64_MAX;
RunFrameCallbacks (&surface->current_state.frame_callbacks,
ms_time);
@@ -1474,3 +1708,83 @@ XLSurfaceMoveBy (Surface *surface, int west, int north)
surface->role->funcs.move_by (surface, surface->role,
west, north);
}
+
+/* The following functions convert from window to surface
+ coordinates and vice versa:
+
+ SurfaceToWindow - take given surface coordinate, and return a
+ window relative coordinate.
+ ScaleToWindow - take given surface dimension, and return a
+ window relative dimension.
+ WindowToSurface - take given window coordinate, and return a
+ surface relative coordinate as a double.
+ ScaleToSurface - take given window dimension, and return a
+ surface relative dimension.
+
+ Functions prefixed by "truncate" return and accept integer values
+ instead of floating point ones; truncation is performed on
+ fractional values. */
+
+void
+SurfaceToWindow (Surface *surface, double x, double y,
+ double *x_out, double *y_out)
+{
+ *x_out = x * surface->factor + surface->input_delta_x;
+ *y_out = y * surface->factor + surface->input_delta_y;
+}
+
+void
+ScaleToWindow (Surface *surface, double width, double height,
+ double *width_out, double *height_out)
+{
+ *width_out = width * surface->factor;
+ *height_out = height * surface->factor;
+}
+
+void
+WindowToSurface (Surface *surface, double x, double y,
+ double *x_out, double *y_out)
+{
+ *x_out = x / surface->factor - surface->input_delta_x;
+ *y_out = y / surface->factor - surface->input_delta_y;
+}
+
+void
+ScaleToSurface (Surface *surface, double width, double height,
+ double *width_out, double *height_out)
+{
+ *width_out = width / surface->factor;
+ *height_out = height / surface->factor;
+}
+
+void
+TruncateSurfaceToWindow (Surface *surface, int x, int y,
+ int *x_out, int *y_out)
+{
+ *x_out = x * surface->factor + surface->input_delta_x;
+ *y_out = y * surface->factor + surface->input_delta_y;
+}
+
+void
+TruncateScaleToWindow (Surface *surface, int width, int height,
+ int *width_out, int *height_out)
+{
+ *width_out = width * surface->factor;
+ *height_out = height * surface->factor;
+}
+
+void
+TruncateWindowToSurface (Surface *surface, int x, int y,
+ int *x_out, int *y_out)
+{
+ *x_out = x / surface->factor - surface->input_delta_x;
+ *y_out = y / surface->factor - surface->input_delta_y;
+}
+
+void
+TruncateScaleToSurface (Surface *surface, int width, int height,
+ int *width_out, int *height_out)
+{
+ *width_out = width / surface->factor;
+ *height_out = height / surface->factor;
+}
diff --git a/xdg_popup.c b/xdg_popup.c
index cccf7ff..332edc1 100644
--- a/xdg_popup.c
+++ b/xdg_popup.c
@@ -278,7 +278,6 @@ MoveWindow (XdgPopup *popup)
int root_x, root_y, parent_gx, parent_gy;
int geometry_x, geometry_y, x, y;
Window window;
- double parent_scale, current_scale;
/* No parent was specified. */
if (!popup->parent)
@@ -292,9 +291,6 @@ MoveWindow (XdgPopup *popup)
scale. */
return;
- parent_scale = popup->parent->surface->factor;
- current_scale = popup->role->surface->factor;
-
window = XLWindowFromXdgRole (popup->role);
XLXdgRoleGetCurrentGeometry (popup->parent, &parent_gx,
@@ -305,17 +301,17 @@ MoveWindow (XdgPopup *popup)
&root_y);
/* Parent geometry is relative to the parent coordinate system. */
- parent_gx *= parent_scale;
- parent_gy *= parent_scale;
+ TruncateSurfaceToWindow (popup->parent->surface, parent_gx, parent_gy,
+ &parent_gx, &parent_gy);
/* geometry_x and geometry_y are relative to the local coordinate
system. */
- geometry_x *= current_scale;
- geometry_y *= current_scale;
+ TruncateSurfaceToWindow (popup->role->surface, geometry_x,
+ geometry_y, &geometry_x, &geometry_y);
/* X and Y are relative to the parent coordinate system. */
- x = popup->x * parent_scale;
- y = popup->y * parent_scale;
+ TruncateSurfaceToWindow (popup->parent->surface, popup->x,
+ popup->y, &x, &y);
XMoveWindow (compositor.display, window,
x + root_x + parent_gx - geometry_x,
@@ -754,6 +750,15 @@ HandleParentResize (void *data)
InternalReposition (popup);
}
+static Bool
+IsWindowMapped (Role *role, XdgRoleImplementation *impl)
+{
+ XdgPopup *popup;
+
+ popup = PopupFromRoleImpl (impl);
+ return popup->state & StateIsMapped;
+}
+
static const struct xdg_popup_interface xdg_popup_impl =
{
.destroy = Destroy,
@@ -798,6 +803,7 @@ XLGetXdgPopup (struct wl_client *client, struct wl_resource *resource,
popup->impl.funcs.ack_configure = AckConfigure;
popup->impl.funcs.note_size = NoteSize;
popup->impl.funcs.handle_geometry_change = HandleGeometryChange;
+ popup->impl.funcs.is_window_mapped = IsWindowMapped;
if (parent_resource)
{
diff --git a/xdg_surface.c b/xdg_surface.c
index 5571aa7..27ea99d 100644
--- a/xdg_surface.c
+++ b/xdg_surface.c
@@ -628,6 +628,13 @@ Unfreeze (XdgRole *role)
XLFrameClockUnfreeze (role->clock);
}
+static Bool
+IsRoleMapped (XdgRole *role)
+{
+ return role->impl->funcs.is_window_mapped (&role->role,
+ role->impl);
+}
+
static void
Commit (Surface *surface, Role *role)
{
@@ -666,6 +673,12 @@ Commit (Surface *surface, Role *role)
ack_configure. */
xdg_role->state &= ~StateWaitingForAckCommit;
+ /* If the window is unmapped, skip all of this code! Once the
+ window is mapped again, the compositor will send _NET_FRAME_DRAWN
+ should a frame still be in progress. */
+ if (!IsRoleMapped (xdg_role))
+ goto start_drawing;
+
/* A frame is already in progress, so instead say that an urgent
update is needed immediately after the frame completes. In any
case, don't run frame callbacks upon buffer release anymore. */
@@ -687,6 +700,8 @@ Commit (Surface *surface, Role *role)
return;
}
+ start_drawing:
+
/* If the frame clock is frozen but we are no longer waiting for the
configure event to be acknowledged by the client, unfreeze the
frame clock. */
@@ -706,7 +721,13 @@ Commit (Surface *surface, Role *role)
callbacks are not provided by the frame clock while it is frozen.
If that happens, just run the frame callback immediately. */
- if (XLFrameClockIsFrozen (xdg_role->clock))
+ if (XLFrameClockIsFrozen (xdg_role->clock)
+ /* If the window is not mapped, then the native frame clock will
+ not draw frames. Some clients do commit before the initial
+ configure event and wait for the frame callback to be called
+ after or before ack_configure, leading to the mapping commit
+ never being performed. */
+ || !IsRoleMapped (xdg_role))
RunFrameCallbacksConditionally (xdg_role);
return;
@@ -843,6 +864,10 @@ Subframe (Surface *surface, Role *role)
return False;
}
+ /* Similarly, return False if the role is unmapped. */
+ if (!IsRoleMapped (xdg_role))
+ return False;
+
/* If a frame is already in progress, return False. Then, require a
late frame. */
if (XLFrameClockFrameInProgress (xdg_role->clock))
@@ -1167,8 +1192,8 @@ GetResizeDimensions (Surface *surface, Role *role, int *x_out,
{
XLXdgRoleGetCurrentGeometry (role, NULL, NULL, x_out, y_out);
- *x_out *= surface->factor;
- *y_out *= surface->factor;
+ /* Scale these surface-local dimensions to window-local ones. */
+ TruncateSurfaceToWindow (surface, *x_out, *y_out, x_out, y_out);
}
static void
@@ -1481,10 +1506,15 @@ XLXdgRoleCalcNewWindowSize (Role *role, int width, int height,
SubcompositorBounds (xdg_role->subcompositor,
&min_x, &min_y, &max_x, &max_y);
- /* Adjust the current_width and current_height by the global scale
+ /* Calculate the current width and height. */
+ current_width = (max_x - min_x + 1);
+ current_height = (max_y - min_y + 1);
+
+ /* Adjust the current_width and current_height by the scale
factor. */
- current_width = (max_x - min_x + 1) / role->surface->factor;
- current_height = (max_y - min_y + 1) / role->surface->factor;
+ TruncateScaleToSurface (role->surface, current_width,
+ current_height, ¤t_width,
+ ¤t_height);
XLXdgRoleGetCurrentGeometry (role, NULL, NULL, &geometry_width,
&geometry_height);
diff --git a/xdg_toplevel.c b/xdg_toplevel.c
index 9f2f75d..421aeaf 100644
--- a/xdg_toplevel.c
+++ b/xdg_toplevel.c
@@ -283,6 +283,15 @@ RunUnmapCallbacks (XdgToplevel *toplevel)
toplevel->unmap_callbacks.last = &toplevel->unmap_callbacks;
}
+static Bool
+IsWindowMapped (Role *role, XdgRoleImplementation *impl)
+{
+ XdgToplevel *toplevel;
+
+ toplevel = ToplevelFromRoleImpl (impl);
+ return toplevel->state & StateIsMapped;
+}
+
static void
WriteHints (XdgToplevel *toplevel)
{
@@ -362,13 +371,9 @@ NoteConfigureTime (Timer *timer, void *data, struct timespec time)
{
XdgToplevel *toplevel;
int width, height, effective_width, effective_height;
- double factor;
toplevel = data;
- /* Obtain the scale factor. toplevel->role->surface should not be
- NULL here, as the timer is cancelled upon role detachment. */
- factor = toplevel->role->surface->factor;
/* If only the window state changed, call SendStates. */
if (!(toplevel->state & StatePendingConfigureSize))
@@ -379,8 +384,14 @@ NoteConfigureTime (Timer *timer, void *data, struct timespec time)
if (toplevel->state & StatePendingConfigureStates)
WriteStates (toplevel);
- effective_width = toplevel->configure_width / factor;
- effective_height = toplevel->configure_height / factor;
+ effective_width = toplevel->configure_width;
+ effective_height = toplevel->configure_height;
+
+ /* toplevel->role->surface should not be NULL here, as the timer
+ is cancelled upon role detachment. */
+ TruncateScaleToSurface (toplevel->role->surface,
+ effective_width, effective_height,
+ &effective_width, &effective_height);
/* Compute the geometry for the configure event based on the
current size of the toplevel. */
@@ -482,24 +493,24 @@ static void
SendStates (XdgToplevel *toplevel)
{
int width, height;
- double factor;
WriteStates (toplevel);
- /* Obtain the scale factor. toplevel->role->surface should not be
- NULL here. */
- factor = toplevel->role->surface->factor;
-
/* Adjust the width and height we're sending by the window
geometry. */
if (toplevel->state & StateMissingState)
XLXdgRoleGetCurrentGeometry (toplevel->role, NULL, NULL,
&width, &height);
else
- XLXdgRoleCalcNewWindowSize (toplevel->role,
- toplevel->width / factor,
- toplevel->height / factor,
- &width, &height);
+ {
+ /* toplevel->role->surface should not be NULL here. */
+ TruncateScaleToSurface (toplevel->role->surface,
+ toplevel->width, toplevel->height,
+ &width, &height);
+
+ XLXdgRoleCalcNewWindowSize (toplevel->role, width,
+ height, &width, &height);
+ }
SendConfigure (toplevel, width, height);
@@ -515,15 +526,11 @@ RecordStateSize (XdgToplevel *toplevel)
{
Bool a, b;
int width, height;
- double factor;
if (!toplevel->role->surface)
/* We can't get the scale factor in this case. */
return;
- /* Obtain the scale factor. */
- factor = toplevel->role->surface->factor;
-
/* Record the last known size of a toplevel before its state is
changed. That way, we can send xdg_toplevel::configure with the
right state, should the window manager send ConfigureNotify
@@ -539,8 +546,10 @@ RecordStateSize (XdgToplevel *toplevel)
upon minimization. */
XLXdgRoleGetCurrentGeometry (toplevel->role, NULL, NULL,
&width, &height);
- width *= factor;
- height *= factor;
+
+ /* Scale the width and height to window dimensions. */
+ TruncateScaleToWindow (toplevel->role->surface, width, height,
+ &width, &height);
}
else
{
@@ -795,11 +804,9 @@ HandleWindowGeometryChange (XdgToplevel *toplevel)
XLXdgRoleGetCurrentGeometry (toplevel->role, &x, &y,
&width, &height);
-
- width *= toplevel->role->surface->factor;
- height *= toplevel->role->surface->factor;
- x *= toplevel->role->surface->factor;
- y *= toplevel->role->surface->factor;
+ TruncateScaleToWindow (toplevel->role->surface, width, height,
+ &width, &height);
+ TruncateSurfaceToWindow (toplevel->role->surface, x, y, &x, &y);
dx = SubcompositorWidth (subcompositor) - width;
dy = SubcompositorHeight (subcompositor) - height;
@@ -813,21 +820,25 @@ HandleWindowGeometryChange (XdgToplevel *toplevel)
/* Initially, specify PSize. After the first MapNotify, also
specify PPosition so that subsurfaces won't move the window. */
- hints->min_width = (toplevel->min_width
- * toplevel->role->surface->factor
- + dx);
- hints->min_height = (toplevel->min_height
- * toplevel->role->surface->factor
- + dy);
+ /* First, make hints->min_width and hints->min_height the min width
+ in terms of the window coordinate system. Then, add deltas. */
+ TruncateScaleToWindow (toplevel->role->surface, toplevel->min_width,
+ toplevel->min_height, &hints->min_width,
+ &hints->min_height);
+
+ /* Add deltas. */
+ hints->min_width += dx;
+ hints->min_height += dy;
if (toplevel->max_width)
{
- hints->max_width = (toplevel->max_width
- * toplevel->role->surface->factor
- + dx);
- hints->max_height = (toplevel->max_height
- * toplevel->role->surface->factor
- + dy);
+ /* Do the same with the max width. */
+ TruncateScaleToWindow (toplevel->role->surface, toplevel->max_width,
+ toplevel->max_height, &hints->max_width,
+ &hints->max_height);
+
+ hints->max_width += dx;
+ hints->max_height += dy;
hints->flags |= PMaxSize;
}
else
@@ -1373,10 +1384,13 @@ NoteWindowPreResize (Role *role, XdgRoleImplementation *impl,
XLXdgRoleGetCurrentGeometry (toplevel->role, &x, &y,
&gwidth, &gheight);
- dx = width - gwidth * toplevel->role->surface->factor;
- dy = height - gheight * toplevel->role->surface->factor;
- x *= toplevel->role->surface->factor;
- y *= toplevel->role->surface->factor;
+ /* Scale the window geometry to window dimensions. */
+ TruncateScaleToWindow (toplevel->role->surface, gwidth, gheight,
+ &gwidth, &gheight);
+ TruncateSurfaceToWindow (toplevel->role->surface, x, y, &x, &y);
+
+ dx = width - gwidth;
+ dy = height - gheight;
ApplyGtkFrameExtents (toplevel, x, y, dx - x, dy - y);
}
@@ -1966,6 +1980,7 @@ XLGetXdgToplevel (struct wl_client *client, struct wl_resource *resource,
toplevel->impl.funcs.handle_geometry_change = HandleGeometryChange;
toplevel->impl.funcs.post_resize = PostResize;
toplevel->impl.funcs.commit_inside_frame = CommitInsideFrame;
+ toplevel->impl.funcs.is_window_mapped = IsWindowMapped;
/* Set up the sentinel node for the list of unmap callbacks. */
toplevel->unmap_callbacks.next = &toplevel->unmap_callbacks;