source: geovis/trunk/RenderServer.cpp @ 5915

Last change on this file since 5915 was 5915, checked in by ldelgass, 9 years ago

Add zoom camera to extent (fit to vertical FOV) by layer or by explicit bounds.
Add support for image layer using ArcGIS server driver. Map projection must
use a projected profile, and "geodetic"/"wgs84"/"plate-carre" are not permitted
as map profiles: use epsg:32663 for equirectangular instead. This has linear
units in meters, not angular units in X/Y.

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