mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-04 06:12:04 +00:00
[client] expose EGL stages in frame latency
Correlate received frames with the texture update actually consumed by the renderer and retain damage until its token becomes renderable. Split client latency into dispatch, import, queue, preparation, setup, effects, desktop, composition, and swap stages in FRAME LATENCY. Exclude diagnostic overlay work from composition and preserve client wait time when producer timing is unavailable. Publish joined samples through a bounded token queue, avoid sleeping while producer timing is finalized, and correct Wayland photon units.
This commit is contained in:
@@ -63,7 +63,8 @@ static void presentationFeedbackPresented(void * opaque,
|
||||
struct timespec delta;
|
||||
|
||||
tsDiff(&delta, &present, &data->sent);
|
||||
ringbuffer_push(wlWm.photonTimings, &(float){ delta.tv_sec + delta.tv_nsec * 1e-6f });
|
||||
ringbuffer_push(wlWm.photonTimings,
|
||||
&(float){ delta.tv_sec * 1e3f + delta.tv_nsec * 1e-6f });
|
||||
free(data);
|
||||
wp_presentation_feedback_destroy(feedback);
|
||||
}
|
||||
|
||||
@@ -146,7 +146,9 @@ GraphHandle app_registerGraph(const char * name, RingBuffer buffer,
|
||||
void app_unregisterGraph(GraphHandle handle);
|
||||
void app_invalidateGraph(GraphHandle handle);
|
||||
void app_setGraphCompact(GraphHandle handle, bool compact);
|
||||
void app_setFrameImportTime(uint64_t time);
|
||||
/* Records nanosecond durations for the current frame's renderer import. The
|
||||
* wait component overlaps producer work and is accounted for separately. */
|
||||
void app_setFrameImportTiming(uint64_t importTime, uint64_t importWaitTime);
|
||||
|
||||
void app_overlayConfigRegister(const char * title,
|
||||
void (*callback)(void * udata, int * id), void * udata);
|
||||
|
||||
@@ -133,6 +133,25 @@ typedef struct LG_RendererCapture
|
||||
}
|
||||
LG_RendererCapture;
|
||||
|
||||
#define LG_RENDERER_FRAME_TOKEN_NONE 0
|
||||
|
||||
typedef uint64_t LG_RendererFrameToken;
|
||||
|
||||
/* Renderer timings are client-clock durations in nanoseconds. frameToken is
|
||||
* the received frame actually consumed by this render, or
|
||||
* LG_RENDERER_FRAME_TOKEN_NONE when the render only refreshed existing
|
||||
* content. */
|
||||
typedef struct LG_RendererFrameTiming
|
||||
{
|
||||
LG_RendererFrameToken frameToken; /* frame consumed by this render */
|
||||
uint64_t setupTime; /* render entry to desktop processing */
|
||||
uint64_t effectsTime; /* post-processing work */
|
||||
uint64_t desktopTime; /* desktop work, excluding effects */
|
||||
uint64_t composeTime; /* composition, excluding UI overlay */
|
||||
uint64_t swapTime; /* display-server buffer swap */
|
||||
}
|
||||
LG_RendererFrameTiming;
|
||||
|
||||
typedef enum LG_RendererCursor
|
||||
{
|
||||
LG_CURSOR_COLOR ,
|
||||
@@ -237,20 +256,24 @@ typedef struct LG_RendererOps
|
||||
bool (*onFrameFormat)(LG_Renderer * renderer,
|
||||
const LG_RendererFormat format);
|
||||
|
||||
/* called when there is a new frame
|
||||
* Context: frameThread */
|
||||
/* called when there is a new frame. frameToken must remain attached to the
|
||||
* update if the renderer coalesces it, and must be reported by render only
|
||||
* when that update is consumed. Context: frameThread */
|
||||
bool (*onFrame)(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFD,
|
||||
const FrameDamageRect * damage, int damageCount);
|
||||
const FrameDamageRect * damage, int damageCount,
|
||||
LG_RendererFrameToken frameToken);
|
||||
|
||||
/* called when the rederer is to startup
|
||||
* Context: renderThread */
|
||||
bool (*renderStartup)(LG_Renderer * renderer, bool useDMA);
|
||||
|
||||
/* called to render the scene
|
||||
* Context: renderThread */
|
||||
/* called to render the scene. The renderer must not consume a received
|
||||
* frame newer than frameTokenLimit, and reports the token it did consume in
|
||||
* timing (or LG_RENDERER_FRAME_TOKEN_NONE). Context: renderThread */
|
||||
bool (*render)(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
const bool newFrame, const bool invalidateWindow,
|
||||
void (*preSwap)(void * udata), void * udata);
|
||||
LG_RendererFrameToken frameTokenLimit, const bool invalidateWindow,
|
||||
void (*preSwap)(void * udata), void * udata,
|
||||
LG_RendererFrameTiming * timing);
|
||||
|
||||
/* Optional test/diagnostic readback of the fully composed framebuffer.
|
||||
* Called on the render thread before swap while the graphics context is
|
||||
|
||||
@@ -94,6 +94,7 @@ typedef uint32_t LG_TransportFrameFlags;
|
||||
|
||||
typedef struct LG_TransportFrameTiming
|
||||
{
|
||||
bool valid; /* producer fields below are available and coherent */
|
||||
uint64_t captureTime;
|
||||
uint64_t postProcessTime;
|
||||
uint64_t copyTime;
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
|
||||
struct DesktopDamage
|
||||
{
|
||||
int count;
|
||||
FrameDamageRect rects[LG_MAX_FRAME_DAMAGE_RECTS];
|
||||
LG_RendererFrameToken frameToken;
|
||||
int count;
|
||||
FrameDamageRect rects[LG_MAX_FRAME_DAMAGE_RECTS];
|
||||
};
|
||||
|
||||
typedef struct EGL_Damage EGL_Damage;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "common/debug.h"
|
||||
#include "common/option.h"
|
||||
#include "common/locking.h"
|
||||
#include "common/time.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "texture.h"
|
||||
@@ -390,14 +391,15 @@ bool egl_desktopSetup(EGL_Desktop * desktop, const LG_RendererFormat format)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd,
|
||||
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame,
|
||||
LG_RendererFrameToken frameToken, int dmaFd,
|
||||
const FrameDamageRect * damageRects, int damageRectsCount,
|
||||
uint64_t * waitTimeNs)
|
||||
{
|
||||
if (likely(desktop->useDMA && dmaFd >= 0))
|
||||
{
|
||||
if (likely(egl_textureUpdateFromDMA(
|
||||
desktop->texture, frame, dmaFd, waitTimeNs)))
|
||||
desktop->texture, frame, frameToken, dmaFd, waitTimeNs)))
|
||||
{
|
||||
atomic_store(&desktop->processFrame, true);
|
||||
return true;
|
||||
@@ -438,7 +440,7 @@ bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dma
|
||||
* the upload. Signalling here can race with that submission and process the
|
||||
* previous texture contents instead.
|
||||
*/
|
||||
if (likely(egl_textureUpdateFromFrame(desktop->texture, frame,
|
||||
if (likely(egl_textureUpdateFromFrame(desktop->texture, frame, frameToken,
|
||||
damageRects, damageRectsCount, waitTimeNs)))
|
||||
return true;
|
||||
|
||||
@@ -454,7 +456,10 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
unsigned int outputHeight, const float x, const float y,
|
||||
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
|
||||
LG_RendererRotate rotate, const struct DamageRects * rects,
|
||||
bool * fullFrame, EGL_Framebuffer * target)
|
||||
LG_RendererFrameToken damageFrameToken,
|
||||
LG_RendererFrameToken frameTokenLimit, bool * fullFrame,
|
||||
LG_RendererFrameToken * consumedFrameToken,
|
||||
uint64_t * effectsTime, EGL_Framebuffer * target)
|
||||
{
|
||||
EGL_Texture * tex;
|
||||
int width, height;
|
||||
@@ -478,21 +483,33 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
if (unlikely(outputWidth == 0 || outputHeight == 0))
|
||||
DEBUG_FATAL("outputWidth || outputHeight == 0");
|
||||
|
||||
const enum EGL_TexStatus status = egl_textureProcess(tex);
|
||||
const bool textureUpdated = status == EGL_TEX_STATUS_UPDATED;
|
||||
LG_RendererFrameToken frameToken;
|
||||
const enum EGL_TexStatus status =
|
||||
egl_textureProcess(tex, frameTokenLimit, &frameToken);
|
||||
const bool textureUpdated = status == EGL_TEX_STATUS_UPDATED;
|
||||
|
||||
*consumedFrameToken = frameToken;
|
||||
*effectsTime = 0;
|
||||
if (unlikely(status != EGL_TEX_STATUS_OK && !textureUpdated))
|
||||
{
|
||||
if (status != EGL_TEX_STATUS_NOTREADY)
|
||||
DEBUG_ERROR("Failed to process the desktop texture");
|
||||
}
|
||||
|
||||
*fullFrame = false;
|
||||
if (frameToken != LG_RENDERER_FRAME_TOKEN_NONE &&
|
||||
frameToken != damageFrameToken)
|
||||
{
|
||||
rects = NULL;
|
||||
*fullFrame = true;
|
||||
}
|
||||
|
||||
int scaleAlgo = EGL_SCALE_NEAREST;
|
||||
|
||||
egl_desktopRectsMatrix(desktop->matrix,
|
||||
width, height, x, y, scaleX, scaleY, rotate);
|
||||
egl_desktopRectsUpdate(desktop->mesh, rects, width, height);
|
||||
|
||||
*fullFrame = false;
|
||||
const bool hdr = desktop->hdr && !desktop->useSpice;
|
||||
uint32_t hdrPeak = 0;
|
||||
if (hdr)
|
||||
@@ -509,16 +526,22 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
bool processFrame = textureUpdated;
|
||||
processFrame |= atomic_exchange(&desktop->processFrame, false);
|
||||
processFrame |= egl_postProcessConfigModified(desktop->pp);
|
||||
if (processFrame &&
|
||||
egl_postProcessRun(desktop->pp, tex, desktop->mesh,
|
||||
width, height, outputWidth, outputHeight, dma,
|
||||
hdr && desktop->hdrPQ, (float)hdrPeak) &&
|
||||
egl_postProcessNeedsFullFrame(desktop->pp))
|
||||
if (processFrame)
|
||||
{
|
||||
/* The filter output may have changed everywhere, but this only applies to
|
||||
* the render that actually evaluated the filter. */
|
||||
egl_desktopRectsUpdate(desktop->mesh, NULL, width, height);
|
||||
*fullFrame = true;
|
||||
const uint64_t effectsStart = nanotime();
|
||||
const bool postProcessed = egl_postProcessRun(
|
||||
desktop->pp, tex, desktop->mesh,
|
||||
width, height, outputWidth, outputHeight, dma,
|
||||
hdr && desktop->hdrPQ, (float)hdrPeak);
|
||||
*effectsTime = nanotime() - effectsStart;
|
||||
|
||||
if (postProcessed && egl_postProcessNeedsFullFrame(desktop->pp))
|
||||
{
|
||||
/* The filter output may have changed everywhere, but this only applies
|
||||
* to the render that actually evaluated the filter. */
|
||||
egl_desktopRectsUpdate(desktop->mesh, NULL, width, height);
|
||||
*fullFrame = true;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int finalSizeX, finalSizeY;
|
||||
|
||||
@@ -48,7 +48,8 @@ void egl_desktopSetNativeHDR(EGL_Desktop * desktop, bool nativeHDR,
|
||||
void egl_desktopGetHDRMapping(EGL_Desktop * desktop, bool * enabled,
|
||||
float * gain, float * contentPeak);
|
||||
bool egl_desktopSetup (EGL_Desktop * desktop, const LG_RendererFormat format);
|
||||
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame, int dmaFd,
|
||||
bool egl_desktopUpdate(EGL_Desktop * desktop, const FrameBuffer * frame,
|
||||
LG_RendererFrameToken frameToken, int dmaFd,
|
||||
const FrameDamageRect * damageRects, int damageRectsCount,
|
||||
uint64_t * waitTimeNs);
|
||||
void egl_desktopResize(EGL_Desktop * desktop, int width, int height);
|
||||
@@ -56,7 +57,10 @@ bool egl_desktopRender(EGL_Desktop * desktop, unsigned int outputWidth,
|
||||
unsigned int outputHeight, const float x, const float y,
|
||||
const float scaleX, const float scaleY, enum EGL_DesktopScaleType scaleType,
|
||||
LG_RendererRotate rotate, const struct DamageRects * rects,
|
||||
bool * fullFrame, EGL_Framebuffer * target);
|
||||
LG_RendererFrameToken damageFrameToken,
|
||||
LG_RendererFrameToken frameTokenLimit, bool * fullFrame,
|
||||
LG_RendererFrameToken * consumedFrameToken,
|
||||
uint64_t * effectsTime, EGL_Framebuffer * target);
|
||||
|
||||
void egl_desktopSpiceConfigure(EGL_Desktop * desktop, int width, int height);
|
||||
void egl_desktopSpiceDrawFill(EGL_Desktop * desktop, int x, int y, int width,
|
||||
|
||||
@@ -378,6 +378,8 @@ static void egl_onRestart(LG_Renderer * renderer)
|
||||
this->frameContext = NULL;
|
||||
|
||||
INTERLOCKED_SECTION(this->desktopDamageLock, {
|
||||
this->desktopDamage[this->desktopDamageIdx].frameToken =
|
||||
LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
this->desktopDamage[this->desktopDamageIdx].count = -1;
|
||||
});
|
||||
}
|
||||
@@ -742,8 +744,9 @@ static bool egl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat fo
|
||||
return egl_desktopSetup(this->desktop, format);
|
||||
}
|
||||
|
||||
static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFd,
|
||||
const FrameDamageRect * damageRects, int damageRectsCount)
|
||||
static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame,
|
||||
int dmaFd, const FrameDamageRect * damageRects, int damageRectsCount,
|
||||
LG_RendererFrameToken frameToken)
|
||||
{
|
||||
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||
egl_stateCheckShared();
|
||||
@@ -751,21 +754,23 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int d
|
||||
const uint64_t start = nanotime();
|
||||
uint64_t waitTimeNs = 0;
|
||||
if (unlikely(!egl_desktopUpdate(
|
||||
this->desktop, frame, dmaFd, damageRects, damageRectsCount,
|
||||
&waitTimeNs)))
|
||||
this->desktop, frame, frameToken, dmaFd,
|
||||
damageRects, damageRectsCount, &waitTimeNs)))
|
||||
{
|
||||
DEBUG_INFO("Failed to to update the desktop");
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint64_t elapsed = nanotime() - start;
|
||||
|
||||
/* Producer Copy already covers the interval before FrameBuffer::wp becomes
|
||||
* ready. Exclude that overlapping wait from the client Import stage. */
|
||||
app_setFrameImportTime(
|
||||
elapsed > waitTimeNs ? elapsed - waitTimeNs : 0);
|
||||
app_setFrameImportTiming(
|
||||
elapsed > waitTimeNs ? elapsed - waitTimeNs : 0, waitTimeNs);
|
||||
|
||||
INTERLOCKED_SECTION(this->desktopDamageLock, {
|
||||
struct DesktopDamage * damage = this->desktopDamage + this->desktopDamageIdx;
|
||||
struct DesktopDamage * damage =
|
||||
this->desktopDamage + this->desktopDamageIdx;
|
||||
if (unlikely(
|
||||
damage->count == -1 ||
|
||||
damageRectsCount == 0 ||
|
||||
@@ -779,6 +784,7 @@ static bool egl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int d
|
||||
damageRectsCount * sizeof(FrameDamageRect));
|
||||
damage->count += damageRectsCount;
|
||||
}
|
||||
damage->frameToken = frameToken;
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -1334,10 +1340,14 @@ inline static void renderLetterBox(struct Inst * this)
|
||||
}
|
||||
|
||||
static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
const bool newFrame, const bool invalidateWindow,
|
||||
void (*preSwap)(void * udata), void * udata)
|
||||
LG_RendererFrameToken frameTokenLimit, const bool invalidateWindow,
|
||||
void (*preSwap)(void * udata), void * udata,
|
||||
LG_RendererFrameTiming * timing)
|
||||
{
|
||||
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||
*timing = (LG_RendererFrameTiming) {};
|
||||
|
||||
const uint64_t setupStart = nanotime();
|
||||
egl_stateCheckShared();
|
||||
EGLint bufferAge = egl_bufferAge(this);
|
||||
const bool hdrStateChanged = egl_updateHDRState(this, false);
|
||||
@@ -1354,9 +1364,10 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
bufferAge <= 0 || bufferAge > MAX_BUFFER_AGE ||
|
||||
this->showSpice;
|
||||
|
||||
bool hasOverlay = false;
|
||||
struct CursorState cursorState = { .visible = false };
|
||||
struct DesktopDamage * desktopDamage;
|
||||
bool hasOverlay = false;
|
||||
struct CursorState cursorState = { .visible = false };
|
||||
struct DesktopDamage noDesktopDamage = {};
|
||||
struct DesktopDamage * desktopDamage = NULL;
|
||||
|
||||
struct DamageRects * accumulated = (struct DamageRects *)alloca(
|
||||
sizeof(struct DamageRects) +
|
||||
@@ -1365,12 +1376,20 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
accumulated->count = 0;
|
||||
|
||||
INTERLOCKED_SECTION(this->desktopDamageLock, {
|
||||
struct DesktopDamage * pendingDamage =
|
||||
this->desktopDamage + this->desktopDamageIdx;
|
||||
const bool damageQueued =
|
||||
pendingDamage->frameToken == LG_RENDERER_FRAME_TOKEN_NONE ||
|
||||
pendingDamage->frameToken <= frameTokenLimit;
|
||||
const int historyOffset = damageQueued ? 0 : 1;
|
||||
|
||||
if (likely(!renderAll))
|
||||
{
|
||||
for (int i = 0; i < bufferAge; ++i)
|
||||
{
|
||||
struct DesktopDamage * damage = this->desktopDamage +
|
||||
IDX_AGO(this->desktopDamageIdx, i, DESKTOP_DAMAGE_COUNT);
|
||||
IDX_AGO(this->desktopDamageIdx, i + historyOffset,
|
||||
DESKTOP_DAMAGE_COUNT);
|
||||
|
||||
if (unlikely(damage->count < 0))
|
||||
{
|
||||
@@ -1387,9 +1406,18 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
}
|
||||
}
|
||||
}
|
||||
desktopDamage = this->desktopDamage + this->desktopDamageIdx;
|
||||
this->desktopDamageIdx = (this->desktopDamageIdx + 1) % DESKTOP_DAMAGE_COUNT;
|
||||
this->desktopDamage[this->desktopDamageIdx].count = 0;
|
||||
|
||||
if (damageQueued)
|
||||
{
|
||||
desktopDamage = pendingDamage;
|
||||
this->desktopDamageIdx =
|
||||
(this->desktopDamageIdx + 1) % DESKTOP_DAMAGE_COUNT;
|
||||
this->desktopDamage[this->desktopDamageIdx].frameToken =
|
||||
LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
this->desktopDamage[this->desktopDamageIdx].count = 0;
|
||||
}
|
||||
else
|
||||
desktopDamage = &noDesktopDamage;
|
||||
});
|
||||
|
||||
if (hdrStateChanged)
|
||||
@@ -1427,17 +1455,46 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
}
|
||||
++this->overlayHistoryIdx;
|
||||
|
||||
bool fullFrame = false;
|
||||
const bool linearHDRComposition = egl_hdrComposeBegin(this->hdrCompose);
|
||||
bool deferredLogicalCursor = false;
|
||||
if (likely(this->destRect.w > 0 && this->destRect.h > 0))
|
||||
bool fullFrame = false;
|
||||
const bool linearHDRComposition = egl_hdrComposeBegin(
|
||||
this->hdrCompose);
|
||||
bool deferredLogicalCursor = false;
|
||||
const bool haveDesktop = likely(
|
||||
this->destRect.w > 0 && this->destRect.h > 0);
|
||||
bool desktopRendered = false;
|
||||
const uint64_t desktopStart = nanotime();
|
||||
uint64_t effectsTime = 0;
|
||||
LG_RendererFrameToken frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
|
||||
timing->setupTime = desktopStart - setupStart;
|
||||
if (haveDesktop)
|
||||
{
|
||||
if (egl_desktopRender(this->desktop,
|
||||
desktopRendered = egl_desktopRender(this->desktop,
|
||||
this->destRect.w, this->destRect.h,
|
||||
this->translateX, this->translateY,
|
||||
this->scaleX , this->scaleY ,
|
||||
this->scaleType , rotate, renderAll ? NULL : accumulated,
|
||||
&fullFrame, egl_hdrComposeGetFramebuffer(this->hdrCompose)))
|
||||
desktopDamage->frameToken, frameTokenLimit, &fullFrame, &frameToken,
|
||||
&effectsTime,
|
||||
egl_hdrComposeGetFramebuffer(this->hdrCompose));
|
||||
}
|
||||
const uint64_t composeStart = nanotime();
|
||||
if (haveDesktop)
|
||||
{
|
||||
const uint64_t totalDesktopTime = composeStart - desktopStart;
|
||||
timing->effectsTime = effectsTime;
|
||||
timing->desktopTime = totalDesktopTime > effectsTime ?
|
||||
totalDesktopTime - effectsTime : 0;
|
||||
}
|
||||
else
|
||||
timing->setupTime += composeStart - desktopStart;
|
||||
|
||||
timing->frameToken = frameToken;
|
||||
const bool frameConsumed = frameToken != LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
|
||||
if (haveDesktop)
|
||||
{
|
||||
if (desktopRendered)
|
||||
{
|
||||
cursorState = egl_cursorRender(this->cursor,
|
||||
(this->format.rotate + rotate) % LG_ROTATE_MAX,
|
||||
@@ -1456,9 +1513,13 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
renderLetterBox(this);
|
||||
|
||||
hasOverlay |=
|
||||
egl_damageRender(this->damage, rotate, newFrame ? desktopDamage : NULL) |
|
||||
egl_damageRender(
|
||||
this->damage, rotate, frameConsumed ? desktopDamage : NULL) |
|
||||
invalidateWindow;
|
||||
|
||||
/* The diagnostics being displayed must not contribute to Compose. */
|
||||
timing->composeTime = nanotime() - composeStart;
|
||||
|
||||
struct Rect damage[LG_MAX_FRAME_DAMAGE_RECTS + MAX_OVERLAY_RECTS + 2];
|
||||
int damageIdx = app_renderOverlay(damage, MAX_OVERLAY_RECTS);
|
||||
if (unlikely(damageIdx != 0))
|
||||
@@ -1482,6 +1543,7 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
for (int i = 0; i < damageIdx; ++i)
|
||||
damage[i].y = this->height - damage[i].y - damage[i].h;
|
||||
}
|
||||
const uint64_t postOverlayStart = nanotime();
|
||||
|
||||
if (likely(damageIdx >= 0 && cursorState.visible))
|
||||
damage[damageIdx++] = cursorState.rect;
|
||||
@@ -1559,13 +1621,19 @@ static bool egl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
egl_cursorRender(this->cursor,
|
||||
(this->format.rotate + rotate) % LG_ROTATE_MAX,
|
||||
this->width, this->height, false, NULL);
|
||||
|
||||
this->hadOverlay = hasOverlay;
|
||||
this->cursorLast = cursorState;
|
||||
|
||||
preSwap(udata);
|
||||
|
||||
const uint64_t composeEnd = nanotime();
|
||||
timing->composeTime += composeEnd - postOverlayStart;
|
||||
|
||||
const uint64_t swapStart = nanotime();
|
||||
app_eglSwapBuffers(this->display, this->surface, damage,
|
||||
this->noSwapDamage ? 0 : damageIdx);
|
||||
const uint64_t swapEnd = nanotime();
|
||||
timing->swapTime = swapEnd - swapStart;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -160,12 +160,14 @@ bool egl_textureUpdateRect(EGL_Texture * this,
|
||||
}
|
||||
|
||||
bool egl_textureUpdateFromFrame(EGL_Texture * this,
|
||||
const FrameBuffer * frame, const FrameDamageRect * damageRects,
|
||||
int damageRectsCount, uint64_t * waitTimeNs)
|
||||
const FrameBuffer * frame, LG_RendererFrameToken frameToken,
|
||||
const FrameDamageRect * damageRects, int damageRectsCount,
|
||||
uint64_t * waitTimeNs)
|
||||
{
|
||||
const struct EGL_TexUpdate update =
|
||||
{
|
||||
.type = EGL_TEXTYPE_FRAMEBUFFER,
|
||||
.frameToken = frameToken,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = this->format.width,
|
||||
@@ -182,18 +184,20 @@ bool egl_textureUpdateFromFrame(EGL_Texture * this,
|
||||
}
|
||||
|
||||
bool egl_textureUpdateFromDMA(EGL_Texture * this,
|
||||
const FrameBuffer * frame, const int dmaFd, uint64_t * waitTimeNs)
|
||||
const FrameBuffer * frame, LG_RendererFrameToken frameToken,
|
||||
const int dmaFd, uint64_t * waitTimeNs)
|
||||
{
|
||||
const struct EGL_TexUpdate update =
|
||||
{
|
||||
.type = EGL_TEXTYPE_DMABUF,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = this->format.width,
|
||||
.height = this->format.height,
|
||||
.pitch = this->format.pitch,
|
||||
.stride = this->format.stride,
|
||||
.dmaFD = dmaFd
|
||||
.type = EGL_TEXTYPE_DMABUF,
|
||||
.frameToken = frameToken,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = this->format.width,
|
||||
.height = this->format.height,
|
||||
.pitch = this->format.pitch,
|
||||
.stride = this->format.stride,
|
||||
.dmaFD = dmaFd
|
||||
};
|
||||
|
||||
/* wait for completion */
|
||||
@@ -204,9 +208,18 @@ bool egl_textureUpdateFromDMA(EGL_Texture * this,
|
||||
return this->ops.update(this, &update);
|
||||
}
|
||||
|
||||
enum EGL_TexStatus egl_textureProcess(EGL_Texture * this)
|
||||
enum EGL_TexStatus egl_textureProcess(EGL_Texture * this,
|
||||
LG_RendererFrameToken frameTokenLimit,
|
||||
LG_RendererFrameToken * consumedFrameToken)
|
||||
{
|
||||
return this->ops.process(this);
|
||||
if (consumedFrameToken)
|
||||
*consumedFrameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
|
||||
const enum EGL_TexStatus status =
|
||||
this->ops.process(this, frameTokenLimit);
|
||||
if (status == EGL_TEX_STATUS_UPDATED && consumedFrameToken)
|
||||
*consumedFrameToken = this->frameToken;
|
||||
return status;
|
||||
}
|
||||
|
||||
enum EGL_TexStatus egl_textureBind(EGL_Texture * this)
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "model.h"
|
||||
#include "common/framebuffer.h"
|
||||
#include "common/types.h"
|
||||
#include "interface/renderer.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
@@ -40,8 +41,9 @@ typedef struct EGL_Model EGL_Model;
|
||||
typedef struct EGL_TexUpdate
|
||||
{
|
||||
/* the type of this update */
|
||||
EGL_TexType type;
|
||||
uint64_t * waitTimeNs;
|
||||
EGL_TexType type;
|
||||
LG_RendererFrameToken frameToken;
|
||||
uint64_t * waitTimeNs;
|
||||
|
||||
int x, y, width, height;
|
||||
|
||||
@@ -90,7 +92,8 @@ typedef struct EGL_TextureOps
|
||||
bool (*update)(EGL_Texture * texture, const EGL_TexUpdate * update);
|
||||
|
||||
/* called from a background job to prepare the texture for use before bind */
|
||||
enum EGL_TexStatus (*process)(EGL_Texture * texture);
|
||||
enum EGL_TexStatus (*process)(EGL_Texture * texture,
|
||||
LG_RendererFrameToken frameTokenLimit);
|
||||
|
||||
/* get the texture for use */
|
||||
enum EGL_TexStatus (*get)(EGL_Texture * texture, GLuint * tex,
|
||||
@@ -104,10 +107,11 @@ EGL_TextureOps;
|
||||
struct EGL_Texture
|
||||
{
|
||||
struct EGL_TextureOps ops;
|
||||
EGL_TexType type;
|
||||
GLuint sampler;
|
||||
EGL_TexType type;
|
||||
GLuint sampler;
|
||||
LG_RendererFrameToken frameToken;
|
||||
|
||||
EGL_TexFormat format;
|
||||
EGL_TexFormat format;
|
||||
};
|
||||
|
||||
bool egl_textureInit(EGL_Texture ** texture, EGLDisplay * display,
|
||||
@@ -125,13 +129,24 @@ bool egl_textureUpdateRect(EGL_Texture * texture,
|
||||
const uint8_t * buffer, bool topDown);
|
||||
|
||||
bool egl_textureUpdateFromFrame(EGL_Texture * texture,
|
||||
const FrameBuffer * frame, const FrameDamageRect * damageRects,
|
||||
int damageRectsCount, uint64_t * waitTimeNs);
|
||||
const FrameBuffer * frame, LG_RendererFrameToken frameToken,
|
||||
const FrameDamageRect * damageRects, int damageRectsCount,
|
||||
uint64_t * waitTimeNs);
|
||||
|
||||
bool egl_textureUpdateFromDMA(EGL_Texture * texture,
|
||||
const FrameBuffer * frame, const int dmaFd, uint64_t * waitTimeNs);
|
||||
const FrameBuffer * frame, LG_RendererFrameToken frameToken,
|
||||
const int dmaFd, uint64_t * waitTimeNs);
|
||||
|
||||
enum EGL_TexStatus egl_textureProcess(EGL_Texture * texture);
|
||||
enum EGL_TexStatus egl_textureProcess(EGL_Texture * texture,
|
||||
LG_RendererFrameToken frameTokenLimit,
|
||||
LG_RendererFrameToken * consumedFrameToken);
|
||||
|
||||
static inline bool egl_textureFrameAllowed(LG_RendererFrameToken frameToken,
|
||||
LG_RendererFrameToken frameTokenLimit)
|
||||
{
|
||||
return frameToken == LG_RENDERER_FRAME_TOKEN_NONE ||
|
||||
frameToken <= frameTokenLimit;
|
||||
}
|
||||
|
||||
static inline EGL_TexStatus egl_textureGet(EGL_Texture * texture, GLuint * tex,
|
||||
unsigned int * sizeX, unsigned int * sizeY, EGL_PixelFormat * fmt)
|
||||
|
||||
@@ -108,11 +108,15 @@ bool egl_texBufferSetup(EGL_Texture * texture, const EGL_TexSetup * setup)
|
||||
NULL);
|
||||
}
|
||||
|
||||
this->bufIndex = 0;
|
||||
this->rIndex = -1;
|
||||
this->bufIndex = 0;
|
||||
this->rIndex = -1;
|
||||
texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
|
||||
for (int i = 0; i < this->texCount; ++i)
|
||||
{
|
||||
this->buf[i].updated = false;
|
||||
this->slotToken[i] = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -138,8 +142,10 @@ static bool egl_texBufferUpdate(EGL_Texture * texture, const EGL_TexUpdate * upd
|
||||
return true;
|
||||
}
|
||||
|
||||
EGL_TexStatus egl_texBufferProcess(EGL_Texture * texture)
|
||||
EGL_TexStatus egl_texBufferProcess(EGL_Texture * texture,
|
||||
LG_RendererFrameToken frameTokenLimit)
|
||||
{
|
||||
(void)frameTokenLimit;
|
||||
return EGL_TEX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -260,13 +266,15 @@ static bool egl_texBufferStreamUpdate(EGL_Texture * texture,
|
||||
}
|
||||
}
|
||||
|
||||
this->slotToken[this->bufIndex] = update->frameToken;
|
||||
this->buf[this->bufIndex].updated = true;
|
||||
LG_UNLOCK(this->copyLock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture)
|
||||
EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture,
|
||||
LG_RendererFrameToken frameTokenLimit)
|
||||
{
|
||||
TextureBuffer * this = UPCAST(TextureBuffer, texture);
|
||||
|
||||
@@ -276,7 +284,8 @@ EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture)
|
||||
GLuint tex = this->tex[index];
|
||||
EGL_TexBuffer * buffer = &this->buf[index];
|
||||
|
||||
if (!buffer->updated)
|
||||
if (!buffer->updated ||
|
||||
!egl_textureFrameAllowed(this->slotToken[index], frameTokenLimit))
|
||||
{
|
||||
LG_UNLOCK(this->copyLock);
|
||||
return EGL_TEX_STATUS_OK;
|
||||
@@ -284,7 +293,8 @@ EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture)
|
||||
|
||||
DEBUG_ASSERT(!this->sync[index]);
|
||||
|
||||
this->rIndex = index;
|
||||
this->rIndex = index;
|
||||
texture->frameToken = this->slotToken[index];
|
||||
if (++this->bufIndex == this->texCount)
|
||||
this->bufIndex = 0;
|
||||
buffer->updated = false;
|
||||
|
||||
@@ -31,14 +31,15 @@ typedef struct TextureBuffer
|
||||
EGL_Texture base;
|
||||
bool free;
|
||||
|
||||
int texCount;
|
||||
GLuint tex[EGL_TEX_BUFFER_MAX];
|
||||
EGL_TexBuffer buf[EGL_TEX_BUFFER_MAX];
|
||||
int bufFree;
|
||||
GLsync sync[EGL_TEX_BUFFER_MAX];
|
||||
LG_Lock copyLock;
|
||||
int bufIndex;
|
||||
int rIndex;
|
||||
int texCount;
|
||||
GLuint tex[EGL_TEX_BUFFER_MAX];
|
||||
EGL_TexBuffer buf[EGL_TEX_BUFFER_MAX];
|
||||
int bufFree;
|
||||
GLsync sync[EGL_TEX_BUFFER_MAX];
|
||||
LG_RendererFrameToken slotToken[EGL_TEX_BUFFER_MAX];
|
||||
LG_Lock copyLock;
|
||||
int bufIndex;
|
||||
int rIndex;
|
||||
}
|
||||
TextureBuffer;
|
||||
|
||||
@@ -46,7 +47,8 @@ bool egl_texBufferInit(EGL_Texture ** texture_, EGL_TexType type,
|
||||
EGLDisplay * display);
|
||||
void egl_texBufferFree(EGL_Texture * texture_);
|
||||
bool egl_texBufferSetup(EGL_Texture * texture_, const EGL_TexSetup * setup);
|
||||
EGL_TexStatus egl_texBufferProcess(EGL_Texture * texture_);
|
||||
EGL_TexStatus egl_texBufferProcess(EGL_Texture * texture_,
|
||||
LG_RendererFrameToken frameTokenLimit);
|
||||
EGL_TexStatus egl_texBufferGet(EGL_Texture * texture_, GLuint * tex,
|
||||
EGL_PixelFormat * fmt);
|
||||
EGL_TexStatus egl_texBufferBind(EGL_Texture * texture_, GLuint unit);
|
||||
@@ -57,6 +59,7 @@ bool egl_texBufferStreamSetup(EGL_Texture * texture_,
|
||||
const EGL_TexSetup * setup);
|
||||
/* Returns with copyLock held when the current upload buffer is safe to write. */
|
||||
bool egl_texBufferStreamLock(TextureBuffer * texture);
|
||||
EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture_);
|
||||
EGL_TexStatus egl_texBufferStreamProcess(EGL_Texture * texture_,
|
||||
LG_RendererFrameToken frameTokenLimit);
|
||||
EGL_TexStatus egl_texBufferStreamGet(EGL_Texture * texture_, GLuint * tex,
|
||||
EGL_PixelFormat * fmt);
|
||||
|
||||
@@ -29,10 +29,11 @@
|
||||
|
||||
struct FdImage
|
||||
{
|
||||
int fd;
|
||||
EGLImage image;
|
||||
GLsync sync;
|
||||
int texIndex;
|
||||
int fd;
|
||||
EGLImage image;
|
||||
GLsync sync;
|
||||
int texIndex;
|
||||
LG_RendererFrameToken frameToken;
|
||||
};
|
||||
|
||||
typedef struct TexDMABUF
|
||||
@@ -43,6 +44,7 @@ typedef struct TexDMABUF
|
||||
|
||||
struct FdImage images[2];
|
||||
int lastIndex;
|
||||
int renderIndex;
|
||||
|
||||
EGL_PixelFormat pixFmt;
|
||||
unsigned fourcc;
|
||||
@@ -76,10 +78,16 @@ static void egl_texDMABUFCleanup(EGL_Texture * texture)
|
||||
glDeleteSync(this->images[i].sync);
|
||||
this->images[i].sync = 0;
|
||||
}
|
||||
this->images[i].fd = -1;
|
||||
this->images[i].texIndex = -1;
|
||||
this->images[i].fd = -1;
|
||||
this->images[i].texIndex = -1;
|
||||
this->images[i].frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
}
|
||||
|
||||
this->lastIndex = -1;
|
||||
this->renderIndex = -1;
|
||||
parent->rIndex = -1;
|
||||
texture->frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
|
||||
egl_texUtilFreeBuffers(parent->buf, parent->texCount);
|
||||
|
||||
if (parent->tex[0])
|
||||
@@ -99,12 +107,14 @@ static bool egl_texDMABUFInit(EGL_Texture ** texture, EGL_TexType type,
|
||||
|
||||
for(int i = 0; i < ARRAY_LENGTH(this->images); ++i)
|
||||
{
|
||||
this->images[i].fd = -1;
|
||||
this->images[i].image = EGL_NO_IMAGE;
|
||||
this->images[i].sync = 0;
|
||||
this->images[i].texIndex = -1;
|
||||
this->images[i].fd = -1;
|
||||
this->images[i].image = EGL_NO_IMAGE;
|
||||
this->images[i].sync = 0;
|
||||
this->images[i].texIndex = -1;
|
||||
this->images[i].frameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
}
|
||||
this->lastIndex = -1;
|
||||
this->lastIndex = -1;
|
||||
this->renderIndex = -1;
|
||||
|
||||
EGL_Texture * parent = &this->base.base;
|
||||
if (!egl_texBufferStreamInit(&parent, type, display))
|
||||
@@ -276,15 +286,45 @@ static bool egl_texDMABUFUpdate(EGL_Texture * texture,
|
||||
|
||||
INTERLOCKED_SECTION(parent->copyLock,
|
||||
{
|
||||
this->lastIndex = (fdImage == &this->images[0]) ? 0 : 1;
|
||||
fdImage->frameToken = update->frameToken;
|
||||
this->lastIndex = (fdImage == &this->images[0]) ? 0 : 1;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static EGL_TexStatus egl_texDMABUFProcess(EGL_Texture * texture)
|
||||
static EGL_TexStatus egl_texDMABUFProcess(EGL_Texture * texture,
|
||||
LG_RendererFrameToken frameTokenLimit)
|
||||
{
|
||||
return EGL_TEX_STATUS_OK;
|
||||
TextureBuffer * parent = UPCAST(TextureBuffer, texture);
|
||||
TexDMABUF * this = UPCAST(TexDMABUF , parent);
|
||||
|
||||
int index = -1;
|
||||
bool haveImage = false;
|
||||
bool updated = false;
|
||||
INTERLOCKED_SECTION(parent->copyLock,
|
||||
{
|
||||
index = this->lastIndex;
|
||||
haveImage = this->renderIndex >= 0;
|
||||
if (index >= 0 && egl_textureFrameAllowed(
|
||||
this->images[index].frameToken, frameTokenLimit))
|
||||
{
|
||||
const struct FdImage * pending = &this->images[index];
|
||||
if (this->renderIndex != index ||
|
||||
texture->frameToken != pending->frameToken)
|
||||
{
|
||||
this->renderIndex = index;
|
||||
parent->rIndex = pending->texIndex;
|
||||
texture->frameToken = pending->frameToken;
|
||||
updated = true;
|
||||
haveImage = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (unlikely(!haveImage))
|
||||
return EGL_TEX_STATUS_NOTREADY;
|
||||
return updated ? EGL_TEX_STATUS_UPDATED : EGL_TEX_STATUS_OK;
|
||||
}
|
||||
|
||||
static EGL_TexStatus egl_texDMABUFGet(EGL_Texture * texture, GLuint * tex,
|
||||
@@ -299,7 +339,7 @@ static EGL_TexStatus egl_texDMABUFGet(EGL_Texture * texture, GLuint * tex,
|
||||
|
||||
INTERLOCKED_SECTION(parent->copyLock,
|
||||
{
|
||||
index = this->lastIndex;
|
||||
index = this->renderIndex;
|
||||
if (index >= 0)
|
||||
{
|
||||
struct FdImage * cur = &this->images[index];
|
||||
|
||||
@@ -156,12 +156,14 @@ static bool egl_texFBUpdate(EGL_Texture * texture, const EGL_TexUpdate * update)
|
||||
{
|
||||
/* The mapped PBO may contain a partial copy. Force a full refresh if the
|
||||
* caller recovers, and never expose this slot to the render context. */
|
||||
damage->count = -1;
|
||||
damage->count = -1;
|
||||
parent->buf[parent->bufIndex].updated = false;
|
||||
parent->slotToken[parent->bufIndex] = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
LG_UNLOCK(parent->copyLock);
|
||||
return false;
|
||||
}
|
||||
|
||||
parent->slotToken[parent->bufIndex] = update->frameToken;
|
||||
parent->buf[parent->bufIndex].updated = true;
|
||||
|
||||
for (int i = 0; i < EGL_TEX_BUFFER_MAX; ++i)
|
||||
|
||||
@@ -107,44 +107,46 @@ struct Inst
|
||||
LG_RendererParams params;
|
||||
struct OpenGL_Options opt;
|
||||
|
||||
bool amdPinnedMemSupport;
|
||||
bool renderStarted;
|
||||
bool configured;
|
||||
bool reconfigure;
|
||||
LG_DSGLContext glContext;
|
||||
bool amdPinnedMemSupport;
|
||||
bool renderStarted;
|
||||
bool configured;
|
||||
bool reconfigure;
|
||||
LG_DSGLContext glContext;
|
||||
|
||||
struct IntPoint window;
|
||||
float uiScale;
|
||||
_Atomic(bool) frameUpdate;
|
||||
struct IntPoint window;
|
||||
float uiScale;
|
||||
_Atomic(bool) frameUpdate;
|
||||
|
||||
LG_Lock formatLock;
|
||||
LG_RendererFormat format;
|
||||
GLuint intFormat;
|
||||
GLuint vboFormat;
|
||||
GLuint dataFormat;
|
||||
size_t texSize;
|
||||
size_t texPos;
|
||||
float scaleX, scaleY;
|
||||
const FrameBuffer * frame;
|
||||
LG_Lock formatLock;
|
||||
LG_RendererFormat format;
|
||||
GLuint intFormat;
|
||||
GLuint vboFormat;
|
||||
GLuint dataFormat;
|
||||
size_t texSize;
|
||||
size_t texPos;
|
||||
float scaleX, scaleY;
|
||||
const FrameBuffer * frame;
|
||||
LG_RendererFrameToken pendingFrameToken;
|
||||
|
||||
uint64_t drawStart;
|
||||
bool hasBuffers;
|
||||
GLuint vboID[BUFFER_COUNT];
|
||||
uint8_t * texPixels[BUFFER_COUNT];
|
||||
LG_Lock frameLock;
|
||||
bool texReady;
|
||||
int texWIndex, texRIndex;
|
||||
int texList;
|
||||
int mouseList;
|
||||
int spiceList;
|
||||
LG_RendererRect destRect;
|
||||
struct IntPoint spiceSize;
|
||||
bool spiceShow;
|
||||
uint64_t drawStart;
|
||||
bool hasBuffers;
|
||||
GLuint vboID[BUFFER_COUNT];
|
||||
uint8_t * texPixels[BUFFER_COUNT];
|
||||
LG_Lock frameLock;
|
||||
bool texReady;
|
||||
int texWIndex, texRIndex;
|
||||
int texList;
|
||||
int mouseList;
|
||||
int spiceList;
|
||||
LG_RendererRect destRect;
|
||||
struct IntPoint spiceSize;
|
||||
bool spiceShow;
|
||||
|
||||
bool hasTextures, hasFrames;
|
||||
GLuint frames[BUFFER_COUNT];
|
||||
GLsync fences[BUFFER_COUNT];
|
||||
GLuint textures[TEXTURE_COUNT];
|
||||
bool hasTextures, hasFrames;
|
||||
GLuint frames[BUFFER_COUNT];
|
||||
GLsync fences[BUFFER_COUNT];
|
||||
LG_RendererFrameToken frameToken[BUFFER_COUNT];
|
||||
GLuint textures[TEXTURE_COUNT];
|
||||
|
||||
LG_Lock mouseLock;
|
||||
LG_RendererCursor mouseCursor;
|
||||
@@ -174,7 +176,9 @@ enum ConfigStatus
|
||||
static void deconfigure(struct Inst * this);
|
||||
static enum ConfigStatus configure(struct Inst * this);
|
||||
static void updateMouseShape(struct Inst * this);
|
||||
static bool drawFrame(struct Inst * this);
|
||||
static bool drawFrame(struct Inst * this,
|
||||
LG_RendererFrameToken frameTokenLimit,
|
||||
LG_RendererFrameToken * consumedFrameToken);
|
||||
static void drawMouse(struct Inst * this);
|
||||
|
||||
const char * opengl_getName(void)
|
||||
@@ -391,12 +395,14 @@ bool opengl_onFrameFormat(LG_Renderer * renderer, const LG_RendererFormat format
|
||||
}
|
||||
|
||||
bool opengl_onFrame(LG_Renderer * renderer, const FrameBuffer * frame, int dmaFd,
|
||||
const FrameDamageRect * damage, int damageCount)
|
||||
const FrameDamageRect * damage, int damageCount,
|
||||
LG_RendererFrameToken frameToken)
|
||||
{
|
||||
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||
|
||||
LG_LOCK(this->frameLock);
|
||||
this->frame = frame;
|
||||
this->frame = frame;
|
||||
this->pendingFrameToken = frameToken;
|
||||
atomic_store_explicit(&this->frameUpdate, true, memory_order_release);
|
||||
LG_UNLOCK(this->frameLock);
|
||||
|
||||
@@ -486,10 +492,13 @@ bool opengl_renderStartup(LG_Renderer * renderer, bool useDMA)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool opengl_render(LG_Renderer * renderer, LG_RendererRotate rotate, const bool newFrame,
|
||||
const bool invalidateWindow, void (*preSwap)(void * udata), void * udata)
|
||||
bool opengl_render(LG_Renderer * renderer, LG_RendererRotate rotate,
|
||||
LG_RendererFrameToken frameTokenLimit, const bool invalidateWindow,
|
||||
void (*preSwap)(void * udata), void * udata,
|
||||
LG_RendererFrameTiming * timing)
|
||||
{
|
||||
struct Inst * this = UPCAST(struct Inst, renderer);
|
||||
*timing = (LG_RendererFrameTiming) {};
|
||||
|
||||
setupModelView(this);
|
||||
|
||||
@@ -501,7 +510,7 @@ bool opengl_render(LG_Renderer * renderer, LG_RendererRotate rotate, const bool
|
||||
|
||||
case CONFIG_STATUS_NOOP :
|
||||
case CONFIG_STATUS_OK :
|
||||
if (!drawFrame(this))
|
||||
if (!drawFrame(this, frameTokenLimit, &timing->frameToken))
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1024,16 +1033,17 @@ static void deconfigure(struct Inst * this)
|
||||
this->hasBuffers = false;
|
||||
}
|
||||
|
||||
if (this->amdPinnedMemSupport)
|
||||
for(int i = 0; i < BUFFER_COUNT; ++i)
|
||||
{
|
||||
for(int i = 0; i < BUFFER_COUNT; ++i)
|
||||
if (this->fences[i])
|
||||
{
|
||||
if (this->fences[i])
|
||||
{
|
||||
g_gl_dynProcs.glDeleteSync(this->fences[i]);
|
||||
this->fences[i] = NULL;
|
||||
}
|
||||
g_gl_dynProcs.glDeleteSync(this->fences[i]);
|
||||
this->fences[i] = NULL;
|
||||
}
|
||||
this->frameToken[i] = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
|
||||
if (this->amdPinnedMemSupport)
|
||||
{
|
||||
if (this->texPixels[i])
|
||||
{
|
||||
free(this->texPixels[i]);
|
||||
@@ -1208,8 +1218,12 @@ static bool opengl_bufferFn(void * opaque, const void * data, size_t size)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool drawFrame(struct Inst * this)
|
||||
static bool drawFrame(struct Inst * this,
|
||||
LG_RendererFrameToken frameTokenLimit,
|
||||
LG_RendererFrameToken * consumedFrameToken)
|
||||
{
|
||||
*consumedFrameToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
|
||||
if (g_gl_dynProcs.glIsSync(this->fences[this->texWIndex]))
|
||||
{
|
||||
switch(g_gl_dynProcs.glClientWaitSync(this->fences[this->texWIndex], 0, GL_TIMEOUT_IGNORED))
|
||||
@@ -1234,16 +1248,21 @@ static bool drawFrame(struct Inst * this)
|
||||
this->fences[this->texWIndex] = NULL;
|
||||
|
||||
this->texRIndex = this->texWIndex;
|
||||
*consumedFrameToken = this->frameToken[this->texRIndex];
|
||||
this->frameToken[this->texRIndex] = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
if (++this->texWIndex == BUFFER_COUNT)
|
||||
this->texWIndex = 0;
|
||||
}
|
||||
|
||||
LG_LOCK(this->frameLock);
|
||||
if (!atomic_exchange_explicit(&this->frameUpdate, false, memory_order_acquire))
|
||||
if (!atomic_load_explicit(&this->frameUpdate, memory_order_acquire) ||
|
||||
this->pendingFrameToken > frameTokenLimit)
|
||||
{
|
||||
LG_UNLOCK(this->frameLock);
|
||||
return true;
|
||||
}
|
||||
atomic_store_explicit(&this->frameUpdate, false, memory_order_release);
|
||||
const LG_RendererFrameToken pendingFrameToken = this->pendingFrameToken;
|
||||
|
||||
LG_LOCK(this->formatLock);
|
||||
glBindTexture(GL_TEXTURE_2D, this->frames[this->texWIndex]);
|
||||
@@ -1317,6 +1336,7 @@ static bool drawFrame(struct Inst * this)
|
||||
// set a fence so we don't overwrite a buffer in use
|
||||
this->fences[this->texWIndex] =
|
||||
g_gl_dynProcs.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
this->frameToken[this->texWIndex] = pendingFrameToken;
|
||||
glFlush();
|
||||
|
||||
LG_UNLOCK(this->formatLock);
|
||||
|
||||
@@ -922,9 +922,10 @@ void app_setGraphCompact(GraphHandle handle, bool compact)
|
||||
overlayGraph_setCompact(handle, compact);
|
||||
}
|
||||
|
||||
void app_setFrameImportTime(uint64_t time)
|
||||
void app_setFrameImportTiming(uint64_t importTime, uint64_t importWaitTime)
|
||||
{
|
||||
g_state.frameImportTime = time;
|
||||
g_state.frameImportTime = importTime;
|
||||
g_state.frameImportWaitTime = importWaitTime;
|
||||
}
|
||||
|
||||
void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params)
|
||||
|
||||
@@ -199,82 +199,296 @@ static bool tickTimerFn(void * unused)
|
||||
return true;
|
||||
}
|
||||
|
||||
struct RenderTiming
|
||||
#define FRAME_TIMING_RECORD_COUNT 1024
|
||||
#define FRAME_TIMING_PUBLISH_BATCH_SIZE 32
|
||||
|
||||
enum FrameTimingReady
|
||||
{
|
||||
uint64_t renderStart;
|
||||
FRAME_TIMING_FRAME_READY = 1 << 0,
|
||||
FRAME_TIMING_RENDER_READY = 1 << 1,
|
||||
};
|
||||
|
||||
struct FrameTimingRecord
|
||||
{
|
||||
LG_RendererFrameToken token;
|
||||
unsigned readyMask;
|
||||
bool producerValid;
|
||||
|
||||
uint64_t captureTime;
|
||||
uint64_t postProcessTime;
|
||||
uint64_t copyTime;
|
||||
uint64_t readyTime;
|
||||
uint64_t importTime;
|
||||
uint64_t importWaitTime;
|
||||
uint64_t dispatchTime;
|
||||
uint64_t queueStart;
|
||||
|
||||
uint64_t prepareStart;
|
||||
uint64_t prepareTime;
|
||||
uint64_t timestamp;
|
||||
uint64_t setupTime;
|
||||
uint64_t effectsTime;
|
||||
uint64_t desktopTime;
|
||||
uint64_t composeTime;
|
||||
uint64_t swapTime;
|
||||
};
|
||||
|
||||
static struct RenderTiming frameTimingLoad(void)
|
||||
static struct
|
||||
{
|
||||
struct RenderTiming timing = {};
|
||||
LG_Lock lock;
|
||||
_Atomic(LG_RendererFrameToken) queuedToken;
|
||||
LG_RendererFrameToken nextToken;
|
||||
LG_RendererFrameToken retireToken;
|
||||
unsigned publishRead;
|
||||
unsigned publishWrite;
|
||||
unsigned publishCount;
|
||||
bool overflowWarning;
|
||||
LG_RendererFrameToken publishToken[FRAME_TIMING_RECORD_COUNT];
|
||||
struct FrameTimingRecord record[FRAME_TIMING_RECORD_COUNT];
|
||||
}
|
||||
l_frameTiming;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const unsigned sequence = atomic_load_explicit(
|
||||
&g_state.frameTimingSequence, memory_order_seq_cst);
|
||||
if (sequence & 1)
|
||||
continue;
|
||||
/* The frame and render threads complete records independently. A token is
|
||||
* published only after both halves have arrived, while queuedToken prevents a
|
||||
* renderer that woke for unrelated work from consuming an unqueued frame. */
|
||||
|
||||
timing.captureTime = atomic_load_explicit(
|
||||
&g_state.producerCaptureTime, memory_order_seq_cst);
|
||||
timing.postProcessTime = atomic_load_explicit(
|
||||
&g_state.producerPostProcessTime, memory_order_seq_cst);
|
||||
timing.copyTime = atomic_load_explicit(
|
||||
&g_state.producerCopyTime, memory_order_seq_cst);
|
||||
timing.readyTime = atomic_load_explicit(
|
||||
&g_state.producerReadyTime, memory_order_seq_cst);
|
||||
timing.importTime = atomic_load_explicit(
|
||||
&g_state.clientImportTime, memory_order_seq_cst);
|
||||
|
||||
if (sequence == atomic_load_explicit(
|
||||
&g_state.frameTimingSequence, memory_order_seq_cst))
|
||||
return timing;
|
||||
}
|
||||
static void frameTimingInit(void)
|
||||
{
|
||||
LG_LOCK_INIT(l_frameTiming.lock);
|
||||
atomic_store_explicit(&l_frameTiming.queuedToken,
|
||||
LG_RENDERER_FRAME_TOKEN_NONE, memory_order_relaxed);
|
||||
l_frameTiming.nextToken = LG_RENDERER_FRAME_TOKEN_NONE;
|
||||
l_frameTiming.retireToken = 1;
|
||||
l_frameTiming.publishRead = 0;
|
||||
l_frameTiming.publishWrite = 0;
|
||||
l_frameTiming.publishCount = 0;
|
||||
l_frameTiming.overflowWarning = false;
|
||||
memset(l_frameTiming.record, 0, sizeof(l_frameTiming.record));
|
||||
}
|
||||
|
||||
static void frameTimingStore(const LG_TransportFrameTiming * timing,
|
||||
uint64_t importTime)
|
||||
static void frameTimingReset(void)
|
||||
{
|
||||
atomic_fetch_add_explicit(
|
||||
&g_state.frameTimingSequence, 1, memory_order_seq_cst);
|
||||
atomic_store_explicit(&g_state.producerCaptureTime,
|
||||
timing->captureTime, memory_order_seq_cst);
|
||||
atomic_store_explicit(&g_state.producerPostProcessTime,
|
||||
timing->postProcessTime, memory_order_seq_cst);
|
||||
atomic_store_explicit(&g_state.producerCopyTime,
|
||||
timing->copyTime, memory_order_seq_cst);
|
||||
atomic_store_explicit(&g_state.producerReadyTime,
|
||||
timing->readyTime, memory_order_seq_cst);
|
||||
atomic_store_explicit(&g_state.clientImportTime,
|
||||
importTime, memory_order_seq_cst);
|
||||
atomic_fetch_add_explicit(
|
||||
&g_state.frameTimingSequence, 1, memory_order_seq_cst);
|
||||
INTERLOCKED_SECTION(l_frameTiming.lock, {
|
||||
memset(l_frameTiming.record, 0, sizeof(l_frameTiming.record));
|
||||
l_frameTiming.retireToken = l_frameTiming.nextToken + 1;
|
||||
l_frameTiming.publishRead = 0;
|
||||
l_frameTiming.publishWrite = 0;
|
||||
l_frameTiming.publishCount = 0;
|
||||
l_frameTiming.overflowWarning = false;
|
||||
});
|
||||
/* Tokens remain monotonic across reconnects so stale renderer state can
|
||||
* never alias a new frame. No frame is consumable until it is queued. */
|
||||
atomic_store_explicit(&l_frameTiming.queuedToken,
|
||||
LG_RENDERER_FRAME_TOKEN_NONE, memory_order_release);
|
||||
}
|
||||
|
||||
static struct FrameTimingRecord * frameTimingRecord(
|
||||
LG_RendererFrameToken token)
|
||||
{
|
||||
return &l_frameTiming.record[
|
||||
(token - 1) % FRAME_TIMING_RECORD_COUNT];
|
||||
}
|
||||
|
||||
static LG_RendererFrameToken frameTimingReserve(void)
|
||||
{
|
||||
LG_RendererFrameToken token;
|
||||
|
||||
LG_LOCK(l_frameTiming.lock);
|
||||
token = ++l_frameTiming.nextToken;
|
||||
if (unlikely(token == LG_RENDERER_FRAME_TOKEN_NONE))
|
||||
token = ++l_frameTiming.nextToken;
|
||||
|
||||
struct FrameTimingRecord * record = frameTimingRecord(token);
|
||||
const bool recordActive =
|
||||
record->token != LG_RENDERER_FRAME_TOKEN_NONE &&
|
||||
((record->readyMask & FRAME_TIMING_RENDER_READY) ||
|
||||
record->token >= l_frameTiming.retireToken);
|
||||
if (unlikely(recordActive && !l_frameTiming.overflowWarning))
|
||||
{
|
||||
DEBUG_WARN("Frame timing record pool exhausted; samples will be lost");
|
||||
l_frameTiming.overflowWarning = true;
|
||||
}
|
||||
*record = (struct FrameTimingRecord) { .token = token };
|
||||
LG_UNLOCK(l_frameTiming.lock);
|
||||
return token;
|
||||
}
|
||||
|
||||
static void frameTimingCancel(LG_RendererFrameToken token)
|
||||
{
|
||||
INTERLOCKED_SECTION(l_frameTiming.lock, {
|
||||
struct FrameTimingRecord * record = frameTimingRecord(token);
|
||||
if (record->token == token)
|
||||
*record = (struct FrameTimingRecord) {};
|
||||
});
|
||||
}
|
||||
|
||||
static void frameTimingQueue(LG_RendererFrameToken token, uint64_t importTime,
|
||||
uint64_t importWaitTime, uint64_t dispatchStart, uint64_t queueStart)
|
||||
{
|
||||
INTERLOCKED_SECTION(l_frameTiming.lock, {
|
||||
struct FrameTimingRecord * record = frameTimingRecord(token);
|
||||
if (record->token == token)
|
||||
{
|
||||
const uint64_t elapsed = queueStart > dispatchStart ?
|
||||
queueStart - dispatchStart : 0;
|
||||
const uint64_t accounted = importTime + importWaitTime;
|
||||
record->importTime = importTime;
|
||||
record->importWaitTime = importWaitTime;
|
||||
record->dispatchTime = elapsed > accounted ? elapsed - accounted : 0;
|
||||
record->queueStart = queueStart;
|
||||
if (record->timestamp < queueStart)
|
||||
record->timestamp = queueStart;
|
||||
}
|
||||
});
|
||||
|
||||
atomic_store_explicit(
|
||||
&l_frameTiming.queuedToken, token, memory_order_release);
|
||||
}
|
||||
|
||||
static LG_RendererFrameToken frameTimingQueuedToken(void)
|
||||
{
|
||||
return atomic_load_explicit(
|
||||
&l_frameTiming.queuedToken, memory_order_acquire);
|
||||
}
|
||||
|
||||
static void frameTimingFinishFrame(LG_RendererFrameToken token,
|
||||
const LG_TransportFrameTiming * timing)
|
||||
{
|
||||
const uint64_t timestamp = nanotime();
|
||||
|
||||
INTERLOCKED_SECTION(l_frameTiming.lock, {
|
||||
struct FrameTimingRecord * record = frameTimingRecord(token);
|
||||
if (record->token == token)
|
||||
{
|
||||
if (token < l_frameTiming.retireToken &&
|
||||
!(record->readyMask & FRAME_TIMING_RENDER_READY))
|
||||
*record = (struct FrameTimingRecord) {};
|
||||
else
|
||||
{
|
||||
record->producerValid = timing->valid;
|
||||
record->captureTime = timing->captureTime;
|
||||
record->postProcessTime = timing->postProcessTime;
|
||||
record->copyTime = timing->copyTime;
|
||||
record->readyTime = timing->readyTime;
|
||||
if (record->timestamp < timestamp)
|
||||
record->timestamp = timestamp;
|
||||
record->readyMask |= FRAME_TIMING_FRAME_READY;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void frameTimingFinishRender(const LG_RendererFrameTiming * timing,
|
||||
uint64_t prepareStart, uint64_t prepareTime, uint64_t timestamp)
|
||||
{
|
||||
INTERLOCKED_SECTION(l_frameTiming.lock, {
|
||||
if (l_frameTiming.retireToken <= timing->frameToken)
|
||||
l_frameTiming.retireToken = timing->frameToken + 1;
|
||||
|
||||
struct FrameTimingRecord * record = frameTimingRecord(timing->frameToken);
|
||||
if (record->token == timing->frameToken)
|
||||
{
|
||||
record->prepareStart = prepareStart;
|
||||
record->prepareTime = prepareTime;
|
||||
if (record->timestamp < timestamp)
|
||||
record->timestamp = timestamp;
|
||||
record->setupTime = timing->setupTime;
|
||||
record->effectsTime = timing->effectsTime;
|
||||
record->desktopTime = timing->desktopTime;
|
||||
record->composeTime = timing->composeTime;
|
||||
record->swapTime = timing->swapTime;
|
||||
record->readyMask |= FRAME_TIMING_RENDER_READY;
|
||||
|
||||
if (unlikely(
|
||||
l_frameTiming.publishCount == FRAME_TIMING_RECORD_COUNT))
|
||||
{
|
||||
if (!l_frameTiming.overflowWarning)
|
||||
{
|
||||
DEBUG_WARN("Frame timing publish queue exhausted; sample lost");
|
||||
l_frameTiming.overflowWarning = true;
|
||||
}
|
||||
*record = (struct FrameTimingRecord) {};
|
||||
}
|
||||
else
|
||||
{
|
||||
l_frameTiming.publishToken[l_frameTiming.publishWrite] =
|
||||
timing->frameToken;
|
||||
l_frameTiming.publishWrite =
|
||||
(l_frameTiming.publishWrite + 1) % FRAME_TIMING_RECORD_COUNT;
|
||||
++l_frameTiming.publishCount;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void frameTimingPublishReady(void)
|
||||
{
|
||||
struct FrameTimingRecord ready[FRAME_TIMING_PUBLISH_BATCH_SIZE];
|
||||
unsigned readyCount = 0;
|
||||
|
||||
LG_LOCK(l_frameTiming.lock);
|
||||
while (readyCount < FRAME_TIMING_PUBLISH_BATCH_SIZE &&
|
||||
l_frameTiming.publishCount)
|
||||
{
|
||||
const LG_RendererFrameToken token =
|
||||
l_frameTiming.publishToken[l_frameTiming.publishRead];
|
||||
struct FrameTimingRecord * record = frameTimingRecord(token);
|
||||
|
||||
if (record->token != token ||
|
||||
!(record->readyMask & FRAME_TIMING_RENDER_READY))
|
||||
{
|
||||
l_frameTiming.publishRead =
|
||||
(l_frameTiming.publishRead + 1) % FRAME_TIMING_RECORD_COUNT;
|
||||
--l_frameTiming.publishCount;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(record->readyMask & FRAME_TIMING_FRAME_READY))
|
||||
break;
|
||||
|
||||
ready[readyCount++] = *record;
|
||||
*record = (struct FrameTimingRecord) {};
|
||||
l_frameTiming.publishRead =
|
||||
(l_frameTiming.publishRead + 1) % FRAME_TIMING_RECORD_COUNT;
|
||||
--l_frameTiming.publishCount;
|
||||
}
|
||||
LG_UNLOCK(l_frameTiming.lock);
|
||||
|
||||
for (unsigned i = 0; i < readyCount; ++i)
|
||||
{
|
||||
const struct FrameTimingRecord * record = &ready[i];
|
||||
const uint64_t queueTime =
|
||||
record->prepareStart > record->queueStart ?
|
||||
record->prepareStart - record->queueStart : 0;
|
||||
const uint32_t validMask =
|
||||
OVERLAY_FRAME_TIMING_VALID_ALL &
|
||||
(record->producerValid ? UINT32_MAX :
|
||||
~OVERLAY_FRAME_TIMING_VALID_PRODUCER);
|
||||
const OverlayFrameTiming timing = {
|
||||
.timestamp = record->timestamp,
|
||||
.validMask = validMask,
|
||||
.capture = record->captureTime * 1e-6f,
|
||||
.postProcess = record->postProcessTime * 1e-6f,
|
||||
.copy = record->copyTime * 1e-6f,
|
||||
.ready = record->readyTime * 1e-6f,
|
||||
.import = (record->importTime +
|
||||
(record->producerValid ? 0 : record->importWaitTime)) * 1e-6f,
|
||||
.dispatch = record->dispatchTime * 1e-6f,
|
||||
.queue = queueTime * 1e-6f,
|
||||
.prepare = record->prepareTime * 1e-6f,
|
||||
.setup = record->setupTime * 1e-6f,
|
||||
.effects = record->effectsTime * 1e-6f,
|
||||
.desktop = record->desktopTime * 1e-6f,
|
||||
.compose = record->composeTime * 1e-6f,
|
||||
.swap = record->swapTime * 1e-6f,
|
||||
};
|
||||
ringbuffer_push(g_state.frameLatency, &timing);
|
||||
}
|
||||
}
|
||||
|
||||
static void preSwapCallback(void * udata)
|
||||
{
|
||||
const struct RenderTiming * timing = (const struct RenderTiming *)udata;
|
||||
const uint64_t timestamp = nanotime();
|
||||
const uint64_t renderTime = timestamp - timing->renderStart;
|
||||
if (timing->captureTime || timing->postProcessTime || timing->copyTime ||
|
||||
timing->readyTime || timing->importTime)
|
||||
{
|
||||
const OverlayFrameTiming frameTiming = {
|
||||
.timestamp = timestamp,
|
||||
.capture = timing->captureTime * 1e-6f,
|
||||
.postProcess = timing->postProcessTime * 1e-6f,
|
||||
.copy = timing->copyTime * 1e-6f,
|
||||
.ready = timing->readyTime * 1e-6f,
|
||||
.import = timing->importTime * 1e-6f,
|
||||
.render = renderTime * 1e-6f,
|
||||
};
|
||||
ringbuffer_push(g_state.frameLatency, &frameTiming);
|
||||
}
|
||||
(void)udata;
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
if (!l_testCapture.enabled || l_testCapture.complete)
|
||||
@@ -425,6 +639,8 @@ static int renderThread(void * unused)
|
||||
}
|
||||
}
|
||||
|
||||
frameTimingPublishReady();
|
||||
|
||||
int resize = atomic_load(&g_state.lgrResize);
|
||||
if (unlikely(resize))
|
||||
{
|
||||
@@ -460,29 +676,32 @@ static int renderThread(void * unused)
|
||||
atomic_compare_exchange_weak(&g_state.lgrResize, &resize, 0);
|
||||
}
|
||||
|
||||
static uint64_t lastFrameCount = 0;
|
||||
const uint64_t frameCount =
|
||||
atomic_load_explicit(&g_state.frameCount, memory_order_relaxed);
|
||||
const bool newFrame = frameCount != lastFrameCount;
|
||||
lastFrameCount = frameCount;
|
||||
|
||||
const bool invalidate = atomic_exchange(&g_state.invalidateWindow, false);
|
||||
|
||||
struct RenderTiming renderTiming =
|
||||
newFrame ? frameTimingLoad() : (struct RenderTiming) {};
|
||||
renderTiming.renderStart = nanotime();
|
||||
const LG_RendererFrameToken frameTokenLimit = frameTimingQueuedToken();
|
||||
const uint64_t prepareStart = nanotime();
|
||||
|
||||
LG_LOCK(g_state.lgrLock);
|
||||
|
||||
renderQueue_process();
|
||||
|
||||
if (unlikely(!RENDERER(render, g_params.winRotate, newFrame, invalidate,
|
||||
preSwapCallback, (void *)&renderTiming)))
|
||||
const uint64_t prepareTime = nanotime() - prepareStart;
|
||||
|
||||
LG_RendererFrameTiming rendererTiming = {};
|
||||
if (unlikely(!RENDERER(render, g_params.winRotate, frameTokenLimit,
|
||||
invalidate, preSwapCallback, NULL, &rendererTiming)))
|
||||
{
|
||||
LG_UNLOCK(g_state.lgrLock);
|
||||
break;
|
||||
}
|
||||
const uint64_t renderEnd = nanotime();
|
||||
LG_UNLOCK(g_state.lgrLock);
|
||||
|
||||
if (rendererTiming.frameToken != LG_RENDERER_FRAME_TOKEN_NONE)
|
||||
frameTimingFinishRender(
|
||||
&rendererTiming, prepareStart, prepareTime, renderEnd);
|
||||
frameTimingPublishReady();
|
||||
|
||||
const uint64_t t = nanotime();
|
||||
const uint64_t delta = t - g_state.lastRenderTime;
|
||||
|
||||
@@ -692,6 +911,7 @@ int main_frameThread(void * unused)
|
||||
continue;
|
||||
}
|
||||
frameSerial = frame.serial;
|
||||
const uint64_t dispatchStart = nanotime();
|
||||
|
||||
const LG_TransportFrameFormat * format = frame.format;
|
||||
if (!format)
|
||||
@@ -842,23 +1062,42 @@ int main_frameThread(void * unused)
|
||||
damageCount = 0;
|
||||
}
|
||||
|
||||
g_state.frameImportTime = 0;
|
||||
const LG_RendererFrameToken frameToken = frameTimingReserve();
|
||||
g_state.frameImportTime = 0;
|
||||
g_state.frameImportWaitTime = 0;
|
||||
if (!RENDERER(onFrame, frame.framebuffer, frame.dmaFD,
|
||||
frame.damageRects, damageCount))
|
||||
frame.damageRects, damageCount, frameToken))
|
||||
{
|
||||
frameTimingCancel(frameToken);
|
||||
g_state.transportOps->releaseFrame(g_state.transport, &frame);
|
||||
DEBUG_ERROR("Renderer onFrame returned failure");
|
||||
app_setState(APP_STATE_SHUTDOWN);
|
||||
break;
|
||||
}
|
||||
|
||||
const uint64_t queueStart = nanotime();
|
||||
atomic_fetch_add_explicit(&g_state.frameCount, 1, memory_order_relaxed);
|
||||
#ifdef ENABLE_TESTS
|
||||
atomic_store_explicit(&l_testFrameSerial, frame.serial,
|
||||
memory_order_release);
|
||||
#endif
|
||||
frameTimingQueue(frameToken, g_state.frameImportTime,
|
||||
g_state.frameImportWaitTime, dispatchStart, queueStart);
|
||||
|
||||
if (g_state.jitRender)
|
||||
{
|
||||
if (atomic_load_explicit(&g_state.pendingCount, memory_order_acquire) < 10)
|
||||
atomic_fetch_add_explicit(&g_state.pendingCount, 1,
|
||||
memory_order_release);
|
||||
}
|
||||
else
|
||||
lgSignalEvent(g_state.frameEvent);
|
||||
|
||||
LG_TransportFrameTiming timing = {};
|
||||
if (g_state.transportOps->getFrameTiming)
|
||||
g_state.transportOps->getFrameTiming(
|
||||
g_state.transport, &frame, &timing);
|
||||
|
||||
frameTimingStore(&timing, g_state.frameImportTime);
|
||||
|
||||
overlaySplash_show(false);
|
||||
if ((frame.flags & LG_TRANSPORT_FRAME_REQUEST_ACTIVATION) &&
|
||||
g_params.requestActivation)
|
||||
@@ -876,19 +1115,7 @@ int main_frameThread(void * unused)
|
||||
g_state.autoIdleInhibitState = blockScreensaver;
|
||||
}
|
||||
|
||||
atomic_fetch_add_explicit(&g_state.frameCount, 1, memory_order_relaxed);
|
||||
#ifdef ENABLE_TESTS
|
||||
atomic_store_explicit(&l_testFrameSerial, frame.serial,
|
||||
memory_order_release);
|
||||
#endif
|
||||
if (g_state.jitRender)
|
||||
{
|
||||
if (atomic_load_explicit(&g_state.pendingCount, memory_order_acquire) < 10)
|
||||
atomic_fetch_add_explicit(&g_state.pendingCount, 1,
|
||||
memory_order_release);
|
||||
}
|
||||
else
|
||||
lgSignalEvent(g_state.frameEvent);
|
||||
frameTimingFinishFrame(frameToken, &timing);
|
||||
|
||||
g_state.transportOps->releaseFrame(g_state.transport, &frame);
|
||||
app_useSpiceDisplay(false);
|
||||
@@ -1322,6 +1549,8 @@ static int transportSessionProbe(void * opaque)
|
||||
|
||||
static int lg_run(void)
|
||||
{
|
||||
frameTimingInit();
|
||||
|
||||
#ifdef ENABLE_TESTS
|
||||
memset(&l_testCapture, 0, sizeof(l_testCapture));
|
||||
atomic_store_explicit(&l_testFrameSerial, 0, memory_order_relaxed);
|
||||
@@ -1620,6 +1849,7 @@ static int lg_run(void)
|
||||
int msgsCount;
|
||||
|
||||
restart:
|
||||
frameTimingReset();
|
||||
msgsCount = 0;
|
||||
memset(msgs, 0, sizeof(msgs));
|
||||
|
||||
@@ -1867,6 +2097,7 @@ static void lg_shutdown(void)
|
||||
// free metrics ringbuffers
|
||||
ringbuffer_free(&g_state.renderTimings);
|
||||
ringbuffer_free(&g_state.frameLatency);
|
||||
LG_LOCK_FREE(l_frameTiming.lock);
|
||||
|
||||
free(g_state.fontName);
|
||||
igDestroyContext(NULL);
|
||||
|
||||
@@ -148,14 +148,9 @@ struct AppState
|
||||
RingBuffer renderTimings;
|
||||
RingBuffer frameLatency;
|
||||
uint64_t frameImportTime;
|
||||
uint64_t frameImportWaitTime;
|
||||
|
||||
atomic_uint_least64_t pendingCount;
|
||||
atomic_uint frameTimingSequence;
|
||||
atomic_uint_least64_t producerCaptureTime;
|
||||
atomic_uint_least64_t producerPostProcessTime;
|
||||
atomic_uint_least64_t producerCopyTime;
|
||||
atomic_uint_least64_t producerReadyTime;
|
||||
atomic_uint_least64_t clientImportTime;
|
||||
atomic_uint_least64_t renderCount, frameCount;
|
||||
_Atomic(float) fps, ups;
|
||||
|
||||
|
||||
@@ -64,6 +64,49 @@ struct OverlayGraph
|
||||
GraphFormatFn formatFn;
|
||||
};
|
||||
|
||||
static void graphFree(struct OverlayGraph * graph)
|
||||
{
|
||||
free(graph->name);
|
||||
free(graph);
|
||||
}
|
||||
|
||||
static struct OverlayGraph * graphNew(const char * name, RingBuffer buffer)
|
||||
{
|
||||
if (!name || !*name)
|
||||
{
|
||||
DEBUG_ERROR("graph name must not be empty");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct OverlayGraph * graph = calloc(1, sizeof(*graph));
|
||||
if (!graph)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
graph->name = lg_strdup(name);
|
||||
if (!graph->name)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
graphFree(graph);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
graph->buffer = buffer;
|
||||
graph->enabled = true;
|
||||
return graph;
|
||||
}
|
||||
|
||||
static GraphHandle graphPublish(struct OverlayGraph * graph)
|
||||
{
|
||||
if (!ll_push(gs.graphs, graph))
|
||||
{
|
||||
graphFree(graph);
|
||||
return NULL;
|
||||
}
|
||||
return graph;
|
||||
}
|
||||
|
||||
static void configCallback(void * udata, int * id)
|
||||
{
|
||||
@@ -127,10 +170,7 @@ static void graphs_free(void * udata)
|
||||
{
|
||||
struct OverlayGraph * graph;
|
||||
while(ll_shift(gs.graphs, (void **)&graph))
|
||||
{
|
||||
free(graph->name);
|
||||
free(graph);
|
||||
}
|
||||
graphFree(graph);
|
||||
ll_free(gs.graphs);
|
||||
gs.graphs = NULL;
|
||||
|
||||
@@ -175,20 +215,37 @@ static bool rbCalcMetrics(int index, void * value_, void * udata_)
|
||||
return true;
|
||||
}
|
||||
|
||||
#define TIMING_PLOT_BUCKETS 100
|
||||
#define TIMING_PLOT_WINDOW_NS 20000000000ULL
|
||||
#define TIMING_PLOT_BUCKET_NS \
|
||||
#define TIMING_PLOT_BUCKETS 100
|
||||
#define TIMING_PLOT_WINDOW_NS 20000000000ULL
|
||||
#define TIMING_PLOT_BUCKET_NS \
|
||||
(TIMING_PLOT_WINDOW_NS / TIMING_PLOT_BUCKETS)
|
||||
#define TIMING_STAGE_COUNT 6
|
||||
#define FRAME_TIMING_STAGE_COUNT OVERLAY_FRAME_TIMING_COUNT
|
||||
|
||||
static const char * const frameTimingLabels[FRAME_TIMING_STAGE_COUNT] = {
|
||||
"Capture",
|
||||
"Post",
|
||||
"Copy",
|
||||
"Ready",
|
||||
"Import",
|
||||
"Dispatch",
|
||||
"Queue",
|
||||
"Prepare",
|
||||
"Setup",
|
||||
"Effects",
|
||||
"Desktop",
|
||||
"Compose",
|
||||
"Swap",
|
||||
};
|
||||
|
||||
struct TimingPlotData
|
||||
{
|
||||
uint64_t windowStart;
|
||||
uint64_t windowEnd;
|
||||
float stageMin[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
float stageMax[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
float stageSum[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
unsigned count[TIMING_PLOT_BUCKETS];
|
||||
float stageMin[FRAME_TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
float stageMax[FRAME_TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
float stageSum[FRAME_TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
unsigned stageSamples[FRAME_TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS];
|
||||
unsigned samples[TIMING_PLOT_BUCKETS];
|
||||
};
|
||||
|
||||
static float roundTimingScale(float value)
|
||||
@@ -223,8 +280,10 @@ static float graphRowWeight(const struct OverlayGraph * graph)
|
||||
return graph->compact ? 0.5f : 1.0f;
|
||||
}
|
||||
|
||||
static bool accumulateTimingSample(int index, void * value_, void * udata_)
|
||||
static bool accumulateFrameTimingSample(int index, void * value_, void * udata_)
|
||||
{
|
||||
(void)index;
|
||||
|
||||
struct TimingPlotData * data = udata_;
|
||||
const OverlayFrameTiming * timing = value_;
|
||||
if (timing->timestamp < data->windowStart)
|
||||
@@ -232,29 +291,44 @@ static bool accumulateTimingSample(int index, void * value_, void * udata_)
|
||||
if (timing->timestamp >= data->windowEnd)
|
||||
return false;
|
||||
|
||||
const uint64_t offset = timing->timestamp - data->windowStart;
|
||||
const int bucket = min(offset / TIMING_PLOT_BUCKET_NS,
|
||||
TIMING_PLOT_BUCKETS - 1);
|
||||
const float values[TIMING_STAGE_COUNT] = {
|
||||
const float values[FRAME_TIMING_STAGE_COUNT] = {
|
||||
timing->capture,
|
||||
timing->postProcess,
|
||||
timing->copy,
|
||||
timing->ready,
|
||||
timing->import,
|
||||
timing->render,
|
||||
timing->dispatch,
|
||||
timing->queue,
|
||||
timing->prepare,
|
||||
timing->setup,
|
||||
timing->effects,
|
||||
timing->desktop,
|
||||
timing->compose,
|
||||
timing->swap,
|
||||
};
|
||||
for (int i = 0; i < TIMING_STAGE_COUNT; ++i)
|
||||
|
||||
const uint64_t offset = timing->timestamp - data->windowStart;
|
||||
const int bucket = min(offset / TIMING_PLOT_BUCKET_NS,
|
||||
TIMING_PLOT_BUCKETS - 1);
|
||||
for (int stage = 0; stage < FRAME_TIMING_STAGE_COUNT; ++stage)
|
||||
{
|
||||
if (!data->count[bucket])
|
||||
data->stageMin[i][bucket] = data->stageMax[i][bucket] = values[i];
|
||||
if (!(timing->validMask & (1U << stage)))
|
||||
continue;
|
||||
|
||||
const float value = values[stage];
|
||||
if (!data->stageSamples[stage][bucket])
|
||||
data->stageMin[stage][bucket] = data->stageMax[stage][bucket] = value;
|
||||
else
|
||||
{
|
||||
data->stageMin[i][bucket] = min(data->stageMin[i][bucket], values[i]);
|
||||
data->stageMax[i][bucket] = max(data->stageMax[i][bucket], values[i]);
|
||||
data->stageMin[stage][bucket] =
|
||||
min(data->stageMin[stage][bucket], value);
|
||||
data->stageMax[stage][bucket] =
|
||||
max(data->stageMax[stage][bucket], value);
|
||||
}
|
||||
data->stageSum[i][bucket] += values[i];
|
||||
data->stageSum[stage][bucket] += value;
|
||||
++data->stageSamples[stage][bucket];
|
||||
}
|
||||
++data->count[bucket];
|
||||
++data->samples[bucket];
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -315,20 +389,24 @@ static void renderLineGraph(struct OverlayGraph * graph, bool interactive,
|
||||
|
||||
static void renderTimingStatistic(struct OverlayGraph * graph,
|
||||
const char * statistic, int statisticId,
|
||||
const float values[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS],
|
||||
const float values[FRAME_TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS],
|
||||
bool interactive, ImVec2 size)
|
||||
{
|
||||
float cumulative[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float zero[TIMING_PLOT_BUCKETS] = {};
|
||||
float cumulative[FRAME_TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float zero[TIMING_PLOT_BUCKETS];
|
||||
float xValues[TIMING_PLOT_BUCKETS];
|
||||
float peak = 0.0f;
|
||||
for (int bucket = 0; bucket < TIMING_PLOT_BUCKETS; ++bucket)
|
||||
{
|
||||
const bool populated = isfinite(values[0][bucket]);
|
||||
xValues[bucket] = bucket;
|
||||
for (int stage = 0; stage < TIMING_STAGE_COUNT; ++stage)
|
||||
cumulative[stage][bucket] = values[stage][bucket] +
|
||||
(stage ? cumulative[stage - 1][bucket] : 0.0f);
|
||||
peak = max(peak, cumulative[TIMING_STAGE_COUNT - 1][bucket]);
|
||||
zero[bucket] = populated ? 0.0f : NAN;
|
||||
for (int stage = 0; stage < FRAME_TIMING_STAGE_COUNT; ++stage)
|
||||
cumulative[stage][bucket] = populated ? values[stage][bucket] +
|
||||
(stage ? cumulative[stage - 1][bucket] : 0.0f) : NAN;
|
||||
|
||||
if (populated)
|
||||
peak = max(peak, cumulative[FRAME_TIMING_STAGE_COUNT - 1][bucket]);
|
||||
}
|
||||
const float valueMax = graphScale(graph, statisticId, peak);
|
||||
|
||||
@@ -346,26 +424,20 @@ static void renderTimingStatistic(struct OverlayGraph * graph,
|
||||
ImPlot_SetupLegend(ImPlotLocation_South,
|
||||
ImPlotLegendFlags_Outside | ImPlotLegendFlags_Horizontal);
|
||||
|
||||
const char * labels[TIMING_STAGE_COUNT] = {
|
||||
"Capture",
|
||||
"Post",
|
||||
"Copy",
|
||||
"Ready",
|
||||
"Import",
|
||||
"Render",
|
||||
};
|
||||
|
||||
gs.plotSpec->Offset = 0;
|
||||
gs.plotSpec->Stride = sizeof(float);
|
||||
for (int stage = 0; stage < TIMING_STAGE_COUNT; ++stage)
|
||||
const int colorCount = ImPlot_GetColormapSize(ImPlotColormap_Paired);
|
||||
for (int stage = 0; stage < FRAME_TIMING_STAGE_COUNT; ++stage)
|
||||
{
|
||||
const ImVec4 color =
|
||||
ImPlot_GetColormapColor(stage, ImPlotColormap_Deep);
|
||||
const ImVec4 color = stage < colorCount ?
|
||||
ImPlot_GetColormapColor(stage, ImPlotColormap_Paired) :
|
||||
(ImVec4) {0.75f, 0.75f, 0.75f, 1.0f};
|
||||
gs.plotSpec->FillColor = color;
|
||||
gs.plotSpec->FillAlpha = 0.35f;
|
||||
gs.plotSpec->LineWeight = 1.0f;
|
||||
gs.plotSpec->Flags = ImPlotShadedFlags_None;
|
||||
ImPlot_PlotShaded_FloatPtrFloatPtrFloatPtr(labels[stage], xValues,
|
||||
ImPlot_PlotShaded_FloatPtrFloatPtrFloatPtr(
|
||||
frameTimingLabels[stage], xValues,
|
||||
stage ? cumulative[stage - 1] : zero, cumulative[stage],
|
||||
TIMING_PLOT_BUCKETS, *gs.plotSpec);
|
||||
|
||||
@@ -373,7 +445,7 @@ static void renderTimingStatistic(struct OverlayGraph * graph,
|
||||
gs.plotSpec->LineWeight = 1.0f;
|
||||
gs.plotSpec->FillAlpha = 1.0f;
|
||||
gs.plotSpec->Flags = ImPlotLineFlags_None;
|
||||
ImPlot_PlotLine_FloatPtrInt(labels[stage], cumulative[stage],
|
||||
ImPlot_PlotLine_FloatPtrInt(frameTimingLabels[stage], cumulative[stage],
|
||||
TIMING_PLOT_BUCKETS, 1.0, 0.0, *gs.plotSpec);
|
||||
}
|
||||
|
||||
@@ -383,31 +455,39 @@ static void renderTimingStatistic(struct OverlayGraph * graph,
|
||||
ImPlot_EndPlot();
|
||||
}
|
||||
|
||||
static void renderTimingGraph(struct OverlayGraph * graph, bool interactive,
|
||||
ImVec2 size)
|
||||
static void renderFrameTimingGraph(struct OverlayGraph * graph,
|
||||
bool interactive, ImVec2 size)
|
||||
{
|
||||
struct TimingPlotData data = {};
|
||||
data.windowEnd = nanotime() / TIMING_PLOT_BUCKET_NS * TIMING_PLOT_BUCKET_NS;
|
||||
data.windowEnd =
|
||||
nanotime() / TIMING_PLOT_BUCKET_NS * TIMING_PLOT_BUCKET_NS;
|
||||
data.windowStart = data.windowEnd > TIMING_PLOT_WINDOW_NS ?
|
||||
data.windowEnd - TIMING_PLOT_WINDOW_NS : 0;
|
||||
ringbuffer_forEach(graph->buffer, accumulateTimingSample, &data, false);
|
||||
ringbuffer_forEach(
|
||||
graph->buffer, accumulateFrameTimingSample, &data, false);
|
||||
|
||||
float minimum[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float maximum[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float average[TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float minimum[FRAME_TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float maximum[FRAME_TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
float average[FRAME_TIMING_STAGE_COUNT][TIMING_PLOT_BUCKETS] = {};
|
||||
for (int bucket = 0; bucket < TIMING_PLOT_BUCKETS; ++bucket)
|
||||
{
|
||||
if (!data.count[bucket])
|
||||
continue;
|
||||
|
||||
for (int stage = 0; stage < TIMING_STAGE_COUNT; ++stage)
|
||||
for (int stage = 0; stage < FRAME_TIMING_STAGE_COUNT; ++stage)
|
||||
{
|
||||
if (!data.samples[bucket])
|
||||
{
|
||||
minimum[stage][bucket] = NAN;
|
||||
maximum[stage][bucket] = NAN;
|
||||
average[stage][bucket] = NAN;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!data.stageSamples[stage][bucket])
|
||||
continue;
|
||||
|
||||
minimum[stage][bucket] = data.stageMin[stage][bucket];
|
||||
maximum[stage][bucket] = data.stageMax[stage][bucket];
|
||||
average[stage][bucket] =
|
||||
data.stageSum[stage][bucket] / data.count[bucket];
|
||||
data.stageSum[stage][bucket] / data.stageSamples[stage][bucket];
|
||||
}
|
||||
}
|
||||
|
||||
const float spacing = igGetStyle()->ItemSpacing.y;
|
||||
const float height = (size.y - spacing * 2.0f) / 3.0f;
|
||||
@@ -477,7 +557,8 @@ static int graphs_render(void * udata, bool interactive,
|
||||
igPushID_Ptr(graph);
|
||||
const float height = rowHeight * graphRowWeight(graph);
|
||||
if (graph->type == OVERLAY_GRAPH_FRAME_TIMING)
|
||||
renderTimingGraph(graph, interactive, (ImVec2) {winSize.x, height});
|
||||
renderFrameTimingGraph(graph, interactive,
|
||||
(ImVec2) {winSize.x, height});
|
||||
else
|
||||
renderLineGraph(graph, interactive, (ImVec2) {winSize.x, height});
|
||||
igPopID();
|
||||
@@ -502,47 +583,26 @@ struct LG_OverlayOps LGOverlayGraphs =
|
||||
GraphHandle overlayGraph_register(const char * name, RingBuffer buffer,
|
||||
float min, float max, GraphFormatFn formatFn)
|
||||
{
|
||||
if (!name || !*name)
|
||||
{
|
||||
DEBUG_ERROR("graph name must not be empty");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct OverlayGraph * graph = malloc(sizeof(*graph));
|
||||
struct OverlayGraph * graph = graphNew(name, buffer);
|
||||
if (!graph)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
graph->name = lg_strdup(name);
|
||||
if (!graph->name)
|
||||
{
|
||||
DEBUG_ERROR("out of memory");
|
||||
free(graph);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
graph->buffer = buffer;
|
||||
graph->enabled = true;
|
||||
graph->compact = false;
|
||||
graph->type = OVERLAY_GRAPH_LINE;
|
||||
graph->min = min;
|
||||
graph->max = max;
|
||||
for (int i = 0; i < TIMING_STATISTIC_COUNT; ++i)
|
||||
graph->yScale[i] = 0.0f;
|
||||
graph->formatFn = formatFn;
|
||||
ll_push(gs.graphs, graph);
|
||||
return graph;
|
||||
return graphPublish(graph);
|
||||
}
|
||||
|
||||
GraphHandle overlayGraph_registerFrameTiming(const char * name,
|
||||
RingBuffer buffer)
|
||||
{
|
||||
GraphHandle graph = overlayGraph_register(name, buffer, 0.0f, 0.0f, NULL);
|
||||
if (graph)
|
||||
graph->type = OVERLAY_GRAPH_FRAME_TIMING;
|
||||
return graph;
|
||||
struct OverlayGraph * graph = graphNew(name, buffer);
|
||||
if (!graph)
|
||||
return NULL;
|
||||
|
||||
graph->type = OVERLAY_GRAPH_FRAME_TIMING;
|
||||
return graphPublish(graph);
|
||||
}
|
||||
|
||||
void overlayGraph_unregister(GraphHandle handle)
|
||||
@@ -551,8 +611,7 @@ void overlayGraph_unregister(GraphHandle handle)
|
||||
return;
|
||||
|
||||
ll_removeData(gs.graphs, handle);
|
||||
free(handle->name);
|
||||
free(handle);
|
||||
graphFree(handle);
|
||||
|
||||
if (gs.show)
|
||||
app_invalidateWindow(false);
|
||||
|
||||
@@ -47,15 +47,46 @@ void overlayAlert_show(LG_MsgAlert type, const char * fmt, va_list args);
|
||||
typedef struct OverlayFrameTiming
|
||||
{
|
||||
uint64_t timestamp;
|
||||
uint32_t validMask;
|
||||
float capture;
|
||||
float postProcess;
|
||||
float copy;
|
||||
float ready;
|
||||
float import;
|
||||
float render;
|
||||
float dispatch;
|
||||
float queue;
|
||||
float prepare;
|
||||
float setup;
|
||||
float effects;
|
||||
float desktop;
|
||||
float compose;
|
||||
float swap;
|
||||
}
|
||||
OverlayFrameTiming;
|
||||
|
||||
enum OverlayFrameTimingStage
|
||||
{
|
||||
OVERLAY_FRAME_TIMING_CAPTURE,
|
||||
OVERLAY_FRAME_TIMING_POST_PROCESS,
|
||||
OVERLAY_FRAME_TIMING_COPY,
|
||||
OVERLAY_FRAME_TIMING_READY,
|
||||
OVERLAY_FRAME_TIMING_IMPORT,
|
||||
OVERLAY_FRAME_TIMING_DISPATCH,
|
||||
OVERLAY_FRAME_TIMING_QUEUE,
|
||||
OVERLAY_FRAME_TIMING_PREPARE,
|
||||
OVERLAY_FRAME_TIMING_SETUP,
|
||||
OVERLAY_FRAME_TIMING_EFFECTS,
|
||||
OVERLAY_FRAME_TIMING_DESKTOP,
|
||||
OVERLAY_FRAME_TIMING_COMPOSE,
|
||||
OVERLAY_FRAME_TIMING_SWAP,
|
||||
OVERLAY_FRAME_TIMING_COUNT,
|
||||
};
|
||||
|
||||
#define OVERLAY_FRAME_TIMING_VALID_ALL \
|
||||
((1U << OVERLAY_FRAME_TIMING_COUNT) - 1U)
|
||||
#define OVERLAY_FRAME_TIMING_VALID_PRODUCER \
|
||||
((1U << OVERLAY_FRAME_TIMING_IMPORT) - 1U)
|
||||
|
||||
GraphHandle overlayGraph_register(const char * name, RingBuffer buffer,
|
||||
float min, float max, GraphFormatFn formatFn);
|
||||
GraphHandle overlayGraph_registerFrameTiming(const char * name,
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define LGMP_TIMING_SPIN_COUNT 4096
|
||||
|
||||
struct DMAFrameInfo
|
||||
{
|
||||
const KVMFRFrame * frame;
|
||||
@@ -622,18 +624,21 @@ static void lgmp_getFrameTiming(LG_Transport * this,
|
||||
this->pendingFrame->frameSerial != frame->serial)
|
||||
return;
|
||||
|
||||
/* The producer writes these immediately after completing the framebuffer.
|
||||
* nextFrame can observe the header earlier, so sample them only after the
|
||||
* renderer's onFrame call has consumed the framebuffer. */
|
||||
/* The producer writes these immediately after publishing FrameBuffer::wp.
|
||||
* nextFrame can observe the header earlier, so briefly observe the
|
||||
* publication tail after onFrame consumes the framebuffer without sleeping
|
||||
* the frame-acquisition thread. */
|
||||
for (unsigned i = 0;
|
||||
!lgmp_frameTimingReady(this->pendingFrame) &&
|
||||
i < 1000;
|
||||
i < LGMP_TIMING_SPIN_COUNT;
|
||||
++i)
|
||||
usleep(1);
|
||||
{
|
||||
}
|
||||
|
||||
if (!lgmp_frameTimingReady(this->pendingFrame))
|
||||
return;
|
||||
|
||||
timing->valid = true;
|
||||
timing->captureTime = this->pendingFrame->captureTime;
|
||||
timing->postProcessTime = this->pendingFrame->postProcessTime;
|
||||
timing->copyTime = this->pendingFrame->copyTime;
|
||||
|
||||
Reference in New Issue
Block a user