Skip to content

Commit

Permalink
kmsdrm: use a black dumb buffer for keeping the PRIMARY PLANE occupie…
Browse files Browse the repository at this point in the history
…d when we destroy the KMS buffers, instead of using the TTY buffer, to avoid flickering.
  • Loading branch information
vanfanel committed Sep 7, 2020
1 parent 4d1c2a1 commit 0f807fd
Show file tree
Hide file tree
Showing 3 changed files with 294 additions and 67 deletions.
1 change: 1 addition & 0 deletions src/video/kmsdrm/SDL_kmsdrmsym.h
Expand Up @@ -41,6 +41,7 @@ SDL_KMSDRM_SYM(void,drmModeFreeCrtc,(drmModeCrtcPtr ptr))
SDL_KMSDRM_SYM(void,drmModeFreeConnector,(drmModeConnectorPtr ptr))
SDL_KMSDRM_SYM(void,drmModeFreeEncoder,(drmModeEncoderPtr ptr))
SDL_KMSDRM_SYM(int,drmGetCap,(int fd, uint64_t capability, uint64_t *value))
SDL_KMSDRM_SYM(int,drmIoctl,(int fd, unsigned long request, void *arg))
SDL_KMSDRM_SYM(drmModeResPtr,drmModeGetResources,(int fd))

SDL_KMSDRM_SYM(int,drmModeAddFB,(int fd, uint32_t width, uint32_t height, uint8_t depth,
Expand Down
275 changes: 230 additions & 45 deletions src/video/kmsdrm/SDL_kmsdrmvideo.c
Expand Up @@ -146,6 +146,148 @@ get_driindex(void)
return -ENOENT;
}

/**********************/
/* DUMB BUFFER Block. */
/**********************/

/* Create a dumb buffer, mmap the dumb buffer and fill it with pixels, */
/* then create a KMS framebuffer wrapping the dumb buffer. */
static dumb_buffer *KMSDRM_CreateDumbBuffer(_THIS)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);

dumb_buffer *ret = calloc(1, sizeof(*ret));
struct drm_mode_create_dumb create;
struct drm_mode_map_dumb map;
struct drm_mode_destroy_dumb destroy;

/*
* The create ioctl uses the combination of depth and bpp to infer
* a format; 24/32 refers to DRM_FORMAT_XRGB8888 as defined in
* the drm_fourcc.h header. These arguments are the same as given
* to drmModeAddFB, which has since been superseded by
* drmModeAddFB2 as the latter takes an explicit format token.
*
* We only specify these arguments; the driver calculates the
* pitch (also known as stride or row length) and total buffer size
* for us, also returning us the GEM handle.
*/
create = (struct drm_mode_create_dumb) {
.width = dispdata->mode.hdisplay,
.height = dispdata->mode.vdisplay,
.bpp = 32,
};

if (KMSDRM_drmIoctl(viddata->drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create)) {
SDL_SetError("failed to create dumb buffer\n");
goto err;
}

ret->gem_handles[0] = create.handle;
ret->format = DRM_FORMAT_XRGB8888;
ret->modifier = DRM_FORMAT_MOD_LINEAR;
ret->width = create.width;
ret->height = create.height;
ret->pitches[0] = create.pitch;

/*
* In order to map the buffer, we call an ioctl specific to the buffer
* type, which returns us a fake offset to use with the mmap syscall.
* mmap itself then works as you expect.
*
* Note this means it is not possible to map arbitrary offsets of
* buffers without specifically requesting it from the kernel.
*/
map = (struct drm_mode_map_dumb) {
.handle = ret->gem_handles[0],
};
if (KMSDRM_drmIoctl(viddata->drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map)) {
SDL_SetError("failed to get mmap offset for the dumb buffer.");
goto err_dumb;
}

ret->dumb.mem = mmap(NULL, create.size, PROT_WRITE, MAP_SHARED,
viddata->drm_fd, map.offset);
if (ret->dumb.mem == MAP_FAILED) {
SDL_SetError("failed to get mmap offset for the dumb buffer.");
goto err_dumb;
}
ret->dumb.size = create.size;

return ret;

err_dumb:
destroy = (struct drm_mode_destroy_dumb) { .handle = create.handle };
KMSDRM_drmIoctl(viddata->drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
err:
SDL_free(ret);
return NULL;
}

static void
KMSDRM_DestroyDumbBuffer(_THIS, dumb_buffer *buffer)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);

struct drm_mode_destroy_dumb destroy = {
.handle = buffer->gem_handles[0],
};

KMSDRM_drmModeRmFB(viddata->drm_fd, buffer->fb_id);

munmap(buffer->dumb.mem, buffer->dumb.size);
KMSDRM_drmIoctl(viddata->drm_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
free(buffer);
}

/* Using the CPU mapping, fill the dumb buffer with black pixels. */
static void
KMSDRM_FillDumbBuffer(dumb_buffer *buffer)
{
for (unsigned int y = 0; y < buffer->height; y++) {
uint32_t *pix = (uint32_t *) ((uint8_t *) buffer->dumb.mem + (y * buffer->pitches[0]));
for (unsigned int x = 0; x < buffer->width; x++) {
*pix++ = (0x00000000);
}
}
}

static dumb_buffer *KMSDRM_CreateBuffer(_THIS)
{
dumb_buffer *ret;
int err;

SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);

ret = KMSDRM_CreateDumbBuffer(_this);

if (!ret)
return NULL;

/*
* Wrap our GEM buffer in a KMS framebuffer, so we can then attach it
* to a plane. Here's where we get out fb_id!
*/
err = KMSDRM_drmModeAddFB2(viddata->drm_fd, ret->width, ret->height,
ret->format, ret->gem_handles, ret->pitches,
ret->offsets, &ret->fb_id, 0);

if (err != 0 || ret->fb_id == 0) {
SDL_SetError("Failed AddFB2 on dumb buffer\n");
goto err;
}
return ret;

err:
KMSDRM_DestroyDumbBuffer(_this, ret);
return NULL;
}

/***************************/
/* DUMB BUFFER Block ends. */
/***************************/

/*********************************/
/* Atomic helper functions block */
/*********************************/
Expand Down Expand Up @@ -388,7 +530,7 @@ static int get_plane_id(_THIS, uint32_t plane_type)
return ret;
}

/* Setup cursor plane and it's props. */
/* Setup a plane and it's props. */
int
setup_plane(_THIS, struct plane **plane, uint32_t plane_type)
{
Expand Down Expand Up @@ -516,7 +658,7 @@ int drm_atomic_commit(_THIS, SDL_bool blocking)
if (ret) {
SDL_SetError("Atomic commit failed, returned %d.", ret);
/* Uncomment this for fast-debugging */
// printf("ATOMIC COMMIT FAILED: %d.\n", ret);
//printf("ATOMIC COMMIT FAILED: %d.\n", ret);
goto out;
}

Expand Down Expand Up @@ -772,16 +914,9 @@ KMSDRM_DestroySurfaces(_THIS, SDL_Window *window)
/* it's using. */
/********************************************************************/

#if AMDGPU_COMPAT
/************************************************************************/
/* We can't do the usual CRTC_ID+FB_ID to 0 with AMDGPU, because */
/* the driver never recovers from the CONNECTOR and CRTC disconnection.*/
/* The with this solution is that crtc->buffer_id is not guaranteed */
/* to be there... */
/************************************************************************/
plane_info.plane = dispdata->display_plane;
plane_info.crtc_id = dispdata->crtc->crtc->crtc_id;
plane_info.fb_id = dispdata->crtc->crtc->buffer_id;
plane_info.fb_id = dispdata->dumb_buffer->fb_id;
plane_info.src_w = dispdata->mode.hdisplay;
plane_info.src_h = dispdata->mode.vdisplay;
plane_info.crtc_w = dispdata->mode.hdisplay;
Expand All @@ -793,32 +928,6 @@ KMSDRM_DestroySurfaces(_THIS, SDL_Window *window)
if (drm_atomic_commit(_this, SDL_TRUE)) {
SDL_SetError("Failed to issue atomic commit on DestroyWindow().");
}
#else
/*********************************************************************************/
/* Disconnect the CONNECTOR from the CRTC (several connectors can read a CRTC), */
/* deactivate the CRTC, and set the PRIMARY PLANE props CRTC_ID and FB_ID to 0. */
/* We have to do this before setting the PLANE CRTC_ID and FB_ID to 0 because */
/* there can be no active CRTC without an active PLANE. */
/* We can leave all like this if we are exiting the program after the window */
/* destruction, or things will be re-connected again on SwapWindow(), if needed. */
/*********************************************************************************/
if (add_connector_property(dispdata->atomic_req, dispdata->connector , "CRTC_ID", 0) < 0)
SDL_SetError("Failed to set CONNECTOR prop CRTC_ID to zero before buffer destruction");

if (add_crtc_property(dispdata->atomic_req, dispdata->crtc , "ACTIVE", 0) < 0)
SDL_SetError("Failed to set CRTC prop ACTIVE to zero before buffer destruction");

/* Since we initialize plane_info to all zeros, ALL PRIMARY PLANE props are set to 0 with this,
including FB_ID and CRTC_ID. Not all drivers like FB_ID and CRTC_ID to 0 yet. */
plane_info.plane = dispdata->display_plane;

drm_atomic_set_plane_props(&plane_info);

/* Issue blocking atomic commit. */
if (drm_atomic_commit(_this, SDL_TRUE)) {
SDL_SetError("Failed to issue atomic commit on window surfaces and buffers destruction.");
}
#endif

/****************************************************************************/
/* BLOCK 2: We can finally destroy the window GBM and EGL surfaces, and */
Expand Down Expand Up @@ -1006,6 +1115,7 @@ KMSDRM_VideoInit(_THIS)
dispdata->kms_fence = NULL;
dispdata->gpu_fence = NULL;
dispdata->kms_out_fence_fd = -1;
dispdata->dumb_buffer = NULL;

if (!dispdata) {
return SDL_OutOfMemory();
Expand Down Expand Up @@ -1185,7 +1295,6 @@ KMSDRM_VideoInit(_THIS)
if (ret) {
ret = SDL_SetError("can't find suitable display plane.");
goto cleanup;

}

/* Get CRTC properties */
Expand All @@ -1212,6 +1321,17 @@ KMSDRM_VideoInit(_THIS)
dispdata->connector->props->props[i]);
}

/* Create aux dumb buffer. It's only useful to keep the PRIMARY PLANE occupied
when we destroy the GBM surface and it's KMS buffers, so not being able to
create it is not fatal. */
dispdata->dumb_buffer = KMSDRM_CreateBuffer(_this);
if (!dispdata->dumb_buffer) {
ret = SDL_SetError("can't find suitable display plane.");
} else {
/* Fill the dumb buffer with black pixels. */
KMSDRM_FillDumbBuffer(dispdata->dumb_buffer);
}

/*********************/
/* Atomic block ends */
/*********************/
Expand Down Expand Up @@ -1259,13 +1379,80 @@ KMSDRM_VideoQuit(_THIS)
{
SDL_VideoData *viddata = ((SDL_VideoData *)_this->driverdata);
SDL_DisplayData *dispdata = (SDL_DisplayData *)SDL_GetDisplayDriverData(0);
KMSDRM_PlaneInfo plane_info = {0};

/*****************************************************************/
/* */
/* BLOCK to safely destroy the DUMB BUFFER. */
/* */
/* Since the program should already have called DestroyWindow() */
/* on all the windows by now, there's no need to destroy the */
/* GBM/EGL surfaces and buffers of the windows here: they have */
/* already been destroyed, and the PRIMARY PLANE is using the */
/* DUMB BUFFER. BUT the DUMB BUFFER we use to keep the PRIMARY */
/* PLANE occupied when we do DestroySurfaces calls is going to */
/* be destroyed one way or another when the program quits, so */
/* to avoid the kernel disabling the CRTC when it detects the */
/* deletion of a buffer that IS IN USE BY THE PRIMARY PLANE, */
/* we do one of these: */
/* */
/* -In AMDGPU, where manually disabling the CRTC and */
/* disconnecting the CONNECTOR from the CRTC is an */
/* unrecoverable situation, so we point the PRIMARY PLANE to */
/* the original TTY buffer (not guaranteed to be there for us!) */
/* and then destroy the DUMB BUFFER). */
/* */
/* -In other drivers, we disconnect the CONNECTOR from the CRTC */
/* (remember: several connectors can read a CRTC), deactivate */
/* the CRTC, and set the PRIMARY PLANE props CRTC_ID and FB_ID */
/* to 0. Then we destroy the DUMB BUFFER. */
/* We can leave all like this if we are exiting the program: */
/* FBCON or whatever will reconfigure things as it needs. */
/* */
/*****************************************************************/

#if AMDGPU_COMPAT

/****************************************************************/
/* Since the program should already have called DestroyWindow() */
/* on all the windows by now, there's no need to destroy the */
/* GBM/EGL surfaces and buffers of the windows here: they have */
/* already been destroyed. */
/****************************************************************/
plane_info.plane = dispdata->display_plane;
plane_info.crtc_id = dispdata->crtc->crtc->crtc_id;
plane_info.fb_id = dispdata->crtc->crtc->buffer_id;
plane_info.src_w = dispdata->mode.hdisplay;
plane_info.src_h = dispdata->mode.vdisplay;
plane_info.crtc_w = dispdata->mode.hdisplay;
plane_info.crtc_h = dispdata->mode.vdisplay;

drm_atomic_set_plane_props(&plane_info);

#else

/*********************************************************************************/
/*********************************************************************************/
if (add_connector_property(dispdata->atomic_req, dispdata->connector , "CRTC_ID", 0) < 0)
SDL_SetError("Failed to set CONNECTOR prop CRTC_ID to zero before buffer destruction");

if (add_crtc_property(dispdata->atomic_req, dispdata->crtc , "ACTIVE", 0) < 0)
SDL_SetError("Failed to set CRTC prop ACTIVE to zero before buffer destruction");

/* Since we initialize plane_info to all zeros, ALL PRIMARY PLANE props are set to 0 with this,
including FB_ID and CRTC_ID. Not all drivers like FB_ID and CRTC_ID to 0 yet. */
plane_info.plane = dispdata->display_plane;

drm_atomic_set_plane_props(&plane_info);

#endif

/* Issue blocking atomic commit. */
if (drm_atomic_commit(_this, SDL_TRUE)) {
SDL_SetError("Failed to issue atomic commit on DestroyWindow().");
}

/* Destroy the DUMB buffer, now that it's not being
used anymore by the PRIMARY PLANE. */
KMSDRM_DestroyDumbBuffer(_this, dispdata->dumb_buffer);

/***************/
/* BLOCK ENDS. */
/***************/

/* Clear out the window list */
SDL_free(viddata->windows);
Expand Down Expand Up @@ -1338,7 +1525,6 @@ KMSDRM_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
}
#endif

#if 1
/* We are NOT really changing the physical display mode, but using
the PRIMARY PLANE and CRTC to scale as we please. But we need that SDL
has knowledge of the video modes we are going to use for fullscreen
Expand Down Expand Up @@ -1370,7 +1556,6 @@ KMSDRM_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
}
}
}
#endif

int
KMSDRM_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
Expand Down

0 comments on commit 0f807fd

Please sign in to comment.