source: geovis/trunk/RenderServer.cpp @ 5972

Last change on this file since 5972 was 5942, checked in by ldelgass, 8 years ago

Require osgearth 2.7, tag place nodes for picking.

File size: 12.2 KB
Line 
1/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 * Copyright (C) 2013-2015  HUBzero Foundation, LLC
4 *
5 * Author: Leif Delgass <ldelgass@purdue.edu>
6 */
7
8#include <cstdio>
9#include <cstring>
10#include <cstdlib>
11#include <cerrno>
12#include <unistd.h>
13#include <signal.h>
14#include <sys/types.h>
15#include <sys/stat.h>
16#include <fcntl.h>
17#include <sys/time.h>
18#include <sys/times.h>
19
20#include <string>
21#include <sstream>
22
23#include <tcl.h>
24
25#include "Trace.h"
26#include "FileUtil.h"
27#include "ReadBuffer.h"
28#include "RenderServer.h"
29#include "RendererCmd.h"
30#include "Renderer.h"
31#include "Stats.h"
32#include "PPMWriter.h"
33#include "TGAWriter.h"
34#ifdef USE_THREADS
35#include <pthread.h>
36#include "ResponseQueue.h"
37#ifdef USE_READ_THREAD
38#include "CommandQueue.h"
39#endif
40#endif
41
42using namespace GeoVis;
43
44Stats GeoVis::g_stats;
45
46int GeoVis::g_statsFile = -1; ///< Stats output file descriptor.
47int GeoVis::g_fdIn = STDIN_FILENO; ///< Input file descriptor
48int GeoVis::g_fdOut = STDOUT_FILENO; ///< Output file descriptor
49FILE *GeoVis::g_fOut = NULL; ///< Output file handle
50FILE *GeoVis::g_fLog = NULL; ///< Trace logging file handle
51Renderer *GeoVis::g_renderer = NULL; ///< Main render worker
52ReadBuffer *GeoVis::g_inBufPtr = NULL; ///< Socket read buffer
53#ifdef USE_THREADS
54ResponseQueue *GeoVis::g_outQueue = NULL;
55#ifdef USE_READER_THREAD
56CommandQueue *GeoVis::g_inQueue = NULL;
57#endif
58#endif
59
60static int
61queueViewpoint()
62{
63    osgEarth::Viewpoint view = g_renderer->getViewpoint();
64
65    std::ostringstream oss;
66    size_t len = 0;
67    oss << "nv>camera get "
68        << view.focalPoint()->x() << " "
69        << view.focalPoint()->y() << " "
70        << view.focalPoint()->z() << " "
71        << view.heading()->getValue() << " "
72        << view.pitch()->getValue() << " "
73        << view.range()->getValue()
74        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getHorizInitString()) << "}"
75        << " {" << ((view.focalPoint()->getSRS() == NULL) ? "" : view.focalPoint()->getSRS()->getVertInitString()) << "}"
76        << "\n";
77    std::string ostr = oss.str();
78    len = ostr.size();
79#ifdef USE_THREADS
80    queueResponse(ostr.c_str(), len, Response::VOLATILE);
81#else
82    ssize_t bytesWritten = SocketWrite(ostr.c_str(), len);
83
84    if (bytesWritten < 0) {
85        return TCL_ERROR;
86    }
87#endif /*USE_THREADS*/
88    return TCL_OK;
89}
90
91#ifdef USE_THREADS
92static void
93queueFrame(ResponseQueue *queue, const unsigned char *imgData)
94{
95#ifdef DEBUG_WRITE_FRAME_FILE
96
97#ifdef RENDER_TARGA
98    writeTGAFile("/tmp/frame.tga",
99                 imgData,
100                 g_renderer->getWindowWidth(),
101                 g_renderer->getWindowHeight(),
102                 TARGA_BYTES_PER_PIXEL);
103#else
104    writeTGAFile("/tmp/frame.tga",
105                 imgData,
106                 g_renderer->getWindowWidth(),
107                 g_renderer->getWindowHeight(),
108                 TARGA_BYTES_PER_PIXEL,
109                 true);
110#endif  /*RENDER_TARGA*/
111
112#else
113 
114#ifdef RENDER_TARGA
115    queueTGA(queue, "nv>image -type image -bytes",
116             imgData,
117             g_renderer->getWindowWidth(),
118             g_renderer->getWindowHeight(),
119             TARGA_BYTES_PER_PIXEL);
120#else
121    char cmd[256];
122    snprintf(cmd, sizeof(cmd), "nv>image -type image -token %lu -bytes", g_stats.nCommands);
123    queuePPM(queue, cmd,
124             imgData,
125             g_renderer->getWindowWidth(),
126             g_renderer->getWindowHeight());
127#endif  /*RENDER_TARGA*/
128#endif  /*DEBUG_WRITE_FRAME_FILE*/
129}
130
131#else
132
133static void
134writeFrame(int fd, const unsigned char *imgData)
135{
136#ifdef DEBUG_WRITE_FRAME_FILE
137
138#ifdef RENDER_TARGA
139    writeTGAFile("/tmp/frame.tga",
140                 imgData,
141                 g_renderer->getWindowWidth(),
142                 g_renderer->getWindowHeight(),
143                 TARGA_BYTES_PER_PIXEL);
144#else
145    writeTGAFile("/tmp/frame.tga",
146                 imgData,
147                 g_renderer->getWindowWidth(),
148                 g_renderer->getWindowHeight(),
149                 TARGA_BYTES_PER_PIXEL,
150                 true);
151#endif  /*RENDER_TARGA*/
152
153#else
154
155#ifdef RENDER_TARGA
156    writeTGA(fd, "nv>image -type image -bytes",
157             imgData,
158             g_renderer->getWindowWidth(),
159             g_renderer->getWindowHeight(),
160             TARGA_BYTES_PER_PIXEL);
161#else
162    writePPM(fd, "nv>image -type image -bytes",
163             imgData,
164             g_renderer->getWindowWidth(),
165             g_renderer->getWindowHeight());
166#endif  /*RENDER_TARGA*/
167#endif  /*DEBUG_WRITE_FRAME_FILE*/
168}
169#endif /*USE_THREADS*/
170
171static int
172sendAck()
173{
174    std::ostringstream oss;
175    oss << "nv>ok -token " << g_stats.nCommands << "\n";
176    std::string ostr = oss.str();
177    int nBytes = ostr.length();
178
179    TRACE("Sending OK for commands through %lu", g_stats.nCommands);
180#ifdef USE_THREADS
181    queueResponse(ostr.c_str(), nBytes, Response::VOLATILE, Response::OK);
182#else
183    if (write(g_fdOut, ostr.c_str(), nBytes) < 0) {
184        ERROR("write failed: %s", strerror(errno));
185        return -1;
186    }
187#endif
188    return 0;
189}
190
191static void
192initService()
193{
194    g_fOut = fdopen(g_fdOut, "w");
195    // If running without socket, use stdout for debugging
196    if (g_fOut == NULL && g_fdOut != STDOUT_FILENO) {
197        g_fdOut = STDOUT_FILENO;
198        g_fOut = fdopen(g_fdOut, "w");
199    }
200
201    const char *user = getenv("USER");
202    char *logName = NULL;
203    int logNameLen = 0;
204
205    if (user == NULL) {
206        logNameLen = 19+1;
207        logName = (char *)calloc(logNameLen, sizeof(char));
208        strncpy(logName, "/tmp/geovis_log.txt", logNameLen);
209    } else {
210        logNameLen = 16+strlen(user)+4+1;
211        logName = (char *)calloc(logNameLen, sizeof(char));
212        strncpy(logName, "/tmp/geovis_log_", logNameLen);
213        strncat(logName, user, strlen(user));
214        strncat(logName, ".txt", 4);
215    }
216
217    // open log and map stderr to log file
218    g_fLog = fopen(logName, "w");
219    dup2(fileno(g_fLog), STDERR_FILENO);
220    // If we are writing to socket, map stdout to log
221    if (g_fdOut != STDOUT_FILENO) {
222        dup2(fileno(g_fLog), STDOUT_FILENO);
223    }
224
225    fflush(stdout);
226
227    // clean up malloc'd memory
228    if (logName != NULL) {
229        free(logName);
230    }
231}
232
233static void
234exitService()
235{
236    TRACE("Enter");
237
238    serverStats(g_stats, 0);
239
240    // close log file
241    if (g_fLog != NULL) {
242        fclose(g_fLog);
243        g_fLog = NULL;
244    }
245}
246
247#ifdef USE_THREADS
248
249#ifdef USE_READ_THREAD
250static void *
251readerThread(void *clientData)
252{
253    Tcl_Interp *interp = (Tcl_Interp *)clientData;
254
255    TRACE("Starting reader thread");
256
257    queueCommands(interp, NULL, g_inBufPtr);
258
259    return NULL;
260}
261#endif
262
263static void *
264writerThread(void *clientData)
265{
266    ResponseQueue *queue = (ResponseQueue *)clientData;
267
268    TRACE("Starting writer thread");
269    for (;;) {
270        Response *response = queue->dequeue();
271        if (response == NULL)
272            continue;
273        if (fwrite(response->message(), sizeof(unsigned char), response->length(),
274                   g_fOut) != response->length()) {
275            ERROR("short write while trying to write %ld bytes",
276                  response->length());
277        }
278        fflush(g_fOut);
279#ifdef TRACE_RESPONSE_QUEUE
280        TRACE("Wrote response of type %d", response->type());
281#endif
282        delete response;
283        if (feof(g_fOut))
284            break;
285    }   
286    return NULL;
287}
288
289#endif  /*USE_THREADS*/
290
291int
292main(int argc, char *argv[])
293{
294    // Ignore SIGPIPE.  **Is this needed? **
295    signal(SIGPIPE, SIG_IGN);
296
297    std::string resourcePath;
298    while (1) {
299        int c = getopt(argc, argv, "p:i:o:");
300        if (c == -1) {
301            break;
302        }
303        switch (c) {
304        case 'p':
305            resourcePath = optarg;
306            break;
307        case 'i': {
308            int fd = atoi(optarg);
309            if (fd >=0 && fd < 5) {
310                g_fdIn = fd;
311            }
312        }
313            break;
314        case 'o': {
315            int fd = atoi(optarg);
316            if (fd >=0 && fd < 5) {
317                g_fdOut = fd;
318            }
319        }
320            break;
321        case '?':
322            break;
323        default:
324            return 1;
325        }
326    }
327
328    initService();
329    initLog();
330
331    memset(&g_stats, 0, sizeof(g_stats));
332    gettimeofday(&g_stats.start, NULL);
333
334    TRACE("Starting GeoVis Server");
335
336    // Sanity check: log descriptor can't be used for client IO
337    if (fileno(g_fLog) == g_fdIn) {
338        ERROR("Invalid input file descriptor");
339        return 1;
340    }
341    if (fileno(g_fLog) == g_fdOut) {
342        ERROR("Invalid output file descriptor");
343        return 1;
344    }
345    TRACE("File descriptors: in %d out %d log %d", g_fdIn, g_fdOut, fileno(g_fLog));
346
347    /* This synchronizes the client with the server, so that the client
348     * doesn't start writing commands before the server is ready. It could
349     * also be used to supply information about the server (version, memory
350     * size, etc). */
351    fprintf(g_fOut, "GeoVis %s (build %s)\n", GEOVIS_VERSION_STRING, SVN_VERSION);
352    fflush(g_fOut);
353
354    g_renderer = new Renderer();
355    g_inBufPtr = new ReadBuffer(g_fdIn, 1<<12);
356
357    if (resourcePath.empty()) {
358        resourcePath = "/usr/share/osgearth/data";
359    }
360    g_renderer->setResourcePath(resourcePath);
361
362    TRACE("Default Tcl encoding dir: %s", Tcl_GetDefaultEncodingDir());
363    //Tcl_SetDefaultEncodingDir("encoding");
364
365    Tcl_Interp *interp = Tcl_CreateInterp();
366    initTcl(interp, NULL);
367
368#ifdef USE_THREADS
369    g_outQueue = new ResponseQueue();
370
371    pthread_t writerThreadId;
372    if (pthread_create(&writerThreadId, NULL, &writerThread, g_outQueue) < 0) {
373        ERROR("Can't create writer thread: %s", strerror(errno));
374    }
375#endif
376
377    osg::ref_ptr<osg::Image> imgData;
378
379    // Start main server loop
380    for (;;) {
381        long timeout = g_renderer->getTimeout();
382#ifdef SLEEP_AFTER_QUEUE_FRAME
383        g_renderer->markFrameStart();
384#endif
385        int cmdStatus = processCommands(interp, NULL, g_inBufPtr, g_fdOut, timeout);
386        if (cmdStatus < 0)
387            break;
388
389        if (g_renderer->render()) {
390            FRAME("Rendered new frame");
391            imgData = g_renderer->getRenderedFrame();
392            if (imgData == NULL) {
393                ERROR("Empty image");
394            } else {
395                FRAME("Image: %d x %d", imgData->s(), imgData->t());
396
397                if (imgData->s() == g_renderer->getWindowWidth() &&
398                    imgData->t() == g_renderer->getWindowHeight()) {
399                    queueViewpoint();
400#ifdef USE_THREADS
401                    queueFrame(g_outQueue, imgData->data());
402#else
403                    writeFrame(g_fdOut, imgData->data());
404#endif
405                }
406                g_stats.nFrames++;
407                g_stats.nFrameBytes += imgData->s() * imgData->t() * 3;
408            }
409#ifdef SLEEP_AFTER_QUEUE_FRAME
410            g_renderer->markFrameEnd();
411#endif
412        } else {
413            //TRACE("No render required");
414            if (cmdStatus > 1) {
415                sendAck();
416            } else {
417                TRACE("No render required and status = %d", cmdStatus);
418            }
419        }
420
421#if 0
422        double x, y, z;
423        if (g_renderer->getMousePoint(&x, &y, &z)) {
424            // send coords to client
425            size_t length;
426            char mesg[256];
427
428            length = snprintf(mesg, sizeof(mesg),
429                              "nv>map coords %g %g %g\n", x, y, z);
430
431            queueResponse(mesg, length, Response::VOLATILE);
432        }
433#endif
434
435        if (g_inBufPtr->status() == ReadBuffer::ENDFILE)
436            break;
437    }
438#ifdef USE_THREADS
439    // Writer thread is probably blocked on sem_wait, so cancel instead
440    // of joining
441    if (pthread_cancel(writerThreadId) < 0) {
442        ERROR("Can't cancel writer thread: %s", strerror(errno));
443    } else {
444        TRACE("Cancelled writer thread");
445    }
446
447    TRACE("Deleting ResponseQueue");
448    delete g_outQueue;
449    g_outQueue = NULL;
450#endif
451
452    TRACE("Stopping Tcl interpreter");
453    exitTcl(interp);
454    interp = NULL;
455
456    TRACE("Deleting ReadBuffer");
457    delete g_inBufPtr;
458    g_inBufPtr = NULL;
459
460    std::string cacheDir = g_renderer->getCacheDirectory();
461    TRACE("Deleting renderer");
462    delete g_renderer;
463    g_renderer = NULL;
464    // Clean up cache directory after renderer's threads have finished
465    if (!cacheDir.empty()) {
466        removeDirectory(cacheDir.c_str());
467    }
468
469    TRACE("Exiting GeoVis Server");
470
471    closeLog();
472    exitService();
473
474    return 0;
475}
Note: See TracBrowser for help on using the repository browser.