Merge #561 into the non-Adreno test branch

This commit is contained in:
The412Banner committed 2026-10-12 18:33:17 -04:00
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;
+39 -17
View File
@@ -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();
}
+63 -16
View File
@@ -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.).