| /* |
| Copyright (c) 2011 NVIDIA Corporation |
| Copyright (c) 2011-2012 Cass Everitt |
| Copyright (c) 2012 Scott Nations |
| Copyright (c) 2012 Mathias Schott |
| Copyright (c) 2012 Nigel Stewart |
| All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without modification, |
| are permitted provided that the following conditions are met: |
| |
| Redistributions of source code must retain the above copyright notice, this |
| list of conditions and the following disclaimer. |
| |
| Redistributions in binary form must reproduce the above copyright notice, |
| this list of conditions and the following disclaimer in the documentation |
| and/or other materials provided with the distribution. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, |
| INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
| OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED |
| OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "pch.h" /* For MS precompiled header support */ |
| |
| #include "RegalUtil.h" |
| |
| #if ! REGAL_HTTP |
| |
| REGAL_GLOBAL_BEGIN |
| #include "RegalHttp.h" |
| |
| REGAL_GLOBAL_END |
| |
| REGAL_NAMESPACE_BEGIN |
| |
| Http::Http() {} |
| Http::~Http() {} |
| void Http::Init() {} |
| void Http::Start() {} |
| void Http::Stop() {} |
| void Http::YieldToHttpServer( RegalContext *, bool ) {} |
| void Regal::Http::GlProcs::Initialize( Regal::Dispatch::GL * ) {} |
| |
| REGAL_NAMESPACE_END |
| |
| #else // REGAL_HTTP |
| |
| #define REGAL_HTTP_LOCAL_JQUERY 0 |
| |
| #define REGAL_HTTP_IMAGE_WRITE_IMPL 1 |
| |
| #if REGAL_HTTP_IMAGE_WRITE_IMPL |
| #define STB_IMAGE_WRITE_IMPLEMENTATION |
| #endif |
| #include "stb_image_write.h" |
| |
| |
| REGAL_GLOBAL_BEGIN |
| |
| //#include "pcre.h" |
| |
| #include "RegalHttp.h" |
| #include "RegalDispatchHttp.h" |
| #include "RegalContext.h" |
| #include "RegalToken.h" |
| #include "RegalShaderInstance.h" |
| #include "RegalWebJs.h" |
| |
| #include "RegalLog.h" |
| #include "RegalInit.h" |
| #include "RegalConfig.h" |
| #include "RegalThread.h" |
| #include "RegalFavicon.h" |
| #include "RegalContextInfo.h" |
| #include "RegalEmuInfo.h" |
| |
| #include <map> |
| |
| #include <string> |
| #include <cstdio> |
| #include <cstdarg> |
| using namespace std; |
| |
| #include "RegalPrint.h" |
| |
| |
| #include "mongoose.h" |
| |
| #if REGAL_SYS_WGL |
| extern "C" { BOOL __stdcall Sleep(DWORD); } |
| #endif |
| |
| REGAL_GLOBAL_END |
| |
| REGAL_NAMESPACE_BEGIN |
| |
| RegalContext::ParkProcs pp; |
| |
| mg_callbacks callbacks; // Callbacks |
| mg_context *ctx = NULL; // Mongoose context |
| |
| struct Connection { |
| Connection( mg_connection * conn ) : connection( conn ) { |
| request_info = mg_get_request_info( conn ); |
| ParseUri( request_info->uri ); |
| } |
| |
| void ParseUri( const string & uri ) { |
| size_t start = 1; |
| path.clear(); |
| for(;;) { |
| size_t end = uri.find( "/", start ); |
| string v = uri.substr( start, end - start ); |
| if( v.size() ) { |
| path.push_back( v ); |
| } |
| if( end == string::npos ) break; |
| start = end+1; |
| } |
| } |
| |
| vector<string> path; |
| mg_connection * connection; |
| const mg_request_info *request_info; |
| }; |
| |
| struct ScopedContextAcquire { |
| ScopedContextAcquire( RegalContext * _ctx ) : ctx(_ctx) { |
| ctx->http.AcquireAppContext( ctx ); |
| } |
| ~ScopedContextAcquire() { |
| ctx->http.ReleaseAppContext( ctx ); |
| } |
| RegalContext * ctx; |
| }; |
| |
| struct RequestHandler { |
| RequestHandler() {} |
| ~RequestHandler() {} |
| |
| virtual void HandleRequest( Connection & c ) = 0; |
| virtual std::string GetHandlerString() = 0; |
| bool Matches( const Connection & conn ) { |
| return conn.path.size() > 0 && conn.path[0] == GetHandlerString(); |
| } |
| |
| }; |
| void Redirect( Connection & conn, const string & redirect_to ); |
| |
| typedef map<string, RequestHandler *> HandlerMap; |
| HandlerMap handlers; |
| |
| void CreateHandlers(); |
| |
| void SendRootDocument( Connection & conn ); |
| |
| |
| |
| const char * const br = "<br/>\n"; |
| |
| static int log_message(const struct mg_connection * conn, const char *message) |
| { |
| UNUSED_PARAMETER(conn); |
| HTrace(message ? message : ""); |
| return 1; |
| } |
| |
| static int begin_request(struct mg_connection * conn) |
| { |
| const struct mg_request_info *request_info = mg_get_request_info( conn ); |
| |
| HTrace(request_info->request_method ? request_info->request_method : "", " ", |
| request_info->uri ? request_info->uri : "", |
| request_info->query_string ? "?" : "", |
| request_info->query_string ? request_info->query_string : ""); |
| |
| Connection connection( conn ); |
| |
| if( connection.path.size() > 0 ) { |
| for( HandlerMap::iterator i = handlers.begin(); i != handlers.end(); ++i ) { |
| RequestHandler *rh = i->second; |
| if( rh->Matches( connection ) ) { |
| rh->HandleRequest( connection ); |
| return 1; |
| } |
| } |
| Redirect( connection, "/" ); |
| } else { |
| SendRootDocument( connection ); |
| } |
| |
| return 1; // Mark as handled for Mongoose |
| } |
| |
| void Http::Start() |
| { |
| Internal("Http::Start","()"); |
| |
| if (enabled && !ctx) |
| { |
| memset(&callbacks,0,sizeof(callbacks)); |
| callbacks.log_message = log_message; |
| callbacks.begin_request = begin_request; |
| |
| // Try listening on the configured port number (8080 by default) |
| // and advance foward until one is available |
| |
| for (int i = 0; i<100; ++i) |
| { |
| string j = print_string(port+i); |
| const char *options[] = { "listening_ports", j.c_str(), "num_threads", "1", NULL}; |
| ctx = mg_start(&callbacks, NULL, options); |
| if (ctx) |
| break; |
| } |
| |
| const char *option = NULL; |
| if (ctx) |
| option = mg_get_option(ctx,"listening_ports"); |
| |
| if (option) |
| { |
| Info("Listening for HTTP connections on port ",option); |
| } |
| else |
| { |
| Info("Not listening for HTTP connections."); |
| } |
| } |
| } |
| |
| void Http::Stop() |
| { |
| Internal("Http::Stop","()"); |
| |
| if (ctx) |
| { |
| HTrace("Closing HTTP connections."); |
| |
| // Currently there is a problem with shutting down mongoose |
| // on Windows - so just skip the cleanup for now. |
| |
| #if !REGAL_SYS_WGL |
| mg_stop(ctx); |
| #endif |
| |
| ctx = NULL; |
| } |
| } |
| |
| void EraseLastComma( string & str ) { |
| if( str[ str.size() - 2 ] == ',' ) { |
| str.erase( str.size() - 2, 1 ); |
| } |
| } |
| |
| void SendText( Connection & conn, const string & contentType, const string & str ) { |
| string http = print_string( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: ", contentType, "\r\n" |
| "Content-Length: ", str.length(), "\r\n" |
| "\r\n" ); |
| http += str; |
| |
| mg_write( conn.connection, http.c_str(), http.length() ); |
| } |
| |
| void SendHTML( Connection & conn, const string & body, const string & head = string() ) { |
| string html = print_string( |
| "<html><head>\n", head, |
| "</head><body>\n" ); |
| html += body; |
| html += "</body></html>\n"; |
| SendText( conn, "text/html", html ); |
| } |
| |
| void SendRootDocument( Connection & conn ) { |
| string head; |
| #if REGAL_HTTP_LOCAL_JQUERY |
| head += "<link rel=\"stylesheet\" href=\"/script/jquery-ui.min.css\" />\n"; |
| head += "<script src=\"/script/jquery.min.js\"></script>\n"; |
| head += "<script src=\"/script/jquery-ui.min.js\"></script>\n"; |
| #else // ! REGAL_HTTP_LOCAL_JQUERY |
| head += "<link rel=\"stylesheet\" href=\"http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/le-frog/jquery-ui.min.css\" />\n"; |
| head += "<script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js\"></script>\n"; |
| head += "<script src=\"http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/jquery-ui.min.js\"></script>\n"; |
| #endif // REGAL_HTTP_LOCAL_JQUERY |
| head += "<script src=\"/script/regalweb.js\"></script>\n"; |
| string body; |
| SendHTML( conn, body, head ); |
| } |
| |
| |
| struct ScriptHandler : public RequestHandler { |
| |
| virtual void HandleRequest( Connection & conn ) { |
| if( conn.path.size() != 2 ) { |
| return; |
| } |
| if( conn.path[1] == "regalweb.js" ) { |
| FILE *fp = fopen( "/tmp/regalweb.js", "rb" ); |
| string code; |
| if( fp ) { |
| char buf[257]; |
| size_t count = 0; |
| while( (count = fread( buf, 1, sizeof( buf ) - 1, fp ) ) > 0 ) { |
| buf[count] = 0; |
| code += buf; |
| count = 0; |
| } |
| fclose( fp ); |
| } else { |
| code = regalwebjs; |
| } |
| SendText( conn, "application/javascript", code ); |
| #if REGAL_HTTP_LOCAL_JQUERY |
| } else if( conn.path[1] == "jquery.min.js" ) { |
| string code = jqueryminjs; |
| SendText( conn, "application/javascript", code ); |
| } else if( conn.path[1] == "jquery-ui.min.js" ) { |
| string code = jqueryuiminjs; |
| SendText( conn, "application/javascript", code ); |
| } else if( conn.path[1] == "jquery-ui.min.css" ) { |
| string css = jqueryuimincss; |
| SendText( conn, "text/css", css ); |
| #endif // REGAL_HTTP_LOCAL_JQUERY |
| } |
| } |
| virtual string GetHandlerString() { |
| return "script"; |
| } |
| |
| }; |
| |
| struct FaviconHandler : public RequestHandler { |
| |
| virtual void HandleRequest( Connection & conn ) { |
| const mg_request_info *request_info = mg_get_request_info( conn.connection ); |
| |
| RegalAssert( strcmp("/favicon.ico",request_info->uri) == 0 ); |
| string http = print_string( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: image/x-icon\r\n" |
| "Content-Length: ", sizeof(favicon), "\r\n" |
| "\r\n"); |
| |
| mg_write(conn.connection, http.c_str(), http.length() ); |
| mg_write(conn.connection, favicon, sizeof(favicon) ); |
| } |
| virtual string GetHandlerString() { |
| return "favicon.ico"; |
| } |
| |
| }; |
| |
| struct ContextsHandler : public RequestHandler { |
| virtual void HandleRequest( Connection & conn ) { |
| //const mg_request_info *request_info = mg_get_request_info( conn.connection ); |
| //RegalAssert( strcmp("/contexts",request_info->uri) == 0 ); |
| string body; |
| ::REGAL_NAMESPACE_INTERNAL::Init::getContextListingHTML(body); |
| SendHTML( conn, body ); |
| } |
| virtual string GetHandlerString() { |
| return "contexts"; |
| } |
| }; |
| |
| enum TextureParameterType { TPT_Float, TPT_Integer, TPT_Enum }; |
| struct TextureObjectParameter { |
| GLenum pname; |
| TextureParameterType type; |
| int numComponents; |
| GLfloat initVal[4]; // should be able to cast a float into whatever is needed |
| }; |
| |
| const TextureObjectParameter top[] = { |
| { GL_DEPTH_STENCIL_TEXTURE_MODE, TPT_Enum, 1, { GL_DEPTH_COMPONENT, 0, 0, 0 } }, |
| { GL_TEXTURE_BASE_LEVEL, TPT_Integer, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_BORDER_COLOR, TPT_Float, 4, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_COMPARE_MODE, TPT_Enum, 1, { GL_NONE, 0, 0, 0 } }, |
| { GL_TEXTURE_COMPARE_FUNC, TPT_Enum, 1, { GL_LEQUAL, 0, 0, 0 } }, |
| { GL_TEXTURE_IMMUTABLE_FORMAT, TPT_Integer, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_IMMUTABLE_LEVELS, TPT_Integer, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_LOD_BIAS, TPT_Float, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_MAG_FILTER, TPT_Enum, 1, { GL_LINEAR, 0, 0, 0 } }, |
| { GL_TEXTURE_MAX_LEVEL, TPT_Integer, 1, { 1000, 0, 0, 0 } }, |
| { GL_TEXTURE_MAX_LOD, TPT_Float, 1, { 1000, 0, 0, 0 } }, |
| { GL_TEXTURE_MIN_FILTER, TPT_Enum, 1, { GL_NEAREST_MIPMAP_LINEAR, 0, 0, 0 } }, |
| { GL_TEXTURE_MIN_LOD, TPT_Float, 1, { -1000, 0, 0, 0 } }, |
| { GL_TEXTURE_SWIZZLE_RGBA, TPT_Enum, 4, { GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA } }, |
| { GL_TEXTURE_VIEW_MIN_LAYER, TPT_Integer, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_VIEW_MIN_LEVEL, TPT_Integer, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_VIEW_NUM_LAYERS, TPT_Integer, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_VIEW_NUM_LEVELS, TPT_Integer, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_WRAP_S, TPT_Enum, 1, { GL_REPEAT, 0, 0, 0 } }, |
| { GL_TEXTURE_WRAP_T, TPT_Enum, 1, { GL_REPEAT, 0, 0, 0 } }, |
| { GL_TEXTURE_WRAP_R, TPT_Enum, 1, { GL_REPEAT, 0, 0, 0 } } |
| }; |
| const TextureObjectParameter tolp[] = { |
| { GL_TEXTURE_INTERNAL_FORMAT, TPT_Enum, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_WIDTH, TPT_Integer, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_HEIGHT, TPT_Integer, 1, { 0, 0, 0, 0 } }, |
| { GL_TEXTURE_DEPTH, TPT_Integer, 1, { 0, 0, 0, 0 } } |
| }; |
| |
| string PrintTextureObjectParameter( const TextureObjectParameter & p, const GLfloat *fval ) { |
| string json; |
| switch( p.type ) { |
| case TPT_Enum: { |
| json += print_string( "\"", Token::GLenumToString( p.pname ), "\": " ); |
| if( p.numComponents > 1 ) { json += "[ "; } |
| for( int comp = 0; comp < p.numComponents; comp++ ) { |
| json += print_string( "\"", Token::GLenumToString( int( fval[comp] ) ), "\"" ); |
| if( comp < p.numComponents - 1 ) { json += ", "; } |
| } |
| if( p.numComponents > 1 ) { json += "]"; } |
| json += ",\n"; |
| } break; |
| case TPT_Integer: { |
| json += print_string( "\"", Token::GLenumToString( p.pname ), "\": " ); |
| if( p.numComponents > 1 ) { json += "[ "; } |
| for( int comp = 0; comp < p.numComponents; comp++ ) { |
| json += print_string( int( fval[comp] ) ); |
| if( comp < p.numComponents - 1 ) { json += ", "; } |
| } |
| if( p.numComponents > 1 ) { json += "]"; } |
| json += ",\n"; |
| } break; |
| case TPT_Float: { |
| json += print_string( "\"", Token::GLenumToString( p.pname ), "\": " ); |
| if( p.numComponents > 1 ) { json += "[ "; } |
| for( int comp = 0; comp < p.numComponents; comp++ ) { |
| json += print_string( fval[comp] ); |
| if( comp < p.numComponents - 1 ) { json += ", "; } |
| } |
| if( p.numComponents > 1 ) { json += "]"; } |
| json += ",\n"; |
| } break; |
| default: break; |
| } |
| return json; |
| } |
| |
| struct TextureHandler : public RequestHandler { |
| virtual void HandleRequest( Connection & conn ) { |
| RegalContext * ctx = ::REGAL_NAMESPACE_INTERNAL::Init::getContextByIndex( 0 ); |
| Http & h = ctx->http; |
| if( ctx == NULL ) { |
| return; |
| } |
| string json; |
| if( conn.path.size() == 1 ) { |
| |
| json += "[ "; |
| for( map<GLuint, HttpTextureInfo >::iterator i = h.texture.begin(); i != h.texture.end(); ++i ) { |
| json += print_string( i->first, ", " ); |
| } |
| EraseLastComma( json ); |
| json += "]\n"; |
| SendText( conn, "application/json", json ); |
| |
| } else if( conn.path.size() >= 2 ) { |
| |
| string tex = conn.path[1]; |
| GLint texname = atoi( tex.c_str() ); |
| HttpTextureInfo & texinfo = h.texture[ texname ]; |
| if( conn.path.size() == 2 ) { |
| int indent = 0; |
| json += "{\n"; |
| indent += 2; |
| if( ctx->emuInfo->gl_ext_direct_state_access == GL_TRUE || ctx->info->gl_ext_direct_state_access ) { |
| h.AcquireAppContext( ctx ); |
| json += " \"name\": " + tex + ",\n"; |
| GLint baseLevel = 0; |
| GLint maxLevel = 0; |
| json += " \"target\": \"" + string( Token::GLenumToString( texinfo.target ) ) + "\",\n"; |
| for( int i = 0; i < sizeof(top)/sizeof(top[0]); i++ ) { |
| GLfloat fval[4] = { 0, 0, 0, 0 }; |
| const TextureObjectParameter & p = top[i]; |
| h.gl.GetTextureParameter( ctx, texname, GL_TEXTURE_2D, p.pname, fval ); |
| switch( p.pname ) { |
| case GL_TEXTURE_BASE_LEVEL: baseLevel = GLint(fval[0]); break; |
| case GL_TEXTURE_MAX_LEVEL: maxLevel = GLint(fval[0]); break; |
| default: break; |
| } |
| if( fval[0] != p.initVal[0] ) { |
| json += string( indent, ' ' ) + PrintTextureObjectParameter( p, fval ); |
| } |
| } |
| json += string( indent, ' ' ) + "\"levels\": [\n"; |
| indent += 2; |
| for( int level = baseLevel; level <= maxLevel; level++ ) { |
| GLfloat fval[4] = { 0, 0, 0, 0 }; |
| h.gl.GetTextureLevelParameter( ctx, texname, GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, fval ); |
| if( int(fval[0]) == 0 ) { |
| break; |
| } |
| json += string( indent, ' ' ) + "{\n"; |
| indent += 2; |
| for( int i = 0; i < sizeof(tolp)/sizeof(tolp[0]); i++ ) { |
| GLfloat fval[4] = { 0, 0, 0, 0 }; |
| const TextureObjectParameter & p = tolp[i]; |
| h.gl.GetTextureLevelParameter( ctx, texname, GL_TEXTURE_2D, level, p.pname, fval ); |
| if( fval[0] != p.initVal[0] ) { |
| json += string( indent, ' ' ) + PrintTextureObjectParameter( p, fval ); |
| } |
| } |
| indent -= 2; |
| EraseLastComma( json ); |
| json += string( indent, ' ' ) + "},\n"; |
| if( (level - baseLevel) >= 16 ) { |
| break; |
| } |
| } |
| EraseLastComma( json ); |
| indent -= 2; |
| json += string( indent, ' ' ) + "]\n"; |
| ctx->http.ReleaseAppContext( ctx ); |
| } |
| EraseLastComma( json ); |
| indent -= 2; |
| json += "}\n"; |
| |
| SendText( conn, "application/json", json ); |
| } else if( conn.path.size() == 3 && conn.path[2] == "image" ) { |
| if( ctx->emuInfo->gl_ext_direct_state_access == GL_TRUE || ctx->info->gl_ext_direct_state_access ) { |
| ctx->http.AcquireAppContext( ctx ); |
| GLfloat fwidth, fheight; |
| h.gl.GetTextureLevelParameter( ctx, texname, texinfo.target, 0, GL_TEXTURE_WIDTH, &fwidth ); |
| h.gl.GetTextureLevelParameter( ctx, texname, texinfo.target, 0, GL_TEXTURE_HEIGHT, &fheight ); |
| |
| int width = int(fwidth); |
| int height = int(fheight); |
| |
| if( width <= 0 || height <= 0 ) { |
| // evil |
| return; |
| } |
| |
| int stride = width * 4; |
| unsigned char * pixels = new unsigned char[ int(height + 1) * stride ]; |
| |
| h.gl.GetTextureImage( ctx, texname, texinfo.target, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels ); |
| ctx->http.ReleaseAppContext( ctx ); |
| |
| for( int j = 0; j < height/2; j++ ) { |
| unsigned char * s = pixels + (height - 1 - j) * stride; |
| unsigned char * d = pixels + j * stride; |
| unsigned char * t = pixels + height * stride; |
| memcpy( t, d, stride ); |
| memcpy( d, s, stride ); |
| memcpy( s, t, stride ); |
| } |
| int out_len = 0; |
| unsigned char * img = stbi_write_png_to_mem(pixels, width * 4, width, height, 4, &out_len ); |
| |
| string http = print_string( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: image/png\r\n" |
| "Expires: Fri, 30 Oct 1998 14:19:41 GMT\r\n" |
| "Cache-Control: no-cache, must-revalidate\r\n" |
| "Content-Length: ", out_len, "\r\n" |
| "\r\n"); |
| |
| mg_write(conn.connection, http.c_str(), http.length() ); |
| mg_write(conn.connection, img, out_len ); |
| |
| delete [] pixels; |
| free( img ); |
| |
| } |
| } |
| } |
| } |
| virtual string GetHandlerString() { |
| return "texture"; |
| } |
| }; |
| |
| |
| |
| enum UniformApiType { |
| UAT_Invalid, UAT_Double, UAT_Float, UAT_Int, UAT_UnsignedInt, UAT_DoubleMatrix, UAT_FloatMatrix |
| }; |
| |
| UniformApiType GetUniformApiType( GLenum type ) { |
| switch( type ) { |
| case GL_FLOAT: |
| case GL_FLOAT_VEC2: |
| case GL_FLOAT_VEC3: |
| case GL_FLOAT_VEC4: |
| return UAT_Float; |
| case GL_DOUBLE: |
| case GL_DOUBLE_VEC2: |
| case GL_DOUBLE_VEC3: |
| case GL_DOUBLE_VEC4: |
| return UAT_Double; |
| case GL_INT: |
| case GL_INT_VEC2: |
| case GL_INT_VEC3: |
| case GL_INT_VEC4: |
| return UAT_Int; |
| case GL_UNSIGNED_INT: |
| case GL_UNSIGNED_INT_VEC2: |
| case GL_UNSIGNED_INT_VEC3: |
| case GL_UNSIGNED_INT_VEC4: |
| return UAT_UnsignedInt; |
| case GL_BOOL: |
| case GL_BOOL_VEC2: |
| case GL_BOOL_VEC3: |
| case GL_BOOL_VEC4: |
| return UAT_Int; |
| case GL_FLOAT_MAT2: |
| case GL_FLOAT_MAT3: |
| case GL_FLOAT_MAT4: |
| case GL_FLOAT_MAT2x3: |
| case GL_FLOAT_MAT2x4: |
| case GL_FLOAT_MAT3x2: |
| case GL_FLOAT_MAT3x4: |
| case GL_FLOAT_MAT4x2: |
| case GL_FLOAT_MAT4x3: |
| return UAT_FloatMatrix; |
| case GL_DOUBLE_MAT2: |
| case GL_DOUBLE_MAT3: |
| case GL_DOUBLE_MAT4: |
| case GL_DOUBLE_MAT2x3: |
| case GL_DOUBLE_MAT2x4: |
| case GL_DOUBLE_MAT3x2: |
| case GL_DOUBLE_MAT3x4: |
| case GL_DOUBLE_MAT4x2: |
| case GL_DOUBLE_MAT4x3: |
| return UAT_DoubleMatrix; |
| case GL_SAMPLER_1D: |
| case GL_SAMPLER_2D: |
| case GL_SAMPLER_3D: |
| case GL_SAMPLER_CUBE: |
| case GL_SAMPLER_1D_SHADOW: |
| case GL_SAMPLER_2D_SHADOW: |
| case GL_SAMPLER_1D_ARRAY: |
| case GL_SAMPLER_2D_ARRAY: |
| case GL_SAMPLER_1D_ARRAY_SHADOW: |
| case GL_SAMPLER_2D_ARRAY_SHADOW: |
| case GL_SAMPLER_2D_MULTISAMPLE: |
| case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: |
| case GL_SAMPLER_CUBE_SHADOW: |
| case GL_SAMPLER_BUFFER: |
| case GL_SAMPLER_2D_RECT: |
| case GL_SAMPLER_2D_RECT_SHADOW: |
| case GL_INT_SAMPLER_1D: |
| case GL_INT_SAMPLER_2D: |
| case GL_INT_SAMPLER_3D: |
| case GL_INT_SAMPLER_CUBE: |
| case GL_INT_SAMPLER_1D_ARRAY: |
| case GL_INT_SAMPLER_2D_ARRAY: |
| case GL_INT_SAMPLER_2D_MULTISAMPLE: |
| case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: |
| case GL_INT_SAMPLER_BUFFER: |
| case GL_INT_SAMPLER_2D_RECT: |
| case GL_UNSIGNED_INT_SAMPLER_1D: |
| case GL_UNSIGNED_INT_SAMPLER_2D: |
| case GL_UNSIGNED_INT_SAMPLER_3D: |
| case GL_UNSIGNED_INT_SAMPLER_CUBE: |
| case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: |
| case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: |
| case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: |
| case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: |
| case GL_UNSIGNED_INT_SAMPLER_BUFFER: |
| case GL_UNSIGNED_INT_SAMPLER_2D_RECT: |
| case GL_IMAGE_1D: |
| case GL_IMAGE_2D: |
| case GL_IMAGE_3D: |
| case GL_IMAGE_2D_RECT: |
| case GL_IMAGE_CUBE: |
| case GL_IMAGE_BUFFER: |
| case GL_IMAGE_1D_ARRAY: |
| case GL_IMAGE_2D_ARRAY: |
| case GL_IMAGE_2D_MULTISAMPLE: |
| case GL_IMAGE_2D_MULTISAMPLE_ARRAY: |
| case GL_INT_IMAGE_1D: |
| case GL_INT_IMAGE_2D: |
| case GL_INT_IMAGE_3D: |
| case GL_INT_IMAGE_2D_RECT: |
| case GL_INT_IMAGE_CUBE: |
| case GL_INT_IMAGE_BUFFER: |
| case GL_INT_IMAGE_1D_ARRAY: |
| case GL_INT_IMAGE_2D_ARRAY: |
| case GL_INT_IMAGE_2D_MULTISAMPLE: |
| case GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY: |
| case GL_UNSIGNED_INT_IMAGE_1D: |
| case GL_UNSIGNED_INT_IMAGE_2D: |
| case GL_UNSIGNED_INT_IMAGE_3D: |
| case GL_UNSIGNED_INT_IMAGE_2D_RECT: |
| case GL_UNSIGNED_INT_IMAGE_CUBE: |
| case GL_UNSIGNED_INT_IMAGE_BUFFER: |
| case GL_UNSIGNED_INT_IMAGE_1D_ARRAY: |
| case GL_UNSIGNED_INT_IMAGE_2D_ARRAY: |
| case GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE: |
| case GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY: |
| case GL_UNSIGNED_INT_ATOMIC_COUNTER: |
| return UAT_Int; |
| |
| default: break; |
| } |
| return UAT_Invalid; |
| } |
| |
| struct MatrixDims { |
| MatrixDims( int r, int c ) : rows( r ), cols( c ) {} |
| MatrixDims( const MatrixDims & rhs ) : rows( rhs.rows ), cols( rhs.cols ) {} |
| int rows; |
| int cols; |
| }; |
| |
| MatrixDims GetMatrixDims( GLenum type ) { |
| switch( type ) { |
| case GL_DOUBLE_MAT2: case GL_FLOAT_MAT2: return MatrixDims( 2, 2 ); |
| case GL_DOUBLE_MAT3: case GL_FLOAT_MAT3: return MatrixDims( 3, 3 ); |
| case GL_DOUBLE_MAT4: case GL_FLOAT_MAT4: return MatrixDims( 4, 4 ); |
| case GL_DOUBLE_MAT2x3: case GL_FLOAT_MAT2x3: return MatrixDims( 2, 3 ); |
| case GL_DOUBLE_MAT2x4: case GL_FLOAT_MAT2x4: return MatrixDims( 2, 4 ); |
| case GL_DOUBLE_MAT3x2: case GL_FLOAT_MAT3x2: return MatrixDims( 3, 2 ); |
| case GL_DOUBLE_MAT3x4: case GL_FLOAT_MAT3x4: return MatrixDims( 3, 4 ); |
| case GL_DOUBLE_MAT4x2: case GL_FLOAT_MAT4x2: return MatrixDims( 4, 2 ); |
| case GL_DOUBLE_MAT4x3: case GL_FLOAT_MAT4x3: return MatrixDims( 4, 3 ); |
| default: break; |
| } |
| return MatrixDims( 0, 0 ); |
| } |
| |
| int GetMatrixElementSize( GLenum type ) { |
| switch( type ) { |
| case GL_FLOAT_MAT2: case GL_FLOAT_MAT3: case GL_FLOAT_MAT4: |
| case GL_FLOAT_MAT2x3: case GL_FLOAT_MAT2x4: case GL_FLOAT_MAT3x2: |
| case GL_FLOAT_MAT3x4: case GL_FLOAT_MAT4x2: case GL_FLOAT_MAT4x3: |
| return sizeof( GLfloat ); |
| case GL_DOUBLE_MAT2: case GL_DOUBLE_MAT3: case GL_DOUBLE_MAT4: |
| case GL_DOUBLE_MAT2x3: case GL_DOUBLE_MAT2x4: case GL_DOUBLE_MAT3x2: |
| case GL_DOUBLE_MAT3x4: case GL_DOUBLE_MAT4x2: case GL_DOUBLE_MAT4x3: |
| return sizeof( GLdouble ); |
| default: break; |
| } |
| return 0; |
| } |
| |
| |
| template <typename T> string PrintArrayElement( int sz, const T *p ) { |
| string el; |
| int count = sz / sizeof(T); |
| if( count > 1 ) { |
| el += "[ "; |
| } |
| for( int j = 0; j < count; j++ ) { |
| el += print_string( p[j] ); |
| if( j < (count - 1) ) { |
| el += ", "; |
| } |
| } |
| if( count > 1 ) { |
| el += " ]"; |
| } |
| return el; |
| } |
| |
| string PrintUniformValue( ShaderInstance::Procs & tbl, GLuint program, GLint location, GLsizei count, GLenum type, int indent ) { |
| char buf[1<<14]; |
| int sz = ShaderInstance::GetTypeSize( type ); |
| if( (count * sz) > sizeof(buf) ) { |
| return "\"uniform too large\""; |
| } |
| ShaderInstance::GetUniform( tbl, program, location, count, type, buf ); |
| |
| string u; |
| if( count > 1 ) { |
| u += "{\n"; |
| indent += 2; |
| u += string( indent, ' ' ); |
| } |
| |
| for( int i = 0; i < count; i++ ) { |
| char *up = buf + i * sz; |
| UniformApiType uat = GetUniformApiType( type ); |
| if( i < 0 ) { |
| u += string( indent, ' ' ); |
| } |
| switch( uat ) { |
| case UAT_Double: |
| case UAT_DoubleMatrix: |
| u += PrintArrayElement( sz, reinterpret_cast<GLdouble *>(up) ); |
| break; |
| case UAT_Float: |
| case UAT_FloatMatrix: |
| u += PrintArrayElement( sz, reinterpret_cast<GLfloat *>(up) ); |
| break; |
| case UAT_Int: |
| u += PrintArrayElement( sz, reinterpret_cast<GLint *>(up) ); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| if( count > 1 ) { |
| indent -= 2; |
| u += string( indent, ' ' ) + "}\n"; |
| } |
| return u; |
| } |
| |
| string PrintMultiLine( char * bigstring, int len, int indent ) { |
| string json; |
| json += "[\n"; |
| indent += 2; |
| char * p = bigstring; |
| int i = 0; |
| while( i < len ) { |
| if( p[i] == '\n' ) { |
| p[i] = 0; |
| json += string( indent, ' ' ) + "\"" + string( p ) + "\",\n"; |
| p += i+1; |
| len -= i+1; |
| i = -1; |
| } |
| i++; |
| } |
| EraseLastComma( json ); |
| indent -= 2; |
| json += string( indent, ' ' ) + print_string( "],\n" ); |
| |
| return json; |
| } |
| |
| struct ProgramHandler : public RequestHandler { |
| virtual void HandleRequest( Connection & conn ) { |
| RegalContext * ctx = ::REGAL_NAMESPACE_INTERNAL::Init::getContextByIndex( 0 ); |
| Http & h = ctx->http; |
| if( ctx == NULL ) { |
| return; |
| } |
| string json; |
| if( conn.path.size() == 1 ) { |
| |
| json += "[ "; |
| for( set<GLuint>::iterator i = h.program.begin(); i != h.program.end(); ++i ) { |
| json += print_string( *i, ", " ); |
| } |
| EraseLastComma( json ); |
| json += "]\n"; |
| SendText( conn, "application/json", json ); |
| } else if ( conn.path.size() == 2 ) { |
| string progNameString = conn.path[1]; |
| GLint prog = atoi( progNameString.c_str() ); |
| int indent = 0; |
| json += "{\n"; |
| indent += 2; |
| Http & h = ctx->http; |
| h.AcquireAppContext( ctx ); |
| json += string( indent, ' ' ) + "\"name\": " + progNameString + ",\n"; |
| GLuint shaders[8]; |
| GLsizei numShaders = 0; |
| h.gl.GetAttachedShaders( ctx, prog, (GLsizei)array_size(shaders), &numShaders, shaders ); |
| if( numShaders > 0 ) { |
| json += string( indent, ' ' ) + "\"shaders\": [ "; |
| for( int i = 0; i < numShaders; i++ ) { |
| json += print_string( shaders[i] ); |
| if( i < numShaders - 1 ) { |
| json += ", "; |
| } |
| } |
| json += " ],\n"; |
| } |
| GLint activeUniforms = 0; |
| h.gl.GetProgramiv( ctx, prog, GL_ACTIVE_UNIFORMS, &activeUniforms ); |
| if( activeUniforms > 0 ) { |
| json += string( indent, ' ' ) + "\"GL_ACTIVE_UNIFORMS\": " + print_string( activeUniforms, ",\n" ); |
| json += string( indent, ' ' ) + "\"uniforms\": {\n"; |
| indent += 2; |
| ShaderInstance::Procs sip; |
| sip.Initialize( ctx->http.next ); |
| sip.ctx = ctx; |
| for( int i = 0; i < activeUniforms; i++ ) { |
| GLchar name[80]; |
| GLsizei nameLen = 0; |
| GLint count; |
| GLenum type; |
| h.gl.GetActiveUniform( ctx, prog, i, 80, &nameLen, &count, &type, name ); |
| name[nameLen] = 0; |
| GLint loc = h.gl.GetUniformLocation( ctx, prog, name ); |
| json +=string( indent, ' ' ) + "\"" + string(name) + "\": {\n"; |
| indent += 2; |
| json +=string( indent, ' ' ) + print_string( "\"location\": ", loc, ",\n" ); |
| json +=string( indent, ' ' ) + print_string( "\"type\": \"", Token::GLenumToString( type ), "\",\n" ); |
| if( count > 1 ) { |
| json +=string( indent, ' ' ) + print_string( "\"count\" :", count, ",\n" ); |
| } |
| json +=string( indent, ' ' ) + print_string( "\"value\": ", PrintUniformValue( sip, prog, loc, count, type, indent ), ",\n" ); |
| EraseLastComma( json ); |
| indent -= 2; |
| json += string( indent, ' ' ) + "},\n"; |
| } |
| EraseLastComma( json ); |
| indent -= 2; |
| json += string( indent, ' ' ) + "},\n"; |
| } |
| { |
| char dbgLog[1<<15]; |
| int dbgLogLen = 0; |
| h.gl.GetProgramInfoLog( ctx, prog, (1<<15) - 2, &dbgLogLen, dbgLog ); |
| dbgLog[ dbgLogLen ] = 0; |
| if( dbgLogLen > 0 ) { |
| json += string( indent, ' ' ) + print_string( "\"infoLog\": " ); |
| json += PrintMultiLine( dbgLog, dbgLogLen, indent ); |
| } |
| } |
| h.ReleaseAppContext( ctx ); |
| EraseLastComma( json ); |
| indent -= 2; |
| json += "}\n"; |
| |
| SendText( conn, "application/json", json ); |
| } |
| } |
| virtual string GetHandlerString() { |
| return "program"; |
| } |
| }; |
| |
| struct ShaderHandler : public RequestHandler { |
| virtual void HandleRequest( Connection & conn ) { |
| RegalContext * ctx = ::REGAL_NAMESPACE_INTERNAL::Init::getContextByIndex( 0 ); |
| Http & h = ctx->http; |
| if( ctx == NULL ) { |
| return; |
| } |
| string json; |
| if( conn.path.size() == 1 ) { |
| |
| json += "[ "; |
| for( set<GLuint>::iterator i = h.shader.begin(); i != h.shader.end(); ++i ) { |
| json += print_string( *i, ", " ); |
| } |
| EraseLastComma( json ); |
| json += "]\n"; |
| SendText( conn, "application/json", json ); |
| } else if ( conn.path.size() == 2 ) { |
| string shaderNameString = conn.path[1]; |
| GLint shader = atoi( shaderNameString.c_str() ); |
| int indent = 0; |
| json += "{\n"; |
| indent += 2; |
| Http & h = ctx->http; |
| h.AcquireAppContext( ctx ); |
| json += string( indent, ' ' ) + "\"name\": " + shaderNameString + ",\n"; |
| GLint ival; |
| h.gl.GetShaderiv(ctx, shader, GL_SHADER_TYPE, &ival ); |
| json += string( indent, ' ' ) + "\"GL_SHADER_TYPE\": \"" + Token::GLenumToString( ival ) + "\",\n"; |
| h.gl.GetShaderiv(ctx, shader, GL_DELETE_STATUS, &ival ); |
| json += string( indent, ' ' ) + "\"GL_DELETE_STATUS\": " + ( ival ? "true" : "false" ) + ",\n"; |
| h.gl.GetShaderiv(ctx, shader, GL_COMPILE_STATUS, &ival ); |
| json += string( indent, ' ' ) + "\"GL_COMPILE_STATUS\": " + ( ival ? "true" : "false" ) + ",\n"; |
| { |
| char str[1<<15]; |
| int strLen = 0; |
| h.gl.GetShaderSource( ctx, shader, sizeof(str) - 2, &strLen, str ); |
| str[ strLen ] = 0; |
| if( strLen > 0 ) { |
| json += string( indent, ' ' ) + print_string( "\"source\": " ); |
| json += PrintMultiLine( str, strLen, indent ); |
| } |
| h.gl.GetShaderInfoLog( ctx, shader, sizeof(str) - 2, &strLen, str ); |
| str[ strLen ] = 0; |
| if( strLen > 0 ) { |
| json += string( indent, ' ' ) + print_string( "\"infoLog\": " ); |
| json += PrintMultiLine( str, strLen, indent ); |
| } |
| } |
| h.ReleaseAppContext( ctx ); |
| EraseLastComma( json ); |
| indent -= 2; |
| json += "}\n"; |
| |
| SendText( conn, "application/json", json ); |
| } |
| } |
| virtual string GetHandlerString() { |
| return "shader"; |
| } |
| }; |
| |
| struct FboHandler : public RequestHandler { |
| virtual void HandleRequest( Connection & conn ) { |
| if( conn.path.size() == 1 ) { |
| string json; |
| json += "[ "; |
| RegalContext * ctx = ::REGAL_NAMESPACE_INTERNAL::Init::getContextByIndex( 0 ); |
| if( ctx ) { |
| Http & h = ctx->http; |
| for( map<GLuint, HttpFboInfo>::iterator it = h.fbo.begin(); it != h.fbo.end(); ++it ) { |
| json += print_string( it->first, ", " ); |
| } |
| EraseLastComma( json ); |
| } |
| json += "]\n"; |
| |
| SendText( conn, "application/json", json ); |
| return; |
| } |
| if( conn.path.size() == 2 ) { |
| string redir = "/fbo/" + conn.path[1] + "/color0"; |
| Redirect( conn, redir ); |
| return; |
| } |
| if( conn.path.size() != 3 ) { |
| // send 404 |
| return; |
| } |
| GLint fbo = atoi( conn.path[1].c_str() ); |
| |
| RegalContext * ctx = ::REGAL_NAMESPACE_INTERNAL::Init::getContextByIndex( 0 ); |
| if( ctx && ctx->http.fbo.count( fbo ) > 0 ) { |
| ScopedContextAcquire sca( ctx ); |
| Http & h = ctx->http; |
| GLint currFbo = -1; |
| h.gl.GetIntegerv( ctx, GL_READ_FRAMEBUFFER_BINDING, & currFbo ); |
| if( fbo != currFbo ) { |
| SendText( conn, "application/json", "\"can't query the non-current FBO yet\"" ); |
| return; |
| } |
| HttpFboInfo & fi = ctx->http.fbo[ fbo ]; |
| |
| h.gl.Finish( ctx ); |
| if( fbo == 0 ) { |
| GLint vp[4]; |
| h.gl.GetIntegerv( ctx, GL_VIEWPORT, vp ); |
| fi.width = vp[0] + vp[2]; |
| fi.height = vp[1] + vp[3]; |
| } |
| |
| int stride = fi.width * 4; |
| |
| unsigned char * pixels = new unsigned char[ (fi.height+1) * stride ]; |
| |
| h.gl.ReadPixels( ctx, 0, 0, fi.width, fi.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels ); |
| for( int j = 0; j < fi.height/2; j++ ) { |
| unsigned char * s = pixels + (fi.height -1 - j) * stride; |
| unsigned char * d = pixels + j * stride; |
| unsigned char * t = pixels + fi.height * stride; |
| memcpy( t, d, stride ); |
| memcpy( d, s, stride ); |
| memcpy( s, t, stride ); |
| } |
| int out_len = 0; |
| unsigned char * img = stbi_write_png_to_mem(pixels, fi.width * 4, fi.width, fi.height, 4, &out_len ); |
| |
| string http = print_string( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: image/png\r\n" |
| "Expires: Fri, 30 Oct 1998 14:19:41 GMT\r\n" |
| "Cache-Control: no-cache, must-revalidate\r\n" |
| "Content-Length: ", out_len, "\r\n" |
| "\r\n"); |
| |
| mg_write(conn.connection, http.c_str(), http.length() ); |
| mg_write(conn.connection, img, out_len ); |
| |
| delete [] pixels; |
| free( img ); |
| |
| return; |
| } |
| |
| SendText( conn, "application/json", "\"invalid context\"" ); |
| } |
| |
| virtual string GetHandlerString() { |
| return "fbo"; |
| } |
| }; |
| |
| string EventCountToJson( Http::EventCount & count, int indent ) { |
| string json; |
| json += "{\n"; |
| indent += 2; |
| json += string( indent, ' ' ) + "\"call\": " + print_string( count.call, ",\n" ); |
| json += string( indent, ' ' ) + "\"draw\": " + print_string( count.draw, ",\n" ); |
| json += string( indent, ' ' ) + "\"group\": " + print_string( count.group, ",\n" ); |
| json += string( indent, ' ' ) + "\"fbo\": " + print_string( count.fbo, ",\n" ); |
| json += string( indent, ' ' ) + "\"frame\": " + print_string( count.frame, ",\n" ); |
| json += string( indent, ' ' ) + "\"lastDraw\": " + print_string( count.lastDraw, ",\n" ); |
| json += string( indent, ' ' ) + "\"lastGroup\": " + print_string( count.lastGroup, ",\n" ); |
| json += string( indent, ' ' ) + "\"lastFbo\": " + print_string( count.lastFbo, ",\n" ); |
| json += string( indent, ' ' ) + "\"lastFrame\": " + print_string( count.lastFrame, ",\n" ); |
| indent -= 2; |
| EraseLastComma( json ); |
| json += string( indent, ' ' ) + "}"; |
| return json; |
| } |
| |
| string EscapeJson( const string & s ) { |
| string r; |
| for( size_t i = 0; i < s.size(); i++ ) { |
| char c = s[i]; |
| switch( c ) { |
| case '"': |
| r.push_back( '\\' ); |
| break; |
| default: |
| break; |
| } |
| r.push_back( c ); |
| } |
| return r; |
| } |
| |
| struct LogHandler : public RequestHandler { |
| virtual void HandleRequest( Connection & conn ) { |
| string json; |
| GLint64 startLine = conn.path.size() > 1 ? atoi( conn.path[1].c_str() ) : 0; |
| GLint64 numLines = conn.path.size() > 2 ? atoi( conn.path[2].c_str() ) : 10000; |
| int indent = 0; |
| json += string( indent, ' ' ) + "{\n"; |
| indent += 2; |
| RegalContext * ctx = ::REGAL_NAMESPACE_INTERNAL::Init::getContextByIndex( 0 ); |
| if( ctx ) { |
| ScopedContextAcquire sca( ctx ); |
| Http & h = ctx->http; |
| json += string( indent, ' ' ) + "\"count\": " + EventCountToJson( h.count, indent ) + ",\n"; |
| GLint64 front = h.count.call - h.callLog.size() + 1; |
| if( startLine < 0 ) { |
| startLine = int(GLint64(h.count.call) + GLint64(startLine) + 1 ); |
| } |
| startLine = max( startLine, front ); |
| numLines = min( numLines, GLint64( h.count.call - startLine + 1) ); |
| json += string( indent, ' ' ) + "\"startLine\": " + print_string( startLine, ",\n" ); |
| json += string( indent, ' ' ) + "\"numLines\": " + print_string( numLines, ",\n" ); |
| json += string( indent, ' ' ) + "\"maxLines\": " + print_string( h.callLog.size(), ",\n" ); |
| |
| json += string( indent, ' ' ) + "\"log\": [\n"; |
| indent += 2; |
| size_t base = size_t(startLine - front); |
| for( size_t i = 0; i < numLines; i++ ) { |
| string esc = EscapeJson( h.callLog[ base + i ] ); |
| json += string( indent, ' ' ) + print_string( "\"", esc, "\",\n"); |
| } |
| indent -= 2; |
| EraseLastComma( json ); |
| json += string( indent, ' ' ) + "],\n"; |
| } |
| indent -= 2; |
| EraseLastComma( json ); |
| json += string( indent, ' ' ) + "}\n"; |
| |
| SendText( conn, "application/json", json ); |
| } |
| virtual string GetHandlerString() { |
| return "log"; |
| } |
| }; |
| |
| struct EnableHandler : public RequestHandler { |
| virtual void HandleRequest( Connection & conn ) { |
| RegalAssert( conn.path.size() == 1 && conn.path[0] == "glEnable" ); |
| string body; |
| if (!strcmp("GL_LOG_INFO_REGAL", conn.request_info->query_string)) Logging::enableError = true; |
| else if (!strcmp("GL_LOG_WARNING_REGAL", conn.request_info->query_string)) Logging::enableWarning = true; |
| else if (!strcmp("GL_LOG_ERROR_REGAL", conn.request_info->query_string)) Logging::enableInfo = true; |
| else if (!strcmp("GL_LOG_APP_REGAL", conn.request_info->query_string)) Logging::enableApp = true; |
| else if (!strcmp("GL_LOG_DRIVER_REGAL", conn.request_info->query_string)) Logging::enableDriver = true; |
| else if (!strcmp("GL_LOG_INTERNAL_REGAL",conn.request_info->query_string)) Logging::enableInternal = true; |
| else if (!strcmp("GL_LOG_HTTP_REGAL", conn.request_info->query_string)) Logging::enableHttp = true; |
| |
| else if (!strcmp("REGAL_FRAME_TIME", conn.request_info->query_string)) Logging::frameTime = true; |
| |
| else if (!strcmp("REGAL_MD5_COLOR", conn.request_info->query_string)) Config::frameMd5Color = true; |
| else if (!strcmp("REGAL_MD5_STENCIL", conn.request_info->query_string)) Config::frameMd5Stencil = true; |
| else if (!strcmp("REGAL_MD5_DEPTH", conn.request_info->query_string)) Config::frameMd5Depth = true; |
| else if (!strcmp("REGAL_SAVE_COLOR", conn.request_info->query_string)) Config::frameSaveColor = true; |
| else if (!strcmp("REGAL_SAVE_STENCIL", conn.request_info->query_string)) Config::frameSaveStencil = true; |
| else if (!strcmp("REGAL_SAVE_DEPTH", conn.request_info->query_string)) Config::frameSaveDepth = true; |
| |
| body += print_string("glEnable(", conn.request_info->query_string, ");",br,br); |
| body += print_string("<a href=\"/glDisable?",conn.request_info->query_string,"\">toggle</a>"); |
| SendHTML( conn, body ); |
| } |
| virtual string GetHandlerString() { |
| return "glEnable"; |
| } |
| }; |
| |
| struct DisableHandler : public RequestHandler { |
| virtual void HandleRequest( Connection & conn ) { |
| RegalAssert( conn.path.size() == 1 && conn.path[0] == "glEnable" ); |
| string body; |
| if (!strcmp("GL_LOG_INFO_REGAL", conn.request_info->query_string)) Logging::enableError = false; |
| else if (!strcmp("GL_LOG_WARNING_REGAL", conn.request_info->query_string)) Logging::enableWarning = false; |
| else if (!strcmp("GL_LOG_ERROR_REGAL", conn.request_info->query_string)) Logging::enableInfo = false; |
| else if (!strcmp("GL_LOG_APP_REGAL", conn.request_info->query_string)) Logging::enableApp = false; |
| else if (!strcmp("GL_LOG_DRIVER_REGAL", conn.request_info->query_string)) Logging::enableDriver = false; |
| else if (!strcmp("GL_LOG_INTERNAL_REGAL",conn.request_info->query_string)) Logging::enableInternal = false; |
| else if (!strcmp("GL_LOG_HTTP_REGAL", conn.request_info->query_string)) Logging::enableHttp = false; |
| |
| else if (!strcmp("REGAL_FRAME_TIME", conn.request_info->query_string)) Logging::frameTime = false; |
| |
| else if (!strcmp("REGAL_MD5_COLOR", conn.request_info->query_string)) Config::frameMd5Color = false; |
| else if (!strcmp("REGAL_MD5_STENCIL", conn.request_info->query_string)) Config::frameMd5Stencil = false; |
| else if (!strcmp("REGAL_MD5_DEPTH", conn.request_info->query_string)) Config::frameMd5Depth = false; |
| else if (!strcmp("REGAL_SAVE_COLOR", conn.request_info->query_string)) Config::frameSaveColor = false; |
| else if (!strcmp("REGAL_SAVE_STENCIL", conn.request_info->query_string)) Config::frameSaveStencil = false; |
| else if (!strcmp("REGAL_SAVE_DEPTH", conn.request_info->query_string)) Config::frameSaveDepth = false; |
| |
| body += print_string("glDisable(", conn.request_info->query_string, ");",br,br); |
| body += print_string("<a href=\"/glEnable?",conn.request_info->query_string,"\">toggle</a>"); |
| SendHTML( conn, body ); |
| } |
| virtual string GetHandlerString() { |
| return "glDisable"; |
| } |
| }; |
| |
| struct DebugHandler : public RequestHandler { |
| virtual void HandleRequest( Connection & conn ) { |
| RegalAssert( conn.path.size() >= 1 && conn.path[0] == "debug" ); |
| bool waitForPause = false; |
| if( conn.path.size() == 2 ) { |
| const string & cmd = conn.path[1]; |
| RegalContext * ctx = ::REGAL_NAMESPACE_INTERNAL::Init::getContextByIndex( 0 ); |
| if( ctx ) { |
| waitForPause = true; |
| Http & h = ctx->http; |
| h.AcquireAppContext( ctx ); |
| if( cmd == "play" ) { |
| h.ContinueFromBreakpoint( ctx, RS_Run ); |
| waitForPause = false; |
| } else if( cmd == "next" ) { |
| h.ContinueFromBreakpoint( ctx, RS_Next ); |
| } else if( cmd == "nextDraw" ) { |
| h.ContinueFromBreakpoint( ctx, RS_NextDraw ); |
| } else if( cmd == "nextFbo" ) { |
| h.ContinueFromBreakpoint( ctx, RS_NextFbo ); |
| } else if( cmd == "nextGroup" ) { |
| h.ContinueFromBreakpoint( ctx, RS_NextGroup ); |
| } else if( cmd == "nextFrame" ) { |
| h.ContinueFromBreakpoint( ctx, RS_NextFrame ); |
| } else if( cmd == "begin" && h.runState != RS_Pause ) { |
| h.ContinueFromBreakpoint( ctx, RS_NextFrame ); |
| } |
| h.ReleaseAppContext( ctx ); |
| } |
| } |
| |
| if( waitForPause ) { |
| RegalContext * ctx = ::REGAL_NAMESPACE_INTERNAL::Init::getContextByIndex( 0 ); |
| // wait up to 16 seconds for the frame to finish |
| for( int i = 0; i < 16000; i++ ) { |
| if( ctx->http.runState == RS_Pause ) { |
| break; |
| } |
| #if ! REGAL_SYS_WGL |
| usleep( 1000 ); |
| #else |
| Sleep(1); |
| #endif |
| } |
| } |
| |
| |
| string body; |
| body += "<a href=\"/debug/continue\">continue</a><br>\n"; |
| body += "<a href=\"/debug/pause\">pause</a><br>\n"; |
| body += "<a href=\"/debug/next\">next</a><br>\n"; |
| body += "<a href=\"/debug/nextDraw\">next draw</a><br>\n"; |
| body += "<a href=\"/debug/nextFbo\">next fbo</a><br>\n"; |
| body += "<a href=\"/debug/nextGroup\">next group</a><br>\n"; |
| SendHTML( conn, body ); |
| } |
| virtual string GetHandlerString() { |
| return "debug"; |
| } |
| }; |
| |
| |
| |
| void CreateHandlers() { |
| RequestHandler *h[] = { new DebugHandler, new TextureHandler, new ContextsHandler, new FboHandler, new ProgramHandler, new ShaderHandler, new LogHandler, new EnableHandler, new DisableHandler, new FaviconHandler, new ScriptHandler }; |
| for( int i = 0; i < sizeof(h)/sizeof(h[0]); i++ ) { |
| handlers[ h[i]->GetHandlerString() ] = h[i]; |
| } |
| } |
| |
| void Redirect( Connection & conn, const string & redirect_to ) { |
| string html = print_string( |
| "<html><body>\nRedirecting to: ", |
| redirect_to, |
| "</body></html>\n"); |
| |
| string http = print_string( |
| "HTTP/1.0 302 Found\r\n" |
| "Location: ", redirect_to, "\r\n" |
| "Content-Type: text/html\r\n" |
| "Content-Length: ", html.length(), "\r\n" |
| "\r\n", |
| html); |
| |
| mg_write( conn.connection, http.c_str(), http.length() ); |
| } |
| |
| |
| Http::Http() : runState( RS_Run ), debugGroupStackDepth( -1 ), stepOverGroupDepth( -1 ), inBeginEnd( 0 ) |
| { |
| contextMutex = new Thread::Mutex( Thread::MT_Normal ); |
| contextMutex->acquire(); |
| breakpointMutex = new Thread::Mutex( Thread::MT_Normal ); |
| breakpointMutex->acquire(); |
| httpServerWantsContext = false; |
| fbo[0] = HttpFboInfo(); |
| /* |
| Breakpoint * b = new Breakpoint; |
| b->SetRegularExpression( "(glDraw.*|glEnd)" ); |
| breakpoint.push_back( b ); |
| currentBreakpoint = -1; |
| */ |
| } |
| |
| bool Http::enabled = REGAL_HTTP; |
| int Http::port = REGAL_HTTP_PORT; |
| |
| void Http::Init() |
| { |
| #if REGAL_SYS_OSX |
| pp.CGLSetCurrentContext = dispatchGlobal.CGLSetCurrentContext; |
| #elif REGAL_SYS_WGL |
| pp.wglMakeCurrent = dispatchGlobal.wglMakeCurrent; |
| #elif REGAL_SYS_GLX |
| pp.glXMakeCurrent = dispatchGlobal.glxMakeCurrent; |
| #elif REGAL_SYS_EGL |
| pp.eglMakeCurrent = dispatchGlobal.eglMakeCurrent; |
| #endif |
| |
| Internal("Http::Init","()"); |
| |
| // Environment variable HTTP configuration |
| |
| #ifndef REGAL_NO_GETENV |
| getEnv("REGAL_HTTP", enabled); |
| getEnv("REGAL_HTTP_PORT", port); |
| #endif |
| |
| // Compile-time HTTP configuration |
| |
| #ifdef REGAL_HTTP_PORT |
| port = REGAL_HTTP_PORT; |
| #endif |
| |
| CreateHandlers(); |
| |
| } |
| |
| Http::~Http() |
| { |
| for( size_t i = 0; i < breakpoint.size(); i++ ) { |
| delete breakpoint[i]; |
| } |
| delete contextMutex; |
| delete breakpointMutex; |
| } |
| |
| void Http::YieldToHttpServer( RegalContext * ctx, bool log ) |
| { |
| if( log ) { |
| callLog.push_back( callString ); |
| static GLuint64 sz = 0; |
| sz = max( sz, ( count.call - count.lastFrame + 1 ) ); |
| if( callLog.size() > sz ) { |
| callLog.erase( callLog.begin(), callLog.begin() + size_t( callLog.size() - sz ) ); |
| } |
| } |
| |
| // invoked by the app thread from the http dispatch |
| if( runState == RS_Pause && inBeginEnd == 0 ) { |
| ctx->parkContext( pp ); |
| contextMutex->release(); |
| breakpointMutex->acquire(); // this will stall until the server releases the breakpoint |
| breakpointMutex->release(); |
| contextMutex->acquire(); |
| ctx->unparkContext( pp ); |
| } |
| |
| /* |
| // check any breakpoints for hits |
| for( size_t i = 0; i < breakpoint.size(); i++ ) { |
| Breakpoint * b = breakpoint[i]; |
| if( b == NULL || b->re == NULL || b->enabled == false ) { |
| continue; |
| } |
| // get the string for this API call |
| string callString; |
| int subStr[30]; |
| int reRet = pcre_exec( b->re, NULL, callString.c_str(), (int)callString.size(), 0, 0, subStr, sizeof( subStr ) / sizeof( subStr[0] ) ); |
| if( reRet ) { |
| if( inBeginEnd ) { |
| runState = RS_Pause; // stop asap, but not right now, otherwise we have problems with other queries |
| } else { |
| currentBreakpoint = (int)i; |
| ctx->parkContext( *dispatcherGlobal.http.next() ); |
| contextMutex->release(); |
| breakpointMutex->acquire(); // this will stall until the server releases the breakpoint |
| breakpointMutex->release(); |
| contextMutex->acquire(); |
| ctx->unparkContext( *dispatcherGlobal.http.next() ); |
| } |
| } |
| } |
| */ |
| currentBreakpoint = -1; |
| if( httpServerWantsContext && inBeginEnd == 0 ) { |
| ctx->http.httpServerWantsContext = false; |
| ctx->parkContext( pp ); |
| contextMutex->release(); |
| // wait a bit? |
| contextMutex->acquire(); |
| ctx->unparkContext( pp ); |
| } |
| if( log ) { |
| count.call++; |
| } |
| } |
| |
| void Http::AcquireAppContext( RegalContext * ctx ) |
| { |
| ctx->http.httpServerWantsContext = true; |
| ctx->http.contextMutex->acquire(); |
| ctx->unparkContext( pp ); |
| } |
| |
| void Http::ReleaseAppContext( RegalContext * ctx ) |
| { |
| ctx->parkContext( pp ); |
| ctx->http.contextMutex->release(); |
| } |
| |
| void Http::ContinueFromBreakpoint( RegalContext * ctx, HttpRunState rs ) |
| { |
| UNUSED_PARAMETER(ctx); |
| if( runState == RS_Pause ) { |
| breakpointMutex->release(); |
| breakpointMutex->acquire(); |
| } |
| runState = rs; |
| } |
| |
| void Http::GlProcs::Initialize( Dispatch::GL * tbl ) { |
| Finish = tbl->glFinish ; |
| GetActiveUniform = tbl->glGetActiveUniform ; |
| GetAttachedShaders = tbl->glGetAttachedShaders ; |
| GetIntegerv = tbl->glGetIntegerv ; |
| GetProgramInfoLog = tbl->glGetProgramInfoLog ; |
| GetProgramiv = tbl->glGetProgramiv ; |
| GetShaderInfoLog = tbl->glGetShaderInfoLog ; |
| GetShaderSource = tbl->glGetShaderSource ; |
| GetShaderiv = tbl->glGetShaderiv ; |
| GetTextureImage = tbl->glGetTextureImageEXT ; |
| GetTextureLevelParameter = tbl->glGetTextureLevelParameterfvEXT ; |
| GetTextureParameter = tbl->glGetTextureParameterfvEXT ; |
| GetUniformLocation = tbl->glGetUniformLocation ; |
| ReadPixels = tbl->glReadPixels ; |
| } |
| |
| REGAL_NAMESPACE_END |
| |
| #endif |
| |