mmozeiko
If you want to do just Xlib software rendering without OpenGL, then keep image in your memory. Just plain bytes. Update them as you wish and then push image to Xlib for display (XCreateImage) and then do the image drawing to window (XPutImage). Performance won't be great, but that's the best you can do. No need to double buffer, because drawing image will be the process that transfers your "background" image from memory to window for displaying it.
joe513
Some of the code here might help you get started. He has some pretty clean code that is easy to read.
https://github.com/vurtun/nuklear/blob/master/demo/x11/nuklear_xlib.h
insofaras
I've recently created a small software rendering engine on Linux here: https://github.com/baines/halcyon
It uses a combination of XShmPutImage and the XPresent extension for vsync.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | XShmSegmentInfo segmentInfo; XImage* image; Pixmap pixelMap; Window window; XVisualInfo visualInfo; Display* display; Atom atomClose; void* buffer; // Init display = XOpenDisplay(NO_POINTER); ASSERT(display); ASSERT(XShmQueryExtension(display)); ASSERT(XMatchVisualInfo(display, DefaultScreen(display), 24, TrueColor, &visualInfo)); u32 width = 1024; u32 height = 768; image = XShmCreateImage( display, visualInfo.visual, visualInfo.depth, ZPixmap, NO_POINTER, &segmentInfo, width, height); // Allocate image memory segmentInfo.shmid = shmget( IPC_PRIVATE, image->bytes_per_line * image->height, IPC_CREAT | 0777); segmentInfo.readOnly = false; // Attach memory to this process segmentInfo.shmaddr = (char*)(shmat(segmentInfo.shmid, 0, 0)); image->data = segmentInfo.shmaddr; buffer = image->data; ASSERT(XShmAttach(display, &segmentInfo)); // Mark for removal after process end shmctl(segmentInfo.shmid, IPC_RMID, NO_POINTER); // Window XSetWindowAttributes attribs; attribs.background_pixmap = BlackPixel(display, visualInfo.screen), attribs.colormap = XCreateColormap( display, RootWindow(display, visualInfo.screen), visualInfo.visual, AllocNone); attribs.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask, window = XCreateWindow( display, RootWindow(display, visualInfo.screen), 0, 0, width, height, 0, visualInfo.depth, InputOutput, visualInfo.visual, CWBackPixel | CWEventMask, &attribs); XStoreName(display, window, "RayMarcher"); XMapRaised(display, window); atomClose = XInternAtom(display, "WM_DELETE_WINDOW", 0); XSetWMProtocols(display, window, &atomClose, 1); pixelMap = XCreatePixmap(display, window, image->width, image->height, visualInfo.depth); |
1 2 3 4 5 | XShmPutImage( display, pixelMap, DefaultGC(display, visualInfo.screen), image, 0, 0, 0, 0, image->width, image->height, true); XFlush(display); |
insofaras
Your code will push pixels into the X Pixmap at 60fps but there's no association between the pixmap and the Window.
In my code I'm using the XPresent stuff to copy the pixmap to the window at vblank.
If you want a simpler method you could probably remove the pixmap entirely and switch to calling XShmPutImage(dpy, window, ...) which should push pixels directly to the window (if I'm not forgetting some reason you can't do that)
Alternatively you can set the window's background pixmap to the actual pixmap in the XSetWindowAttributes. I think XClearWindow might be needed to get it to redraw with this approach.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void RenderTiles(renderQueue* q) { PRINT_STRING("Begin work"); q->numTilesDone = 0; //volatile q->nextTile = 0; // volatile loop (i, 0, q->numThreads, 1) { Increment(&q->semaphore); // Calls sem_post(&s->handle); } PRINT_STRING("Wait for work"); while (q->numTilesDone < q->numTilesTotal) // Both volatile { } PRINT_STRING("Work done"); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | void* WorkerThread(void* params) { CAST(threadParameters, parameters, params); PRINT_STRING("Thread %u started", parameters->thread->id); CAST(renderQueue, queue, parameters->data); while (true) { if (queue->nextTile < queue->numTilesTotal) { u32 indexTile = SyncedGetAndAdd(&queue->nextTile, 1); PRINT_STRING("Thread %u doing tile %u", parameters->thread->id, indexTile); renderTileWork* tile = queue->workQueue + indexTile; RenderTile( queue->image, queue->view, tile->xMin, tile->xMax, tile->yMin, tile->yMax); SyncedGetAndAdd(&queue->numTilesDone, 1); // Calls __sync_fetch_and_add } else { PRINT_STRING("Thread %u pausing", parameters->thread->id); Decrement(&queue->semaphore); // Calls sem_wait(&s->handle); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void RenderTile( renderImage* image, renderView* view, u32 xMin, u32 xMax, u32 yMin, u32 yMax) { DrawRectangle(image, xMin, yMin, xMax, yMax, V4(1.0f, 1.0f, 0.0f, 0.5f)); loop (y, yMin, yMax, 1) { loop (x, xMin, xMax, 1) { RenderPixel(image, view, x, y, 0.0f); } } DrawLinedRectangle(image, xMin, yMin, xMax, yMax, V4(0.0f, 1.0f, 1.0f, 0.5f)); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | Begin work Thread 0 doing tile 0 Thread 4 doing tile 3 Thread 1 doing tile 1 Thread 7 doing tile 5 Wait for work Thread 6 doing tile 6 Thread 3 doing tile 2 Thread 2 doing tile 4 Thread 5 started Thread 5 doing tile 7 Thread 5 doing tile 8 Thread 1 doing tile 9 Thread 0 doing tile 10 Thread 2 doing tile 11 Thread 3 doing tile 12 Thread 4 doing tile 13 Thread 0 doing tile 14 Thread 6 doing tile 15 Thread 5 doing tile 16 Thread 7 doing tile 17 Thread 4 doing tile 18 Thread 1 doing tile 19 Thread 2 doing tile 20 ... |
1 2 3 4 5 | while (true) { if (queue->nextTile < queue->numTilesTotal) { u32 indexTile = SyncedGetAndAdd(&queue->nextTile, 1); <== no guarantee queue->nextTile will have the same value as above |
1 2 3 4 5 6 | while (true) { u32 indexTile = SyncedGetAndAdd(&queue->nextTile, 1); if (indexTile < queue->numTilesTotal) { // do work |
1 2 3 4 5 6 7 8 9 10 11 12 | while (run) { if () // Time to update { if (CameraControls(viewP, inputP)) { RenderTiles(queueP); XShmPutImage(display, windowP->window, context, imageX, 0, 0, 0, 0, imageX->width, imageX->height, true); } } } |
marcc
This could be an issue if you're updating the image memory at the same time X11 reads it.
To be sure, you could copy the RenderTiles result to an intermediate buffer and then send that one to X11.