mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 19:11:08 +00:00
Merge #561 into the non-Adreno test branch
This commit is contained in:
commit
e282318dc7
10 files changed
+1049
-33
No files matched your search
@@ -86,6 +86,9 @@ static struct ahb_buf *find_buf(uint64_t id) {
|
||||
}
|
||||
|
||||
static void buf_free(struct ahb_buf *ab) {
|
||||
/* A display release does not prove the compositor's Vulkan reads finished.
|
||||
* Keep the AHB reference and its DMA-BUF record even if the client is gone. */
|
||||
if (vkp_completion_unknown()) return;
|
||||
if (ab->resource) { wl_list_remove(&ab->resource_destroy.link); ab->resource = NULL; }
|
||||
if (ab->fence_src) { wl_event_source_remove(ab->fence_src); ab->fence_src = NULL; }
|
||||
if (ab->fence_fd >= 0) { close(ab->fence_fd); ab->fence_fd = -1; }
|
||||
@@ -112,6 +115,7 @@ static void on_buffer_resource_destroyed(struct wl_listener *l, void *data) {
|
||||
/* ---- release: SurfaceFlinger is done with the buffer -> the game's next acquire waits, then wl_buffer.release */
|
||||
|
||||
static void send_deferred_release(struct ahb_buf *ab) {
|
||||
if (vkp_completion_unknown()) return;
|
||||
if (ab->release_pending && ab->resource)
|
||||
droiddeck_release_buffer(ab->surface, ab->resource, ab->surface != NULL, ab->deferred_ns);
|
||||
ab->release_pending = 0;
|
||||
|
||||
@@ -377,6 +377,18 @@ static void perf_note_release(int64_t since_ns) {
|
||||
|
||||
/* ------------------------------------------------------------------ buffer release pacing */
|
||||
|
||||
/* Completion-unknown is terminal for externally owned DMA-BUF contents too.
|
||||
* Keeping a VkImage alone cannot stop the client from overwriting its backing
|
||||
* memory after wl_buffer.release. All paths (including cursors and idle roles)
|
||||
* use this gate; copied SHM buffers retain their normal release behaviour. */
|
||||
static struct dmabuf_buffer *get_dmabuf(struct wl_resource *buffer);
|
||||
static int buffer_release_blocked(struct wl_resource *buffer) {
|
||||
return vkp_completion_unknown() && get_dmabuf(buffer) != NULL;
|
||||
}
|
||||
static void send_buffer_release(struct wl_resource *buffer) {
|
||||
if (!buffer_release_blocked(buffer)) wl_buffer_send_release(buffer);
|
||||
}
|
||||
|
||||
static void pending_release_free(struct pending_release *pr) {
|
||||
if (pr->surface && pr->surface->releases_pending > 0) pr->surface->releases_pending--;
|
||||
wl_list_remove(&pr->destroy.link);
|
||||
@@ -384,7 +396,8 @@ static void pending_release_free(struct pending_release *pr) {
|
||||
free(pr);
|
||||
}
|
||||
|
||||
/* A surface is going away: its queued releases still go out on time, they just stop counting. */
|
||||
/* A surface is going away: detach its accounting pointer, including queued
|
||||
* DMA-BUF releases held in the completion-unknown state. */
|
||||
static void pending_releases_forget_surface(struct surface *s) {
|
||||
struct pending_release *pr;
|
||||
wl_list_for_each(pr, &g_pending_releases, link)
|
||||
@@ -401,7 +414,8 @@ static void arm_release_timer(void) {
|
||||
int64_t earliest = 0;
|
||||
if (g_release_timer_fd < 0) return;
|
||||
wl_list_for_each(pr, &g_pending_releases, link)
|
||||
if (!earliest || pr->at_ns < earliest) earliest = pr->at_ns;
|
||||
if (!buffer_release_blocked(pr->buffer) && (!earliest || pr->at_ns < earliest))
|
||||
earliest = pr->at_ns;
|
||||
struct itimerspec its = {0};
|
||||
if (earliest) { its.it_value.tv_sec = earliest / 1000000000LL; its.it_value.tv_nsec = earliest % 1000000000LL; }
|
||||
timerfd_settime(g_release_timer_fd, TFD_TIMER_ABSTIME, &its, NULL);
|
||||
@@ -413,8 +427,8 @@ static int on_release_timer(int fd, uint32_t mask, void *data) {
|
||||
int64_t now = now_ns() + 300000; /* anything due within 0.3 ms goes now */
|
||||
if (read(fd, &expirations, sizeof(expirations)) < 0 && errno != EAGAIN) return 0;
|
||||
wl_list_for_each_safe(pr, tmp, &g_pending_releases, link) {
|
||||
if (pr->at_ns <= now) {
|
||||
wl_buffer_send_release(pr->buffer);
|
||||
if (pr->at_ns <= now && !buffer_release_blocked(pr->buffer)) {
|
||||
send_buffer_release(pr->buffer);
|
||||
perf_note_release(pr->since_ns);
|
||||
pending_release_free(pr);
|
||||
}
|
||||
@@ -435,6 +449,9 @@ static int on_release_timer(int fd, uint32_t mask, void *data) {
|
||||
* delivered (a swapchain rebuild destroys buffers with queued releases) or that a coarser earlier
|
||||
* limit had spaced out, and the game's first frames after that waited on empty slots. */
|
||||
static void release_buffer(struct surface *s, struct wl_resource *buffer, int64_t since_ns) {
|
||||
/* No allocation or immediate-release fallback is allowed for a quarantined
|
||||
* DMA-BUF. Existing queued releases stay held; their timer is disarmed. */
|
||||
if (buffer_release_blocked(buffer)) return;
|
||||
const int limit = g_fps_limit;
|
||||
/* Without a limit of its own a game is paced to the screen. A swapchain that does not wait for
|
||||
* frame callbacks (MAILBOX, IMMEDIATE) is held back by nothing else here, so handing a replaced
|
||||
@@ -443,7 +460,7 @@ static void release_buffer(struct surface *s, struct wl_resource *buffer, int64_
|
||||
* One release per refresh is the back-pressure X11 gives, and the screen shows the same frames
|
||||
* either way. */
|
||||
const int64_t interval = limit > 0 ? 1000000000LL / limit : g_refresh_ns;
|
||||
if (interval <= 0 || g_release_timer_fd < 0) { wl_buffer_send_release(buffer); perf_note_release(since_ns); return; }
|
||||
if (interval <= 0 || g_release_timer_fd < 0) { send_buffer_release(buffer); perf_note_release(since_ns); return; }
|
||||
int64_t now = now_ns();
|
||||
int64_t at = s->next_release_ns + interval;
|
||||
int64_t latest = now + interval * (int64_t)(s->releases_pending + 1);
|
||||
@@ -451,7 +468,7 @@ static void release_buffer(struct surface *s, struct wl_resource *buffer, int64_
|
||||
if (at > latest) at = latest;
|
||||
s->next_release_ns = at;
|
||||
struct pending_release *pr = calloc(1, sizeof(*pr));
|
||||
if (!pr) { wl_buffer_send_release(buffer); perf_note_release(since_ns); return; }
|
||||
if (!pr) { send_buffer_release(buffer); perf_note_release(since_ns); return; }
|
||||
pr->buffer = buffer;
|
||||
pr->surface = s;
|
||||
pr->at_ns = at;
|
||||
@@ -586,7 +603,10 @@ static void constraints_focus_entered(struct wl_resource *target, struct wl_clie
|
||||
/* ------------------------------------------------------------------ dmabuf buffers */
|
||||
|
||||
void dmabuf_buffer_unref(struct dmabuf_buffer *b) {
|
||||
if (!b || --b->refs > 0) return;
|
||||
/* Surface/client destruction must not close the backing FDs or free the
|
||||
* imported-buffer record while a GPU read may still be pending. Retain the
|
||||
* reference without allocating a quarantine node; process exit reclaims it. */
|
||||
if (!b || vkp_completion_unknown() || --b->refs > 0) return;
|
||||
vkp_image_destroy(b->img);
|
||||
for (int i = 0; i < b->n_planes; i++)
|
||||
if (b->fd[i] >= 0) close(b->fd[i]);
|
||||
@@ -677,7 +697,8 @@ static void unmap_toplevel(struct surface *s) {
|
||||
|
||||
/* Let go of the surface's dmabuf content. paced = 1: the buffer was replaced, give it back on the
|
||||
* limiter's cadence; paced = 0: the surface is going away, give it back at once (a buffer of a
|
||||
* destroyed surface is not shown again and must not sit unreleased with the client). */
|
||||
* destroyed surface is not shown again). Completion-unknown overrides both
|
||||
* release modes: prior GPU reads may outlive the surface. */
|
||||
static void drop_dmabuf(struct surface *s, int paced) {
|
||||
if (s->dmabuf) {
|
||||
wl_list_remove(&s->dmabuf_destroy.link);
|
||||
@@ -685,7 +706,7 @@ static void drop_dmabuf(struct surface *s, int paced) {
|
||||
* ahb_swapchain.c releases it then. */
|
||||
if (!ahb_swapchain_defer_release(s->dmabuf_buf, s->dmabuf, s, paced)) {
|
||||
if (paced) release_buffer(s, s->dmabuf, now_ns());
|
||||
else wl_buffer_send_release(s->dmabuf);
|
||||
else send_buffer_release(s->dmabuf);
|
||||
}
|
||||
s->dmabuf = NULL;
|
||||
}
|
||||
@@ -735,7 +756,7 @@ static void take_shm(struct surface *s, struct wl_shm_buffer *shm, struct wl_res
|
||||
vkp_image_upload_shm(s->shm_img, wl_shm_buffer_get_data(shm), stride);
|
||||
wl_shm_buffer_end_access(shm);
|
||||
}
|
||||
wl_buffer_send_release(buffer);
|
||||
send_buffer_release(buffer);
|
||||
s->buf_w = w;
|
||||
s->buf_h = h;
|
||||
s->buf_alpha = wl_shm_buffer_get_format(shm) == WL_SHM_FORMAT_ARGB8888;
|
||||
@@ -753,7 +774,7 @@ static void take_pixel(struct surface *s, const uint8_t bgra[4], struct wl_resou
|
||||
}
|
||||
if (!s->shm_img) s->shm_img = vkp_image_create_shm(1, 1);
|
||||
if (s->shm_img) vkp_image_upload_shm(s->shm_img, bgra, 4);
|
||||
wl_buffer_send_release(buffer);
|
||||
send_buffer_release(buffer);
|
||||
s->buf_w = 1;
|
||||
s->buf_h = 1;
|
||||
s->buf_alpha = bgra[3] != 0xff;
|
||||
@@ -880,8 +901,9 @@ void **droiddeck_dmabuf_ahb_slot(struct dmabuf_buffer *b) { return &b->ahb_state
|
||||
void droiddeck_dmabuf_ref(struct dmabuf_buffer *b) { b->refs++; }
|
||||
void droiddeck_dmabuf_unref(struct dmabuf_buffer *b) { dmabuf_buffer_unref(b); }
|
||||
void droiddeck_release_buffer(struct surface *s, struct wl_resource *buffer, int paced, int64_t since_ns) {
|
||||
if (buffer_release_blocked(buffer)) return;
|
||||
if (paced && s) release_buffer(s, buffer, since_ns);
|
||||
else { wl_buffer_send_release(buffer); perf_note_release(since_ns); }
|
||||
else { send_buffer_release(buffer); perf_note_release(since_ns); }
|
||||
}
|
||||
void droiddeck_surface_describe(const struct surface *s, char *out, size_t size) { describe(s, out, size); }
|
||||
const struct droiddeck_color *droiddeck_surface_color(const struct surface *s) {
|
||||
@@ -1046,7 +1068,7 @@ static void surface_hold_idle(struct surface *s, struct wl_resource *buffer) {
|
||||
struct wl_shm_buffer *shm = db ? NULL : wl_shm_buffer_get(buffer);
|
||||
int64_t px = db ? (int64_t)db->width * db->height
|
||||
: shm ? (int64_t)wl_shm_buffer_get_width(shm) * wl_shm_buffer_get_height(shm) : 0;
|
||||
if (px <= 0 || px > CURSOR_MAX_PX) { wl_buffer_send_release(buffer); return; }
|
||||
if (px <= 0 || px > CURSOR_MAX_PX) { send_buffer_release(buffer); return; }
|
||||
s->idle_buffer = buffer;
|
||||
s->idle_buffer_destroy.notify = on_idle_buffer_destroyed;
|
||||
wl_resource_add_destroy_listener(buffer, &s->idle_buffer_destroy);
|
||||
@@ -1064,7 +1086,7 @@ static struct wl_resource *surface_take_idle(struct surface *s) {
|
||||
|
||||
static void surface_drop_idle(struct surface *s) {
|
||||
struct wl_resource *buffer = surface_take_idle(s);
|
||||
if (buffer) wl_buffer_send_release(buffer);
|
||||
if (buffer) send_buffer_release(buffer);
|
||||
}
|
||||
|
||||
static void surface_commit(struct wl_client *c, struct wl_resource *r) {
|
||||
@@ -1119,7 +1141,7 @@ static void surface_commit(struct wl_client *c, struct wl_resource *r) {
|
||||
cursor_publish_hidden(); /* wlroots clears its cursor surface to hide the pointer */
|
||||
} else {
|
||||
cursor_publish_buffer(s, buffer);
|
||||
wl_buffer_send_release(buffer);
|
||||
send_buffer_release(buffer);
|
||||
}
|
||||
} else if (s->role == ROLE_NONE) {
|
||||
/* Role-less surface: never drawn, but its buffer is kept for a set_cursor to come. */
|
||||
@@ -1135,7 +1157,7 @@ static void surface_commit(struct wl_client *c, struct wl_resource *r) {
|
||||
} else {
|
||||
drop_dmabuf(s, 1);
|
||||
s->has_content = 0;
|
||||
if (buffer) wl_buffer_send_release(buffer);
|
||||
if (buffer) send_buffer_release(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2322,7 +2344,7 @@ static void pointer_set_cursor(struct wl_client *c, struct wl_resource *r, uint3
|
||||
struct wl_resource *buffer = surface_take_idle(s);
|
||||
if (buffer) {
|
||||
cursor_publish_buffer(s, buffer);
|
||||
wl_buffer_send_release(buffer);
|
||||
send_buffer_release(buffer);
|
||||
} else if (g_cursor_shown == s) {
|
||||
cursor_publish_shown();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ struct vkp_image {
|
||||
|
||||
static ANativeWindow *g_window; /* the window frames go to; compositor thread only */
|
||||
static char *g_driver_path, *g_library_name, *g_native_lib_dir;
|
||||
static int g_dev_state; /* 0 = not yet, 1 = ok, -1 = failed, -2 = lost (VK_ERROR_DEVICE_LOST) */
|
||||
static int g_dev_state; /* 0 = not yet, 1 = ok, -1 = failed, -2 = lost, -3 = completion unknown */
|
||||
static void device_lost(const char *where);
|
||||
static VkInstance g_inst;
|
||||
static VkPhysicalDevice g_pd;
|
||||
static VkDevice g_dev;
|
||||
@@ -112,11 +113,26 @@ static void perf_add(int64_t *sum, int64_t *max, unsigned *count, int64_t d) {
|
||||
if (d > *max) *max = d;
|
||||
(*count)++;
|
||||
}
|
||||
/* A timeout is not device loss and does not cancel a submit. Keep its images
|
||||
* and memory alive until this compositor process exits. Resuming
|
||||
* would reset a pending command buffer/fence or free memory still read by the GPU. */
|
||||
static void quarantine_pending_work(VkResult result, const char *where) {
|
||||
if (g_dev_state != 1) return;
|
||||
g_dev_state = -3;
|
||||
g_base_black = 0;
|
||||
LOGE("GPU completion unconfirmed (%d in %s): presenting stopped; pending resources retained; "
|
||||
"restart DroidDeck", (int)result, where);
|
||||
}
|
||||
|
||||
/* vkWaitForFences on the compositor's own work, timed for the perf line. */
|
||||
static VkResult timed_wait(VkFence fence, uint64_t timeout) {
|
||||
if (g_dev_state != 1)
|
||||
return g_dev_state == -2 ? VK_ERROR_DEVICE_LOST : VK_ERROR_UNKNOWN;
|
||||
int64_t t0 = perf_now();
|
||||
VkResult r = g_vk.WaitForFences(g_dev, 1, &fence, VK_TRUE, timeout);
|
||||
perf_add(&g_perf.wait_ns, &g_perf.wait_max_ns, &g_perf.waits, perf_now() - t0);
|
||||
if (r != VK_SUCCESS && r != VK_ERROR_DEVICE_LOST)
|
||||
quarantine_pending_work(r, "fence wait");
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -236,7 +252,12 @@ static void reset_sync(void) {
|
||||
static void destroy_swapchain(void) {
|
||||
g_base_black = 0; /* a new swapchain starts with nothing on it */
|
||||
if (g_dev_state != 1) return;
|
||||
g_vk.DeviceWaitIdle(g_dev);
|
||||
VkResult idle = g_vk.DeviceWaitIdle(g_dev);
|
||||
if (idle != VK_SUCCESS) {
|
||||
if (idle == VK_ERROR_DEVICE_LOST) device_lost("swapchain idle");
|
||||
else quarantine_pending_work(idle, "swapchain idle");
|
||||
return;
|
||||
}
|
||||
reset_sync();
|
||||
for (uint32_t i = 0; i < g_nimg; i++) blendp_forget_image(g_images[i]);
|
||||
if (g_swapchain) g_vk.DestroySwapchainKHR(g_dev, g_swapchain, NULL);
|
||||
@@ -302,6 +323,7 @@ int vkp_has_window(void) {
|
||||
}
|
||||
|
||||
int vkp_device_lost(void) { return g_dev_state == -2; }
|
||||
int vkp_completion_unknown(void) { return g_dev_state == -3; }
|
||||
|
||||
void vk_present_set_scale_mode(int mode, int alignment) {
|
||||
if (mode < VKP_MODE_OFF || mode > VKP_MODE_INTEGER) mode = VKP_MODE_FIT;
|
||||
@@ -546,6 +568,7 @@ static int dev_init(void) {
|
||||
g_dev_state = 1;
|
||||
reset_sync();
|
||||
init_black_image();
|
||||
if (g_dev_state != 1) return -1;
|
||||
vkp_framegen_device_ready(g_dev, g_queue, g_qfam, fg_features != NULL);
|
||||
return 0;
|
||||
}
|
||||
@@ -557,7 +580,7 @@ static int swap_init_locked(void);
|
||||
static int swap_init(void) {
|
||||
struct timespec ts;
|
||||
int64_t now;
|
||||
if (!g_window) return -1;
|
||||
if (!g_window || g_dev_state != 1) return -1;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
now = (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec;
|
||||
if (now < g_swap_retry_at_ns) return -1;
|
||||
@@ -842,10 +865,15 @@ static void init_black_image(void) {
|
||||
g_vk.EndCommandBuffer(cmd);
|
||||
VkSubmitInfo si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd};
|
||||
g_vk.ResetFences(g_dev, 1, &g_fence);
|
||||
if (g_vk.QueueSubmit(g_queue, 1, &si, g_fence) == VK_SUCCESS &&
|
||||
g_vk.WaitForFences(g_dev, 1, &g_fence, VK_TRUE, 1000000000ULL) == VK_SUCCESS)
|
||||
g_black_state = 1;
|
||||
else
|
||||
VkResult qr = g_vk.QueueSubmit(g_queue, 1, &si, g_fence);
|
||||
if (qr == VK_SUCCESS) {
|
||||
VkResult fr = timed_wait(g_fence, 1000000000ULL);
|
||||
if (fr == VK_SUCCESS) g_black_state = 1;
|
||||
else if (fr == VK_ERROR_DEVICE_LOST) device_lost("black image");
|
||||
} else if (qr == VK_ERROR_DEVICE_LOST) {
|
||||
device_lost("black image submit");
|
||||
}
|
||||
if (g_black_state != 1)
|
||||
droiddeck_log("gpu", "letterbox black image unavailable: the whole output is cleared every copy-path frame");
|
||||
}
|
||||
|
||||
@@ -1084,10 +1112,10 @@ struct vkp_image *vkp_image_create_shm(int w, int h) {
|
||||
return img;
|
||||
}
|
||||
|
||||
/* Renders are synchronous (we wait for the frame's fence), so writing between frames
|
||||
* never races the GPU. */
|
||||
/* Between successfully completed frames the GPU is idle. After an unconfirmed
|
||||
* wait, retain the mapped bytes too: the previous submit may still read them. */
|
||||
void vkp_image_upload_shm(struct vkp_image *img, const void *data, int stride) {
|
||||
if (!img || !img->map || !data) return;
|
||||
if (g_dev_state != 1 || !img || !img->map || !data) return;
|
||||
size_t rowbytes = (size_t)img->w * 4;
|
||||
if ((size_t)stride < rowbytes) rowbytes = (size_t)stride;
|
||||
for (int y = 0; y < img->h; y++)
|
||||
@@ -1100,6 +1128,7 @@ int vkp_image_height(const struct vkp_image *img) { return img ? img->h : 0; }
|
||||
|
||||
void vkp_image_destroy(struct vkp_image *img) {
|
||||
if (!img) return;
|
||||
if (g_dev_state == -3) return; /* completion unknown: retain GPU ownership */
|
||||
if (g_dev) {
|
||||
blendp_forget_image(img->image);
|
||||
if (img->map) g_vk.UnmapMemory(g_dev, img->mem);
|
||||
@@ -1391,6 +1420,7 @@ static void record_screen_blit(VkCommandBuffer cmd, VkImage src, int src_w, int
|
||||
* so they never rebuild; they fail and the frame ends early. 0 on success. */
|
||||
static int acquire_image(int k, int first, uint32_t *img, VkResult *ar_out) {
|
||||
for (int attempt = 0; ; attempt++) {
|
||||
if (g_dev_state != 1) return -1;
|
||||
if (!g_swapchain && (!first || swap_init() != 0)) return -1;
|
||||
/* A bounded wait: a surface that went away without telling us must not park the
|
||||
* compositor thread forever (clients are paced from this thread). */
|
||||
@@ -1402,7 +1432,7 @@ static int acquire_image(int k, int first, uint32_t *img, VkResult *ar_out) {
|
||||
if (ar == VK_ERROR_OUT_OF_DATE_KHR || ar == VK_ERROR_SURFACE_LOST_KHR) {
|
||||
droiddeck_log("gpu", "screen surface %s on acquire: rebuilding the swapchain", vk_result_name(ar));
|
||||
destroy_swapchain();
|
||||
if (first && attempt == 0) continue;
|
||||
if (first && attempt == 0 && g_dev_state == 1) continue;
|
||||
return -1;
|
||||
}
|
||||
droiddeck_log("error", "present: acquire %d failed (%s %d)", k, vk_result_name(ar), (int)ar);
|
||||
@@ -1530,6 +1560,7 @@ static void send_hdr_metadata(const struct droiddeck_color *c) {
|
||||
static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, int n,
|
||||
const struct vkp_hdr_frame *hf, int *how) {
|
||||
if (how) *how = 0;
|
||||
if (g_dev_state < 0) return -1;
|
||||
vkp_apply_window_request();
|
||||
check_surface_changed(); /* a rotation or a fold the rebind did not cover: rebuild at the new size */
|
||||
if (g_dev_state == -2) return -1;
|
||||
@@ -1552,6 +1583,7 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i
|
||||
: "no HDR frames through the screen swapchain any more: rebuilding it as SDR");
|
||||
destroy_swapchain();
|
||||
}
|
||||
if (g_dev_state != 1) return -1;
|
||||
g_swap_hdr_want = want_hdr;
|
||||
|
||||
uint32_t img = 0;
|
||||
@@ -1762,6 +1794,10 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i
|
||||
if (k > 0) {
|
||||
VkResult ark;
|
||||
if (acquire_image(k, 0, &img, &ark) != 0) {
|
||||
/* Teardown may have retained this frame's pending work, or
|
||||
* acquire may have reported real device loss. Neither permits
|
||||
* a completion-marker submit or another fence wait. */
|
||||
if (g_dev_state != 1) return -1;
|
||||
droiddeck_log("framegen", "present %d/%d: no swapchain image; the frame ends early", k + 1, presents);
|
||||
pr = VK_ERROR_OUT_OF_DATE_KHR;
|
||||
break;
|
||||
@@ -1794,17 +1830,24 @@ static int render_impl(int scene_w, int scene_h, const struct vkp_draw *draws, i
|
||||
pr = g_vk.QueuePresentKHR(g_queue, &pi);
|
||||
perf_add(&g_perf.present_ns, &g_perf.present_max_ns, &g_perf.presents, perf_now() - t_pres);
|
||||
if (k < ngen) presented_gen++;
|
||||
if (pr == VK_ERROR_OUT_OF_DATE_KHR || pr == VK_ERROR_SURFACE_LOST_KHR || pr == VK_ERROR_DEVICE_LOST) break;
|
||||
if (pr == VK_ERROR_DEVICE_LOST) { device_lost("present"); return -1; }
|
||||
if (pr == VK_ERROR_OUT_OF_DATE_KHR || pr == VK_ERROR_SURFACE_LOST_KHR) break;
|
||||
}
|
||||
if (!fence_submitted) {
|
||||
/* Ended early: an empty submit carrying the fence is ordered after everything queued
|
||||
* above, so the wait below still means "this frame's work is done". */
|
||||
VkSubmitInfo si = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
||||
g_vk.QueueSubmit(g_queue, 1, &si, g_fence);
|
||||
VkResult marker = g_vk.QueueSubmit(g_queue, 1, &si, g_fence);
|
||||
if (marker != VK_SUCCESS) {
|
||||
if (marker == VK_ERROR_DEVICE_LOST) device_lost("frame completion submit");
|
||||
else quarantine_pending_work(marker, "frame completion submit");
|
||||
return -1; /* no fence was submitted; never wait on its unsignalled state */
|
||||
}
|
||||
}
|
||||
VkResult fr = timed_wait(g_fence, UINT64_MAX);
|
||||
if (fg) vkp_framegen_presented(presented_gen);
|
||||
if (fr == VK_ERROR_DEVICE_LOST || pr == VK_ERROR_DEVICE_LOST) { device_lost("present"); return -1; }
|
||||
if (fr != VK_SUCCESS) return -1;
|
||||
if (fg) vkp_framegen_presented(presented_gen);
|
||||
if (pr == VK_ERROR_OUT_OF_DATE_KHR || pr == VK_ERROR_SURFACE_LOST_KHR) {
|
||||
/* The surface changed or went away under this frame: rebuild before the next one. */
|
||||
droiddeck_log("gpu", "screen surface %s on present: rebuilding the swapchain", vk_result_name(pr));
|
||||
@@ -1878,6 +1921,7 @@ void vkp_signal_first_frame(void) {
|
||||
int vkp_update_map(int scene_w, int scene_h) {
|
||||
if (g_dev_state == -2 || dev_init() != 0 || !g_window || scene_w <= 0 || scene_h <= 0) return -1;
|
||||
check_surface_changed(); /* layer mode may present nothing on the base surface for a long time */
|
||||
if (g_dev_state != 1) return -1;
|
||||
if (!g_swapchain && swap_init() != 0) return -1;
|
||||
update_map(scene_w, scene_h);
|
||||
return g_map.valid ? 0 : -1;
|
||||
@@ -2116,6 +2160,7 @@ static int pass_begin_impl(int scene_w, int scene_h, const struct vkp_draw *draw
|
||||
if (g_pass.active) vkp_pass_abort();
|
||||
if (g_dev_state == -2 || dev_init() != 0 || !g_window || scene_w <= 0 || scene_h <= 0) return -1;
|
||||
check_surface_changed(); /* as in render_impl: the effects pass maps onto the output's size */
|
||||
if (g_dev_state != 1) return -1;
|
||||
if (!g_swapchain && swap_init() != 0) return -1; /* the mapping is in output pixels */
|
||||
update_map(scene_w, scene_h);
|
||||
if (!g_map.valid) return -1;
|
||||
@@ -2211,8 +2256,9 @@ static int pass_begin_impl(int scene_w, int scene_h, const struct vkp_draw *draw
|
||||
int vkp_pass_copy_to(struct vkp_image *dst, int wait_fd) {
|
||||
if (!g_pass.active) { if (wait_fd >= 0) close(wait_fd); return -1; }
|
||||
g_pass.active = 0;
|
||||
if (g_dev_state != 1) { if (wait_fd >= 0) close(wait_fd); return -1; }
|
||||
VkCommandBuffer cmd = g_cmds[0];
|
||||
if (!dst || !dst->blit_dst || g_dev_state == -2) {
|
||||
if (!dst || !dst->blit_dst) {
|
||||
if (wait_fd >= 0) close(wait_fd);
|
||||
g_vk.EndCommandBuffer(cmd);
|
||||
return -1;
|
||||
@@ -2267,5 +2313,6 @@ int vkp_pass_copy_to(struct vkp_image *dst, int wait_fd) {
|
||||
void vkp_pass_abort(void) {
|
||||
if (!g_pass.active) return;
|
||||
g_pass.active = 0;
|
||||
g_vk.EndCommandBuffer(g_cmds[0]); /* never submitted; the buffer is reset before its next use */
|
||||
if (g_dev_state == 1)
|
||||
g_vk.EndCommandBuffer(g_cmds[0]); /* never submitted; reset before its next use */
|
||||
}
|
||||
@@ -91,6 +91,10 @@ int vkp_ready(void);
|
||||
int vkp_has_window(void);
|
||||
/* 1 once the Vulkan device was lost: nothing is presented any more (the session must restart). */
|
||||
int vkp_device_lost(void);
|
||||
/* Compositor thread only. Submitted reads may still be pending: retain imported
|
||||
* buffer references and withhold DMA-BUF releases until the app process exits.
|
||||
* This terminal state is distinct from genuine device loss. */
|
||||
int vkp_completion_unknown(void);
|
||||
|
||||
// Clear to black, blit the draws in order (first = bottom) and present.
|
||||
// Returns 0 on success, -1 if nothing could be presented (no window yet, etc.).
|
||||
|
||||
Reference in new issue
Block a user