Revision: 75619
http://sourceforge.net/p/brlcad/code/75619
Author: starseeker
Date: 2020-04-28 15:38:02 +0000 (Tue, 28 Apr 2020)
Log Message:
-----------
checkpoint
Modified Paths:
--------------
brlcad/trunk/src/libdm/tests/tcl_img.cpp
Modified: brlcad/trunk/src/libdm/tests/tcl_img.cpp
===================================================================
--- brlcad/trunk/src/libdm/tests/tcl_img.cpp 2020-04-28 12:03:12 UTC (rev
75618)
+++ brlcad/trunk/src/libdm/tests/tcl_img.cpp 2020-04-28 15:38:02 UTC (rev
75619)
@@ -37,53 +37,147 @@
const char *DM_CANVAS = ".dm0";
TCL_DECLARE_MUTEX(dilock)
+TCL_DECLARE_MUTEX(threadMutex)
-
/* Container holding image generation information - need to be able
* to pass these to the update command */
struct img_data {
- std::default_random_engine *gen;
- std::uniform_int_distribution<int> *colors;
- std::uniform_int_distribution<int> *vals;
- int *dflag;
+ // These flags should be mutex guarded.
+ int render_needed;
+ int render_running;
+ int render_ready;
+
+ // Parent application sets this when it's time to shut
+ // down the dm rendering
+ int render_shutdown;
+
+ // Main thread id
+ Tcl_ThreadId parent;
+ // DO NOT USE in thread - present here to pass through event back to parent
+ Tcl_Interp *parent_interp;
+
+ // Image info
+ int width;
+ int height;
+
+ // This gets interesting - a Tcl interp can only be used by the
+ // thread that created it. We provide the Tk_PhotoImageBlock
+ // to the thread, but the thread can't do any operations on
+ // the data that require the interp, so the update process
+ // must be partially in the thread and partially in the parent.
+ unsigned char *pixelPtr;
};
-int
-image_update_data(ClientData clientData, Tcl_Interp *interp, int UNUSED(argc),
char **UNUSED(argv))
+struct DmEventResult {
+ Tcl_Condition done;
+ int ret;
+};
+
+struct DmRenderEvent {
+ Tcl_Event event; /* Must be first */
+ struct DmEventResult *result;
+ struct img_data *idata;
+};
+
+static int
+DmRenderProc(Tcl_Event *evPtr, int UNUSED(mask))
{
- struct img_data *idata = (struct img_data *)clientData;
+ struct DmRenderEvent *DmEventPtr = (DmRenderEvent *) evPtr;
+ Tcl_Interp *interp = DmEventPtr->idata->parent_interp;
- // Look up the internals of the image - we're going to directly manipulate
- // the values of the image to simulate a display manager or framebuffer
- // changing the visual via rendering.
+ // Let Tcl/Tk know the photo data has changed, so it can update the
visuals accordingly
Tk_PhotoImageBlock dm_data;
Tk_PhotoHandle dm_img = Tk_FindPhoto(interp, DM_PHOTO);
Tk_PhotoGetImage(dm_img, &dm_data);
+ Tk_PhotoPutBlock(interp, dm_img, &dm_data, 0, 0, dm_data.width,
dm_data.height, TK_PHOTO_COMPOSITE_SET);
- // To get a little visual variation and make it easer to see changes,
- // randomly turn on/off the individual colors for each pass.
- int r = (*idata->colors)((*idata->gen));
- int g = (*idata->colors)((*idata->gen));
- int b = (*idata->colors)((*idata->gen));
+ // Render complete
+ Tcl_MutexLock(&dilock);
+ DmEventPtr->idata->render_ready = 0;
+ Tcl_MutexUnlock(&dilock);
- // For each pixel, get a random color value and set it in the image memory
buffer.
- // This alters the actual data, but Tcl/Tk doesn't know about it yet.
- for (int i = 0; i < (dm_data.width * dm_data.height * 4); i+=4) {
- // Red
- dm_data.pixelPtr[i] = (r) ? (*idata->vals)((*idata->gen)) : 0;
- // Green
- dm_data.pixelPtr[i+1] = (g) ? (*idata->vals)((*idata->gen)) : 0;
- // Blue
- dm_data.pixelPtr[i+2] = (b) ? (*idata->vals)((*idata->gen)) : 0;
- // Alpha stays at 255 (Don't really need to set it since it should
- // already be set, just doing so for local clarity about what should be
- // happening with the buffer data...)
- dm_data.pixelPtr[i+3] = 255;
+ DmEventPtr->result->ret = 0;
+ Tcl_ConditionNotify(&resultPtr->done);
+
+ return 1;
+}
+
+
+
+int
+image_update_data(ClientData clientData, Tcl_Interp *interp, int argc, char
**argv)
+{
+ struct img_data *idata = (struct img_data *)clientData;
+
+ if (argc == 3) {
+ // Unpack the current width and height. If these don't match what
idata has
+ // or thinks its working on, set the flag indicating we need a render.
+ // (Note: checking errno, although it may not truly be necessary if we
+ // trust Tk to always give us valid coordinates...)
+ char *p_end;
+ errno = 0;
+ long width = strtol(argv[1], &p_end, 10);
+ if (errno == ERANGE || (errno != 0 && width == 0) || p_end == argv[1]) {
+ std::cerr << "Invalid width: " << argv[1] << "\n";
+ return TCL_ERROR;
+ }
+ errno = 0;
+ long height = strtol(argv[2], &p_end, 10);
+ if (errno == ERANGE || (errno != 0 && height == 0) || p_end == argv[1])
{
+ std::cerr << "Invalid height: " << argv[2] << "\n";
+ return TCL_ERROR;
+ }
+
+ // TODO - if possible, the event generated from the render thread that
+ // prompts an actual Tk_Photo update should have enough info to
differentiate
+ // it here from an app generated event that needs to reset the
render_needed
+ // flag. render_needed should be set whenever the app calls in here,
not
+ // just when the dimensions change, but as the thread is likely going to
+ // need to generate a <Configure> event to make sure the GUI properly
updates
+ // we'll need to find a way to distinguish in the callback.
+
+ // Even if we already have a render going or ready, we might have made
a new update
+ // request to invalidate the currently-in-progress render.
+ if (idata->render_running || idata->render_ready) {
+ if (width != idata->width || height != idata->height) {
+ Tcl_MutexLock(&dilock);
+ idata->render_needed = 1;
+ Tcl_MutexUnlock(&dilock);
+ }
+ } else {
+ // No render in progress, but we got an update request - set
+ // the flag regardless of dimensions
+ Tcl_MutexLock(&dilock);
+ idata->render_needed = 1;
+ Tcl_MutexUnlock(&dilock);
+ }
+ } else {
+ Tcl_MutexLock(&dilock);
+ idata->render_needed = 1;
+ Tcl_MutexUnlock(&dilock);
}
- // Let Tcl/Tk know the photo data has changed, so it can update the
visuals accordingly
- Tk_PhotoPutBlock(interp, dm_img, &dm_data, 0, 0, dm_data.width,
dm_data.height, TK_PHOTO_COMPOSITE_SET);
+ if (idata->render_needed && !idata->render_running) {
+ // Look up the internals of the image - the rendering thread is going
+ // to directly manipulate the values of the image to simulate a display
+ // manager or framebuffer changing the visual via rendering. Once this
+ // process begins, the Tk_Photo can't be manipualted until the rendering
+ // is complete.
+ Tk_PhotoImageBlock dm_data;
+ Tk_PhotoHandle dm_img = Tk_FindPhoto(interp, DM_PHOTO);
+ Tk_PhotoGetImage(dm_img, &dm_data);
+ Tcl_MutexLock(&dilock);
+ idata->render_needed = 0; // Don't let the thread start until we're
ready
+ idata->width = dm_data.width;
+ idata->height = dm_data.height;
+ idata->pixelPtr = dm_data.pixelPtr;
+ idata->render_needed = 1; // Ready - thread can now render
+ Tcl_MutexUnlock(&dilock);
+ // We've asked for a render, that's all we can do right now. The
+ // render thread will need to generate an event when it's done.
+ }
+
return TCL_OK;
}
@@ -150,72 +244,90 @@
return TCL_OK;
}
-int
-image_resize_view(ClientData clientData, Tcl_Interp *interp, int argc, char
**argv)
+static Tcl_ThreadCreateType
+Dm_Draw(ClientData clientData)
{
- int go_flag = 0;
- if (argc != 3) {
- std::cerr << "Unexpected argc: " << argc << "\n";
- return TCL_ERROR;
- }
-
struct img_data *idata = (struct img_data *)clientData;
+ std::default_random_engine gen;
+ std::uniform_int_distribution<int> colors(0,1);
+ std::uniform_int_distribution<int> vals(0,255);
- Tcl_MutexLock(&dilock);
- if ((*idata->dflag) > 1) {
- std::cout << "dflag: " << (*idata->dflag) << "\n";
- (*idata->dflag) = 0;
- go_flag = 1;
- }
- Tcl_MutexUnlock(&dilock);
+ while (!idata->render_shutdown) {
+ if (!idata->render_needed) {
+ // If we havne't been asked for an update,
+ // sleep a bit and then check again.
+ Tcl_Sleep(1000);
+ continue;
+ }
+ // If we do need to render, get started
+ Tcl_MutexLock(&dilock);
+ idata->render_needed = 0;
+ idata->render_running = 1;
+ idata->render_ready = 0;
+ Tcl_MutexUnlock(&dilock);
- if (!go_flag) {
- std::cout << "Skipping redraw\n";
- return TCL_OK;
- }
+ // If we have no image data to work on, nothing
+ // to do
+ if (!idata->pixelPtr) {
+ Tcl_MutexLock(&dilock);
+ idata->render_running = 0;
+ idata->render_ready = 1;
+ Tcl_MutexUnlock(&dilock);
+ }
- // Unpack the coordinates (checking errno, although it may not truly be
- // necessary if we trust Tk to always give us valid coordinates...)
- char *p_end;
- errno = 0;
- long width = strtol(argv[1], &p_end, 10);
- if (errno == ERANGE || (errno != 0 && width == 0) || p_end == argv[1]) {
- std::cerr << "Invalid width: " << argv[1] << "\n";
- return TCL_ERROR;
- }
- errno = 0;
- long height = strtol(argv[2], &p_end, 10);
- if (errno == ERANGE || (errno != 0 && height == 0) || p_end == argv[1]) {
- std::cerr << "Invalid height: " << argv[2] << "\n";
- return TCL_ERROR;
- }
+ // To get a little visual variation and make it easer to see changes,
+ // randomly turn on/off the individual colors for each pass.
+ int r = colors(gen);
+ int g = colors(gen);
+ int b = colors(gen);
- // Set the new photo size.
- Tk_PhotoHandle dm_img = Tk_FindPhoto(interp, DM_PHOTO);
- Tk_PhotoSetSize(interp, dm_img, width, height);
+ // For each pixel, get a random color value and set it in the image
memory buffer.
+ // This alters the actual data, but Tcl/Tk doesn't know about it yet.
+ for (int i = 0; i < (idata->width * idata->height * 4); i+=4) {
+ // Red
+ idata->pixelPtr[i] = (r) ? vals(gen) : 0;
+ // Green
+ idata->pixelPtr[i+1] = (g) ? vals(gen) : 0;
+ // Blue
+ idata->pixelPtr[i+2] = (b) ? vals(gen) : 0;
+ // Alpha stays at 255 (Don't really need to set it since it should
+ // already be set, just doing so for local clarity about what
should be
+ // happening with the buffer data...)
+ idata->pixelPtr[i+3] = 255;
+ }
- image_update_data(clientData, interp, 0, NULL);
+ // Update done - let the parent structure know. We don't set the
+ // needed flag here, since the parent window may have changed between
+ // the start and the end of this render and if it has we need to start
+ // over.
+ Tcl_MutexLock(&dilock);
+ idata->render_running = 0;
+ idata->render_ready = 1;
+ Tcl_MutexUnlock(&dilock);
- return TCL_OK;
-}
+ // Generate an event for the parent thread to let it know its time
+ // to update the Tk_Photo holding the DM
+ Tcl_MutexLock(&threadMutex);
+ struct DmRenderEvent *threadEventPtr = (struct DmRenderEvent
*)ckalloc(sizeof(DmRenderEvent));
+ struct DmEventResult *resultPtr = (struct DmEventResult
*)ckalloc(sizeof(DmEventResult));
+ threadEventPtr->result = resultPtr;
+ threadEventPtr->idata = idata;
+ threadEventPtr->event.proc = DmRenderProc;
+ resultPtr->done = NULL;
+ resultPtr->ret = 1;
+ Tcl_ThreadQueueEvent(idata->parent, (Tcl_Event *) threadEventPtr,
TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(idata->parent);
+ while (resultPtr->ret) {
+ Tcl_ConditionWait(&resultPtr->done, &threadMutex, NULL);
+ }
-struct draw_info {
- int flag; /* Identifier for this handler. */
-};
+ Tcl_MutexUnlock(&threadMutex);
-
-static Tcl_ThreadCreateType
-Dm_Draw(ClientData clientData)
-{
- struct draw_info *di = (struct draw_info *)clientData;
-
- while (di->flag >= 0) {
- Tcl_Sleep(1000);
- Tcl_MutexLock(&dilock);
- di->flag++;
- Tcl_MutexUnlock(&dilock);
+ Tcl_ConditionFinalize(&resultPtr->done);
+ ckfree(resultPtr);
}
+ // We're well and truly done - quit the thread
Tcl_ExitThread(TCL_OK);
TCL_THREAD_CREATE_RETURN;
}
@@ -233,13 +345,13 @@
// raster changing the view data. We need to use these for subsequent
// image updating, so pack them into a structure we can pass through Tcl's
// evaluations.
- std::default_random_engine gen;
- std::uniform_int_distribution<int> colors(0,1);
- std::uniform_int_distribution<int> vals(0,255);
struct img_data idata;
- idata.gen = &gen;
- idata.colors = &colors;
- idata.vals = &vals;
+ idata.render_needed = 1;
+ idata.render_running = 0;
+ idata.render_ready = 0;
+ idata.render_shutdown = 0;
+ idata.pixelPtr = NULL;
+ idata.parent = Tcl_GetCurrentThread();
// Set up Tcl/Tk
Tcl_FindExecutable(argv[0]);
@@ -247,6 +359,9 @@
Tcl_Init(interp);
Tk_Init(interp);
+ // Save a pointer so we can get at the interp later
+ idata.parent_interp = interp;
+
// Make a simple toplevel window
Tk_Window tkwin = Tk_MainWindow(interp);
Tk_GeometryRequest(tkwin, wsize, wsize);
@@ -285,13 +400,12 @@
exit(1);
}
- // For each pixel, get a random color value and set it in the image memory
buffer.
- // This alters the actual data, but Tcl/Tk doesn't know about it yet.
+ // Initialize. This alters the actual data, but Tcl/Tk doesn't know about
it yet.
for (int i = 0; i < (dm_data.width * dm_data.height * 4); i+=4) {
// Red
dm_data.pixelPtr[i] = 0;
// Green
- dm_data.pixelPtr[i+1] = (*idata.vals)((*idata.gen));
+ dm_data.pixelPtr[i+1] = 255;
// Blue
dm_data.pixelPtr[i+2] = 0;
// Alpha at 255 - we dont' want transparency for this demo.
@@ -312,7 +426,7 @@
Tcl_Eval(interp, canvas_img_cmd.c_str());
- // Register a paint command so we can change the image contents neary the
cursor position
+ // Register a paint command so we can change the image contents near the
cursor position
(void)Tcl_CreateCommand(interp, "image_paint", (Tcl_CmdProc
*)image_paint_xy, NULL, (Tcl_CmdDeleteProc* )NULL);
// Establish the Button-1+Motion combination event as the trigger for
drawing on the image
bind_cmd = std::string("bind . <B1-Motion> {image_paint %x %y}");
@@ -327,25 +441,16 @@
bind_cmd = std::string("bind . <Button-3> {image_update}");
Tcl_Eval(interp, bind_cmd.c_str());
- // Register a callback to change the image size in response to a window
change
- (void)Tcl_CreateCommand(interp, "image_resize", (Tcl_CmdProc
*)image_resize_view, (ClientData)&idata, (Tcl_CmdDeleteProc* )NULL);
- // Establish the Button-1+Motion combination event as the trigger for
drawing on the image
- bind_cmd = std::string("bind ") + std::string(DM_CANVAS) + std::string("
<Configure> {image_resize [winfo width %W] [winfo height %W]\"}");
+ // Update the photo if the window changes
+ bind_cmd = std::string("bind ") + std::string(DM_CANVAS) + std::string("
<Configure> {image_update [winfo width %W] [winfo height %W]\"}");
Tcl_Eval(interp, bind_cmd.c_str());
-
-
// Multithreading experiment
Tcl_ThreadId threadID;
- struct draw_info di;
- di.flag = 0;
- if (Tcl_CreateThread(&threadID, Dm_Draw, (ClientData)&di,
TCL_THREAD_STACK_DEFAULT, TCL_THREAD_JOINABLE) != TCL_OK) {
+ if (Tcl_CreateThread(&threadID, Dm_Draw, (ClientData)&idata,
TCL_THREAD_STACK_DEFAULT, TCL_THREAD_JOINABLE) != TCL_OK) {
std::cerr << "can't create thread\n";
}
- // Let the update routines see the flag
- idata.dflag = &di.flag;
-
// Enter the main applicatio loop - the initial image will appear, and
Button-1 mouse
// clicks on the window should generate and display new images
while (1) {
@@ -353,7 +458,7 @@
if (!Tk_GetNumMainWindows()) {
// If we've closed the window, we're done
Tcl_MutexLock(&dilock);
- di.flag = -INT_MAX;
+ idata.render_shutdown = 1;
Tcl_MutexUnlock(&dilock);
int tret;
Tcl_JoinThread(threadID, &tret);
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