source: geovis/trunk/RenderServer.cpp @ 4635

Last change on this file since 4635 was 4635, checked in by ldelgass, 6 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.