JBR-6452 Wayland: measure and improve surface buffer management

Improved rendering performance by
* reducing memory copy and making it more efficient,
* tying the next frame display to the frame event from Wayland,
  which dramatically reduces load for very quick Swing apps,
* limiting the number of buffers to 2.

(cherry picked from commit e625eeca1e)
This commit is contained in:
Maxim Kartashev
2023-12-21 18:11:37 +04:00
committed by jbrbot
parent 1ffe380251
commit 7b596d81eb
4 changed files with 236 additions and 247 deletions

View File

@@ -82,6 +82,7 @@ public class WLComponentPeer implements ComponentPeer {
{"hand"}, // HAND_CURSOR
{"move"}, // MOVE_CURSOR
};
private static final int WHEEL_SCROLL_AMOUNT = 3;
private long nativePtr;
private volatile boolean surfaceAssigned = false;
@@ -381,9 +382,9 @@ public class WLComponentPeer implements ComponentPeer {
* Commits changes accumulated in the underlying SurfaceData object
* to the server for displaying on the screen. The request may not be
* granted immediately as the server may be busy reading data provided
* previously. In the latter case, the commit will happen later when
* the server notifies us (through an event on EDT) that the displaying
* buffer is ready to accept new data.
* previously. In the latter case, the commit will automatically happen
* later when the server notifies us (through an event on EDT) that
* the displaying buffer is ready to accept new data.
*/
public void commitToServer() {
performLocked(() -> {
@@ -391,6 +392,7 @@ public class WLComponentPeer implements ComponentPeer {
surfaceData.flush();
}
});
Toolkit.getDefaultToolkit().sync();
}
public Component getTarget() {
@@ -1068,7 +1070,7 @@ public class WLComponentPeer implements ComponentPeer {
isPopupTrigger,
MouseWheelEvent.WHEEL_UNIT_SCROLL,
1,
e.getAxis0Value());
Integer.signum(e.getAxis0Value()) * WHEEL_SCROLL_AMOUNT);
postMouseEvent(mouseEvent);
}

View File

@@ -150,10 +150,7 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
@SuppressWarnings("removal")
public WLToolkit() {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
final String extraButtons = "sun.awt.enableExtraMouseButtons";
areExtraMouseButtonsEnabled =
Boolean.parseBoolean(System.getProperty(extraButtons, "true"));
System.setProperty(extraButtons, String.valueOf(areExtraMouseButtonsEnabled));
initSystemProperties();
return null;
});
@@ -180,6 +177,13 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
}
}
private static void initSystemProperties() {
final String extraButtons = "sun.awt.enableExtraMouseButtons";
areExtraMouseButtonsEnabled =
Boolean.parseBoolean(System.getProperty(extraButtons, "true"));
System.setProperty(extraButtons, String.valueOf(areExtraMouseButtonsEnabled));
}
public static boolean isToolkitThread() {
return Thread.currentThread() == toolkitThread;
}
@@ -861,9 +865,9 @@ public class WLToolkit extends UNIXToolkit implements Runnable {
@Override
public boolean useBufferPerWindow() {
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine("Not implemented: WLToolkit.useBufferPerWindow()");
}
// TODO: this may depend on the rendering engine used.
// When rendering is performed into memory buffers shared with Wayland,
// there's no sense in having additional buffers in AWT/Swing.
return false;
}

View File

@@ -36,17 +36,12 @@
#include "awt.h"
#include "WLBuffers.h"
#include "WLToolkit.h"
#ifndef HEADLESS
typedef struct WLSurfaceBuffer WLSurfaceBuffer;
extern struct wl_shm_pool *
CreateShmPool(size_t size, const char *name, void **data); // defined in WLToolkit.c
static bool
TrySendDrawBufferToWayland(WLSurfaceBufferManager * manager);
static WLSurfaceBuffer *
SurfaceBufferCreate(WLSurfaceBufferManager * manager);
@@ -56,9 +51,6 @@ SurfaceBufferNotifyReleased(WLSurfaceBufferManager * manager, struct wl_buffer *
static bool
ShowBufferIsAvailable(WLSurfaceBufferManager * manager);
static void
ShowBufferPrepareFreshOneWithPixelsFrom(WLSurfaceBufferManager * manager, WLSurfaceBuffer * lastBuffer);
static void
ShowBufferInvalidateForNewSize(WLSurfaceBufferManager * manager);
@@ -68,14 +60,11 @@ SurfaceBufferDestroy(WLSurfaceBuffer * buffer);
static void
ScheduleFrameCallback(WLSurfaceBufferManager * manager);
static inline void
RegisterFrameLost(const char* reason)
{
if (getenv("J2D_STATS")) {
fprintf(stderr, "WLBuffers: frame lost, reason '%s'\n", reason);
fflush(stderr);
}
}
static void
CopyDrawBufferToShowBuffer(WLSurfaceBufferManager * manager);
static void
SendShowBufferToWayland(WLSurfaceBufferManager * manager);
static inline void
ReportFatalError(const char* file, int line, const char *msg)
@@ -95,6 +84,22 @@ AssertDrawLockIsHeld(WLSurfaceBufferManager* manager, const char * file, int lin
#define MUTEX_LOCK(m) if (pthread_mutex_lock(&(m))) { WL_FATAL_ERROR("Failed to lock mutex"); }
#define MUTEX_UNLOCK(m) if (pthread_mutex_unlock(&(m))) { WL_FATAL_ERROR("Failed to unlock mutex"); }
/**
* The maximum number of buffers that can be simultaneously in use by Wayland.
* When a new frame is ready to be sent to Wayland and the number of buffers
* already sent plus this new buffer exceeds MAX_BUFFERS_IN_USE, that frame is
* skipped. We will wait for a buffer to be released.
* NB: neither the buffer for drawing nor the next buffer reserved to be sent to
* Wayland count towards MAX_BUFFERS_IN_USE.
* Cannot be less than two because some compositors will not release the buffer
* given to them until a new one has been attached. See the description of
* the wl_buffer::release event in the Wayland documentation.
*/
const int MAX_BUFFERS_IN_USE = 2;
static bool traceEnabled; // set the J2D_STATS env var to enable
static bool traceFPSEnabled; // set the J2D_FPS env var to enable
/**
* Represents one rectangular area linked into a list.
*
@@ -213,28 +218,24 @@ struct WLDrawBuffer {
/**
* Contains data necessary to manage multiple backing buffers for one wl_surface.
*
* There's one and only buffer attached to the surface that Wayland reads from,
* which is allocated in shared memory: bufferForShow.
* There's one buffer that will be sent to Wayland next: bufferForShow. When it is
* ready to be sent to Wayland, it is added to the buffersInUse list and a new one
* is put in its place so that bufferForShow is always available.
* If the number of buffers in use is >= MAX_BUFFERS_IN_USE, no new buffer is sent
* to Wayland until some buffers have been released by Wayland. This effectively
* skips some of the frames.
*
* There's one buffer (but it possible to have more) that can be used for drawing.
* When Wayland is ready to receive updates, the modified portions of that buffer
* are copied over to bufferForShow.
* There's one buffer that can be drawn upon: bufferForDraw. When we're done drawing,
* pixels from that buffer are copied over to bufferForShow.
*
* The size of bufferForShow is determined by width and height fields; the size of
* bufferForShow can lag behind and will re-adjust after Wayland has released it
* back to us.
* The size of bufferForShow is determined by the width and height fields.
*/
struct WLSurfaceBufferManager {
struct wl_surface * wlSurface; // only accessed under showLock
int bgPixel;
int format; // one of enum wl_shm_format
struct wl_surface * wlSurface; // only accessed under showLock
bool isBufferAttached; // is there a buffer attached to the surface?
int bgPixel; // the pixel value to be used to clear new buffers
int format; // one of enum wl_shm_format
/**
* ID of the "drawing" frame to be sent to Wayland.
* Is set by committing a new frame with WLSBM_SurfaceCommit().
* Gets re-set to 0 right after sending to Wayland.
*/
frame_id_t commitFrameID; // only accessed under showLock
struct wl_callback* wl_frame_callback; // only accessed under showLock
pthread_mutex_t showLock;
@@ -244,16 +245,17 @@ struct WLSurfaceBufferManager {
* When sent to Wayland, its WLSurfaceBuffer is added to the buffersInUse list
* and a fresh one created or re-used from the buffersFree list so that
* this buffer is available at all times.
* When "draw" buffer (bufferForDraw) size is changed, this one is
* When the "draw" buffer (bufferForDraw) size is changed, this one is
* immediately invalidated along with all those from the buffersFree list.
*/
WLShowBuffer bufferForShow; // only accessed under showLock
/// A list of buffers that can be re-used as bufferForShow.wlSurfaceBuffer.
/// The list of buffers that can be re-used as bufferForShow.wlSurfaceBuffer.
WLSurfaceBuffer * buffersFree; // only accessed under showLock
/// A list of buffers sent to Wayland and not yet released; when released,
/// The list of buffers sent to Wayland and not yet released; when released,
/// they may be added to the buffersFree list.
/// Does not exceed MAX_BUFFERS_IN_USE elements.
WLSurfaceBuffer * buffersInUse; // only accessed under showLock
/// The scale of wl_surface (see Wayland docs for more info on that).
@@ -272,37 +274,69 @@ struct WLSurfaceBufferManager {
static inline void
AssertDrawLockIsHeld(WLSurfaceBufferManager* manager, const char * file, int line)
{
// TODO: would be nice to be able to check the mutex owner
// The drawLock is recursive, so can't effectively check if it is locked
// with trylock. Maybe add a manual lock count or current owner?
}
static inline void
AssertShowLockIsHeld(WLSurfaceBufferManager* manager, const char * file, int line)
{
#ifdef DEBUG
if (pthread_mutex_trylock(&manager->showLock) == 0) {
fprintf(stderr, "showLock not acquired at %s:%d\n", file, line);
fflush(stderr);
assert(0);
}
#endif
}
static jlong
GetJavaTimeNanos(void) {
jlong result = 0;
if (traceEnabled || traceFPSEnabled) {
struct timespec tp;
const jlong NANOSECS_PER_SEC = 1000000000L;
clock_gettime(CLOCK_MONOTONIC, &tp);
result = (jlong)(tp.tv_sec) * NANOSECS_PER_SEC + (jlong)(tp.tv_nsec);
}
return result;
}
static inline void
ShowFrameNumbers(WLSurfaceBufferManager* manager, const char *fmt, ...)
WLBufferTrace(WLSurfaceBufferManager* manager, const char *fmt, ...)
{
// TODO: this is temporary debugging code that will be removed in the future
if (getenv("J2D_TRACE_LEVEL")) {
if (traceEnabled) {
va_list args;
va_start(args, fmt);
fprintf(stderr, ">>> ");
jlong t = GetJavaTimeNanos();
fprintf(stderr, "[%07dms] ", t / 1000000);
vfprintf(stderr, fmt, args);
fprintf(stderr, "; showing frame %d, drawing frame %d, frame to be committed %d\n",
fprintf(stderr, "; frames [^%03d, *%03d]\n",
manager->bufferForShow.frameID,
manager->bufferForDraw.frameID,
manager->commitFrameID);
manager->bufferForDraw.frameID);
fflush(stderr);
va_end(args);
}
}
static void
WLBufferTraceFrame(WLSurfaceBufferManager* manager)
{
if (traceFPSEnabled) {
static jlong lastFrameTime = 0;
static int frameCount = 0;
jlong curTime = GetJavaTimeNanos();
frameCount++;
if (curTime - lastFrameTime > 1000000000L) {
fprintf(stderr, "FPS: %d\n", frameCount);
fflush(stderr);
lastFrameTime = curTime;
frameCount = 0;
}
}
}
static inline size_t
SurfaceBufferSizeInBytes(WLSurfaceBuffer * buffer)
{
@@ -324,12 +358,6 @@ DrawBufferSizeInBytes(WLSurfaceBufferManager * manager)
return stride * manager->bufferForDraw.height;
}
static inline jint
SurfaceBufferSizeInPixels(WLSurfaceBuffer * buffer)
{
return buffer->width * buffer->height;
}
/**
* Returns the number of pixels in the "draw" buffer.
*/
@@ -344,9 +372,8 @@ wl_buffer_release(void * data, struct wl_buffer * wl_buffer)
{
/* Sent by the compositor when it's no longer using this buffer */
WLSurfaceBufferManager * manager = (WLSurfaceBufferManager *) data;
WLBufferTrace(manager, "wl_buffer_release");
SurfaceBufferNotifyReleased(manager, wl_buffer);
ShowFrameNumbers(manager, "wl_buffer_release");
}
static const struct wl_buffer_listener wl_buffer_listener = {
@@ -369,21 +396,21 @@ SurfaceBufferDestroy(WLSurfaceBuffer * buffer)
// the surface contents" (source: wayland.xml)
wl_buffer_destroy(buffer->wlBuffer);
memset(buffer, 0, sizeof(WLSurfaceBuffer));
free(buffer);
}
static WLSurfaceBuffer *
SurfaceBufferCreate(WLSurfaceBufferManager * manager)
{
ASSERT_DRAW_LOCK_IS_HELD(manager);
WLBufferTrace(manager, "SurfaceBufferCreate");
WLSurfaceBuffer * buffer = calloc(1, sizeof(WLSurfaceBuffer));
if (!buffer) return NULL;
MUTEX_LOCK(manager->drawLock);
buffer->width = manager->bufferForDraw.width;
buffer->height = manager->bufferForDraw.height;
MUTEX_UNLOCK(manager->drawLock);
const size_t size = SurfaceBufferSizeInBytes(buffer);
buffer->wlPool = CreateShmPool(size, "jwlshm", (void**)&buffer->data);
@@ -410,6 +437,22 @@ SurfaceBufferNotifyReleased(WLSurfaceBufferManager * manager, struct wl_buffer *
{
assert(manager);
if (traceEnabled) {
int used = 0;
int free = 0;
WLSurfaceBuffer * cur = manager->buffersInUse;
while (cur) {
used++;
cur = cur->next;
}
cur = manager->buffersFree;
while (cur) {
free++;
cur = cur->next;
}
WLBufferTrace(manager, "SurfaceBufferNotifyReleased (%d in use, %d free)", used, free);
}
MUTEX_LOCK(manager->showLock);
WLSurfaceBuffer * cur = manager->buffersInUse;
@@ -449,12 +492,27 @@ ShowBufferIsAvailable(WLSurfaceBufferManager * manager)
{
ASSERT_SHOW_LOCK_IS_HELD(manager);
return manager->bufferForShow.wlSurfaceBuffer;
assert(manager->bufferForShow.wlSurfaceBuffer);
// Skip sending the next frame if the number of buffers that
// had been sent to Wayland for displaying earlier is too large.
// Clearly the server can't support our frame rate in that case.
int used = 0;
WLSurfaceBuffer * cur = manager->buffersInUse;
while (cur) {
used++;
cur = cur->next;
}
WLBufferTrace(manager, "ShowBufferIsAvailable: %d/%d in use", used, MAX_BUFFERS_IN_USE);
// NB: account for one extra buffer about to be sent to Wayland and added to the used list
return used < MAX_BUFFERS_IN_USE;
}
static void
ShowBufferCreate(WLSurfaceBufferManager * manager)
{
ASSERT_SHOW_LOCK_IS_HELD(manager);
manager->bufferForShow.wlSurfaceBuffer = SurfaceBufferCreate(manager);
}
@@ -482,32 +540,22 @@ ShowBufferPrepareFreshOne(WLSurfaceBufferManager * manager)
}
}
/**
* Makes sure that there's a fresh "show" buffer of suitable size available
* that can be sent to Wayland. That fresh buffer's pixels are copied over
* from the given buffer.
*/
static void
ShowBufferPrepareFreshOneWithPixelsFrom(WLSurfaceBufferManager * manager, WLSurfaceBuffer * lastBuffer)
TrySendShowBufferToWayland(WLSurfaceBufferManager * manager, bool sendNow)
{
ASSERT_SHOW_LOCK_IS_HELD(manager);
assert(lastBuffer);
WLBufferTrace(manager, "TrySendShowBufferToWayland(%s)", sendNow ? "now" : "later");
ShowBufferPrepareFreshOne(manager);
if (manager->bufferForShow.wlSurfaceBuffer) {
// Copy the last image data from the buffer provided.
const bool sameSize =
manager->bufferForShow.wlSurfaceBuffer->width == lastBuffer->width
&& manager->bufferForShow.wlSurfaceBuffer->height == lastBuffer->height;
assert(sameSize);
// TODO: better do memcpy
for (jint i = 0; i < SurfaceBufferSizeInPixels(lastBuffer); ++i) {
manager->bufferForShow.wlSurfaceBuffer->data[i] = lastBuffer->data[i];
}
sendNow = sendNow && ShowBufferIsAvailable(manager);
if (sendNow) {
CopyDrawBufferToShowBuffer(manager);
SendShowBufferToWayland(manager);
} else {
ScheduleFrameCallback(manager);
}
WLBufferTrace(manager, "wl_surface_commit");
// Need to commit either the damage done to the surface or the re-scheduled callback.
wl_surface_commit(manager->wlSurface);
}
static void
@@ -528,6 +576,9 @@ ShowBufferInvalidateForNewSize(WLSurfaceBufferManager * manager)
manager->buffersFree = next;
}
// NB: the buffers that are currently in use will be destroyed
// as soon as they are released (see wl_buffer_release()).
ShowBufferCreate(manager);
MUTEX_UNLOCK(manager->showLock);
@@ -539,26 +590,20 @@ wl_frame_callback_done(void * data,
uint32_t callback_data)
{
WLSurfaceBufferManager * manager = (WLSurfaceBufferManager *) data;
WLBufferTrace(manager, "wl_frame_callback_done");
MUTEX_LOCK(manager->showLock);
assert(manager->wl_frame_callback == wl_callback);
wl_callback_destroy(manager->wl_frame_callback);
manager->wl_frame_callback = NULL;
struct wl_surface * wlSurface = manager->wlSurface;
if (wlSurface) {
// Wayland is ready to get a new frame from us. Send whatever we have
// managed to draw by this time (maybe nothing).
if (!TrySendDrawBufferToWayland(manager)) {
// Re-schedule the same callback if we were unable to send the
// new frame to Wayland.
ScheduleFrameCallback(manager);
}
ShowFrameNumbers(manager, "wl_frame_callback_done");
// Need to commit either the damage done to the surface or the re-scheduled
// callback.
wl_surface_commit(wlSurface);
if (manager->wlSurface) {
const bool hasSomethingToSend = (manager->bufferForDraw.damageList != NULL);
TrySendShowBufferToWayland(manager, hasSomethingToSend);
}
MUTEX_UNLOCK(manager->showLock);
}
@@ -579,144 +624,86 @@ ScheduleFrameCallback(WLSurfaceBufferManager * manager)
}
/**
* Copies all the damaged areas from the drawing buffer to show buffer and
* transfers manager to the "new frame sent" state.
*
* Returns true if a buffer (possibly with damage) was attached to the managed
* Wayland surface and false otherwise.
* Attaches the current show buffer to the Wayland surface, notifying Wayland
* of all the damaged areas in that buffer.
* Prepares a fresh buffer for the next frame to show.
*/
static bool
static void
SendShowBufferToWayland(WLSurfaceBufferManager * manager)
{
ASSERT_SHOW_LOCK_IS_HELD(manager);
assert(manager->wlSurface);
if (manager->wlSurface) { // may get called without an associated wl_surface
WLSurfaceBuffer * buffer = manager->bufferForShow.wlSurfaceBuffer;
assert(buffer);
jlong startTime = GetJavaTimeNanos();
ShowBufferPrepareFreshOneWithPixelsFrom(manager, buffer);
WLSurfaceBuffer * buffer = manager->bufferForShow.wlSurfaceBuffer;
assert(buffer);
// wl_buffer_listener will release bufferForShow when Wayland's done with it
wl_surface_attach(manager->wlSurface, buffer->wlBuffer, 0, 0);
wl_surface_set_buffer_scale(manager->wlSurface, manager->scale);
ShowBufferPrepareFreshOne(manager);
DamageList_SendAll(manager->bufferForShow.damageList, manager->wlSurface);
DamageList_FreeAll(manager->bufferForShow.damageList);
manager->bufferForShow.damageList = NULL;
// wl_buffer_listener will release bufferForShow when Wayland's done with it
wl_surface_attach(manager->wlSurface, buffer->wlBuffer, 0, 0);
wl_surface_set_buffer_scale(manager->wlSurface, manager->scale);
buffer->next = manager->buffersInUse;
manager->buffersInUse = buffer;
// Wayland will not issue frame callbacks before a buffer is attached to the surface.
// So we need to take note of the fact of attaching.
manager->isBufferAttached = true;
manager->bufferForShow.frameID = manager->bufferForDraw.frameID;
manager->bufferForDraw.frameID++;
manager->commitFrameID = 0;
return true;
}
DamageList_SendAll(manager->bufferForShow.damageList, manager->wlSurface);
DamageList_FreeAll(manager->bufferForShow.damageList);
manager->bufferForShow.damageList = NULL;
return false;
}
buffer->next = manager->buffersInUse;
manager->buffersInUse = buffer;
static void
CopyDamagedArea(WLSurfaceBufferManager * manager, jint x, jint y, jint width, jint height)
{
assert(manager->bufferForShow.wlSurfaceBuffer);
assert(manager->bufferForDraw.width == manager->bufferForShow.wlSurfaceBuffer->width);
assert(manager->bufferForDraw.height == manager->bufferForShow.wlSurfaceBuffer->height);
assert(x >= 0);
assert(y >= 0);
assert(width >= 0);
assert(height >= 0);
assert(height + y >= 0);
assert(width + x >= 0);
manager->bufferForShow.frameID = manager->bufferForDraw.frameID;
manager->bufferForDraw.frameID++;
pixel_t * dest = manager->bufferForShow.wlSurfaceBuffer->data;
pixel_t * src = manager->bufferForDraw.data;
for (jint i = y; i < height + y; i++) {
pixel_t * dest_row = &dest[i * manager->bufferForDraw.width];
pixel_t * src_row = &src [i * manager->bufferForDraw.width];
for (jint j = x; j < width + x; j++) {
dest_row[j] = src_row[j];
}
}
jlong endTime = GetJavaTimeNanos();
WLBufferTrace(manager, "SendShowBufferToWayland (%lldns)", endTime - startTime);
WLBufferTraceFrame(manager);
}
/**
* Copies areas from the current damageList of the drawing surface to
* the buffer associated with the Wayland surface for displaying.
* Copies the contents of the drawing surface to the buffer associated
* with the Wayland surface for displaying, the "show" buffer.
*
* Clears the list of damaged areas from the drawing buffer and
* moves that list to the displaying buffer so that Wayland can get
* moves that list to the "show" buffer so that Wayland can get
* notified of what has changed in the buffer.
*/
static bool
CopyDamagedAreasToShowBuffer(WLSurfaceBufferManager * manager)
static void
CopyDrawBufferToShowBuffer(WLSurfaceBufferManager * manager)
{
ASSERT_SHOW_LOCK_IS_HELD(manager);
ASSERT_DRAW_LOCK_IS_HELD(manager);
MUTEX_LOCK(manager->drawLock);
assert(manager->bufferForShow.wlSurfaceBuffer);
assert(manager->wlSurface != NULL);
assert(manager->bufferForShow.damageList == NULL);
const bool willCommit = (manager->bufferForDraw.damageList != NULL) && manager->wlSurface != NULL;
if (willCommit) {
manager->bufferForShow.damageList = manager->bufferForDraw.damageList;
manager->bufferForDraw.damageList = NULL;
jlong startTime = GetJavaTimeNanos();
for (DamageList* l = manager->bufferForShow.damageList; l; l = l->next) {
CopyDamagedArea(manager, l->x, l->y, l->width, l->height);
}
}
manager->bufferForShow.damageList = manager->bufferForDraw.damageList;
manager->bufferForDraw.damageList = NULL;
return willCommit;
}
// Don't know how old the "show" buffer is, so have to copy the entire draw buffer to it.
// TODO: There's a room for improvement here.
memcpy(manager->bufferForShow.wlSurfaceBuffer->data,
manager->bufferForDraw.data,
SurfaceBufferSizeInBytes(manager->bufferForShow.wlSurfaceBuffer));
/**
* Attempts to send damaged areas in the drawing buffer from the frame specified
* by manager->commitFrameID to Wayland by copying them to the show buffer
* and notifying Wayland of that.
*
* Returns false if another attempt to send this frame must be scheduled
* and true otherwise (a new frame is expected).
*/
static bool
TrySendDrawBufferToWayland(WLSurfaceBufferManager * manager)
{
ASSERT_SHOW_LOCK_IS_HELD(manager);
jlong endTime = GetJavaTimeNanos();
WLBufferTrace(manager, "CopyDrawBufferToShowBuffer: copied in %lldns", endTime - startTime);
if (manager->commitFrameID != manager->bufferForDraw.frameID) {
RegisterFrameLost("Attempt to show a frame with ID different from the one we committed");
ShowFrameNumbers(manager, "TrySendDrawBufferToWayland - skipped frame");
return true;
}
bool needAnotherTry = true;
ShowFrameNumbers(manager, "TrySendDrawBufferToWayland");
const bool bufferForDrawWasLocked = pthread_mutex_trylock(&manager->drawLock);
if (bufferForDrawWasLocked) {
// Can't display the buffer with new pixels, so let's give what we already have.
RegisterFrameLost("Repeating last frame while the new one isn't ready yet");
} else { // bufferForDraw was not locked, but is locked now
if (ShowBufferIsAvailable(manager)) { // may be out of memory
const bool needCommit = CopyDamagedAreasToShowBuffer(manager);
pthread_mutex_unlock(&manager->drawLock);
if (needCommit) {
needAnotherTry = !SendShowBufferToWayland(manager);
} else {
needAnotherTry = false; // nothing to show, wait for the next WLSBM_SurfaceCommit()
}
} else {
pthread_mutex_unlock(&manager->drawLock);
}
}
return !needAnotherTry;
MUTEX_UNLOCK(manager->drawLock);
}
static void
DrawBufferCreate(WLSurfaceBufferManager * manager)
{
ASSERT_DRAW_LOCK_IS_HELD(manager);
assert(manager->bufferForDraw.data == NULL);
assert(manager->bufferForDraw.damageList == NULL);
@@ -741,6 +728,9 @@ DrawBufferDestroy(WLSurfaceBufferManager * manager)
WLSurfaceBufferManager *
WLSBM_Create(jint width, jint height, jint scale, jint bgPixel, jint wlShmFormat)
{
traceEnabled = getenv("J2D_STATS");
traceFPSEnabled = getenv("J2D_FPS");
WLSurfaceBufferManager * manager = calloc(1, sizeof(WLSurfaceBufferManager));
if (!manager) {
return NULL;
@@ -762,8 +752,14 @@ WLSBM_Create(jint width, jint height, jint scale, jint bgPixel, jint wlShmFormat
// once for reading.
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&manager->drawLock, &attr);
MUTEX_LOCK(manager->drawLock); // satisfy assertions
DrawBufferCreate(manager);
MUTEX_UNLOCK(manager->drawLock);
MUTEX_LOCK(manager->showLock); // satisfy assertions
ShowBufferCreate(manager);
MUTEX_UNLOCK(manager->showLock);
J2dTrace3(J2D_TRACE_INFO, "WLSBM_Create: created %p for %dx%d px\n", manager, width, height);
return manager;
@@ -775,7 +771,12 @@ WLSBM_SurfaceAssign(WLSurfaceBufferManager * manager, struct wl_surface* wl_surf
J2dTrace2(J2D_TRACE_INFO, "WLSBM_SurfaceAssign: assigned surface %p to manger %p\n", wl_surface, manager);
MUTEX_LOCK(manager->showLock);
manager->wlSurface = wl_surface;
if (manager->wlSurface == NULL || wl_surface == NULL) {
manager->wlSurface = wl_surface;
manager->isBufferAttached = false;
} else {
assert(manager->wlSurface == wl_surface);
}
MUTEX_UNLOCK(manager->showLock);
}
@@ -785,6 +786,7 @@ WLSBM_Destroy(WLSurfaceBufferManager * manager)
J2dTrace1(J2D_TRACE_INFO, "WLSBM_Destroy: manger %p\n", manager);
// NB: must never be called in parallel with the Wayland event handlers
// because their callbacks retain a pointer to this manager.
MUTEX_LOCK(manager->showLock);
MUTEX_LOCK(manager->drawLock);
if (manager->wl_frame_callback) {
@@ -834,22 +836,8 @@ WLSBM_HeightGet(WLSurfaceBufferManager * manager)
WLDrawBuffer *
WLSBM_BufferAcquireForDrawing(WLSurfaceBufferManager * manager)
{
WLBufferTrace(manager, "WLSBM_BufferAcquireForDrawing(%d)", manager->bufferForDraw.frameID);
MUTEX_LOCK(manager->drawLock);
// We are going to "damage" the drawing frame now and therefore
// shall not commit until WLSBM_SurfaceCommit() is issued.
// Setting commitFrameID to a number that no draw frame has
// achieves just that.
// This effectively disables redrawing during very fast interactive
// resize of applications that draw often (like animated pictures)
// as new frames often appear after the size had been changed, but before
// the change has been committed to Wayland. In this case we don't
// commit and wait for a finished frame, which may not come quick
// enough before the next size change, which re-starts the same cycle.
MUTEX_LOCK(manager->showLock);
manager->commitFrameID = 0;
MUTEX_UNLOCK(manager->showLock);
return &manager->bufferForDraw;
}
@@ -858,7 +846,7 @@ WLSBM_BufferReturn(WLSurfaceBufferManager * manager, WLDrawBuffer * buffer)
{
if (&manager->bufferForDraw == buffer) {
MUTEX_UNLOCK(buffer->manager->drawLock);
ShowFrameNumbers(manager, "WLSBM_BufferReturn");
WLBufferTrace(manager, "WLSBM_BufferReturn(%d)", manager->bufferForDraw.frameID);
} else {
WL_FATAL_ERROR("WLSBM_BufferReturn() called with an unidentified buffer");
}
@@ -868,26 +856,13 @@ void
WLSBM_SurfaceCommit(WLSurfaceBufferManager * manager)
{
MUTEX_LOCK(manager->showLock);
// Request that the frame with this ID to be committed to Wayland
// and no other. Any attempt to draw on this frame will cancel
// the commit.
manager->commitFrameID = manager->bufferForDraw.frameID;
ShowFrameNumbers(manager, "WLSBM_SurfaceCommit");
if (manager->wlSurface && manager->wl_frame_callback == NULL) {
// Wayland is ready to get a new frame from us. Send whatever we have
// managed to draw by this time (maybe nothing).
if (!TrySendDrawBufferToWayland(manager)) {
// Re-schedule the same callback if we were unable to send the
// new frame to Wayland. This can happen, for instance, if Wayland
// haven't released the surface buffer to us yet.
ScheduleFrameCallback(manager);
}
ShowFrameNumbers(manager, "wl_frame_callback_done");
// Need to commit either the damage done to the surface or the re-scheduled
// callback.
wl_surface_commit(manager->wlSurface);
WLBufferTrace(manager, "WLSBM_SurfaceCommit");
const bool frameCallbackScheduled = manager->wl_frame_callback != NULL;
if (manager->wlSurface && !frameCallbackScheduled) {
bool canScheduleFrameCallback = manager->isBufferAttached;
// Don't always send the frame immediately so as not to overwhelm Wayland
bool sendNow = !canScheduleFrameCallback;
TrySendShowBufferToWayland(manager, sendNow);
}
MUTEX_UNLOCK(manager->showLock);
}
@@ -903,7 +878,7 @@ WLSB_Damage(WLDrawBuffer * buffer, jint x, jint y, jint width, jint height)
assert(y + height <= buffer->manager->bufferForDraw.height);
buffer->damageList = DamageList_Add(buffer->damageList, x, y, width, height);
ShowFrameNumbers(buffer->manager, "WLSB_Damage (at %d, %d %dx%d)", x, y, width, height);
WLBufferTrace(buffer->manager, "WLSB_Damage (at %d, %d %dx%d)", x, y, width, height);
}
pixel_t *
@@ -927,7 +902,7 @@ WLSBM_SizeChangeTo(WLSurfaceBufferManager * manager, jint width, jint height, ji
ShowBufferInvalidateForNewSize(manager);
DrawBufferCreate(manager);
ShowFrameNumbers(manager, "WLSBM_SizeChangeTo %dx%d", width, height);
WLBufferTrace(manager, "WLSBM_SizeChangeTo %dx%d", width, height);
}
MUTEX_LOCK(manager->showLock);

View File

@@ -54,7 +54,7 @@ typedef uint32_t pixel_t;
*
* At least two buffers are associated with the manager:
* - a drawing buffer that SurfaceDataOps operate with (see WLSMSurfaceData.c) and
* - a displaying buffer that is essentially wl_buffer attached to wl_surface.
* - one or more displaying buffer(s) that is essentially wl_buffer attached to wl_surface.
*
* Wayland displays pixels from the displaying buffer and we draw pixels to
* the drawing buffer. The manager is responsible for timely copying from
@@ -76,6 +76,14 @@ void WLSBM_Destroy(WLSurfaceBufferManager *);
*/
void WLSBM_SurfaceAssign(WLSurfaceBufferManager *, struct wl_surface *);
/**
* Arrange to send the current drawing buffer to the Wayland server
* to show on the screen.
* If the attempt to send the buffer immediately fails (for example,
* because the drawing buffer is still locked or there's nothing
* new to send), arranges a re-try at the next 'frame' event
* from Wayland.
*/
void WLSBM_SurfaceCommit(WLSurfaceBufferManager *);
/**