source: geovis/trunk/RenderServer.cpp @ 4643

Last change on this file since 4643 was 4635, checked in by ldelgass, 10 years ago

Add resources directory command line option, preliminary testing of new map
coords syntax

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