mirror of
https://github.com/JetBrains/JetBrainsRuntime.git
synced 2025-12-06 09:29:38 +01:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 *);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user