mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 02:00:06 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64c4eec59f |
No files matched your search
+124
-24
@@ -279,6 +279,9 @@ PFNEGLCREATEIMAGEKHRPROC pCreateImage;
|
||||
PFNEGLDESTROYIMAGEKHRPROC pDestroyImage;
|
||||
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC pImageTargetTexture;
|
||||
PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC pImageTargetRenderbuffer;
|
||||
PFNEGLCREATESYNCKHRPROC pCreateSync;
|
||||
PFNEGLDESTROYSYNCKHRPROC pDestroySync;
|
||||
PFNEGLCLIENTWAITSYNCKHRPROC pClientWaitSync;
|
||||
|
||||
const char *kVertex = R"(
|
||||
attribute vec2 pos; // the unit square
|
||||
@@ -400,7 +403,11 @@ bool Renderer::Init(const std::vector<uint64_t> &modifiers, std::function<void(c
|
||||
pImageTargetTexture = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
|
||||
pImageTargetRenderbuffer = reinterpret_cast<PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC>(
|
||||
eglGetProcAddress("glEGLImageTargetRenderbufferStorageOES"));
|
||||
if (!gbm_ || !pGetPlatformDisplay || !pCreateImage || !pImageTargetTexture || !pImageTargetRenderbuffer) {
|
||||
pCreateSync = reinterpret_cast<PFNEGLCREATESYNCKHRPROC>(eglGetProcAddress("eglCreateSyncKHR"));
|
||||
pDestroySync = reinterpret_cast<PFNEGLDESTROYSYNCKHRPROC>(eglGetProcAddress("eglDestroySyncKHR"));
|
||||
pClientWaitSync = reinterpret_cast<PFNEGLCLIENTWAITSYNCKHRPROC>(eglGetProcAddress("eglClientWaitSyncKHR"));
|
||||
if (!gbm_ || !pGetPlatformDisplay || !pCreateImage || !pImageTargetTexture || !pImageTargetRenderbuffer ||
|
||||
!pCreateSync || !pDestroySync || !pClientWaitSync) {
|
||||
std::fprintf(stderr, "handcut: GBM or EGL extensions missing\n");
|
||||
return false;
|
||||
}
|
||||
@@ -451,6 +458,9 @@ void Renderer::Forget(const void *key) {
|
||||
glDeleteTextures(1, &it->second.tex);
|
||||
pDestroyImage(EGLDisplay(dpy_), EGLImageKHR(it->second.image));
|
||||
imported_.erase(it);
|
||||
for (auto &[k, r] : rings_) // a new buffer at the same address isn't this one
|
||||
for (Output &o : r.out)
|
||||
if (o.key == key) o.drawn = false;
|
||||
}
|
||||
|
||||
bool Renderer::MakeOutput(Output &o, int w, int h) {
|
||||
@@ -489,6 +499,7 @@ bool Renderer::MakeOutput(Output &o, int w, int h) {
|
||||
|
||||
void Renderer::FreeOutput(Output &o) {
|
||||
if (o.bo && released_) released_(&o);
|
||||
if (o.fence) pDestroySync(EGLDisplay(dpy_), EGLSyncKHR(o.fence));
|
||||
if (o.fbo) glDeleteFramebuffers(1, &o.fbo);
|
||||
if (o.rb) glDeleteRenderbuffers(1, &o.rb);
|
||||
if (o.image) pDestroyImage(EGLDisplay(dpy_), EGLImageKHR(o.image));
|
||||
@@ -505,26 +516,68 @@ void Renderer::DropPanel(int panel) {
|
||||
rings_.erase(it);
|
||||
}
|
||||
|
||||
const Output *Renderer::Composite(int panel, const void *key, const ft_dmabuf &src, const std::vector<Capsule2D> eyes[2]) {
|
||||
if (!ready_) return nullptr;
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
const int w = src.width, h = src.height;
|
||||
Ring &ring = rings_[panel];
|
||||
if (ring.w != w || ring.h != h) {
|
||||
for (Output &old : ring.out) FreeOutput(old);
|
||||
ring.w = w, ring.h = h, ring.next = 0;
|
||||
}
|
||||
Output &o = ring.out[ring.next];
|
||||
if (!o.bo && !MakeOutput(o, 2 * w, h)) return nullptr;
|
||||
const GLuint tex = Texture(key, src);
|
||||
if (!tex) return nullptr;
|
||||
ring.next = (ring.next + 1) % 3;
|
||||
namespace {
|
||||
|
||||
// The pixels a cutout's quad covers (see Draw), as x0 y0 x1 y1 in the eye's half.
|
||||
void Bounds(const Capsule2D &c, float b[4]) {
|
||||
const float feather = std::max(1.5f, 0.15f * std::min(c.ra, c.rb));
|
||||
const float r = std::max(c.ra, c.rb) + feather;
|
||||
b[0] = std::min(c.ax, c.bx) - r, b[1] = std::min(c.ay, c.by) - r;
|
||||
b[2] = std::max(c.ax, c.bx) + r, b[3] = std::max(c.ay, c.by) + r;
|
||||
}
|
||||
|
||||
// Within a quarter pixel: the same picture.
|
||||
bool SameSpots(const std::vector<Capsule2D> a[2], const std::vector<Capsule2D> b[2]) {
|
||||
for (int e = 0; e < 2; ++e) {
|
||||
if (a[e].size() != b[e].size()) return false;
|
||||
for (size_t i = 0; i < a[e].size(); ++i) {
|
||||
const Capsule2D &p = a[e][i], &q = b[e][i];
|
||||
for (float d : {p.ax - q.ax, p.ay - q.ay, p.bx - q.bx, p.by - q.by, p.ra - q.ra, p.rb - q.rb})
|
||||
if (std::fabs(d) > 0.25f) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t SteadyNs() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool Renderer::Passed(Output &o, int64_t timeoutNs) {
|
||||
if (!o.fence) return true;
|
||||
const EGLint r = pClientWaitSync(EGLDisplay(dpy_), EGLSyncKHR(o.fence), 0, EGLTimeKHR(timeoutNs));
|
||||
if (r == EGL_TIMEOUT_EXPIRED_KHR) return false;
|
||||
pDestroySync(EGLDisplay(dpy_), EGLSyncKHR(o.fence)); // passed, or failed: don't wait on it again
|
||||
o.fence = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Draws one buffer. Partial: the buffer holds this client frame already, with o.spots cut
|
||||
// out, so each eye is drawn again only inside the box around those and the new cutouts.
|
||||
void Renderer::Draw(Output &o, unsigned tex, int w, int h, const std::vector<Capsule2D> eyes[2], bool partial) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, o.fbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
|
||||
for (int e = 0; e < 2; ++e) {
|
||||
if (partial) {
|
||||
float box[4] = {1e9f, 1e9f, -1e9f, -1e9f}, b[4];
|
||||
const std::vector<Capsule2D> *lists[2] = {&o.spots[e], &eyes[e]};
|
||||
for (const std::vector<Capsule2D> *list : lists)
|
||||
for (const Capsule2D &c : *list) {
|
||||
Bounds(c, b);
|
||||
box[0] = std::min(box[0], b[0]), box[1] = std::min(box[1], b[1]);
|
||||
box[2] = std::max(box[2], b[2]), box[3] = std::max(box[3], b[3]);
|
||||
}
|
||||
// Window y is the buffer's row, the same way down as the cutouts' y (see kVertex).
|
||||
const int x0 = std::clamp(int(std::floor(box[0])) - 1, 0, w), y0 = std::clamp(int(std::floor(box[1])) - 1, 0, h);
|
||||
const int x1 = std::clamp(int(std::ceil(box[2])) + 1, 0, w), y1 = std::clamp(int(std::ceil(box[3])) + 1, 0, h);
|
||||
if (x1 <= x0 || y1 <= y0) continue; // no cutout in this eye, then or now
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(e * w + x0, y0, x1 - x0, y1 - y0);
|
||||
}
|
||||
glViewport(e * w, 0, w, h);
|
||||
glDisable(GL_BLEND);
|
||||
glUseProgram(copyProg_);
|
||||
@@ -543,22 +596,69 @@ const Output *Renderer::Composite(int panel, const void *key, const ft_dmabuf &s
|
||||
uB = glGetUniformLocation(cutProg_, "b"), uR = glGetUniformLocation(cutProg_, "r"),
|
||||
uF = glGetUniformLocation(cutProg_, "feather");
|
||||
for (const Capsule2D &c : eyes[e]) {
|
||||
const float feather = std::max(1.5f, 0.15f * std::min(c.ra, c.rb));
|
||||
const float r = std::max(c.ra, c.rb) + feather;
|
||||
glUniform4f(uRect, std::min(c.ax, c.bx) - r, std::min(c.ay, c.by) - r, std::max(c.ax, c.bx) + r,
|
||||
std::max(c.ay, c.by) + r);
|
||||
float b[4];
|
||||
Bounds(c, b);
|
||||
glUniform4f(uRect, b[0], b[1], b[2], b[3]);
|
||||
glUniform2f(uA, c.ax, c.ay);
|
||||
glUniform2f(uB, c.bx, c.by);
|
||||
glUniform2f(uR, c.ra, c.rb);
|
||||
glUniform1f(uF, feather);
|
||||
glUniform1f(uF, std::max(1.5f, 0.15f * std::min(c.ra, c.rb)));
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
glDisable(GL_BLEND);
|
||||
// SteamVR reads the buffer from another process and GPU queue; make sure it's done.
|
||||
glFinish();
|
||||
lastMs_ = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - t0).count();
|
||||
return &o;
|
||||
}
|
||||
|
||||
const Output *Renderer::Composite(int panel, const void *key, uint64_t serial, const ft_dmabuf &src,
|
||||
const std::vector<Capsule2D> eyes[2]) {
|
||||
if (!ready_) return nullptr;
|
||||
const int64_t t0 = SteadyNs();
|
||||
const int w = src.width, h = src.height;
|
||||
Ring &ring = rings_[panel];
|
||||
if (ring.w != w || ring.h != h) {
|
||||
for (Output &old : ring.out) FreeOutput(old);
|
||||
ring.w = w, ring.h = h, ring.shown = ring.before = ring.drawing = -1;
|
||||
}
|
||||
// After a pause the panel showed its client buffer, so nothing of ours is on it.
|
||||
if (t0 - ring.lastCall > 30'000'000) ring.shown = ring.before = -1;
|
||||
ring.lastCall = t0;
|
||||
auto promote = [&ring] {
|
||||
ring.before = ring.shown, ring.shown = ring.drawing, ring.drawing = -1;
|
||||
};
|
||||
if (ring.drawing >= 0 && Passed(ring.out[ring.drawing], 0)) promote();
|
||||
|
||||
const int newest = ring.drawing >= 0 ? ring.drawing : ring.shown;
|
||||
const Output *n = newest >= 0 ? &ring.out[newest] : nullptr;
|
||||
if (n && n->key == key && n->serial == serial && SameSpots(n->spots, eyes)) {
|
||||
++stats_.same;
|
||||
} else if (ring.drawing >= 0) {
|
||||
++stats_.busy; // drawn on a later tick, from what's current then
|
||||
} else {
|
||||
int i = 0;
|
||||
while (i == ring.shown || i == ring.before) ++i;
|
||||
Output &o = ring.out[i];
|
||||
if (!o.bo && !MakeOutput(o, 2 * w, h)) return nullptr;
|
||||
const GLuint tex = Texture(key, src);
|
||||
if (!tex) return nullptr;
|
||||
const bool partial = o.drawn && o.key == key && o.serial == serial;
|
||||
Draw(o, tex, w, h, eyes, partial);
|
||||
o.fence = pCreateSync(EGLDisplay(dpy_), EGL_SYNC_FENCE_KHR, nullptr);
|
||||
glFlush();
|
||||
if (!o.fence) glFinish(); // no fence: wait here, as before
|
||||
o.key = key, o.serial = serial, o.drawn = true;
|
||||
for (int e = 0; e < 2; ++e) o.spots[e] = eyes[e];
|
||||
ring.drawing = i;
|
||||
++stats_.draws, stats_.partial += partial;
|
||||
}
|
||||
// Nothing of ours to show yet: wait for this one rather than show none.
|
||||
if (ring.shown < 0 && ring.drawing >= 0) {
|
||||
++stats_.waits;
|
||||
if (Passed(ring.out[ring.drawing], 50'000'000)) promote();
|
||||
}
|
||||
lastMs_ = (SteadyNs() - t0) / 1e6;
|
||||
stats_.cpuMs += lastMs_, stats_.worstMs = std::max(stats_.worstMs, lastMs_);
|
||||
return ring.shown >= 0 ? &ring.out[ring.shown] : nullptr;
|
||||
}
|
||||
|
||||
} // namespace handcut
|
||||
+42
-6
@@ -70,7 +70,7 @@ private:
|
||||
std::map<uint32_t, Motion> motion_;
|
||||
std::vector<Capsule> world_; // base_, moved ahead
|
||||
bool predict_ = true;
|
||||
int64_t leadNs_ = 25'000'000;
|
||||
int64_t leadNs_ = 36'000'000; // 25 ms to the displays, plus the tick a cutout buffer waits for its fence
|
||||
int fd_ = -1;
|
||||
const void *map_ = nullptr;
|
||||
uint64_t seq_ = 0;
|
||||
@@ -91,6 +91,22 @@ struct Output {
|
||||
void *bo = nullptr;
|
||||
unsigned fbo = 0, rb = 0;
|
||||
void *image = nullptr;
|
||||
// What's drawn in it: the client buffer and its frame, and the cutouts (a later draw
|
||||
// with the same frame only redraws around the old and new cutouts).
|
||||
void *fence = nullptr; // the GPU is still drawing it
|
||||
const void *key = nullptr;
|
||||
uint64_t serial = 0;
|
||||
bool drawn = false;
|
||||
std::vector<Capsule2D> spots[2];
|
||||
};
|
||||
|
||||
// Composite's counts since the last TakeStats.
|
||||
struct CutStats {
|
||||
int draws = 0, partial = 0; // buffers drawn, of them only around the cutouts
|
||||
int same = 0; // nothing changed: the newest buffer stays
|
||||
int busy = 0; // the GPU hadn't finished the last one: drawn next tick
|
||||
int waits = 0; // a panel's first buffer, waited for
|
||||
double cpuMs = 0, worstMs = 0;
|
||||
};
|
||||
|
||||
class Renderer {
|
||||
@@ -99,20 +115,34 @@ public:
|
||||
// modifiers: what SteamVR takes for DRM_FORMAT_ABGR8888, the outputs' format.
|
||||
// released: an output is about to be freed (drop its SteamVR import).
|
||||
bool Init(const std::vector<uint64_t> &modifiers, std::function<void(const Output *)> released);
|
||||
// Draw client buffer `src` (identified by `key`) into the next output buffer of
|
||||
// panel `panel`, both eyes, cutting out `eyes`. Returns that buffer, or null.
|
||||
const Output *Composite(int panel, const void *key, const ft_dmabuf &src, const std::vector<Capsule2D> eyes[2]);
|
||||
// Draw client buffer `src` (identified by `key`; `serial` counts its frames) into a
|
||||
// buffer of panel `panel`, both eyes, cutting out `eyes`. Returns the newest buffer the
|
||||
// GPU has finished, or null.
|
||||
//
|
||||
// It doesn't wait for the GPU: a buffer is drawn, fenced, and returned from a later call
|
||||
// once the fence has passed, so what SteamVR shows is a tick behind. Each panel has three
|
||||
// buffers: the one shown, the one shown before it (SteamVR may still be reading it), and
|
||||
// the one being drawn. Nothing is drawn when the frame and the cutouts are what the newest
|
||||
// buffer has, and when only the cutouts moved, a buffer that holds the same client frame
|
||||
// is drawn again only around them. The first call after a pause (no call for 30 ms, about
|
||||
// 3 ticks: the panel showed its client buffer meanwhile) waits for its buffer, so a stale
|
||||
// one never shows.
|
||||
const Output *Composite(int panel, const void *key, uint64_t serial, const ft_dmabuf &src,
|
||||
const std::vector<Capsule2D> eyes[2]);
|
||||
// A client buffer is going away.
|
||||
void Forget(const void *key);
|
||||
// A panel is gone: drop its outputs.
|
||||
void DropPanel(int panel);
|
||||
// How long the last Composite took, ms (it waits for the GPU).
|
||||
// How long the last Composite took on the CPU, ms.
|
||||
double lastMs() const { return lastMs_; }
|
||||
CutStats TakeStats() { CutStats s = stats_; stats_ = {}; return s; }
|
||||
|
||||
private:
|
||||
unsigned Texture(const void *key, const ft_dmabuf &src);
|
||||
bool MakeOutput(Output &o, int w, int h);
|
||||
void FreeOutput(Output &o);
|
||||
bool Passed(Output &o, int64_t timeoutNs);
|
||||
void Draw(Output &o, unsigned tex, int w, int h, const std::vector<Capsule2D> eyes[2], bool partial);
|
||||
bool ready_ = false;
|
||||
int drm_ = -1;
|
||||
void *gbm_ = nullptr, *dpy_ = nullptr, *ctx_ = nullptr;
|
||||
@@ -121,9 +151,15 @@ private:
|
||||
std::function<void(const Output *)> released_;
|
||||
struct Imported { void *image; unsigned tex; };
|
||||
std::map<const void *, Imported> imported_;
|
||||
struct Ring { Output out[3]; int next = 0; int w = 0, h = 0; };
|
||||
struct Ring {
|
||||
Output out[3];
|
||||
int shown = -1, before = -1, drawing = -1; // indices into out
|
||||
int w = 0, h = 0;
|
||||
int64_t lastCall = 0; // steady clock ns
|
||||
};
|
||||
std::map<int, Ring> rings_;
|
||||
double lastMs_ = 0;
|
||||
CutStats stats_;
|
||||
};
|
||||
|
||||
} // namespace handcut
|
||||
@@ -163,7 +163,7 @@ int main(int argc, char **argv) {
|
||||
handcut::EyePositions(head, eyes);
|
||||
cut = handcut::Project(panel, hands.capsules(), eyes, eyes2d);
|
||||
}
|
||||
const handcut::Output *out = cut ? renderer.Composite(0, bo, client, eyes2d) : nullptr;
|
||||
const handcut::Output *out = cut ? renderer.Composite(0, bo, 1, client, eyes2d) : nullptr;
|
||||
if (out) {
|
||||
auto it = imports.find(out);
|
||||
if (it == imports.end()) {
|
||||
@@ -177,8 +177,7 @@ int main(int argc, char **argv) {
|
||||
vr::VROverlay()->SetOverlayFlag(ov, vr::VROverlayFlags_SideBySide_Parallel, true);
|
||||
cutting = true;
|
||||
}
|
||||
shown = it->second;
|
||||
vr::VROverlay()->SetOverlayTexture(ov, &tex);
|
||||
if (shown != it->second) shown = it->second, vr::VROverlay()->SetOverlayTexture(ov, &tex);
|
||||
ms += renderer.lastMs(), worst = std::max(worst, renderer.lastMs());
|
||||
++cutFrames;
|
||||
caps2d += eyes2d[0].size() + eyes2d[1].size();
|
||||
@@ -192,10 +191,12 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
++frames;
|
||||
if (now - lastReport > 2'000'000'000) {
|
||||
const handcut::CutStats st = renderer.TakeStats();
|
||||
std::printf("%.0f s: %d ticks, %d with a cutout (%.1f capsules per eye), composite %.2f ms avg %.2f ms worst, "
|
||||
"%zu hand capsules known\n",
|
||||
"%zu hand capsules known; %d draws (%d partial), %d same, %d busy\n",
|
||||
(now - start) / 1e9, frames, cutFrames, cutFrames ? caps2d / 2.0 / cutFrames : 0.0,
|
||||
cutFrames ? ms / cutFrames : 0.0, worst, hands.capsules().size());
|
||||
cutFrames ? ms / cutFrames : 0.0, worst, hands.capsules().size(), st.draws, st.partial, st.same,
|
||||
st.busy);
|
||||
std::fflush(stdout);
|
||||
lastReport = now, frames = cutFrames = 0, ms = worst = 0, caps2d = 0;
|
||||
}
|
||||
|
||||
+18
-7
@@ -270,7 +270,9 @@ struct Screen {
|
||||
const void *key = nullptr; // the client buffer on it now, and its dmabuf (for cutouts)
|
||||
ft_dmabuf buf{};
|
||||
vr::SharedTextureHandle_t plain = 0; // that buffer's SteamVR import
|
||||
uint64_t frames = 0; // client frames presented (a cutout buffer's "serial")
|
||||
bool cutting = false; // showing a cutout buffer (side by side) instead
|
||||
vr::SharedTextureHandle_t cutShown = 0; // ...this one
|
||||
double chrome = 0.3; // the bar's width; the other controls follow it (ChromeSize)
|
||||
double grip = 0.04; // the corner tab's and the round buttons' size
|
||||
// A floating window's panel (see the top): the window's rectangle in the buffer, its
|
||||
@@ -1438,7 +1440,7 @@ void StopCutting(Screen &s) {
|
||||
vr::VROverlay()->SetOverlayFlag(s.overlay, vr::VROverlayFlags_SideBySide_Parallel, false);
|
||||
vr::VROverlay()->SetOverlayFlag(s.overlay, vr::VROverlayFlags_IgnoreTextureAlpha, true);
|
||||
if (s.plain) SetScreenTexture(s, s.plain);
|
||||
s.cutting = false;
|
||||
s.cutting = false, s.cutShown = 0;
|
||||
}
|
||||
|
||||
// Each tick: for each visible screen with a hand in front of it (for either eye), draw its
|
||||
@@ -1459,7 +1461,7 @@ void UpdateCutouts() {
|
||||
bool cut = hands && s.visible && !s.floating && s.key && s.width > 0 && ScreenPose(s, &p) &&
|
||||
handcut::Project({p, s.metres, s.heightMetres(), s.curve, s.width, s.height}, g_hands.capsules(),
|
||||
eyes, spots);
|
||||
const handcut::Output *out = cut && CutterReady() ? g_cutter.Composite(i, s.key, s.buf, spots) : nullptr;
|
||||
const handcut::Output *out = cut && CutterReady() ? g_cutter.Composite(i, s.key, s.frames, s.buf, spots) : nullptr;
|
||||
const vr::SharedTextureHandle_t h = out ? ImportCutout(out) : 0;
|
||||
if (!h) {
|
||||
StopCutting(s);
|
||||
@@ -1470,7 +1472,7 @@ void UpdateCutouts() {
|
||||
vr::VROverlay()->SetOverlayFlag(s.overlay, vr::VROverlayFlags_SideBySide_Parallel, true);
|
||||
s.cutting = true;
|
||||
}
|
||||
SetScreenTexture(s, h);
|
||||
if (h != s.cutShown) SetScreenTexture(s, h), s.cutShown = h; // the same buffer stays: no new frame for SteamVR
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1699,7 +1701,7 @@ bool ft_vr_screen_present(int index, const void *key, const struct ft_dmabuf *b)
|
||||
PlaceChrome(s); // the height changed
|
||||
std::printf("screen %d: %dx%d\n", index + 1, s.width, s.height);
|
||||
}
|
||||
s.key = key, s.buf = *b, s.plain = it->second;
|
||||
s.key = key, s.buf = *b, s.plain = it->second, ++s.frames;
|
||||
// While cutting, the next tick draws the new buffer with the cutouts (never floating).
|
||||
if (!s.cutting) SetScreenTexture(s, it->second);
|
||||
vr::SharedTextureHandle_t handle = it->second;
|
||||
@@ -2131,9 +2133,18 @@ void ft_vr_command(const char *cmd, char *reply, int size) {
|
||||
g_hands.SetPrediction(g_hands.predicting(), ms);
|
||||
else if (std::strcmp(word, "state") != 0)
|
||||
return (void)std::snprintf(reply, size, "error cutouts on|off|state|predict on|off|lead <ms>");
|
||||
std::snprintf(reply, size, "ok %s %s %.2f ms, predict %s lead %.0f ms", g_cutouts ? "on" : "off",
|
||||
g_cutterState > 0 ? "ready" : g_cutterState < 0 ? "unavailable" : "idle", g_cutter.lastMs(),
|
||||
g_hands.predicting() ? "on" : "off", g_hands.leadMs());
|
||||
// Composite's counts since the last state, per second.
|
||||
static auto since = Clock::now();
|
||||
const double dt = std::max(1e-3, std::chrono::duration<double>(Clock::now() - since).count());
|
||||
since = Clock::now();
|
||||
const handcut::CutStats c = g_cutter.TakeStats();
|
||||
const int calls = c.draws + c.same + c.busy;
|
||||
std::snprintf(reply, size,
|
||||
"ok %s %s %.2f ms, predict %s lead %.0f ms; per s: %.1f draws (%.1f partial), %.1f same, %.1f busy, "
|
||||
"%.1f waits; CPU %.2f ms avg %.2f worst",
|
||||
g_cutouts ? "on" : "off", g_cutterState > 0 ? "ready" : g_cutterState < 0 ? "unavailable" : "idle",
|
||||
g_cutter.lastMs(), g_hands.predicting() ? "on" : "off", g_hands.leadMs(), c.draws / dt,
|
||||
c.partial / dt, c.same / dt, c.busy / dt, c.waits / dt, calls ? c.cpuMs / calls : 0.0, c.worstMs);
|
||||
} else if (int x0, y0, w0, h0, t0; std::sscanf(cmd, "float %d %lf %d %d %d %d %d", &n, &w, &x0, &y0, &w0, &h0, &t0) == 7) {
|
||||
Screen *s = Find(n);
|
||||
if (!s || !s->floating) return (void)std::snprintf(reply, size, "error no floating window panel %d", n);
|
||||
|
||||
Reference in new issue
Block a user