Revision: 75630
http://sourceforge.net/p/brlcad/code/75630
Author: starseeker
Date: 2020-04-29 16:02:41 +0000 (Wed, 29 Apr 2020)
Log Message:
-----------
Merge changes from trunk through r75629
Modified Paths:
--------------
brlcad/branches/dm-fb-merge/src/fb/CMakeLists.txt
brlcad/branches/dm-fb-merge/src/libdm/CMakeLists.txt
brlcad/branches/dm-fb-merge/src/libdm/tests/CMakeLists.txt
brlcad/branches/dm-fb-merge/src/libged/typein.c
Added Paths:
-----------
brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_img.cpp
brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_tevent.cpp
Removed Paths:
-------------
brlcad/branches/dm-fb-merge/src/fb/tests/
Property Changed:
----------------
brlcad/branches/dm-fb-merge/
Index: brlcad/branches/dm-fb-merge
===================================================================
--- brlcad/branches/dm-fb-merge 2020-04-29 15:00:45 UTC (rev 75629)
+++ brlcad/branches/dm-fb-merge 2020-04-29 16:02:41 UTC (rev 75630)
Property changes on: brlcad/branches/dm-fb-merge
___________________________________________________________________
Modified: svn:mergeinfo
## -7,4 +7,4 ##
/brlcad/branches/osg:62110-62113
/brlcad/branches/prep-cache:68236-68933
/brlcad/branches/tcltk86:68300-75257
-/brlcad/trunk:75440-75597
\ No newline at end of property
+/brlcad/trunk:75440-75629
\ No newline at end of property
Modified: brlcad/branches/dm-fb-merge/src/fb/CMakeLists.txt
===================================================================
--- brlcad/branches/dm-fb-merge/src/fb/CMakeLists.txt 2020-04-29 15:00:45 UTC
(rev 75629)
+++ brlcad/branches/dm-fb-merge/src/fb/CMakeLists.txt 2020-04-29 16:02:41 UTC
(rev 75630)
@@ -48,8 +48,6 @@
BRLCAD_ADDEXEC(spm-fb spm-fb.c "libdm;libbn")
-add_subdirectory(tests)
-
CMAKEFILES(CMakeLists.txt)
# Local Variables:
Modified: brlcad/branches/dm-fb-merge/src/libdm/CMakeLists.txt
===================================================================
--- brlcad/branches/dm-fb-merge/src/libdm/CMakeLists.txt 2020-04-29
15:00:45 UTC (rev 75629)
+++ brlcad/branches/dm-fb-merge/src/libdm/CMakeLists.txt 2020-04-29
16:02:41 UTC (rev 75630)
@@ -101,6 +101,9 @@
add_subdirectory(tests)
+
+add_subdirectory(tests)
+
set(libdm_ignore_files
CMakeLists.txt
README
Modified: brlcad/branches/dm-fb-merge/src/libdm/tests/CMakeLists.txt
===================================================================
--- brlcad/branches/dm-fb-merge/src/libdm/tests/CMakeLists.txt 2020-04-29
15:00:45 UTC (rev 75629)
+++ brlcad/branches/dm-fb-merge/src/libdm/tests/CMakeLists.txt 2020-04-29
16:02:41 UTC (rev 75630)
@@ -37,7 +37,14 @@
BRLCAD_ADDEXEC(dm_test dm_test.c "libdm;libbu" TEST)
+if (BRLCAD_ENABLE_TK)
+ BRLCAD_ADDEXEC(tcl_img tcl_img.cpp
"libdm;libbu;${TCL_LIBRARY};${TK_LIBRARY}" TEST_USEDATA)
+ BRLCAD_ADDEXEC(tcl_tevent tcl_tevent.cpp
"libdm;libbu;${TCL_LIBRARY};${TK_LIBRARY}" TEST_USEDATA)
+endif (BRLCAD_ENABLE_TK)
+
CMAKEFILES(CMakeLists.txt)
+CMAKEFILES(tcl_img.cpp)
+CMAKEFILES(tcl_tevent.cpp)
# Local Variables:
# tab-width: 8
@@ -44,4 +51,5 @@
# mode: cmake
# indent-tabs-mode: t
# End:
-# ex: shiftwidth=2 tabstop=8 textwidth=0 wrapmargin=0
+# ex: shiftwidth=2 tabstop=8
+
Added: brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_img.cpp
===================================================================
--- brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_img.cpp
(rev 0)
+++ brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_img.cpp 2020-04-29
16:02:41 UTC (rev 75630)
@@ -0,0 +1,603 @@
+/* T C L _ I M G . C P P
+ * BRL-CAD
+ *
+ * Published in 2020 by the United States Government.
+ * This work is in the public domain.
+ *
+ */
+/** @file tcl_img.cpp
+ *
+ * Self contained example of working with image data
+ * in Tcl/Tk in C/C++
+ *
+ * Eventually this should probably turn into a Togl-esque
+ * widget that we properly include, so we ensure that our
+ * window is behaving the way Tk expects it to for things
+ * like refresh. Our current behavior in that regard is
+ * a bit iffy - ogl works but I'm not convinced that's
+ * because we're doing the Tcl/Tk bits right, and X is
+ * doing low level blitting and other X calls (which
+ * isn't great for maintainability/portability and
+ * can be a headache for debugging.)
+ *
+ */
+
+#include "common.h"
+
+#include <climits>
+#include <random>
+#include <iostream>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "bu/malloc.h"
+
+#include "tcl.h"
+#include "tk.h"
+
+const char *DM_PHOTO = ".dm0.photo";
+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 {
+ // 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 - used to send a rendering event back to
+ // the parent thread.
+ Tcl_ThreadId parent_id;
+
+ // Tcl interp can only be used by the thread that created it, and the
+ // Tk_Photo calls require an interpreter. Thus we keep track of which
+ // interpreter is the parent interp in the primary data container, as this
+ // interp (and ONLY this interp) is requried to do Tk_Photo processing when
+ // triggered from thread events sent back to the parent. Those callbacks
+ // don't by default encode knowledge about the parent's Tcl_Interp, so we
+ // store it where we know we can access it.
+ Tcl_Interp *parent_interp;
+
+ // Manager thread id
+ Tcl_ThreadId manager_id;
+
+ // Image info. Reflects the currently requested parent image
+ // size - the render thread may have a different size during
+ // rendering, as it "snapshots" these numbers to have a stable
+ // buffer size when actually writing pixels.
+ int width;
+ int height;
+
+ // Actual allocated sizes of image buffer
+ int bwidth;
+ int bheight;
+
+ // Used for image generation - these are presisted across renders so the
+ // image contents will vary as expected.
+ std::default_random_engine *gen;
+ std::uniform_int_distribution<int> *colors;
+ std::uniform_int_distribution<int> *vals;
+
+ // The rendering memory used to actually generate the image contents.
+ long buff_size;
+ unsigned char *pixelPtr;
+};
+
+// Event container passed to routines triggered by events.
+struct DmRenderEvent {
+ Tcl_Event event; /* Must be first */
+ struct img_data *idata;
+};
+
+// 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;
+}
+
+// The actual Tk_Photo updating must be done by the creater of the
+// Tcl_Interp - which is the parent thread. The interp below must
+// thus be the PARENT's interp, and we have passed it through the
+// various structures to be available here.
+static int
+DmUpdateProc(Tcl_Event *evPtr, int UNUSED(mask))
+{
+ struct DmRenderEvent *DmEventPtr = (DmRenderEvent *) evPtr;
+ struct img_data *idata = DmEventPtr->idata;
+ Tcl_Interp *interp = idata->parent_interp;
+ int width = idata->bwidth;
+ int height = idata->bheight;
+
+ // 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);
+ if (dm_data.width != width || dm_data.height != height) {
+ Tk_PhotoSetSize(interp, dm_img, width, height);
+ Tk_PhotoGetImage(dm_img, &dm_data);
+ }
+
+ Tcl_MutexLock(&dilock);
+
+ // Tk_PhotoPutBlock appears to be making a copy of the data, so we should
+ // be able to point to our thread's rendered data to feed it in for
+ // copying without worrying that Tk might alter it.
+ dm_data.pixelPtr = idata->pixelPtr;
+ Tk_PhotoPutBlock(interp, dm_img, &dm_data, 0, 0, dm_data.width,
dm_data.height, TK_PHOTO_COMPOSITE_SET);
+
+ // Render processed - reset the ready flag
+ idata->render_ready = 0;
+ Tcl_MutexUnlock(&dilock);
+
+ // Return one to signify a successful completion of the process execution
+ 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, update the image size.
+ // (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;
+ }
+
+ Tcl_MutexLock(&dilock);
+ idata->width = width;
+ idata->height = height;
+ Tcl_MutexUnlock(&dilock);
+
+ Tk_PhotoImageBlock dm_data;
+ Tk_PhotoHandle dm_img = Tk_FindPhoto(interp, DM_PHOTO);
+ Tk_PhotoGetImage(dm_img, &dm_data);
+ if (dm_data.width != idata->width || dm_data.height != idata->height) {
+ Tk_PhotoSetSize(interp, dm_img, idata->width, idata->height);
+ }
+ }
+
+ // Generate an event for the manager thread to let it know we need work
+ Tcl_MutexLock(&threadMutex);
+ struct DmRenderEvent *threadEventPtr = (struct DmRenderEvent
*)ckalloc(sizeof(DmRenderEvent));
+ threadEventPtr->idata = idata;
+ threadEventPtr->event.proc = noop_proc;
+ Tcl_ThreadQueueEvent(idata->manager_id, (Tcl_Event *) threadEventPtr,
TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(idata->manager_id);
+ Tcl_MutexUnlock(&threadMutex);
+
+ return TCL_OK;
+}
+
+// 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;
+}
+
+int
+image_paint_xy(ClientData UNUSED(clientData), Tcl_Interp *interp, int argc,
char **argv)
+{
+ if (argc != 3) {
+ std::cerr << "Unexpected argc: " << argc << "\n";
+ return TCL_ERROR;
+ }
+
+ // 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 xcoor = strtol(argv[1], &p_end, 10);
+ if (errno == ERANGE || (errno != 0 && xcoor == 0) || p_end == argv[1]) {
+ std::cerr << "Invalid image_paint_xy X coordinate: " << argv[1] << "\n";
+ return TCL_ERROR;
+ }
+ errno = 0;
+ long ycoor = strtol(argv[2], &p_end, 10);
+ if (errno == ERANGE || (errno != 0 && ycoor == 0) || p_end == argv[1]) {
+ std::cerr << "Invalid image_paint_xy Y coordinate: " << argv[2] << "\n";
+ return TCL_ERROR;
+ }
+
+ // 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.
+ Tk_PhotoImageBlock dm_data;
+ Tk_PhotoHandle dm_img = Tk_FindPhoto(interp, DM_PHOTO);
+ Tk_PhotoGetImage(dm_img, &dm_data);
+
+
+ for (int i = -2; i < 3; i++) {
+ for (int j = -2; j < 3; j++) {
+ int pindex = img_xy_index(dm_data.width, dm_data.height, xcoor,
ycoor, i, j);
+ // Set to opaque white
+ dm_data.pixelPtr[pindex] = 255;
+ dm_data.pixelPtr[pindex+1] = 255;
+ dm_data.pixelPtr[pindex+2] = 255;
+ dm_data.pixelPtr[pindex+3] = 255;
+ }
+ }
+
+ // 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);
+
+ return TCL_OK;
+}
+
+static Tcl_ThreadCreateType
+Dm_Render(ClientData clientData)
+{
+ // Unpack the shared data object
+ struct img_data *idata = (struct img_data *)clientData;
+
+ // Lock updating of the idata values until we've gotten this run's key
+ // information - we don't want this changing mid-render.
+ Tcl_MutexLock(&dilock);
+
+ // Anything before this point will be convered by this render. (This
+ // flag may get set after this render starts by additional events, but
+ // to this point this rendering will handle it.)
+ idata->render_needed = 0;
+
+ // We're now in process - don't start another frame until this one
+ // is complete.
+ idata->render_running = 1;
+
+ // Until we're done, make sure nobody thinks we are ready
+ idata->render_ready = 0;
+
+ // Lock in this width and height - that's what we've got memory for,
+ // so that's what the remainder of this rendering pass will use.
+ idata->bwidth = idata->width;
+ idata->bheight = idata->height;
+
+ // If we have insufficient memory, allocate or reallocate a local
+ // buffer big enough for the purpose. We use our own buffer for
+ // rendering to allow Tk full control over what it wants to do behind
+ // the scenes. We need this memory to persist, so handle it from the
+ // management thread.
+ long b_minsize = idata->bwidth * idata->bheight * 4;
+ if (!idata->pixelPtr || idata->buff_size < b_minsize) {
+ idata->pixelPtr = (unsigned char *)bu_realloc(idata->pixelPtr,
2*b_minsize*sizeof(char), "realloc pixbuf");
+ idata->buff_size = b_minsize * 2;
+ }
+
+ // Have the key values now, we can unlock and allow interactivity in
+ // the parent. We'll finish this rendering pass before paying any
+ // further attention to parent settings, but the parent may now set
+ // them for future consideration.
+ Tcl_MutexUnlock(&dilock);
+
+
//////////////////////////////////////////////////////////////////////////////
+ // Rendering operation - this is where the work of a DM/FB render pass
+ // would occur in a real application
+
//////////////////////////////////////////////////////////////////////////////
+ // 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));
+
+ // 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 < b_minsize; i+=4) {
+ // Red
+ idata->pixelPtr[i] = (r) ? (*idata->vals)((*idata->gen)) : 0;
+ // Green
+ idata->pixelPtr[i+1] = (g) ? (*idata->vals)((*idata->gen)) : 0;
+ // Blue
+ idata->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...)
+ idata->pixelPtr[i+3] = 255;
+ }
+
//////////////////////////////////////////////////////////////////////////////
+
//////////////////////////////////////////////////////////////////////////////
+
+ // Update done - let the parent structure know. We don't clear the
+ // render_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);
+
+ // Generate an event for the manager thread to let it know we're done
+ Tcl_MutexLock(&threadMutex);
+ struct DmRenderEvent *threadEventPtr = (struct DmRenderEvent
*)ckalloc(sizeof(DmRenderEvent));
+ threadEventPtr->idata = idata;
+ threadEventPtr->event.proc = noop_proc;
+ Tcl_ThreadQueueEvent(idata->manager_id, (Tcl_Event *) threadEventPtr,
TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(idata->manager_id);
+ Tcl_MutexUnlock(&threadMutex);
+
+ // Render complete, we're done with this thread
+ Tcl_ExitThread(TCL_OK);
+ TCL_THREAD_CREATE_RETURN;
+}
+
+static Tcl_ThreadCreateType
+Dm_Update_Manager(ClientData clientData)
+{
+ // This thread needs to process events - give it its own interp so it can
do so.
+ Tcl_Interp *interp = Tcl_CreateInterp();
+ Tcl_Init(interp);
+
+ // Unpack the shared data object
+ struct img_data *idata = (struct img_data *)clientData;
+
+ // Set up our data sources - we'll reuse these for each render, so they are
+ // set up in the managing thread
+ std::default_random_engine gen;
+ std::uniform_int_distribution<int> colors(0,1);
+ std::uniform_int_distribution<int> vals(0,255);
+ idata->gen = &gen;
+ idata->colors = &colors;
+ idata->vals = &vals;
+
+ // Until we're shutting down, check for and process events - this thread
will persist
+ // for the life of the application.
+ while (!idata->render_shutdown) {
+
+ // Events can come from either the parent (requesting a rendering,
announcing a shutdown)
+ // or from the rendering thread (announcing completion of a rendering.)
+ Tcl_DoOneEvent(0);
+
+ // If anyone flipped the shutdown switch while we were waiting on an
+ // event, don't do any more work - there's no point.
+ if (idata->render_shutdown) {
+ continue;
+ }
+
+ // If we have a render ready, let the parent know to update the GUI
before we
+ // go any further. Even if a completed render is out of data, it's
closer to
+ // current than what was previously displayed so the application should
go ahead
+ // and show it.
+ if (idata->render_ready) {
+
+ // Generate an event for the parent thread - its time to update the
+ // Tk_Photo in the GUI that's holding the image
+ Tcl_MutexLock(&threadMutex);
+ struct DmRenderEvent *threadEventPtr = (struct DmRenderEvent
*)ckalloc(sizeof(DmRenderEvent));
+ threadEventPtr->idata = idata;
+ threadEventPtr->event.proc = DmUpdateProc;
+ Tcl_ThreadQueueEvent(idata->parent_id, (Tcl_Event *)
threadEventPtr, TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(idata->parent_id);
+ Tcl_MutexUnlock(&threadMutex);
+
+ // If we don't have any work pending, go back to waiting. If we
know
+ // we're already out of date, proceed without waiting for another
event
+ // from the parent - we've already had one.
+ if (!idata->render_needed) {
+ continue;
+ }
+ }
+
+ // If we're already rendering and we have another rendering event, it's
+ // an update request from the parent - set the flag and continue
+ if (idata->render_running) {
+ idata->render_needed = 1;
+ continue;
+ }
+
+ // If we need a render and aren't already running, it's time to go to
work.
+
+
+ // Start a rendering thread.
+ Tcl_ThreadId threadID;
+ if (Tcl_CreateThread(&threadID, Dm_Render, (ClientData)idata,
TCL_THREAD_STACK_DEFAULT, TCL_THREAD_NOFLAGS) != TCL_OK) {
+ std::cerr << "can't create render thread\n";
+ }
+ }
+
+ // We're well and truly done - the application is closing down - free the
+ // rendering buffer and quit the thread
+ if (idata->pixelPtr) {
+ bu_free(idata->pixelPtr, "free pixbuf");
+ }
+ Tcl_ExitThread(TCL_OK);
+ TCL_THREAD_CREATE_RETURN;
+}
+
+int
+main(int UNUSED(argc), const char *argv[])
+{
+ std::string bind_cmd;
+
+ // For now, just use a fixed 512x512 image size
+ int wsize = 512;
+
+ // Set up random image data generation so we can simulate a raytrace or
+ // 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.
+ struct img_data *idata;
+ BU_GET(idata, struct img_data);
+ idata->render_needed = 1;
+ idata->render_running = 0;
+ idata->render_ready = 0;
+ idata->render_shutdown = 0;
+ idata->pixelPtr = NULL;
+ idata->parent_id = Tcl_GetCurrentThread();
+
+ // Set up Tcl/Tk
+ Tcl_FindExecutable(argv[0]);
+ Tcl_Interp *interp = Tcl_CreateInterp();
+ 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);
+ Tk_MakeWindowExist(tkwin);
+ Tk_MapWindow(tkwin);
+
+ // Create the (initially empty) Tcl/Tk Photo (a.k.a color image) we will
+ // use as our rendering canvas for the images.
+ //
+ // Note: confirmed with Tcl/Tk community that (at least as of Tcl/Tk 8.6)
+ // Tcl_Eval is the ONLY way to create an image object. The C API doesn't
+ // expose that ability, although it does support manipulation of the
+ // created object.
+ std::string img_cmd = std::string("image create photo ") +
std::string(DM_PHOTO);
+ Tcl_Eval(interp, img_cmd.c_str());
+ Tk_PhotoHandle dm_img = Tk_FindPhoto(interp, DM_PHOTO);
+ Tk_PhotoBlank(dm_img);
+ Tk_PhotoSetSize(interp, dm_img, wsize, wsize);
+
+ // Initialize the PhotoImageBlock information for a color image of size
+ // 500x500 pixels.
+ Tk_PhotoImageBlock dm_data;
+ dm_data.width = wsize;
+ dm_data.height = wsize;
+ dm_data.pixelSize = 4;
+ dm_data.pitch = wsize * 4;
+ dm_data.offset[0] = 0;
+ dm_data.offset[1] = 1;
+ dm_data.offset[2] = 2;
+ dm_data.offset[3] = 3;
+
+ // Actually create our memory for the image buffer. Expects RGBA
information
+ dm_data.pixelPtr = (unsigned char *)Tcl_AttemptAlloc(dm_data.width *
dm_data.height * 4);
+ if (!dm_data.pixelPtr) {
+ std::cerr << "Tcl/Tk photo memory allocation failed!\n";
+ exit(1);
+ }
+
+ // 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] = 255;
+ // Blue
+ dm_data.pixelPtr[i+2] = 0;
+ // Alpha at 255 - we dont' want transparency for this demo.
+ dm_data.pixelPtr[i+3] = 255;
+ }
+
+ // Let Tk_Photo know we have data
+ Tk_PhotoPutBlock(interp, dm_img, &dm_data, 0, 0, dm_data.width,
dm_data.height, TK_PHOTO_COMPOSITE_SET);
+
+ // We now have a window - set the default idata size
+ idata->width = dm_data.width;
+ idata->height = dm_data.height;
+
+ // Define a canvas widget, pack it into the root window, and associate the
image with it
+ // TODO - should the canvas be inside a frame?
+ std::string canvas_cmd = std::string("canvas ") + std::string(DM_CANVAS) +
std::string(" -width ") + std::to_string(wsize) + std::string(" -height ") +
std::to_string(wsize) + std::string(" -borderwidth 0");
+ Tcl_Eval(interp, canvas_cmd.c_str());
+ std::string pack_cmd = std::string("pack ") + std::string(DM_CANVAS) +
std::string(" -fill both -expand 1");
+ Tcl_Eval(interp, pack_cmd.c_str());
+ std::string canvas_img_cmd = std::string(DM_CANVAS) + std::string(" create
image 0 0 -image ") + std::string(DM_PHOTO) + std::string(" -anchor nw");
+ Tcl_Eval(interp, canvas_img_cmd.c_str());
+
+
+ // 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}");
+ Tcl_Eval(interp, bind_cmd.c_str());
+
+
+ // Register an update command so we can change the image contents
+ (void)Tcl_CreateCommand(interp, "image_update", (Tcl_CmdProc
*)image_update_data, (ClientData)idata, (Tcl_CmdDeleteProc* )NULL);
+ // Establish the Button-2 and Button-3 event as triggers for updating the
image contents
+ bind_cmd = std::string("bind . <Button-2> {image_update}");
+ Tcl_Eval(interp, bind_cmd.c_str());
+ bind_cmd = std::string("bind . <Button-3> {image_update}");
+ Tcl_Eval(interp, bind_cmd.c_str());
+
+ // 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;
+ if (Tcl_CreateThread(&threadID, Dm_Update_Manager, (ClientData)idata,
TCL_THREAD_STACK_DEFAULT, TCL_THREAD_JOINABLE) != TCL_OK) {
+ std::cerr << "can't create thread\n";
+ }
+ idata->manager_id = threadID;
+
+ // 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) {
+ Tcl_DoOneEvent(0);
+ if (!Tk_GetNumMainWindows()) {
+
+ // If we've closed the window, we're done - start spreading the word
+ Tcl_MutexLock(&dilock);
+ idata->render_shutdown = 1;
+
+ // After setting the shutdown flag, send an event to wake up the
update manager thread.
+ // It will see the change in status and proceed to shut itself down.
+ struct DmRenderEvent *threadEventPtr = (struct DmRenderEvent
*)ckalloc(sizeof(DmRenderEvent));
+ threadEventPtr->idata = idata;
+ threadEventPtr->event.proc = noop_proc;
+ Tcl_ThreadQueueEvent(idata->manager_id, (Tcl_Event *)
threadEventPtr, TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(idata->manager_id);
+ Tcl_MutexUnlock(&dilock);
+
+ // Wait for the thread cleanup to complete
+ int tret;
+ Tcl_JoinThread(threadID, &tret);
+ if (tret != TCL_OK) {
+ std::cerr << "Failed to shut down display management thread\n";
+ } else {
+ std::cout << "Successful display management thread shutdown\n";
+ }
+
+ // Clean up memory and exit
+ BU_PUT(idata, struct img_data);
+ exit(0);
+ }
+ }
+}
+
+// Local Variables:
+// tab-width: 8
+// mode: C++
+// c-basic-offset: 4
+// indent-tabs-mode: t
+// c-file-style: "stroustrup"
+// End:
+// ex: shiftwidth=4 tabstop=8
Property changes on: brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_img.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Added: brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_tevent.cpp
===================================================================
--- brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_tevent.cpp
(rev 0)
+++ brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_tevent.cpp 2020-04-29
16:02:41 UTC (rev 75630)
@@ -0,0 +1,196 @@
+/* T C L _ T E V E N T . C P P
+ * BRL-CAD
+ *
+ * Published in 2020 by the United States Government.
+ * This work is in the public domain.
+ *
+ */
+/** @file tcl_tevent.cpp
+ *
+ */
+
+#include "common.h"
+
+#include <iostream>
+
+#include "tcl.h"
+#include "tk.h"
+
+TCL_DECLARE_MUTEX(calclock)
+TCL_DECLARE_MUTEX(threadMutex)
+
+struct cdata {
+ int val1;
+ int val2;
+ int val3;
+ int shutdown;
+ Tcl_ThreadId parent_id;
+ Tcl_ThreadId worker_id;
+ Tcl_Interp *parent_interp;
+};
+
+struct EventResult {
+ Tcl_Condition done;
+ int ret;
+};
+
+struct UpdateEvent {
+ Tcl_Event event; /* Must be first */
+ struct EventResult *result;
+ struct cdata *d;
+};
+
+struct CalcEvent {
+ Tcl_Event event; /* Must be first */
+ struct EventResult *result;
+ struct cdata *d;
+};
+
+
+static int
+UpdateProc1(Tcl_Event *evPtr, int UNUSED(mask))
+{
+ struct UpdateEvent *EventPtr = (UpdateEvent *) evPtr;
+ struct cdata *d = EventPtr->d;
+
+ Tcl_MutexLock(&calclock);
+ d->val1 = d->val1 + 1000;
+ Tcl_MutexUnlock(&calclock);
+
+ std::cout << "val1: " << d->val1 << "\n";
+
+ // Return one to signify a successful completion of the process execution
+ return 1;
+}
+
+static int
+UpdateProc3(Tcl_Event *evPtr, int UNUSED(mask))
+{
+ struct UpdateEvent *EventPtr = (UpdateEvent *) evPtr;
+ struct cdata *d = EventPtr->d;
+
+ Tcl_MutexLock(&calclock);
+ d->val3 = d->val3 + 2000;
+ Tcl_MutexUnlock(&calclock);
+
+ std::cout << "val3: " << d->val3 << "\n";
+
+ // Return one to signify a successful completion of the process execution
+ return 1;
+}
+
+static Tcl_ThreadCreateType
+Calc_Thread(ClientData clientData)
+{
+ struct cdata *d = (struct cdata *)clientData;
+ Tcl_Interp *interp = Tcl_CreateInterp();
+ Tcl_Init(interp);
+
+ while (!d->shutdown) {
+
+ Tcl_DoOneEvent(0);
+
+ Tcl_MutexLock(&calclock);
+ d->val2 = d->val2 + 500;
+ Tcl_MutexUnlock(&calclock);
+
+ std::cout << "val2: " << d->val2 << "\n";
+ Tcl_Sleep(100);
+
+ // Generate an event for the parent thread to let it know to update
+ // based on the calculation result
+ struct UpdateEvent *threadEventPtr = (struct UpdateEvent
*)ckalloc(sizeof(UpdateEvent));
+ threadEventPtr->d = d;
+ threadEventPtr->event.proc = UpdateProc3;
+ Tcl_MutexLock(&threadMutex);
+ Tcl_ThreadQueueEvent(d->parent_id, (Tcl_Event *) threadEventPtr,
TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(d->parent_id);
+ Tcl_MutexUnlock(&threadMutex);
+ }
+
+ // We're well and truly done - the application is closing down
+ Tcl_ExitThread(TCL_OK);
+ TCL_THREAD_CREATE_RETURN;
+}
+
+int
+calc_f1(ClientData clientData, Tcl_Interp *UNUSED(interp), int UNUSED(argc),
char **UNUSED(argv))
+{
+ struct cdata *d = (struct cdata *)clientData;
+ // Generate an event for the worker thread to let it know to update
+ // based on the calculation result
+ struct UpdateEvent *threadEventPtr = (struct UpdateEvent
*)ckalloc(sizeof(UpdateEvent));
+ threadEventPtr->d = d;
+ threadEventPtr->event.proc = UpdateProc1;
+ Tcl_MutexLock(&threadMutex);
+ Tcl_ThreadQueueEvent(d->worker_id, (Tcl_Event *) threadEventPtr,
TCL_QUEUE_TAIL);
+ Tcl_ThreadAlert(d->worker_id);
+ Tcl_MutexUnlock(&threadMutex);
+
+ return TCL_OK;
+}
+
+int
+main(int UNUSED(argc), const char *argv[])
+{
+ struct cdata ddata;
+ ddata.val1 = 0;
+ ddata.val2 = 0;
+ ddata.val3 = 0;
+ ddata.shutdown = 0;
+ ddata.parent_id = Tcl_GetCurrentThread();
+
+ // Set up Tcl/Tk
+ Tcl_FindExecutable(argv[0]);
+ Tcl_Interp *interp = Tcl_CreateInterp();
+ Tcl_Init(interp);
+ Tk_Init(interp);
+
+ // Save a pointer so we can get at the interp later
+ ddata.parent_interp = interp;
+
+ // Make a simple toplevel window
+ Tk_Window tkwin = Tk_MainWindow(interp);
+ Tk_GeometryRequest(tkwin, 512, 512);
+ Tk_MakeWindowExist(tkwin);
+ Tk_MapWindow(tkwin);
+
+ // Whenever the window changes, call the incrementer
+ (void)Tcl_CreateCommand(interp, "calc1", (Tcl_CmdProc *)calc_f1,
(ClientData)&ddata, (Tcl_CmdDeleteProc* )NULL);
+ Tcl_Eval(interp, "bind . <Configure> {calc1}");
+
+ // Multithreading experiment
+ Tcl_ThreadId threadID;
+ if (Tcl_CreateThread(&threadID, Calc_Thread, (ClientData)&ddata,
TCL_THREAD_STACK_DEFAULT, TCL_THREAD_JOINABLE) != TCL_OK) {
+ std::cerr << "can't create thread\n";
+ }
+ ddata.worker_id = threadID;
+
+ // Enter the main application loop - the initial image will appear, and
Button-1 mouse
+ // clicks on the window should generate and display new images
+ while (1) {
+ Tcl_DoOneEvent(0);
+ if (ddata.val3 > 100000 && ddata.val2 > 100000) {
+ Tcl_MutexLock(&calclock);
+ ddata.shutdown = 1;
+ Tcl_MutexUnlock(&calclock);
+ int tret;
+ Tcl_JoinThread(threadID, &tret);
+ if (tret != TCL_OK) {
+ std::cerr << "Failure to shut down work thread\n";
+ } else {
+ std::cout << "Successful work thread shutdown\n";
+ }
+ exit(0);
+ }
+ }
+}
+
+// Local Variables:
+// tab-width: 8
+// mode: C++
+// c-basic-offset: 4
+// indent-tabs-mode: t
+// c-file-style: "stroustrup"
+// End:
+// ex: shiftwidth=4 tabstop=8
Property changes on: brlcad/branches/dm-fb-merge/src/libdm/tests/tcl_tevent.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Modified: brlcad/branches/dm-fb-merge/src/libged/typein.c
===================================================================
--- brlcad/branches/dm-fb-merge/src/libged/typein.c 2020-04-29 15:00:45 UTC
(rev 75629)
+++ brlcad/branches/dm-fb-merge/src/libged/typein.c 2020-04-29 16:02:41 UTC
(rev 75630)
@@ -3110,14 +3110,7 @@
script_in(struct ged *UNUSED(gedp), const char **cmd_argvs, struct
rt_db_internal *intern)
{
struct rt_script_internal *script_ip;
- int i=0;
- /* !!! temporary debugging, print out our args */
- while (cmd_argvs && cmd_argvs[i] != NULL) {
- bu_log("cmd_argvs[%d] = [%s]\n", i, cmd_argvs[i]);
- i++;
- }
-
intern->idb_type = ID_SCRIPT;
intern->idb_meth = &OBJ[ID_SCRIPT];
BU_ALLOC(intern->idb_ptr, struct rt_script_internal);
@@ -3127,9 +3120,6 @@
bu_vls_init(&script_ip->s_type);
bu_vls_strcpy(&script_ip->s_type, cmd_argvs[3]);
- /* !!! */
- bu_log("done creating script object, next it gets exported\n");
-
return GED_OK;
}
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