You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

286 lines
7.9 KiB

#include <assert.h>
#include <drm_fourcc.h>
#include <stdlib.h>
#include <wlr/interfaces/wlr_output.h>
#include <wlr/render/interface.h>
#include <wlr/render/swapchain.h>
#include <wlr/util/log.h>
#include <xf86drm.h>
#include "backend/backend.h"
#include "render/allocator/allocator.h"
#include "render/drm_format_set.h"
#include "render/wlr_renderer.h"
#include "render/pixel_format.h"
#include "types/wlr_output.h"
bool wlr_output_init_render(struct wlr_output *output,
struct wlr_allocator *allocator, struct wlr_renderer *renderer) {
assert(allocator != NULL && renderer != NULL);
assert(output->back_buffer == NULL);
uint32_t backend_caps = backend_get_buffer_caps(output->backend);
uint32_t renderer_caps = renderer_get_render_buffer_caps(renderer);
if (!(backend_caps & allocator->buffer_caps)) {
wlr_log(WLR_ERROR, "output backend and allocator buffer capabilities "
"don't match");
return false;
} else if (!(renderer_caps & allocator->buffer_caps)) {
wlr_log(WLR_ERROR, "renderer and allocator buffer capabilities "
"don't match");
return false;
}
wlr_swapchain_destroy(output->swapchain);
output->swapchain = NULL;
wlr_swapchain_destroy(output->cursor_swapchain);
output->cursor_swapchain = NULL;
output->allocator = allocator;
output->renderer = renderer;
return true;
}
void output_clear_back_buffer(struct wlr_output *output) {
if (output->back_buffer == NULL) {
return;
}
struct wlr_renderer *renderer = output->renderer;
assert(renderer != NULL);
renderer_bind_buffer(renderer, NULL);
wlr_buffer_unlock(output->back_buffer);
output->back_buffer = NULL;
}
bool wlr_output_attach_render(struct wlr_output *output, int *buffer_age) {
assert(output->back_buffer == NULL);
if (!wlr_output_configure_primary_swapchain(output, &output->pending,
&output->swapchain)) {
return false;
}
struct wlr_renderer *renderer = output->renderer;
assert(renderer != NULL);
struct wlr_buffer *buffer =
wlr_swapchain_acquire(output->swapchain, buffer_age);
if (buffer == NULL) {
return false;
}
if (!renderer_bind_buffer(renderer, buffer)) {
wlr_buffer_unlock(buffer);
return false;
}
output->back_buffer = buffer;
return true;
}
static struct wlr_buffer *output_acquire_empty_buffer(struct wlr_output *output,
const struct wlr_output_state *state) {
assert(!(state->committed & WLR_OUTPUT_STATE_BUFFER));
// wlr_output_configure_primary_swapchain() function will call
// wlr_output_test_state(), which can call us again. This is dangerous: we
// risk infinite recursion. However, a buffer will always be supplied in
// wlr_output_test_state(), which will prevent us from being called.
if (!wlr_output_configure_primary_swapchain(output, state,
&output->swapchain)) {
return false;
}
struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, NULL);
if (buffer == NULL) {
return false;
}
struct wlr_render_pass *pass =
wlr_renderer_begin_buffer_pass(output->renderer, buffer, NULL);
if (pass == NULL) {
wlr_buffer_unlock(buffer);
return NULL;
}
wlr_render_pass_add_rect(pass, &(struct wlr_render_rect_options){
.color = { 0, 0, 0, 0 },
.blend_mode = WLR_RENDER_BLEND_MODE_NONE,
});
if (!wlr_render_pass_submit(pass)) {
wlr_buffer_unlock(buffer);
return NULL;
}
return buffer;
}
// This function may attach a new, empty buffer if necessary.
// If so, the new_back_buffer out parameter will be set to true.
bool output_ensure_buffer(struct wlr_output *output,
struct wlr_output_state *state, bool *new_buffer) {
assert(*new_buffer == false);
// If we already have a buffer, we don't need to allocate a new one
if (state->committed & WLR_OUTPUT_STATE_BUFFER) {
return true;
}
// If the compositor hasn't called wlr_output_init_render(), they will use
// their own logic to attach buffers
if (output->renderer == NULL) {
return true;
}
output: don't attach buffer on first commit if disabled In output_ensure_buffer() we create a swapchain and attach an empty buffer to the output if necessary. We do that during the first commit. This is fine when the first commit enables the output, however this breaks when the first commit disables the output. A commit which disables an output and has a buffer attached is invalid (see output_basic_test()), and makes the DRM backend crash: 00:00:00.780 [wlr] [backend/drm/drm.c:622] connector eDP-1: Turning off ../subprojects/wlroots/backend/drm/drm.c:652:44: runtime error: member access within null pointer of type 'struct wlr_drm_crtc' AddressSanitizer:DEADLYSIGNAL ================================================================= ==2524==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7f22e894afc1 bp 0x7ffe1d57c550 sp 0x7ffe1d57c420 T0) ==2524==The signal is caused by a READ memory access. ==2524==Hint: address points to the zero page. #0 0x7f22e894afc1 in drm_connector_commit_state ../subprojects/wlroots/backend/drm/drm.c:652 #1 0x7f22e894b1f5 in drm_connector_commit ../subprojects/wlroots/backend/drm/drm.c:674 #2 0x7f22e89e8da9 in wlr_output_commit_state ../subprojects/wlroots/types/output/output.c:756 #3 0x555ab325624d in apply_output_config ../sway/config/output.c:517 #4 0x555ab31a1aa1 in handle_new_output ../sway/desktop/output.c:974 #5 0x7f22e9272f6d in wl_signal_emit_mutable (/usr/lib/libwayland-server.so.0+0x9f6d) #6 0x7f22e899b012 in new_output_reemit ../subprojects/wlroots/backend/multi/backend.c:161 #7 0x7f22e9272f6d in wl_signal_emit_mutable (/usr/lib/libwayland-server.so.0+0x9f6d) #8 0x7f22e895a153 in scan_drm_connectors ../subprojects/wlroots/backend/drm/drm.c:1488 #9 0x7f22e893c2e4 in backend_start ../subprojects/wlroots/backend/drm/backend.c:24 #10 0x7f22e892ed00 in wlr_backend_start ../subprojects/wlroots/backend/backend.c:56 #11 0x7f22e8999b83 in multi_backend_start ../subprojects/wlroots/backend/multi/backend.c:31 #12 0x7f22e892ed00 in wlr_backend_start ../subprojects/wlroots/backend/backend.c:56 #13 0x555ab317d5cc in server_start ../sway/server.c:316 #14 0x555ab317748d in main ../sway/main.c:400 #15 0x7f22e783c28f (/usr/lib/libc.so.6+0x2328f) #16 0x7f22e783c349 in __libc_start_main (/usr/lib/libc.so.6+0x23349) #17 0x555ab3134c84 in _start (/home/simon/src/sway/build/sway/sway+0x377c84) Fixes: 3be6658ee7b6 ("output: allocate swapchain on first commit") Closes: https://github.com/swaywm/sway/issues/7373
2 years ago
bool enabled = output->enabled;
if (state->committed & WLR_OUTPUT_STATE_ENABLED) {
enabled = state->enabled;
}
// If we're lighting up an output or changing its mode, make sure to
// provide a new buffer
bool needs_new_buffer = false;
if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && state->enabled) {
needs_new_buffer = true;
}
if (state->committed & WLR_OUTPUT_STATE_MODE) {
needs_new_buffer = true;
}
if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) {
needs_new_buffer = true;
}
output: don't attach buffer on first commit if disabled In output_ensure_buffer() we create a swapchain and attach an empty buffer to the output if necessary. We do that during the first commit. This is fine when the first commit enables the output, however this breaks when the first commit disables the output. A commit which disables an output and has a buffer attached is invalid (see output_basic_test()), and makes the DRM backend crash: 00:00:00.780 [wlr] [backend/drm/drm.c:622] connector eDP-1: Turning off ../subprojects/wlroots/backend/drm/drm.c:652:44: runtime error: member access within null pointer of type 'struct wlr_drm_crtc' AddressSanitizer:DEADLYSIGNAL ================================================================= ==2524==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7f22e894afc1 bp 0x7ffe1d57c550 sp 0x7ffe1d57c420 T0) ==2524==The signal is caused by a READ memory access. ==2524==Hint: address points to the zero page. #0 0x7f22e894afc1 in drm_connector_commit_state ../subprojects/wlroots/backend/drm/drm.c:652 #1 0x7f22e894b1f5 in drm_connector_commit ../subprojects/wlroots/backend/drm/drm.c:674 #2 0x7f22e89e8da9 in wlr_output_commit_state ../subprojects/wlroots/types/output/output.c:756 #3 0x555ab325624d in apply_output_config ../sway/config/output.c:517 #4 0x555ab31a1aa1 in handle_new_output ../sway/desktop/output.c:974 #5 0x7f22e9272f6d in wl_signal_emit_mutable (/usr/lib/libwayland-server.so.0+0x9f6d) #6 0x7f22e899b012 in new_output_reemit ../subprojects/wlroots/backend/multi/backend.c:161 #7 0x7f22e9272f6d in wl_signal_emit_mutable (/usr/lib/libwayland-server.so.0+0x9f6d) #8 0x7f22e895a153 in scan_drm_connectors ../subprojects/wlroots/backend/drm/drm.c:1488 #9 0x7f22e893c2e4 in backend_start ../subprojects/wlroots/backend/drm/backend.c:24 #10 0x7f22e892ed00 in wlr_backend_start ../subprojects/wlroots/backend/backend.c:56 #11 0x7f22e8999b83 in multi_backend_start ../subprojects/wlroots/backend/multi/backend.c:31 #12 0x7f22e892ed00 in wlr_backend_start ../subprojects/wlroots/backend/backend.c:56 #13 0x555ab317d5cc in server_start ../sway/server.c:316 #14 0x555ab317748d in main ../sway/main.c:400 #15 0x7f22e783c28f (/usr/lib/libc.so.6+0x2328f) #16 0x7f22e783c349 in __libc_start_main (/usr/lib/libc.so.6+0x23349) #17 0x555ab3134c84 in _start (/home/simon/src/sway/build/sway/sway+0x377c84) Fixes: 3be6658ee7b6 ("output: allocate swapchain on first commit") Closes: https://github.com/swaywm/sway/issues/7373
2 years ago
if (state->allow_artifacts && output->commit_seq == 0 && enabled) {
// On first commit, require a new buffer if the compositor called a
// mode-setting function, even if the mode won't change. This makes it
// so the swapchain is created now.
needs_new_buffer = true;
}
if (!needs_new_buffer) {
return true;
}
wlr_log(WLR_DEBUG, "Attaching empty buffer to output for modeset");
struct wlr_buffer *buffer = output_acquire_empty_buffer(output, state);
if (buffer == NULL) {
return false;
}
*new_buffer = true;
wlr_output_state_set_buffer(state, buffer);
wlr_buffer_unlock(buffer);
return true;
}
void wlr_output_lock_attach_render(struct wlr_output *output, bool lock) {
if (lock) {
++output->attach_render_locks;
} else {
assert(output->attach_render_locks > 0);
--output->attach_render_locks;
}
wlr_log(WLR_DEBUG, "%s direct scan-out on output '%s' (locks: %d)",
lock ? "Disabling" : "Enabling", output->name,
output->attach_render_locks);
}
bool output_pick_format(struct wlr_output *output,
const struct wlr_drm_format_set *display_formats,
struct wlr_drm_format *format, uint32_t fmt) {
struct wlr_renderer *renderer = output->renderer;
struct wlr_allocator *allocator = output->allocator;
assert(renderer != NULL && allocator != NULL);
const struct wlr_drm_format_set *render_formats =
wlr_renderer_get_render_formats(renderer);
if (render_formats == NULL) {
wlr_log(WLR_ERROR, "Failed to get render formats");
return false;
}
const struct wlr_drm_format *render_format =
wlr_drm_format_set_get(render_formats, fmt);
if (render_format == NULL) {
wlr_log(WLR_DEBUG, "Renderer doesn't support format 0x%"PRIX32, fmt);
return false;
}
if (display_formats != NULL) {
const struct wlr_drm_format *display_format =
wlr_drm_format_set_get(display_formats, fmt);
if (display_format == NULL) {
wlr_log(WLR_DEBUG, "Output doesn't support format 0x%"PRIX32, fmt);
return false;
}
if (!wlr_drm_format_intersect(format, display_format, render_format)) {
wlr_log(WLR_DEBUG, "Failed to intersect display and render "
"modifiers for format 0x%"PRIX32 " on output %s",
fmt, output->name);
return false;
}
} else {
// The output can display any format
if (!wlr_drm_format_copy(format, render_format)) {
return false;
}
}
if (format->len == 0) {
wlr_drm_format_finish(format);
wlr_log(WLR_DEBUG, "Failed to pick output format");
return false;
}
return true;
}
uint32_t wlr_output_preferred_read_format(struct wlr_output *output) {
struct wlr_renderer *renderer = output->renderer;
assert(renderer != NULL);
if (!renderer->impl->preferred_read_format || !renderer->impl->read_pixels) {
return DRM_FORMAT_INVALID;
}
if (!wlr_output_attach_render(output, NULL)) {
return false;
}
uint32_t fmt = renderer->impl->preferred_read_format(renderer);
output_clear_back_buffer(output);
return fmt;
}
struct wlr_render_pass *wlr_output_begin_render_pass(struct wlr_output *output,
struct wlr_output_state *state, int *buffer_age, struct wlr_render_timer *timer) {
if (!wlr_output_configure_primary_swapchain(output, state, &output->swapchain)) {
return NULL;
}
struct wlr_buffer *buffer = wlr_swapchain_acquire(output->swapchain, buffer_age);
if (buffer == NULL) {
return NULL;
}
struct wlr_renderer *renderer = output->renderer;
assert(renderer != NULL);
struct wlr_render_pass *pass = wlr_renderer_begin_buffer_pass(renderer, buffer,
&(struct wlr_buffer_pass_options){
.timer = timer,
});
if (pass == NULL) {
return NULL;
}
wlr_output_state_set_buffer(state, buffer);
wlr_buffer_unlock(buffer);
return pass;
}