Revision: 75844
http://sourceforge.net/p/brlcad/code/75844
Author: starseeker
Date: 2020-05-20 01:03:47 +0000 (Wed, 20 May 2020)
Log Message:
-----------
Switch Tk libfb backend to using Tcl threads, which should (in theory) be a
cross platform solution.
Modified Paths:
--------------
brlcad/trunk/src/libfb/if_tk.cpp
Modified: brlcad/trunk/src/libfb/if_tk.cpp
===================================================================
--- brlcad/trunk/src/libfb/if_tk.cpp 2020-05-19 20:07:41 UTC (rev 75843)
+++ brlcad/trunk/src/libfb/if_tk.cpp 2020-05-20 01:03:47 UTC (rev 75844)
@@ -33,8 +33,11 @@
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
+
#include <tcl.h>
#include <tk.h>
+
+
#include "bio.h"
#include "bnetwork.h"
@@ -54,124 +57,117 @@
struct tk_info {
Tcl_Interp *fbinterp;
Tk_Window fbwin;
- int p[2];
- char *tkwrite_buffer;
+ // The rendering memory used to actually assemble the framebuffer contents.
+ long fb_buff_size;
+ unsigned char *fbpixel;
Tk_PhotoHandle fbphoto;
- Tk_PhotoImageBlock scanline;
+ int draw;
+
+ Tcl_ThreadId parent_id;
+ Tcl_ThreadId fb_id;
+ int ready;
+ int shutdown;
};
-HIDDEN int
-fb_tk_open(fb *ifp, const char *file, int width, int height)
-{
- int pid = -1;
+TCL_DECLARE_MUTEX(drawMutex)
- char *buffer;
- char *linebuffer;
+TCL_DECLARE_MUTEX(fbthreadMutex)
+// Event container passed to routines triggered by events.
+struct FbEvent {
+ Tcl_Event event; /* Must be first */
+ fb *ifp;
+};
- FB_CK_FB(ifp);
- if (file == (char *)NULL)
- fb_log("fb_open(0x%lx, NULL, %d, %d)\n",
- (unsigned long)ifp, width, height);
- else
- fb_log("fb_open(0x%lx, \"%s\", %d, %d)\n",
- (unsigned long)ifp, file, width, height);
+// Even for events where we don't intend to actually run a proc,
+// we need to tell Tcl it successfully processed them. For that
+// we define a no-op callback proc.
+static int
+noop_proc(Tcl_Event *UNUSED(evPtr), int UNUSED(mask))
+{
+ // Return one to signify a successful completion of the process execution
+ return 1;
+}
- /* check for default size */
- if (width <= 0)
- width = ifp->if_width;
- if (height <= 0)
- height = ifp->if_height;
+static void
+ImageUpdate(ClientData clientData)
+{
+ fb *ifp = (fb *)clientData;
+ struct tk_info *tki = TKINFO(ifp);
- /* set debug bit vector */
- if (file != NULL) {
- const char *cp;
- for (cp = file; *cp != '\0' && !isdigit((unsigned char)*cp); cp++)
- ;
- sscanf(cp, "%d", &ifp->if_debug);
- } else {
- ifp->if_debug = 0;
- }
+ Tcl_MutexLock(&fbthreadMutex);
+ tki->draw = 0;
+ Tcl_MutexUnlock(&fbthreadMutex);
- /* Give the user whatever width was asked for */
- ifp->if_width = width;
- ifp->if_height = height;
+ Tcl_MutexLock(&drawMutex);
+ Tk_PhotoImageBlock dm_data;
+ Tk_PhotoGetImage(tki->fbphoto, &dm_data);
+ dm_data.pixelPtr = tki->fbpixel;
+ Tk_PhotoPutBlock(tki->fbinterp, tki->fbphoto, &dm_data, 0, 0,
dm_data.width, dm_data.height, TK_PHOTO_COMPOSITE_SET);
+ Tcl_MutexUnlock(&drawMutex);
+}
- /* Set up Tk specific info */
- if ((TKINFOL(ifp) = (char *)calloc(1, sizeof(struct tk_info))) == NULL) {
- fb_log("fb_tk_open: tk_info malloc failed\n");
- return -1;
- }
-
+static Tcl_ThreadCreateType
+fb_tk_run(ClientData clientData)
+{
+ fb *ifp = (fb *)clientData;
struct tk_info *tki = TKINFO(ifp);
- tki->p[0] = 0;
- tki->p[1] = 0;
- tki->scanline.pixelPtr = NULL; /*Pointer to first pixel*/
- tki->scanline.width = 0; /*Width of block in pixels*/
- tki->scanline.height = 1; /*Height of block in pixels - always one
for a scanline*/
- tki->scanline.pitch = 0; /*Address difference between successive
lines*/
- tki->scanline.pixelSize = 3; /*Address difference between successive
pixels on one scanline*/
- tki->scanline.offset[0] = RED;
- tki->scanline.offset[1] = GRN;
- tki->scanline.offset[2] = BLU;
- tki->scanline.offset[3] = 0; /* alpha */
tki->fbinterp = Tcl_CreateInterp();
if (Tcl_Init(tki->fbinterp) == TCL_ERROR) {
- fb_log("Tcl_Init returned error in fb_open.");
+ return;
}
if (Tcl_Eval(tki->fbinterp, "package require Tk") != TCL_OK) {
- fb_log("Error returned attempting to start tk in fb_open.");
+ return;
}
tki->fbwin = Tk_MainWindow(tki->fbinterp);
- Tk_GeometryRequest(tki->fbwin, width, height);
+ Tk_GeometryRequest(tki->fbwin, ifp->if_width, ifp->if_height);
Tk_MakeWindowExist(tki->fbwin);
if (Tcl_Eval(tki->fbinterp, "wm resizable . 0 0") != TCL_OK) {
- fb_log("Error locking window size.");
+ return;
}
if (Tcl_Eval(tki->fbinterp, "wm title . \"Frame buffer\"") != TCL_OK) {
- fb_log("Error locking window size.");
+ return;
}
char frame_create_cmd[255] = {'\0'};
- sprintf(frame_create_cmd, "pack [frame .fb -borderwidth 0
-highlightthickness 0 -height %d -width %d]", width, height);
+ sprintf(frame_create_cmd, "pack [frame .fb -borderwidth 0
-highlightthickness 0 -height %d -width %d]", ifp->if_width, ifp->if_height);
if (Tcl_Eval(tki->fbinterp, frame_create_cmd) != TCL_OK) {
- fb_log("Error returned attempting to create frame in fb_open.");
+ return;
}
char canvas_create_cmd[255] = {'\0'};
- sprintf(canvas_create_cmd, "pack [canvas .fb.canvas -borderwidth 0
-highlightthickness 0 -insertborderwidth 0 -selectborderwidth 0 -height %d
-width %d]", width, height);
+ sprintf(canvas_create_cmd, "pack [canvas .fb.canvas -borderwidth 0
-highlightthickness 0 -insertborderwidth 0 -selectborderwidth 0 -height %d
-width %d]", ifp->if_width, ifp->if_height);
if (Tcl_Eval(tki->fbinterp, canvas_create_cmd) != TCL_OK) {
- fb_log("Error returned attempting to create canvas in fb_open.");
+ return;
}
//const char canvas_pack_cmd[255] = "pack .fb_tk_canvas -fill both -expand
true";
char image_create_cmd[255] = {'\0'};
- sprintf(image_create_cmd, "image create photo .fb.canvas.photo -height %d
-width %d", width, height);
+ sprintf(image_create_cmd, "image create photo .fb.canvas.photo -height %d
-width %d", ifp->if_width, ifp->if_height);
if (Tcl_Eval(tki->fbinterp, image_create_cmd) != TCL_OK) {
- fb_log("Error returned attempting to create image in fb_open.");
+ return;
}
if ((tki->fbphoto = Tk_FindPhoto(tki->fbinterp, ".fb.canvas.photo")) ==
NULL) {
- fb_log("Image creation unsuccessful in fb_open.");
+ return;
}
const char place_image_cmd[255] = ".fb.canvas create image 0 0 -image
.fb.canvas.photo -anchor nw";
if (Tcl_Eval(tki->fbinterp, place_image_cmd) != TCL_OK) {
- fb_log("Error returned attempting to place image in fb_open. %s",
- Tcl_GetStringResult(tki->fbinterp));
+ return;
}
char reportcolorcmd[255] = {'\0'};
- sprintf (reportcolorcmd, "bind . <Button-2> {puts \"At image (%%x, [expr
%d - %%y]), real RGB = ([.fb.canvas.photo get %%x %%y])\n\"}", height);
+ sprintf (reportcolorcmd, "bind . <Button-2> {puts \"At image (%%x, [expr
%d - %%y]), real RGB = ([.fb.canvas.photo get %%x %%y])\n\"}", ifp->if_height);
/* Set our Tcl variable pertaining to whether a
* window closing event has been seen from the
@@ -185,98 +181,117 @@
const char *wmclosecmd = "wm protocol . WM_DELETE_WINDOW {set CloseWindow
\"close\"}";
if (Tcl_Eval(tki->fbinterp, wmclosecmd) != TCL_OK) {
+ return;
fb_log("Error binding WM_DELETE_WINDOW.");
}
const char *bindclosecmd = "bind . <Button-3> {set CloseWindow \"close\"}";
if (Tcl_Eval(tki->fbinterp, bindclosecmd) != TCL_OK) {
- fb_log("Error binding right mouse button.");
+ return;
}
if (Tcl_Eval(tki->fbinterp, reportcolorcmd) != TCL_OK) {
- fb_log("Error binding middle mouse button.");
+ return;
}
+ // Clear out any events up to this point
while (Tcl_DoOneEvent(TCL_ALL_EVENTS|TCL_DONT_WAIT));
- /* FIXME: malloc() is necessary here because there are callers
- * that acquire a BU_SYM_SYSCALL semaphore for libfb calls.
- * this should be investigated more closely to see if the
- * semaphore acquires are critical or if they can be pushed
- * down into libfb proper. in the meantime, manually call
- * malloc()/free().
- */
- buffer = (char *)malloc(sizeof(uint32_t)*3+ifp->if_width*3);
- linebuffer = (char *)malloc(ifp->if_width*3);
- tki->tkwrite_buffer = (char *)malloc(ifp->if_width*3);
+ tki->ready = 1;
- if (pipe(tki->p) == -1) {
- perror("pipe failed");
+ while (!tki->shutdown) {
+
+ Tcl_DoOneEvent(0);
+
+ /* If the Tk window gets a close event, wrap up */
+ if (BU_STR_EQUAL(Tcl_GetVar(tki->fbinterp, "CloseWindow", 0), "close"))
{
+ tki->shutdown = 1;
+ continue;
+ }
+
+ if (tki->draw) {
+ Tk_DoWhenIdle(ImageUpdate, (ClientData) ifp);
+ }
}
- pid = fork();
- if (pid < 0) {
- printf("Problem forking Tk framebuffer window\n");
- } else if (pid > 0) {
- int line = 0;
- uint32_t lines[3];
- int i;
- int y[2];
- y[0] = 0;
+ Tcl_Eval(tki->fbinterp, "destroy .");
- /* parent */
- while (y[0] >= 0) {
- int count;
+ // Let the parent thread know we're done
+ Tcl_MutexLock(&fbthreadMutex);
+ struct FbEvent *threadEventPtr = (struct FbEvent
*)ckalloc(sizeof(FbEvent));
+ threadEventPtr->ifp = ifp;
+ threadEventPtr->event.proc = noop_proc;
+ Tcl_ThreadQueueEvent(tki->parent_id, (Tcl_Event *) threadEventPtr,
TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(tki->parent_id);
+ Tcl_MutexUnlock(&fbthreadMutex);
- /* If the Tk window gets a close event, bail */
- if (BU_STR_EQUAL(Tcl_GetVar(tki->fbinterp, "CloseWindow", 0),
"close")) {
- free(buffer);
- free(linebuffer);
- free(tki->tkwrite_buffer);
- fclose(stdin);
- printf("Close Window event\n");
- Tcl_Eval(tki->fbinterp, "destroy .");
- Tcl_DeleteInterp(tki->fbinterp);
- bu_exit(0, NULL);
- }
+ Tcl_DeleteInterp(tki->fbinterp);
- /* Unpack inputs from pipe */
- count = read(tki->p[0], buffer, sizeof(uint32_t)*3+ifp->if_width*3);
- memcpy(lines, buffer, sizeof(uint32_t)*3);
- memcpy(linebuffer, buffer+sizeof(uint32_t)*3, ifp->if_width*3);
- y[0] = ntohl(lines[0]);
- count = ntohl(lines[1]);
+ Tcl_ExitThread(TCL_OK);
+ TCL_THREAD_CREATE_RETURN;
+}
- if (y[0] < 0) {
- break;
- }
- line++;
- tki->scanline.pixelPtr = (unsigned char *)linebuffer;
- tki->scanline.width = count;
- tki->scanline.pitch = 3 * ifp->if_width;
+HIDDEN int
+fb_tk_open(fb *ifp, const char *file, int width, int height)
+{
+ FB_CK_FB(ifp);
+ if (file == (char *)NULL)
+ fb_log("fb_open(0x%lx, NULL, %d, %d)\n",
+ (unsigned long)ifp, width, height);
+ else
+ fb_log("fb_open(0x%lx, \"%s\", %d, %d)\n",
+ (unsigned long)ifp, file, width, height);
- Tk_PhotoPutBlock(tki->fbinterp, tki->fbphoto, &tki->scanline, 0,
ifp->if_height-y[0]-1, count, 1, TK_PHOTO_COMPOSITE_SET);
+ /* check for default size */
+ if (width <= 0)
+ width = ifp->if_width;
+ if (height <= 0)
+ height = ifp->if_height;
- do {
- i = Tcl_DoOneEvent(TCL_ALL_EVENTS|TCL_DONT_WAIT);
- } while (i);
- }
- /* very bad things will happen if the parent does not terminate here */
- free(buffer);
- free(linebuffer);
- free(tki->tkwrite_buffer);
- fclose(stdin);
- Tcl_Eval(tki->fbinterp, "vwait CloseWindow");
- if (BU_STR_EQUAL(Tcl_GetVar(tki->fbinterp, "CloseWindow", 0), "close"))
{
- printf("Close Window event\n");
- Tcl_Eval(tki->fbinterp, "destroy .");
- }
- bu_exit(0, NULL);
+ /* set debug bit vector */
+ if (file != NULL) {
+ const char *cp;
+ for (cp = file; *cp != '\0' && !isdigit((unsigned char)*cp); cp++)
+ ;
+ sscanf(cp, "%d", &ifp->if_debug);
} else {
- /* child */
- fflush(stdout);
+ ifp->if_debug = 0;
}
+ /* Give the user whatever width was asked for */
+ ifp->if_width = width;
+ ifp->if_height = height;
+
+ /* Set up Tk specific info */
+ if ((TKINFOL(ifp) = (char *)calloc(1, sizeof(struct tk_info))) == NULL) {
+ fb_log("fb_tk_open: tk_info malloc failed\n");
+ return -1;
+ }
+
+ struct tk_info *tki = TKINFO(ifp);
+ tki->shutdown = 0;
+ tki->ready = 0;
+ tki->draw = 0;
+
+ long b_size = ifp->if_width * ifp->if_height * 4;
+ tki->fbpixel = (unsigned char *)calloc(b_size+1, sizeof(char));
+
+ Tcl_MutexLock(&fbthreadMutex);
+ Tcl_ThreadId fbthreadID;
+ if (Tcl_CreateThread(&fbthreadID, fb_tk_run, (ClientData)ifp,
TCL_THREAD_STACK_DEFAULT, TCL_THREAD_JOINABLE) != TCL_OK) {
+ fb_log("can't create fb thread\n");
+ return -1;
+ }
+ tki->fb_id = fbthreadID;
+ Tcl_MutexUnlock(&fbthreadMutex);
+
+#if 0
+ // Let the window finish setting up before open is considered done
+ while (!tki->ready) {
+ Tcl_Sleep(1);
+ }
+#endif
+
return 0;
}
@@ -320,17 +335,44 @@
HIDDEN int
fb_tk_close(fb *ifp)
{
+ FB_CK_FB(ifp);
struct tk_info *tki = TKINFO(ifp);
- int y[2];
- int ret;
- y[0] = -1;
- y[1] = 0;
- printf("Entering fb_tk_close\n");
- FB_CK_FB(ifp);
- ret = write(tki->p[1], y, sizeof(y));
- close(tki->p[1]);
+
+ // Make a local interp so we can get events from the window
+ Tcl_Interp *cinterp = Tcl_CreateInterp();
+
+ // Let the window know who to tell when it's time to shut down
+ tki->parent_id = Tcl_GetCurrentThread();
+
+ while (!tki->shutdown) {
+ Tcl_DoOneEvent(0);
+ if (tki->shutdown) {
+ int tret;
+ Tcl_DeleteInterp(cinterp);
+ Tcl_JoinThread(tki->fb_id, &tret);
+ if (tret != TCL_OK) {
+ printf("shutdown of thread failed\n");
+ return -1;
+ }
+
+ bu_free(tki->fbpixel, "fbpixel");
+ bu_free(tki, "tkinfo");
+ return 0;
+ }
+ }
+
+ // Just in case shutdown got set before fb_tk_close was ever
+ // called, be prepared to clean up outside the loop as well.
+ int tret;
+ Tcl_DeleteInterp(cinterp);
+ Tcl_JoinThread(tki->fb_id, &tret);
+ if (tret != TCL_OK) {
+ printf("shutdown of thread failed\n");
+ return -1;
+ }
+ bu_free(tki->fbpixel, "fbpixel");
bu_free(tki, "tkinfo");
- printf("Sent write (ret=%d) from fb_tk_close\n", ret);
+
return 0;
}
@@ -360,35 +402,66 @@
return (ssize_t)count;
}
+// Given an X,Y coordinate in a TkPhoto image and a desired offset in
+// X and Y, return the index value into the pixelPtr array N such that
+// N is the integer value of the R color at that X,Y coordiante, N+1
+// is the G value, N+2 is the B value and N+3 is the Alpha value.
+// If either desired offset is beyond the width and height boundaries,
+// cap the return at the minimum/maximum allowed value.
+static int
+img_xy_index(int width, int height, int x, int y, int dx, int dy)
+{
+ int nx = ((x + dx) > width) ? width : ((x + dx) < 0) ? 0 : x + dx;
+ int ny = ((y + dy) > height) ? height : ((y + dy) < 0) ? 0 : y + dy;
+ return (ny * width * 4) + nx * 4;
+}
HIDDEN ssize_t
-tk_write(fb *ifp, int UNUSED(x), int y, const unsigned char *pixelp, size_t
count)
+tk_write(fb *ifp, int x, int y, const unsigned char *pixelp, size_t count)
{
- uint32_t line[3];
-
FB_CK_FB(ifp);
struct tk_info *tki = TKINFO(ifp);
- /* Set local values of Tk_PhotoImageBlock */
- tki->scanline.pixelPtr = (unsigned char *)pixelp;
- tki->scanline.width = count;
- tki->scanline.pitch = 3 * ifp->if_width;
+#if 0
+ // If the shutdown flag got set and we're still processing, bail
+ // (TODO - is this the right thing to do? We're rendering to a framebuffer
+ // that's not there anymore, so if we don't do this the process is arguably
+ // a zombie, but maybe it's desirable to keep going in some situations ...)
+ if (tki->shutdown) {
+ bu_exit(0, "Quitting - framebuffer shutdown flag found set during
tk_write");
+ }
+#endif
- /* Pack values to be sent to parent */
- line[0] = htonl(y);
- line[1] = htonl((long)count);
- line[2] = 0;
+ int pindex = img_xy_index(ifp->if_width, ifp->if_height, x,
ifp->if_height-y-1, 0, 0);
- memcpy(tki->tkwrite_buffer, line, sizeof(uint32_t)*3);
- memcpy(tki->tkwrite_buffer+sizeof(uint32_t)*3, tki->scanline.pixelPtr, 3 *
ifp->if_width);
+ // Touching the image buffer - lock drawing mutex
+ Tcl_MutexLock(&drawMutex);
- /* Send values and data to parent for display */
- if (write(tki->p[1], tki->tkwrite_buffer, 3 * ifp->if_width +
3*sizeof(uint32_t)) == -1) {
- perror("Unable to write to pipe");
- bu_snooze(BU_SEC2USEC(1));
+ // TkPhoto uses an alpha channel, but at the moment the incoming pix data
doesn't.
+ // Write pixels accordingly, setting alpha to opaque (for now)
+ for (size_t i = 0; i < count; i++) {
+ tki->fbpixel[pindex+i*4] = pixelp[i*3];
+ tki->fbpixel[pindex+(i*4+1)] = pixelp[i*3+1];
+ tki->fbpixel[pindex+(i*4+2)] = pixelp[i*3+2];
+ tki->fbpixel[pindex+(i*4+3)] = 255;
}
+ // Lock the drawing thread - we have updated image data, and want to make
sure
+ // the drawing thread doesn't clear anything in the middle of this block.
+ Tcl_MutexLock(&fbthreadMutex);
+ tki->draw = 1;
+
+ struct FbEvent *threadEventPtr = (struct FbEvent
*)ckalloc(sizeof(FbEvent));
+ threadEventPtr->ifp = ifp;
+ threadEventPtr->event.proc = noop_proc;
+ Tcl_ThreadQueueEvent(tki->fb_id, (Tcl_Event *) threadEventPtr,
TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(tki->fb_id);
+
+ // Flag set, event queued, drawing done - unlock everything
+ Tcl_MutexUnlock(&fbthreadMutex);
+ Tcl_MutexUnlock(&drawMutex);
+
return (ssize_t)count;
}
@@ -544,6 +617,19 @@
tk_flush(fb *ifp)
{
FB_CK_FB(ifp);
+ struct tk_info *tki = TKINFO(ifp);
+
+ Tcl_MutexLock(&drawMutex);
+ tki->draw = 1;
+ Tcl_MutexLock(&fbthreadMutex);
+ struct FbEvent *threadEventPtr = (struct FbEvent
*)ckalloc(sizeof(FbEvent));
+ threadEventPtr->ifp = ifp;
+ threadEventPtr->event.proc = noop_proc;
+ Tcl_ThreadQueueEvent(tki->fb_id, (Tcl_Event *) threadEventPtr,
TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(tki->fb_id);
+ Tcl_MutexUnlock(&fbthreadMutex);
+ Tcl_MutexUnlock(&drawMutex);
+
fb_log("if_flush(0x%lx)\n", (unsigned long)ifp);
return 0;
}
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
_______________________________________________
BRL-CAD Source Commits mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/brlcad-commits