瀏覽代碼

Commit work-in-progress emscripten/WebGL port

rdb 10 年之前
父節點
當前提交
1a3ffb19f9

+ 3 - 0
dtool/src/dtoolbase/dtool_platform.h

@@ -60,6 +60,9 @@
 #define DTOOL_PLATFORM "android_i386"
 #endif
 
+#elif defined(__EMSCRIPTEN__)
+#define DTOOL_PLATFORM "emscripten"
+
 #elif defined(__x86_64)
 #define DTOOL_PLATFORM "linux_amd64"
 

+ 1 - 1
dtool/src/dtoolbase/dtoolbase.h

@@ -105,7 +105,7 @@
 
 // This is a workaround for a glibc bug that is triggered by
 // clang when compiling with -ffast-math.
-#ifdef __clang__
+#if defined(__clang__) && !defined(__EMSCRIPTEN__)
 #include <sys/cdefs.h>
 #ifndef __extern_always_inline
 #define __extern_always_inline extern __always_inline

+ 0 - 21
dtool/src/prc/androidLogStream.cxx

@@ -133,25 +133,4 @@ AndroidLogStream::
   delete rdbuf();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: AndroidLogStream::out
-//       Access: Public, Static
-//  Description: Returns an AndroidLogStream suitable for writing
-//               log messages with the indicated severity.
-////////////////////////////////////////////////////////////////////
-ostream &AndroidLogStream::
-out(NotifySeverity severity) {
-  static AndroidLogStream* streams[NS_fatal + 1] = {NULL};
-
-  if (streams[severity] == NULL) {
-    int priority = ANDROID_LOG_UNKNOWN;
-    if (severity != NS_unspecified) {
-      priority = ((int)severity) + 1;
-    }
-    streams[severity] = new AndroidLogStream(priority);
-  }
-
-  return *streams[severity];
-}
-
 #endif  // ANDROID

+ 2 - 1
dtool/src/prc/androidLogStream.h

@@ -51,7 +51,8 @@ private:
 
 public:
   virtual ~AndroidLogStream();
-  static ostream &out(NotifySeverity severity);
+
+  friend class Notify;
 };
 
 #endif  // ANDROID

+ 66 - 0
dtool/src/prc/notify.cxx

@@ -28,6 +28,11 @@
 
 #ifdef ANDROID
 #include <android/log.h>
+#include "androidLogStream.h"
+#endif
+
+#ifdef __EMSCRIPTEN__
+#include "emscriptenLogStream.h"
 #endif
 
 Notify *Notify::_global_ptr = (Notify *)NULL;
@@ -289,6 +294,24 @@ get_category(const string &fullname) {
   return get_category(basename, parent_category);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Notify::out
+//       Access: Public, Static
+//  Description: A convenient way to get the ostream that should be
+//               written to for a Notify-type message of a given
+//               severity.  Also see Category::out() for a message
+//               that is specific to a particular Category.
+////////////////////////////////////////////////////////////////////
+ostream &Notify::
+out(NotifySeverity severity) {
+#if defined(ANDROID) || defined(__EMSCRIPTEN__)
+  // Android and JavaScript have dedicated log systems.
+  return *(ptr()->_log_streams[severity]);
+#else
+  return *(ptr()->_ostream_ptr);
+#endif
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Notify::out
 //       Access: Public, Static
@@ -396,7 +419,14 @@ assert_failure(const char *expression, int line,
   }
 
 #ifdef ANDROID
+  // Write to Android log system.
   __android_log_assert("assert", "Panda3D", "Assertion failed: %s", message.c_str());
+
+#elif defined(__EMSCRIPTEN__)
+  // Write to JavaScript console.
+  emscripten_log(EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_C_STACK,
+                 "Assertion failed: %s", message.c_str());
+
 #else
   nout << "Assertion failed: " << message << "\n";
 #endif
@@ -424,6 +454,11 @@ assert_failure(const char *expression, int line,
     int *ptr = (int *)NULL;
     *ptr = 1;
 
+#elif defined(__EMSCRIPTEN__) && defined(_DEBUG)
+    // This should drop us into the browser's JavaScript debugger, but
+    // adding this disables ASM.js validation.
+    emscripten_debugger();
+
 #else  // WIN32
     abort();
 #endif  // WIN32
@@ -491,6 +526,36 @@ config_initialized() {
   }
   already_initialized = true;
 
+#if defined(ANDROID)
+  // Android redirects stdio and stderr to /dev/null,
+  // but does provide its own logging system.  We use a special
+  // type of stream that redirects it to Android's log system.
+
+  for (int i = 0; i <= NS_fatal; ++i) {
+    int priority = ANDROID_LOG_UNKNOWN;
+    if (severity != NS_unspecified) {
+      priority = i + 1;
+    }
+    _log_streams[i] = new AndroidLogStream(priority);
+  }
+
+#elif defined(__EMSCRIPTEN__)
+  // We have no writable filesystem in JavaScript.  Instead, we set up a
+  // special stream that logs straight into the Javascript console.
+
+  EmscriptenLogStream *error_stream = new EmscriptenLogStream(EM_LOG_CONSOLE | EM_LOG_ERROR);
+  EmscriptenLogStream *warn_stream = new EmscriptenLogStream(EM_LOG_CONSOLE | EM_LOG_WARN);
+  EmscriptenLogStream *info_stream = new EmscriptenLogStream(EM_LOG_CONSOLE);
+
+  _log_streams[NS_unspecified] = info_stream;
+  _log_streams[NS_spam] = info_stream;
+  _log_streams[NS_debug] = info_stream;
+  _log_streams[NS_info] = info_stream;
+  _log_streams[NS_warning] = warn_stream;
+  _log_streams[NS_error] = error_stream;
+  _log_streams[NS_fatal] = error_stream;
+
+#else
   if (_ostream_ptr == &cerr) {
     ConfigVariableFilename notify_output
       ("notify-output", "",
@@ -535,4 +600,5 @@ config_initialized() {
       }
     }
   }
+#endif
 }

+ 4 - 21
dtool/src/prc/notifyCategory.cxx

@@ -19,10 +19,6 @@
 #include "configVariableBool.h"
 #include "config_prc.h"
 
-#ifdef ANDROID
-#include "androidLogStream.h"
-#endif
-
 #include <time.h>  // for strftime().
 #include <assert.h>
 
@@ -65,18 +61,6 @@ NotifyCategory(const string &fullname, const string &basename,
 ostream &NotifyCategory::
 out(NotifySeverity severity, bool prefix) const {
   if (is_on(severity)) {
-
-#ifdef ANDROID
-    // Android redirects stdio and stderr to /dev/null,
-    // but does provide its own logging system.  We use a special
-    // type of stream that redirects it to Android's log system.
-    if (prefix) {
-      return AndroidLogStream::out(severity) << *this << ": ";
-    } else {
-      return AndroidLogStream::out(severity);
-    }
-#else
-
     if (prefix) {
       if (get_notify_timestamp()) {
         // Format a timestamp to include as a prefix as well.
@@ -85,18 +69,17 @@ out(NotifySeverity severity, bool prefix) const {
 
         char buffer[128];
         strftime(buffer, 128, ":%m-%d-%Y %H:%M:%S ", ptm);
-        nout << buffer;
+        Notify::out(severity) << buffer;
       }
 
       if (severity == NS_info) {
-        return nout << *this << ": ";
+        return Notify::out(severity) << *this << ": ";
       } else {
-        return nout << *this << "(" << severity << "): ";
+        return Notify::out(severity) << *this << "(" << severity << "): ";
       }
     } else {
-      return nout;
+      return Notify::out(severity);
     }
-#endif
 
   } else if (severity <= NS_debug && get_check_debug_notify_protect()) {
     // Someone issued a debug Notify output statement without

+ 1 - 0
dtool/src/prc/p3prc_composite2.cxx

@@ -2,6 +2,7 @@
 #include "configVariableManager.cxx"
 #include "configVariableSearchPath.cxx"
 #include "configVariableString.cxx"
+#include "emscriptenLogStream.cxx"
 #include "encryptStreamBuf.cxx"
 #include "encryptStream.cxx"
 #include "nativeNumericData.cxx"

+ 21 - 0
dtool/src/prc/pnotify.h

@@ -19,7 +19,13 @@
 #include "notifySeverity.h"
 #include <map>
 
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+#endif
+
 class NotifyCategory;
+class AndroidLogStream;
+class EmscriptenLogStream;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : Notify
@@ -62,6 +68,7 @@ PUBLISHED:
                                const string &parent_fullname);
   NotifyCategory *get_category(const string &fullname);
 
+  static ostream &out(NotifySeverity severity);
   static ostream &out();
   static ostream &null();
   static void write_string(const string &str);
@@ -93,6 +100,12 @@ private:
   typedef map<string, NotifyCategory *> Categories;
   Categories _categories;
 
+#if defined(ANDROID)
+  AndroidLogStream *_log_streams[NS_fatal + 1];
+#elif defined(__EMSCRIPTEN__)
+  EmscriptenLogStream *_log_streams[NS_fatal + 1];
+#endif
+
   static Notify *_global_ptr;
 };
 
@@ -185,11 +198,19 @@ private:
 
 #define nassert_raise(message) Notify::ptr()->assert_failure(message, __LINE__, __FILE__)
 
+#ifdef __EMSCRIPTEN__
+#define enter_debugger_if(condition) \
+  if (condition) { \
+    Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__); \
+    emscripten_debugger(); \
+  }
+#else
 #define enter_debugger_if(condition) \
   if (condition) { \
     Notify::ptr()->assert_failure(#condition, __LINE__, __FILE__); \
     __asm { int 3 } \
   }
+#endif
 
 
 #endif  // NDEBUG

+ 58 - 8
makepanda/makepanda.py

@@ -1204,6 +1204,9 @@ def CompileCxx(obj,src,opts):
             if arch.startswith('arm') and PkgSkip("NEON") == 0:
                 cmd += ' -mfpu=neon'
 
+        elif GetTarget() == 'emscripten':
+            cmd += " -s WARN_ON_UNDEFINED_SYMBOLS=1"
+
         else:
             cmd += " -pthread"
 
@@ -1228,7 +1231,7 @@ def CompileCxx(obj,src,opts):
             # Fast math is nice, but we'd like to see NaN in dev builds.
             cmd += " -fno-finite-math-only"
 
-        if (optlevel==1): cmd += " -ggdb -D_DEBUG"
+        if (optlevel==1): cmd += " -g -D_DEBUG"
         if (optlevel==2): cmd += " -O1 -D_DEBUG"
         if (optlevel==3): cmd += " -O2"
         if (optlevel==4): cmd += " -O3 -DNDEBUG"
@@ -1596,9 +1599,14 @@ def CompileLink(dll, obj, opts):
                     cmd += " -Wl,-rpath '-Wl,$ORIGIN'"
                 cmd += ' -o ' + dll + ' -L' + GetOutputDir() + '/lib -L' + GetOutputDir() + '/tmp'
 
-        for x in obj:
-            if GetOrigExt(x) != ".dat":
-                cmd += ' ' + x
+        if GetTarget() == 'emscripten' and GetOrigExt(dll) != ".exe":
+            for x in obj:
+                if GetOrigExt(x) not in (".dat", ".dll"):
+                    cmd += ' ' + x
+        else:
+            for x in obj:
+                if GetOrigExt(x) != ".dat":
+                    cmd += ' ' + x
 
         if (GetOrigExt(dll) == ".exe" and GetTarget() == 'windows' and "NOICON" not in opts):
             cmd += " " + GetOutputDir() + "/tmp/pandaIcon.res"
@@ -1623,6 +1631,10 @@ def CompileLink(dll, obj, opts):
             if GetTargetArch() == 'armv7a':
                 cmd += " -march=armv7-a -Wl,--fix-cortex-a8"
             cmd += ' -lc -lm'
+
+        elif GetTarget() == 'emscripten':
+            cmd += " -s WARN_ON_UNDEFINED_SYMBOLS=1"
+
         else:
             cmd += " -pthread"
 
@@ -1639,9 +1651,15 @@ def CompileLink(dll, obj, opts):
             if (opt=="ALWAYS") or (opt in opts):
                 cmd += ' ' + BracketNameWithQuotes(name)
 
-        if GetTarget() != 'freebsd':
+        if GetTarget() not in ('freebsd', 'emscripten'):
             cmd += " -ldl"
 
+        if GetTarget() == 'emscripten':
+            optlevel = GetOptimizeOption(opts)
+            if optlevel == 2: cmd += " -O1"
+            if optlevel == 3: cmd += " -O2"
+            if optlevel == 4: cmd += " -O3"
+
         oscmd(cmd)
 
         if GetTarget() == 'android':
@@ -2248,6 +2266,13 @@ def WriteConfigSettings():
         dtool_config["HAVE_LOCKF"] = 'UNDEF'
         dtool_config["HAVE_VIDEO4LINUX"] = 'UNDEF'
 
+    if (GetTarget() == "emscripten"):
+        # There are no threads in JavaScript, so don't bother using them.
+        dtool_config["HAVE_THREADS"] = 'UNDEF'
+        dtool_config["HAVE_POSIX_THREADS"] = 'UNDEF'
+        dtool_config["IS_LINUX"] = 'UNDEF'
+        dtool_config["HAVE_VIDEO4LINUX"] = 'UNDEF'
+
     if (GetOptimize() <= 2 and GetTarget() == "windows"):
         dtool_config["USE_DEBUG_PYTHON"] = '1'
 
@@ -2275,7 +2300,7 @@ def WriteConfigSettings():
     #if (GetOptimize() <= 2):
     #    dtool_config["TRACK_IN_INTERPRETER"] = '1'
 
-    if (GetOptimize() <= 3):
+    if (GetOptimize() <= 3) and GetTarget() != 'emscripten':
         dtool_config["DO_MEMORY_USAGE"] = '1'
 
     if (GetOptimize() <= 3):
@@ -3281,7 +3306,9 @@ if (not RUNTIME):
   OPTS=['DIR:panda/src/pnmimage', 'BUILDING:PANDA',  'ZLIB']
   TargetAdd('p3pnmimage_composite1.obj', opts=OPTS, input='p3pnmimage_composite1.cxx')
   TargetAdd('p3pnmimage_composite2.obj', opts=OPTS, input='p3pnmimage_composite2.cxx')
-  TargetAdd('p3pnmimage_convert_srgb_sse2.obj', opts=OPTS+['SSE2'], input='convert_srgb_sse2.cxx')
+
+  if GetTarget() != "emscripten":
+    TargetAdd('p3pnmimage_convert_srgb_sse2.obj', opts=OPTS+['SSE2'], input='convert_srgb_sse2.cxx')
 
   OPTS=['DIR:panda/src/pnmimage', 'ZLIB']
   IGATEFILES=GetDirectoryContents('panda/src/pnmimage', ["*.h", "*_composite*.cxx"])
@@ -3710,7 +3737,6 @@ if (not RUNTIME):
   TargetAdd('libpanda.dll', input='p3pnmimagetypes_composite2.obj')
   TargetAdd('libpanda.dll', input='p3pnmimage_composite1.obj')
   TargetAdd('libpanda.dll', input='p3pnmimage_composite2.obj')
-  TargetAdd('libpanda.dll', input='p3pnmimage_convert_srgb_sse2.obj')
   TargetAdd('libpanda.dll', input='p3text_composite1.obj')
   TargetAdd('libpanda.dll', input='p3text_composite2.obj')
   TargetAdd('libpanda.dll', input='p3tform_composite1.obj')
@@ -3729,6 +3755,9 @@ if (not RUNTIME):
   TargetAdd('libpanda.dll', input='libp3dtoolconfig.dll')
   TargetAdd('libpanda.dll', input='libp3dtool.dll')
 
+  if GetTarget() != "emscripten":
+    TargetAdd('libpanda.dll', input='p3pnmimage_convert_srgb_sse2.obj')
+
   if PkgSkip("FREETYPE")==0:
     TargetAdd('libpanda.dll', input="p3pnmtext_composite1.obj")
 
@@ -4368,6 +4397,23 @@ if (PkgSkip("EGL")==0 and PkgSkip("GLES2")==0 and PkgSkip("X11")==0 and not RUNT
   TargetAdd('libpandagles2.dll', input=COMMON_PANDA_LIBS)
   TargetAdd('libpandagles2.dll', opts=['MODULE', 'GLES2', 'EGL', 'X11', 'XRANDR', 'XF86DGA', 'XCURSOR'])
 
+#
+# DIRECTORY: panda/src/webgldisplay/
+#
+
+if GetTarget() == 'emscripten' and not PkgSkip("GLES2") and not RUNTIME:
+  DefSymbol('GLES2', 'OPENGLES_2', '')
+  OPTS=['DIR:panda/src/webgldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGLES2',  'GLES2', 'WEBGL']
+  TargetAdd('p3webgldisplay_webgldisplay_composite1.obj', opts=OPTS, input='p3webgldisplay_composite1.cxx')
+  OPTS=['DIR:panda/metalibs/pandagles2', 'BUILDING:PANDAGLES2', 'GLES2', 'EGL']
+  TargetAdd('p3webgldisplay_pandagles2.obj', opts=OPTS, input='pandagles2.cxx')
+  TargetAdd('libp3webgldisplay.dll', input='p3webgldisplay_pandagles2.obj')
+  TargetAdd('libp3webgldisplay.dll', input='p3gles2gsg_config_gles2gsg.obj')
+  TargetAdd('libp3webgldisplay.dll', input='p3gles2gsg_gles2gsg.obj')
+  TargetAdd('libp3webgldisplay.dll', input='p3webgldisplay_webgldisplay_composite1.obj')
+  TargetAdd('libp3webgldisplay.dll', input=COMMON_PANDA_LIBS)
+  TargetAdd('libp3webgldisplay.dll', opts=['MODULE', 'GLES2', 'WEBGL'])
+
 #
 # DIRECTORY: panda/src/ode/
 #
@@ -4595,6 +4641,10 @@ if (not RTDIST and not RUNTIME and PkgSkip("PVIEW")==0 and GetTarget() != 'andro
   TargetAdd('pview.exe', input=COMMON_PANDA_LIBS_PYSTUB)
   TargetAdd('pview.exe', opts=['ADVAPI', 'WINSOCK2', 'WINSHELL'])
 
+  if GetTarget() == 'emscripten':
+      # Link in a graphical back-end.
+      TargetAdd('pview.exe', input='libp3webgldisplay.dll')
+
 #
 # DIRECTORY: panda/src/android/
 #

+ 28 - 4
makepanda/makepandacore.py

@@ -42,6 +42,8 @@ ANDROID_ABI = None
 SYS_LIB_DIRS = []
 SYS_INC_DIRS = []
 DEBUG_DEPENDENCIES = False
+DEFAULT_CC = "gcc"
+DEFAULT_CXX = "g++"
 
 # Is the current Python a 32-bit or 64-bit build?  There doesn't
 # appear to be a universal test for this.
@@ -357,6 +359,11 @@ def SetTarget(target, arch=None):
         elif host != 'linux':
             exit('Should specify an architecture when building for Linux')
 
+    elif target == 'emscripten':
+        global DEFAULT_CC, DEFAULT_CXX
+        DEFAULT_CC = "emcc"
+        DEFAULT_CXX = "em++"
+
     elif target == host:
         if arch is None or arch == host_arch:
             # Not a cross build.
@@ -398,10 +405,10 @@ def CrossCompiling():
     return GetTarget() != GetHost()
 
 def GetCC():
-    return os.environ.get('CC', TOOLCHAIN_PREFIX + 'gcc')
+    return os.environ.get('CC', TOOLCHAIN_PREFIX + DEFAULT_CC)
 
 def GetCXX():
-    return os.environ.get('CXX', TOOLCHAIN_PREFIX + 'g++')
+    return os.environ.get('CXX', TOOLCHAIN_PREFIX + DEFAULT_CXX)
 
 def GetStrip():
     # Hack
@@ -1151,6 +1158,9 @@ def GetThirdpartyDir():
     elif (target == 'android'):
         THIRDPARTYDIR = GetThirdpartyBase()+"/android-libs-%s/" % (GetTargetArch())
 
+    elif (target == 'emscripten'):
+        THIRDPARTYDIR = base + "/emscripten-libs/"
+
     else:
         print("%s Unsupported platform: %s" % (ColorText("red", "WARNING:"), target))
         return
@@ -1473,6 +1483,9 @@ def SmartPkgEnable(pkg, pkgconfig = None, libs = None, incs = None, defs = None,
         for d in olddefs:
             defs[d] = ""
 
+    if GetTarget() == "emscripten":
+        libs = ()
+
     custom_loc = PkgHasCustomLocation(pkg)
 
     if pkg.lower() == "swscale" and os.path.isfile(GetThirdpartyDir() + "ffmpeg/include/libswscale/swscale.h"):
@@ -1552,7 +1565,7 @@ def SmartPkgEnable(pkg, pkgconfig = None, libs = None, incs = None, defs = None,
             if (have_all_pkgs):
                 return
 
-    if pkgconfig is not None and not libs:
+    if pkgconfig is not None and not incs:
         # pkg-config is all we can do, abort if it wasn't found.
         if pkg in PkgListGet():
             print("%sWARNING:%s Could not locate pkg-config package %s, excluding from build" % (GetColor("red"), GetColor(), pkgconfig))
@@ -2316,7 +2329,9 @@ def SetupBuildEnvironment(compiler):
             sysroot_flag = ' --sysroot=%s -no-canonical-prefixes' % (SDK["SYSROOT"])
 
         # Extract the dirs from the line that starts with 'libraries: ='.
-        cmd = GetCXX() + " -print-search-dirs" + sysroot_flag
+        # The -E is mostly to keep emscripten happy by preventing it from
+        # running the compiler and complaining about the lack of input files.
+        cmd = GetCXX() + " -E -print-search-dirs" + sysroot_flag
         handle = os.popen(cmd)
         for line in handle:
             if not line.startswith('libraries: ='):
@@ -2752,6 +2767,15 @@ def CalcLocation(fn, ipath):
         if (fn.endswith(".exe")):   return OUTPUTDIR+"/tmp/lib"+fn[:-4]+".so"
         if (fn.endswith(".lib")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".a"
         if (fn.endswith(".ilb")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".a"
+    elif (target == 'emscripten'):
+        if (fn.endswith(".obj")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".bc"
+        if (fn.endswith(".dll")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".bc"
+        if (fn.endswith(".pyd")):   return OUTPUTDIR+"/panda3d/"+fn[:-4]+".js"
+        if (fn.endswith(".mll")):   return OUTPUTDIR+"/plugins/"+fn
+        if (fn.endswith(".plugin")):return OUTPUTDIR+"/plugins/"+fn[:-7]+dllext+".js"
+        if (fn.endswith(".exe")):   return OUTPUTDIR+"/bin/"+fn[:-4]+".js"
+        if (fn.endswith(".lib")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".a"
+        if (fn.endswith(".ilb")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".a"
     else:
         if (fn.endswith(".obj")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".o"
         if (fn.endswith(".dll")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".so"

+ 50 - 0
panda/src/express/trueClock.cxx

@@ -515,6 +515,56 @@ set_time_scale(double time, double new_time_scale) {
   _time_scale = new_time_scale;
 }
 
+#elif defined(__EMSCRIPTEN__)
+
+////////////////////////////////////////////////////////////////////
+//
+// The Emscripten implementation.  This uses either the JavaScript
+// function performance.now() if available, otherwise Date.now().
+//
+////////////////////////////////////////////////////////////////////
+
+#include <emscripten.h>
+
+////////////////////////////////////////////////////////////////////
+//     Function: TrueClock::get_long_time, Emscripten implementation
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+double TrueClock::
+get_long_time() {
+  return emscripten_get_now() * 0.001;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TrueClock::get_short_raw_time, Emscripten implementation
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+double TrueClock::
+get_short_raw_time() {
+  return emscripten_get_now() * 0.001;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TrueClock::set_cpu_affinity, Emscripten implementation
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+bool TrueClock::
+set_cpu_affinity(PN_uint32 mask) const {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TrueClock::Constructor, Emscripten implementation
+//       Access: Protected
+//  Description:
+////////////////////////////////////////////////////////////////////
+TrueClock::
+TrueClock() {
+}
+
 #else  // !WIN32_VC
 
 ////////////////////////////////////////////////////////////////////

+ 20 - 0
panda/src/framework/pandaFramework.cxx

@@ -32,6 +32,21 @@
 #include "transformState.h"
 #include "renderState.h"
 
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+
+void em_do_frame(void *arg) {
+  PandaFramework *fwx = (PandaFramework *)arg;
+  nassertv_always(fwx != NULL);
+
+  if (!fwx->do_frame(Thread::get_current_thread())) {
+    emscripten_cancel_main_loop();
+    framework_cat.info() << "Main loop cancelled.\n";
+  }
+}
+
+#endif
+
 LoaderOptions PandaFramework::_loader_options;
 
 ////////////////////////////////////////////////////////////////////
@@ -837,9 +852,14 @@ do_frame(Thread *current_thread) {
 ////////////////////////////////////////////////////////////////////
 void PandaFramework::
 main_loop() {
+#ifdef __EMSCRIPTEN__
+  framework_cat.info() << "Starting main loop.\n";
+  emscripten_set_main_loop_arg(&em_do_frame, (void *)this, 0, true);
+#else
   Thread *current_thread = Thread::get_current_thread();
   while (do_frame(current_thread)) {
   }
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////

+ 8 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.I

@@ -173,7 +173,7 @@ maybe_gl_finish() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: GLGraphicsStateGuardian::has_extension
-//       Access: Published, Final
+//       Access: Published, Virtual
 //  Description: Returns true if the indicated extension is reported
 //               by the GL system, false otherwise.  The extension
 //               name is case-sensitive.
@@ -227,6 +227,13 @@ INLINE bool CLP(GraphicsStateGuardian)::
 is_at_least_gles_version(int major_version, int minor_version) const {
 #ifndef OPENGLES
   return false;
+
+#elif defined(__EMSCRIPTEN__)
+  // We're running WebGL.  WebGL 1 is based on OpenGL ES 2, and
+  // WebGL 2 is based on OpenGL ES 3.  So add one to the major version.
+  // There don't appear to be minor WebGL versions (yet).
+  return (_gl_version_major + 1 >= major_version);
+
 #else
   if (_gl_version_major < major_version) {
     return false;

+ 63 - 11
panda/src/glstuff/glGraphicsStateGuardian_src.cxx

@@ -127,21 +127,30 @@ null_glBlendColor(GLclampf, GLclampf, GLclampf, GLclampf) {
 }
 
 #ifdef OPENGLES_2
-// We have a default shader that will be applied when there
-// isn't any shader applied (e.g. if it failed to compile).
-// We need this because OpenGL ES 2.x does not have
-// a fixed-function pipeline.
-// This default shader just outputs a red color, telling
-// the user that something went wrong.
+// We have a default shader that will be applied when there isn't any
+// shader applied (e.g. if it failed to compile).  We need this because
+// OpenGL ES 2.x does not have a fixed-function pipeline.
+// This default shader just applies a single texture.
 static const string default_vshader =
-  "uniform mediump mat4 p3d_ModelViewProjectionMatrix;\n"
-  "attribute highp vec4 p3d_Vertex;\n"
+  "precision mediump float;\n"
+  "uniform mat4 p3d_ModelViewProjectionMatrix;\n"
+  "attribute vec4 p3d_Vertex;\n"
+  "attribute vec2 p3d_MultiTexCoord0;\n"
+  "varying vec2 texcoord;\n"
   "void main(void) {\n"
   "  gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;\n"
+  "  texcoord = p3d_MultiTexCoord0;\n"
   "}\n";
+
 static const string default_fshader =
+  "precision mediump float;\n"
+  "varying vec2 texcoord;\n"
+  "uniform vec4 p3d_Color;\n"
+  "uniform vec4 p3d_ColorScale;\n"
+  "uniform sampler2D p3d_Texture0;\n"
   "void main(void) {\n"
-  "  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+  "  gl_FragColor = texture2D(p3d_Texture0, texcoord).bgra;\n"
+  "  gl_FragColor *= p3d_Color * p3d_ColorScale;\n"
   "}\n";
 #endif
 
@@ -324,6 +333,8 @@ CLP(GraphicsStateGuardian)(GraphicsEngine *engine, GraphicsPipe *pipe) :
   _scissor_enabled = false;
   _scissor_attrib_active = false;
 
+  _white_texture = 0;
+
 #ifdef HAVE_CG
   _cg_context = 0;
 #endif
@@ -985,7 +996,8 @@ reset() {
   }
 
 #ifdef OPENGLES_2
-  _supports_bgr = false;
+  // OpenGL ES 2 does not support BGRA.  However, we swizzle in shader.
+  _supports_bgr = true;
 #else
   _supports_bgr =
     has_extension("GL_EXT_bgra") || is_at_least_gl_version(1, 2);
@@ -1099,8 +1111,13 @@ reset() {
 #endif
 
 #ifdef OPENGLES
+#ifdef __EMSCRIPTEN__
+  if (has_extension("WEBGL_depth_texture") ||
+      has_extension("GL_ANGLE_depth_texture")) {
+#else
   if (has_extension("GL_ANGLE_depth_texture")) {
-    // This extension provides both depth textures and depth-stencil support.
+#endif
+    // These extensions provide both depth textures and depth-stencil support.
     _supports_depth_texture = true;
     _supports_depth_stencil = true;
 
@@ -1691,6 +1708,13 @@ reset() {
   _max_color_targets = 1;
 
 #elif defined(OPENGLES_2)
+#ifdef __EMSCRIPTEN__
+  if (has_extension("WEBGL_draw_buffers")) {
+    _glDrawBuffers = (PFNGLDRAWBUFFERSPROC)
+      get_extension_func("glDrawBuffers");
+
+  } else
+#endif  // EMSCRIPTEN
   if (has_extension("GL_EXT_draw_buffers")) {
     _glDrawBuffers = (PFNGLDRAWBUFFERSPROC)
       get_extension_func("glDrawBuffersEXT");
@@ -8096,6 +8120,11 @@ get_internal_image_format(Texture *tex, bool force_sized) const {
     }
   }
 
+#if defined(__EMSCRIPTEN__) && defined(OPENGLES)
+  // WebGL has no sized formats, it would seem.
+  return get_external_image_format(tex);
+#endif
+
   switch (format) {
 #ifndef OPENGLES
   case Texture::F_color_index:
@@ -9701,7 +9730,30 @@ update_standard_texture_bindings() {
 }
 #endif  // !OPENGLES_2
 
+////////////////////////////////////////////////////////////////////
+//     Function: GLGraphicsStateGuardian::apply_white_texture
+//       Access: Private
+//  Description: Applies a white dummy texture.  This is useful to
+//               bind to a texture slot when a texture is missing.
+////////////////////////////////////////////////////////////////////
+void CLP(GraphicsStateGuardian)::
+apply_white_texture() {
+  if (_white_texture != 0) {
+    glBindTexture(GL_TEXTURE_2D, _white_texture);
+    return;
+  }
 
+  glGenTextures(1, &_white_texture);
+  glBindTexture(GL_TEXTURE_2D, _white_texture);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+  unsigned char data[] = {0xff, 0xff, 0xff, 0xff};
+  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0,
+               GL_RGBA, GL_UNSIGNED_BYTE, data);
+}
 
 #ifndef NDEBUG
 ////////////////////////////////////////////////////////////////////

+ 5 - 1
panda/src/glstuff/glGraphicsStateGuardian_src.h

@@ -425,7 +425,7 @@ protected:
   void save_extensions(const char *extensions);
   virtual void get_extra_extensions();
   void report_extensions() const;
-  INLINE virtual bool has_extension(const string &extension) const FINAL;
+  INLINE virtual bool has_extension(const string &extension) const;
   INLINE bool is_at_least_gl_version(int major_version, int minor_version) const;
   INLINE bool is_at_least_gles_version(int major_version, int minor_version) const;
   void *get_extension_func(const char *name);
@@ -518,6 +518,8 @@ protected:
   void update_standard_texture_bindings();
 #endif
 
+  void apply_white_texture();
+
 #ifndef NDEBUG
   void update_show_usage_texture_bindings(int show_stage_index);
   void upload_usage_texture(int width, int height);
@@ -897,6 +899,8 @@ public:
   bool _use_object_labels;
   PFNGLOBJECTLABELPROC _glObjectLabel;
 
+  GLuint _white_texture;
+
 #ifndef NDEBUG
   bool _show_texture_usage;
   int _show_texture_usage_max_size;

+ 4 - 0
panda/src/glstuff/glShaderContext_src.cxx

@@ -1533,6 +1533,10 @@ update_shader_texture_bindings(ShaderContext *prev) {
 
     } else {
       if (texunit >= texattrib->get_num_on_stages()) {
+        // Apply a white texture in order to make it easier to use a shader
+        // that takes a texture on a model that doesn't have a texture applied.
+        _glgsg->_glActiveTexture(GL_TEXTURE0 + i);
+        _glgsg->apply_white_texture();
         continue;
       }
       TextureStage *stage = texattrib->get_on_stage(texunit);

+ 3 - 1
panda/src/nativenet/socket_portable.h

@@ -244,7 +244,7 @@ const int LOCAL_CONNECT_BLOCKING = EINPROGRESS;
 * LINUX and FreeBSD STUFF
 ************************************************************************/
 
-#elif defined(IS_LINUX) || defined(IS_OSX) || defined(IS_FREEBSD)
+#elif defined(IS_LINUX) || defined(IS_OSX) || defined(IS_FREEBSD) || defined(__EMSCRIPTEN__)
 
 #include <sys/types.h>
 #include <sys/time.h>
@@ -322,7 +322,9 @@ inline int DO_RECV_FROM(SOCKET sck, char * data, int len, sockaddr_in * addr)
 
 inline int init_network()
 {
+#ifndef __EMSCRIPTEN__
     signal(SIGPIPE, SIG_IGN); // hmm do i still need this ...
+#endif
     return ALL_OK;
 }