From 82e786490343980d1453b4600bf08707e20ee1fb Mon Sep 17 00:00:00 2001 From: John Lorentzson Date: Sat, 26 Apr 2025 14:44:58 +0200 Subject: [PATCH] Add double-buffering to avoid screen flashes with XShm When using the X shared memory extension, the X server may start reading the buffer at a time when the client is writing to it. This can result in the next unfinished frame flashing onto the screen. The canonical way to solve this is to wait for an event to come in signaling that it's safe to continue, but that would add considerable latency to rendering. Instead, we bet on the fact that it won't take all that long, and so we double-buffer. We have two buffers shared with the X server and swap them at the end of each frame. We only draw to the one not being displayed. That way the X server should always have time to read out a full finished frame. --- .../Rendering/Window/Software/X11.cpp | 90 ++++++++++++------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/src/Backends/Rendering/Window/Software/X11.cpp b/src/Backends/Rendering/Window/Software/X11.cpp index e5f8f0dc..c2e1bb64 100644 --- a/src/Backends/Rendering/Window/Software/X11.cpp +++ b/src/Backends/Rendering/Window/Software/X11.cpp @@ -13,15 +13,22 @@ #include static Window window; + +// Framebuffer is the image that the game gets to write to static XImage* framebufferImage; -static unsigned char* framebufferPixels; +static char *framebufferPixels; +static XShmSegmentInfo framebufferSegInfo; +// Display is the one X gets to read from, we swap them every frame +static XImage* displayImage; +static char *displayPixels; +static XShmSegmentInfo displaySegInfo; + static int framebufferPitch; static char* xfriendlyFB; static GC gc; static int screenWidth; static int screenHeight; static bool useShmP = false; -static XShmSegmentInfo seginfo; extern Display* xDisplay; extern Visual* xVisual; @@ -45,22 +52,34 @@ void createFramebuffer() { int depth = DefaultDepth(xDisplay, DefaultScreen(xDisplay)); // Setting up the framebuffer - xfriendlyFB = NULL; + displayPixels = NULL; if(useShmP) { + displayImage = XShmCreateImage(xDisplay, xVisual, + depth, ZPixmap, NULL, + &displaySegInfo, screenWidth, screenHeight); + displaySegInfo.shmid = shmget(IPC_PRIVATE, + displayImage->bytes_per_line * displayImage->height, + IPC_CREAT | 0777); + displaySegInfo.shmaddr = displayPixels = displayImage->data = + (char*)shmat(displaySegInfo.shmid, NULL, 0); + XShmAttach(xDisplay, &displaySegInfo); + framebufferImage = XShmCreateImage(xDisplay, xVisual, depth, ZPixmap, NULL, - &seginfo, screenWidth, screenHeight); - seginfo.shmid = shmget(IPC_PRIVATE, - framebufferImage->bytes_per_line * framebufferImage->height, - IPC_CREAT | 0777); - seginfo.shmaddr = xfriendlyFB = framebufferImage->data = (char*)shmat(seginfo.shmid, NULL, 0); - XShmAttach(xDisplay, &seginfo); + &framebufferSegInfo, screenWidth, screenHeight); + framebufferSegInfo.shmid = shmget(IPC_PRIVATE, + framebufferImage->bytes_per_line * displayImage->height, + IPC_CREAT | 0777); + framebufferSegInfo.shmaddr = framebufferPixels = framebufferImage->data = + (char*)shmat(framebufferSegInfo.shmid, NULL, 0); + XShmAttach(xDisplay, &framebufferSegInfo); } else { - xfriendlyFB = (char*)malloc(screenWidth * screenHeight * 4); - framebufferImage = XCreateImage(xDisplay, xVisual, + displayPixels = (char*)malloc(screenWidth * screenHeight * 4); + framebufferPixels = displayPixels; + displayImage = XCreateImage(xDisplay, xVisual, depth, ZPixmap, 0, - (char*)xfriendlyFB, screenWidth, screenHeight, + (char*)displayPixels, screenWidth, screenHeight, 32, 0); } } @@ -91,12 +110,12 @@ bool WindowBackend_Software_CreateWindow(const char *window_title, size_t screen XSetWMProtocols(xDisplay, window, &WM_DELETE_WINDOW, 1); createFramebuffer(); - if(checkImageOK(framebufferImage) == false) { + if(checkImageOK(displayImage) == false) { Backend_PrintError("Something's off with the framebuffer.\n"); return false; } - if(framebufferImage == 0) { + if(displayImage == 0) { return false; } @@ -108,16 +127,24 @@ unsigned char* WindowBackend_Software_GetFramebuffer(size_t *pitch) { *pitch = framebufferPitch; - return (unsigned char*)xfriendlyFB; + return (unsigned char*)framebufferPixels; } void WindowBackend_Software_DestroyWindow(void) { if(useShmP) { - XShmDetach(xDisplay, &seginfo); + XShmDetach(xDisplay, &displaySegInfo); + XShmDetach(xDisplay, &framebufferSegInfo); + + XDestroyImage(displayImage); XDestroyImage(framebufferImage); - shmdt(seginfo.shmaddr); - shmctl(seginfo.shmid, IPC_RMID, 0); + + shmdt(displaySegInfo.shmaddr); + shmctl(displaySegInfo.shmid, IPC_RMID, 0); + + shmdt(framebufferSegInfo.shmaddr); + shmctl(framebufferSegInfo.shmid, IPC_RMID, 0); } + XDestroyWindow(xDisplay, window); } @@ -129,22 +156,19 @@ void WindowBackend_Software_HandleWindowResize(size_t width, size_t height) { } void WindowBackend_Software_Display(void) { - // Convert Cave Story's framebuffer format to the Sun's - // This is the exact reverse of how it is on modern - // computers. Yup, it's endianess. - // int srci = 0; - // for(int i = 0; i < screenWidth * screenHeight * 4; i += 4) { - // int src; - // xfriendlyFB[i + 3] = 0; // UNUSED - // xfriendlyFB[i + 2] = framebufferPixels[srci + 0]; // BLUE - // xfriendlyFB[i + 1] = framebufferPixels[srci + 1]; // GREEN - // xfriendlyFB[i + 0] = framebufferPixels[srci + 2]; // RED - // srci += 4; - // } - //memcpy(xfriendlyFB, framebufferPixels, screenWidth * screenHeight * 4); if(useShmP) { - XShmPutImage(xDisplay, window, gc, framebufferImage, 0, 0, 0, 0, screenWidth, screenHeight, 0); + XShmPutImage(xDisplay, window, gc, displayImage, 0, 0, 0, 0, screenWidth, screenHeight, 0); + + // Swap images + XImage* tempI = displayImage; + displayImage = framebufferImage; + framebufferImage = tempI; + + // Swap pixel pointers + char* tempP = displayPixels; + displayPixels = framebufferPixels; + framebufferPixels = tempP; } else { - XPutImage(xDisplay, window, gc, framebufferImage, 0, 0, 0, 0, screenWidth, screenHeight); + XPutImage(xDisplay, window, gc, displayImage, 0, 0, 0, 0, screenWidth, screenHeight); } }