wlroots-hyprland/types/wlr_output_swapchain_manager.c

252 lines
8.0 KiB
C

#include <assert.h>
#include <drm_fourcc.h>
#include <stdlib.h>
#include <string.h>
#include <wlr/backend.h>
#include <wlr/render/allocator.h>
#include <wlr/render/swapchain.h>
#include <wlr/types/wlr_output_swapchain_manager.h>
#include <wlr/util/log.h>
#include "render/drm_format_set.h"
#include "types/wlr_output.h"
struct wlr_output_swapchain_manager_output {
struct wlr_output *output;
// Newly allocated swapchain. Can be NULL if the old swapchain is re-used
// or if the output is disabled.
struct wlr_swapchain *new_swapchain;
// True if the output was included in the last successful call to
// wlr_output_swapchain_manager_prepare().
bool test_success;
// Pending swapchain which will replace the old one when
// wlr_output_swapchain_manager_apply() is called. Can be either a pointer
// to the newly allocated swapchain, or the old swapchain, or NULL.
struct wlr_swapchain *pending_swapchain;
};
void wlr_output_swapchain_manager_init(struct wlr_output_swapchain_manager *manager,
struct wlr_backend *backend) {
*manager = (struct wlr_output_swapchain_manager){
.backend = backend,
};
}
static struct wlr_output_swapchain_manager_output *manager_get_output(
struct wlr_output_swapchain_manager *manager, struct wlr_output *output) {
struct wlr_output_swapchain_manager_output *manager_output;
wl_array_for_each(manager_output, &manager->outputs) {
if (manager_output->output == output) {
return manager_output;
}
}
return NULL;
}
static struct wlr_output_swapchain_manager_output *manager_get_or_add_output(
struct wlr_output_swapchain_manager *manager, struct wlr_output *output) {
struct wlr_output_swapchain_manager_output *manager_output = manager_get_output(manager, output);
if (manager_output != NULL) {
return manager_output;
}
manager_output = wl_array_add(&manager->outputs, sizeof(*manager_output));
if (manager_output == NULL) {
return NULL;
}
*manager_output = (struct wlr_output_swapchain_manager_output){
.output = output,
};
return manager_output;
}
static bool swapchain_is_compatible(struct wlr_swapchain *swapchain,
int width, int height, const struct wlr_drm_format *format) {
if (swapchain == NULL) {
return false;
}
if (swapchain->width != width || swapchain->height != height) {
return false;
}
if (swapchain->format.format != format->format || swapchain->format.len != format->len) {
return false;
}
assert(format->len > 0);
return memcmp(swapchain->format.modifiers, format->modifiers, format->len * sizeof(format->modifiers[0])) == 0;
}
static struct wlr_swapchain *manager_output_get_swapchain(
struct wlr_output_swapchain_manager_output *manager_output,
int width, int height, const struct wlr_drm_format *format) {
struct wlr_output *output = manager_output->output;
if (swapchain_is_compatible(output->swapchain, width, height, format)) {
return output->swapchain;
}
if (swapchain_is_compatible(manager_output->new_swapchain, width, height, format)) {
return manager_output->new_swapchain;
}
struct wlr_swapchain *swapchain = wlr_swapchain_create(output->allocator, width, height, format);
if (swapchain == NULL) {
return NULL;
}
wlr_swapchain_destroy(manager_output->new_swapchain);
manager_output->new_swapchain = swapchain;
return swapchain;
}
static bool manager_output_prepare(struct wlr_output_swapchain_manager_output *manager_output,
struct wlr_output_state *state, bool explicit_modifiers) {
struct wlr_output *output = manager_output->output;
struct wlr_allocator *allocator = output->allocator;
assert(allocator != NULL);
if (!output_pending_enabled(output, state)) {
manager_output->pending_swapchain = NULL;
return true;
}
int width, height;
output_pending_resolution(output, state, &width, &height);
uint32_t fmt = output->render_format;
if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) {
fmt = state->render_format;
}
const struct wlr_drm_format_set *display_formats =
wlr_output_get_primary_formats(output, allocator->buffer_caps);
struct wlr_drm_format format = {0};
if (!output_pick_format(output, display_formats, &format, fmt)) {
return false;
}
if (!explicit_modifiers && (format.len != 1 || format.modifiers[0] != DRM_FORMAT_MOD_LINEAR)) {
if (!wlr_drm_format_has(&format, DRM_FORMAT_MOD_INVALID)) {
wlr_log(WLR_DEBUG, "Implicit modifiers not supported");
wlr_drm_format_finish(&format);
return false;
}
format.len = 0;
if (!wlr_drm_format_add(&format, DRM_FORMAT_MOD_INVALID)) {
wlr_log(WLR_DEBUG, "Failed to add implicit modifier to format");
wlr_drm_format_finish(&format);
return false;
}
}
struct wlr_swapchain *swapchain =
manager_output_get_swapchain(manager_output, width, height, &format);
wlr_drm_format_finish(&format);
if (swapchain == NULL) {
return false;
}
struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain, NULL);
if (buffer == NULL) {
return false;
}
wlr_output_state_set_buffer(state, buffer);
wlr_buffer_unlock(buffer);
manager_output->pending_swapchain = swapchain;
return true;
}
static bool manager_test(struct wlr_output_swapchain_manager *manager,
struct wlr_backend_output_state *states, size_t states_len,
bool explicit_modifiers) {
wlr_log(WLR_DEBUG, "Preparing test commit for %zu outputs with %s modifiers",
states_len, explicit_modifiers ? "explicit": "implicit");
struct wlr_output_swapchain_manager_output *manager_output;
wl_array_for_each(manager_output, &manager->outputs) {
manager_output->test_success = false;
}
for (size_t i = 0; i < states_len; i++) {
struct wlr_backend_output_state *state = &states[i];
struct wlr_output_swapchain_manager_output *manager_output =
manager_get_or_add_output(manager, state->output);
if (manager_output == NULL) {
return false;
}
if (!manager_output_prepare(manager_output, &state->base, explicit_modifiers)) {
return false;
}
}
bool ok = wlr_backend_test(manager->backend, states, states_len);
wlr_log(WLR_DEBUG, "Test commit for %zu outputs %s",
states_len, ok ? "succeeded" : "failed");
if (!ok) {
return false;
}
for (size_t i = 0; i < states_len; i++) {
struct wlr_output_swapchain_manager_output *manager_output =
manager_get_output(manager, states[i].output);
assert(manager_output != NULL);
manager_output->test_success = true;
}
return true;
}
bool wlr_output_swapchain_manager_prepare(struct wlr_output_swapchain_manager *manager,
const struct wlr_backend_output_state *states, size_t states_len) {
bool ok = false;
struct wlr_backend_output_state *pending = malloc(states_len * sizeof(states[0]));
if (pending == NULL) {
return false;
}
for (size_t i = 0; i < states_len; i++) {
pending[i] = states[i];
pending[i].base.buffer = NULL;
}
ok = manager_test(manager, pending, states_len, true);
if (!ok) {
ok = manager_test(manager, pending, states_len, false);
}
for (size_t i = 0; i < states_len; i++) {
wlr_buffer_unlock(pending[i].base.buffer);
}
free(pending);
return ok;
}
struct wlr_swapchain *wlr_output_swapchain_manager_get_swapchain(
struct wlr_output_swapchain_manager *manager, struct wlr_output *output) {
struct wlr_output_swapchain_manager_output *manager_output =
manager_get_output(manager, output);
assert(manager_output != NULL && manager_output->test_success);
return manager_output->pending_swapchain;
}
void wlr_output_swapchain_manager_apply(struct wlr_output_swapchain_manager *manager) {
struct wlr_output_swapchain_manager_output *manager_output;
wl_array_for_each(manager_output, &manager->outputs) {
struct wlr_output *output = manager_output->output;
if (!manager_output->test_success || manager_output->pending_swapchain == output->swapchain) {
continue;
}
wlr_swapchain_destroy(output->swapchain);
output->swapchain = manager_output->new_swapchain;
manager_output->new_swapchain = NULL;
manager_output->test_success = false;
}
}
void wlr_output_swapchain_manager_finish(struct wlr_output_swapchain_manager *manager) {
struct wlr_output_swapchain_manager_output *manager_output;
wl_array_for_each(manager_output, &manager->outputs) {
wlr_swapchain_destroy(manager_output->new_swapchain);
}
wl_array_release(&manager->outputs);
}