فهرست منبع

Merge branch 'release/1.10.x'

rdb 4 سال پیش
والد
کامیت
eb6eb9f1dd
31فایلهای تغییر یافته به همراه446 افزوده شده و 118 حذف شده
  1. 2 1
      direct/src/dist/commands.py
  2. 9 0
      direct/src/showbase/PythonUtil.py
  3. 29 5
      makepanda/makepanda.py
  4. 2 1
      makepanda/makewheel.py
  5. 10 3
      panda/metalibs/pandagl/pandagl.cxx
  6. 4 0
      panda/metalibs/pandagl/pandagl.h
  7. 6 0
      panda/metalibs/pandagles/CMakeLists.txt
  8. 6 0
      panda/metalibs/pandagles2/CMakeLists.txt
  9. 12 0
      panda/src/egldisplay/CMakeLists.txt
  10. 1 1
      panda/src/egldisplay/config_egldisplay.cxx
  11. 3 3
      panda/src/egldisplay/eglGraphicsPipe.cxx
  12. 3 1
      panda/src/egldisplay/eglGraphicsPipe.h
  13. 2 2
      panda/src/egldisplay/eglGraphicsPixmap.cxx
  14. 2 2
      panda/src/egldisplay/eglGraphicsPixmap.h
  15. 5 5
      panda/src/egldisplay/eglGraphicsStateGuardian.cxx
  16. 2 2
      panda/src/egldisplay/eglGraphicsStateGuardian.h
  17. 2 2
      panda/src/egldisplay/eglGraphicsWindow.cxx
  18. 2 2
      panda/src/egldisplay/eglGraphicsWindow.h
  19. 4 0
      panda/src/glxdisplay/CMakeLists.txt
  20. 40 7
      panda/src/gobj/geomPrimitive.cxx
  21. 8 0
      panda/src/gobj/texturePeeker.I
  22. 113 73
      panda/src/gobj/texturePeeker.cxx
  23. 4 2
      panda/src/gobj/texturePeeker.h
  24. 7 0
      panda/src/pgraph/config_pgraph.cxx
  25. 2 0
      panda/src/pgraph/config_pgraph.h
  26. 32 6
      panda/src/pgraph/cullResult.cxx
  27. 1 0
      panda/src/pgraph/cullResult.h
  28. 1 0
      samples/shader-terrain/main.py
  29. 62 0
      tests/gobj/test_geom_primitives.py
  30. 48 0
      tests/gobj/test_texture_peek.py
  31. 22 0
      tests/showbase/test_PythonUtil.py

+ 2 - 1
direct/src/dist/commands.py

@@ -216,7 +216,8 @@ class build_apps(setuptools.Command):
             'libpthread.so.*', 'libc.so.*', 'ld-linux-x86-64.so.*',
             'libgl.so.*', 'libx11.so.*', 'libncursesw.so.*', 'libz.so.*',
             'librt.so.*', 'libutil.so.*', 'libnsl.so.1', 'libXext.so.6',
-            'libXrender.so.1', 'libICE.so.6', 'libSM.so.6',
+            'libXrender.so.1', 'libICE.so.6', 'libSM.so.6', 'libEGL.so.1',
+            'libOpenGL.so.0', 'libGLdispatch.so.0', 'libGLX.so.0',
             'libgobject-2.0.so.0', 'libgthread-2.0.so.0', 'libglib-2.0.so.0',
 
             # macOS

+ 9 - 0
direct/src/showbase/PythonUtil.py

@@ -1199,18 +1199,24 @@ class SerialNumGen:
         if start is None:
             start = 0
         self.__counter = start-1
+
     def next(self):
         self.__counter += 1
         return self.__counter
 
+    __next__ = next
+
 class SerialMaskedGen(SerialNumGen):
     def __init__(self, mask, start=None):
         self._mask = mask
         SerialNumGen.__init__(self, start)
+
     def next(self):
         v = SerialNumGen.next(self)
         return v & self._mask
 
+    __next__ = next
+
 _serialGen = SerialNumGen()
 def serialNum():
     global _serialGen
@@ -2447,6 +2453,7 @@ class AlphabetCounter:
     # object that produces 'A', 'B', 'C', ... 'AA', 'AB', etc.
     def __init__(self):
         self._curCounter = ['A']
+
     def next(self):
         result = ''.join([c for c in self._curCounter])
         index = -1
@@ -2470,6 +2477,8 @@ class AlphabetCounter:
                 break
         return result
 
+    __next__ = next
+
 if __debug__ and __name__ == '__main__':
     def testAlphabetCounter():
         tempList = []

+ 29 - 5
makepanda/makepanda.py

@@ -1059,6 +1059,14 @@ if not PkgSkip("EIGEN"):
             # will turn them into runtime assertions.
             DefSymbol("ALWAYS", "EIGEN_NO_STATIC_ASSERT")
 
+if not PkgSkip("EGL"):
+    DefSymbol('EGL', 'HAVE_EGL', '')
+    if PkgSkip("X11"):
+        DefSymbol('EGL', 'EGL_NO_X11', '')
+
+if not PkgSkip("X11"):
+    DefSymbol('X11', 'USE_X11', '')
+
 ########################################################################
 ##
 ## Give a Status Report on Command-Line Options
@@ -2832,6 +2840,8 @@ configprc = configprc.replace('\r\n', '\n')
 
 if (GetTarget() == 'windows'):
     configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "$USER_APPDATA/Panda3D-%s" % MAJOR_VERSION)
+elif not PkgSkip("X11") and not PkgSkip("GL") and not PkgSkip("EGL") and not GetLinkAllStatic():
+    configprc = configprc.replace("#load-display pandadx9", "aux-display p3headlessgl")
 else:
     configprc = configprc.replace("aux-display pandadx9", "")
 
@@ -3172,9 +3182,10 @@ elif GetTarget() == 'darwin':
 elif GetTarget() == 'android':
     CopyAllHeaders('panda/src/android')
     CopyAllHeaders('panda/src/androiddisplay')
-else:
+if not PkgSkip('X11'):
     CopyAllHeaders('panda/src/x11display')
-    CopyAllHeaders('panda/src/glxdisplay')
+    if not PkgSkip('GL'):
+        CopyAllHeaders('panda/src/glxdisplay')
 CopyAllHeaders('panda/src/egldisplay')
 CopyAllHeaders('panda/metalibs/pandagl')
 CopyAllHeaders('panda/metalibs/pandagles')
@@ -4587,7 +4598,6 @@ if GetTarget() == 'windows' and not PkgSkip("GL"):
 # If we're not compiling with any windowing system at all, but we do have EGL,
 # we can use that to create a headless libpandagl instead.
 if not PkgSkip("EGL") and not PkgSkip("GL") and PkgSkip("X11") and GetTarget() not in ('windows', 'darwin'):
-    DefSymbol('EGL', 'HAVE_EGL', '')
     OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGL', 'GL', 'EGL']
     TargetAdd('pandagl_egldisplay_composite1.obj', opts=OPTS, input='p3egldisplay_composite1.cxx')
     OPTS=['DIR:panda/metalibs/pandagl', 'BUILDING:PANDAGL', 'GL', 'EGL']
@@ -4599,13 +4609,27 @@ if not PkgSkip("EGL") and not PkgSkip("GL") and PkgSkip("X11") and GetTarget() n
     TargetAdd('libpandagl.dll', input=COMMON_PANDA_LIBS)
     TargetAdd('libpandagl.dll', opts=['MODULE', 'GL', 'EGL', 'CGGL'])
 
+elif not PkgSkip("EGL") and not PkgSkip("GL") and GetTarget() not in ('windows', 'darwin'):
+    # As a temporary solution for #1086, build this module, which we can use as a
+    # fallback to OpenGL for headless systems.
+    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGL', 'GL', 'EGL']
+    TargetAdd('p3headlessgl_egldisplay_composite1.obj', opts=OPTS, input='p3egldisplay_composite1.cxx')
+    OPTS=['DIR:panda/metalibs/pandagl', 'BUILDING:PANDAGL', 'GL', 'EGL']
+    TargetAdd('p3headlessgl_pandagl.obj', opts=OPTS, input='pandagl.cxx')
+    TargetAdd('libp3headlessgl.dll', input='p3headlessgl_pandagl.obj')
+    TargetAdd('libp3headlessgl.dll', input='p3glgsg_config_glgsg.obj')
+    TargetAdd('libp3headlessgl.dll', input='p3glgsg_glgsg.obj')
+    TargetAdd('libp3headlessgl.dll', input='p3headlessgl_egldisplay_composite1.obj')
+    TargetAdd('libp3headlessgl.dll', input=COMMON_PANDA_LIBS)
+    TargetAdd('libp3headlessgl.dll', opts=['MODULE', 'GL', 'EGL', 'CGGL'])
+
 #
 # DIRECTORY: panda/src/egldisplay/
 #
 
 if not PkgSkip("EGL") and not PkgSkip("GLES"):
     DefSymbol('GLES', 'OPENGLES_1', '')
-    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGLES', 'GLES', 'EGL']
+    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGLES', 'GLES', 'EGL', 'X11']
     TargetAdd('pandagles_egldisplay_composite1.obj', opts=OPTS, input='p3egldisplay_composite1.cxx')
     OPTS=['DIR:panda/metalibs/pandagles', 'BUILDING:PANDAGLES', 'GLES', 'EGL']
     TargetAdd('pandagles_pandagles.obj', opts=OPTS, input='pandagles.cxx')
@@ -4624,7 +4648,7 @@ if not PkgSkip("EGL") and not PkgSkip("GLES"):
 
 if not PkgSkip("EGL") and not PkgSkip("GLES2"):
     DefSymbol('GLES2', 'OPENGLES_2', '')
-    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGLES2', 'GLES2', 'EGL']
+    OPTS=['DIR:panda/src/egldisplay', 'DIR:panda/src/glstuff', 'BUILDING:PANDAGLES2', 'GLES2', 'EGL', 'X11']
     TargetAdd('pandagles2_egldisplay_composite1.obj', opts=OPTS, input='p3egldisplay_composite1.cxx')
     OPTS=['DIR:panda/metalibs/pandagles2', 'BUILDING:PANDAGLES2', 'GLES2', 'EGL']
     TargetAdd('pandagles2_pandagles2.obj', opts=OPTS, input='pandagles2.cxx')

+ 2 - 1
makepanda/makewheel.py

@@ -82,7 +82,7 @@ ABI_TAG = get_abi_tag()
 EXCLUDE_EXT = [".pyc", ".pyo", ".N", ".prebuilt", ".xcf", ".plist", ".vcproj", ".sln"]
 
 # Plug-ins to install.
-PLUGIN_LIBS = ["pandagl", "pandagles", "pandagles2", "pandadx9", "p3tinydisplay", "p3ptloader", "p3assimp", "p3ffmpeg", "p3openal_audio", "p3fmod_audio"]
+PLUGIN_LIBS = ["pandagl", "pandagles", "pandagles2", "pandadx9", "p3tinydisplay", "p3ptloader", "p3assimp", "p3ffmpeg", "p3openal_audio", "p3fmod_audio", "p3headlessgl"]
 
 # Libraries included in manylinux ABI that should be ignored.  See PEP 513/571/599.
 MANYLINUX_LIBS = [
@@ -95,6 +95,7 @@ MANYLINUX_LIBS = [
     # These are not mentioned in manylinux1 spec but should nonetheless always
     # be excluded.
     "linux-vdso.so.1", "linux-gate.so.1", "ld-linux.so.2", "libdrm.so.2",
+    "libEGL.so.1", "libOpenGL.so.0", "libGLX.so.0", "libGLdispatch.so.0",
 ]
 
 # Binaries to never scan for dependencies on non-Windows systems.

+ 10 - 3
panda/metalibs/pandagl/pandagl.cxx

@@ -23,7 +23,7 @@
 #include "glxGraphicsPipe.h"
 #endif
 
-#if defined(HAVE_EGL) && !defined(HAVE_X11)
+#ifdef HAVE_EGL
 #include "config_egldisplay.h"
 #include "eglGraphicsPipe.h"
 #endif
@@ -54,7 +54,7 @@ init_libpandagl() {
   init_libglxdisplay();
 #endif
 
-#if defined(HAVE_EGL) && !defined(HAVE_X11)
+#ifdef HAVE_EGL
   init_libegldisplay();
 #endif
 }
@@ -77,9 +77,16 @@ get_pipe_type_pandagl() {
   return glxGraphicsPipe::get_class_type().get_index();
 #endif
 
-#if defined(HAVE_EGL) && !defined(HAVE_X11)
+#ifdef HAVE_EGL
   return eglGraphicsPipe::get_class_type().get_index();
 #endif
 
   return 0;
 }
+
+#if defined(HAVE_EGL) && !defined(USE_X11)
+int
+get_pipe_type_p3headlessgl() {
+  return eglGraphicsPipe::get_class_type().get_index();
+}
+#endif

+ 4 - 0
panda/metalibs/pandagl/pandagl.h

@@ -12,4 +12,8 @@
 EXPCL_PANDAGL void init_libpandagl();
 extern "C" EXPCL_PANDAGL int get_pipe_type_pandagl();
 
+#if defined(HAVE_EGL) && !defined(USE_X11)
+extern "C" EXPCL_PANDAGL int get_pipe_type_p3headlessgl();
+#endif
+
 #endif

+ 6 - 0
panda/metalibs/pandagles/CMakeLists.txt

@@ -20,6 +20,12 @@ add_metalib(pandagles ${MODULE_TYPE}
   COMPONENTS p3egldisplay_gles1 p3glesgsg p3glstuff p3x11display)
 unset(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME)
 
+if(HAVE_X11)
+  target_compile_definitions(pandagles PUBLIC USE_X11)
+else()
+  target_compile_definitions(pandagles PRIVATE EGL_NO_X11)
+endif()
+
 install(TARGETS pandagles
   EXPORT OpenGLES1 COMPONENT OpenGLES1
   DESTINATION ${MODULE_DESTINATION}

+ 6 - 0
panda/metalibs/pandagles2/CMakeLists.txt

@@ -10,6 +10,12 @@ add_metalib(pandagles2 ${MODULE_TYPE}
   COMPONENTS p3egldisplay_gles2 p3gles2gsg p3glstuff p3x11display)
 unset(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME)
 
+if(HAVE_X11)
+  target_compile_definitions(pandagles2 PUBLIC USE_X11)
+else()
+  target_compile_definitions(pandagles2 PRIVATE EGL_NO_X11)
+endif()
+
 install(TARGETS pandagles2
   EXPORT OpenGLES2 COMPONENT OpenGLES2
   DESTINATION ${MODULE_DESTINATION}

+ 12 - 0
panda/src/egldisplay/CMakeLists.txt

@@ -35,6 +35,12 @@ if(HAVE_GLES1)
   target_link_libraries(p3egldisplay_gles1 p3glesgsg p3x11display
     PKG::EGL PKG::GLES1)
 
+  if(HAVE_X11)
+    target_compile_definitions(p3egldisplay_gles1 PUBLIC USE_X11)
+  else()
+    target_compile_definitions(p3egldisplay_gles1 PRIVATE EGL_NO_X11)
+  endif()
+
   if(NOT BUILD_METALIBS)
     install(TARGETS p3egldisplay_gles1
       EXPORT OpenGLES1 COMPONENT OpenGLES1
@@ -55,6 +61,12 @@ if(HAVE_GLES2)
   target_link_libraries(p3egldisplay_gles2 p3gles2gsg p3x11display
     PKG::EGL PKG::GLES2)
 
+  if(HAVE_X11)
+    target_compile_definitions(p3egldisplay_gles2 PUBLIC USE_X11)
+  else()
+    target_compile_definitions(p3egldisplay_gles2 PRIVATE EGL_NO_X11)
+  endif()
+
   if(NOT BUILD_METALIBS)
     install(TARGETS p3egldisplay_gles2
       EXPORT OpenGLES2 COMPONENT OpenGLES2

+ 1 - 1
panda/src/egldisplay/config_egldisplay.cxx

@@ -48,7 +48,7 @@ init_libegldisplay() {
 
   eglGraphicsBuffer::init_type();
   eglGraphicsPipe::init_type();
-#ifdef HAVE_X11
+#ifdef USE_X11
   eglGraphicsPixmap::init_type();
   eglGraphicsWindow::init_type();
 #endif

+ 3 - 3
panda/src/egldisplay/eglGraphicsPipe.cxx

@@ -53,7 +53,7 @@ eglGraphicsPipe() {
 
   //NB. if the X11 display failed to open, _display will be 0, which is a valid
   // input to eglGetDisplay - it means to open the default display.
-#ifdef HAVE_X11
+#ifdef USE_X11
   _egl_display = eglGetDisplay((NativeDisplayType) _display);
 #else
   _egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
@@ -207,7 +207,7 @@ make_output(const std::string &name,
   // First thing to try: an eglGraphicsWindow
 
   if (retry == 0) {
-#ifdef HAVE_X11
+#ifdef USE_X11
     if (!_display) {
       return nullptr;
     }
@@ -290,7 +290,7 @@ make_output(const std::string &name,
 
   // Fourth thing to try: an eglGraphicsPixmap.
   if (retry == 3) {
-#ifdef HAVE_X11
+#ifdef USE_X11
     if (!_display) {
       return nullptr;
     }

+ 3 - 1
panda/src/egldisplay/eglGraphicsPipe.h

@@ -16,12 +16,14 @@
 
 #include "pandabase.h"
 
-#ifdef HAVE_X11
+#ifdef USE_X11
 #include "x11GraphicsPipe.h"
 typedef x11GraphicsPipe BaseGraphicsPipe;
 #else
 #include "graphicsPipe.h"
 typedef GraphicsPipe BaseGraphicsPipe;
+#undef EGL_NO_X11
+#define EGL_NO_X11 1
 #endif
 
 #ifdef OPENGLES_2

+ 2 - 2
panda/src/egldisplay/eglGraphicsPixmap.cxx

@@ -13,7 +13,7 @@
 
 #include "eglGraphicsPixmap.h"
 
-#ifdef HAVE_X11
+#ifdef USE_X11
 
 #include "eglGraphicsWindow.h"
 #include "eglGraphicsStateGuardian.h"
@@ -245,4 +245,4 @@ open_buffer() {
   return true;
 }
 
-#endif  // HAVE_X11
+#endif  // USE_X11

+ 2 - 2
panda/src/egldisplay/eglGraphicsPixmap.h

@@ -16,7 +16,7 @@
 
 #include "pandabase.h"
 
-#ifdef HAVE_X11
+#ifdef USE_X11
 
 #include "eglGraphicsPipe.h"
 #include "graphicsBuffer.h"
@@ -69,6 +69,6 @@ private:
   static TypeHandle _type_handle;
 };
 
-#endif  // HAVE_X11
+#endif  // USE_X11
 
 #endif

+ 5 - 5
panda/src/egldisplay/eglGraphicsStateGuardian.cxx

@@ -187,7 +187,7 @@ choose_pixel_format(const FrameBufferProperties &properties,
       best_props = fbprops;
     }
   }
-#ifdef HAVE_X11
+#ifdef USE_X11
   X11_Display *display = egl_pipe->get_display();
   if (display) {
     int screen = egl_pipe->get_screen();
@@ -233,7 +233,7 @@ choose_pixel_format(const FrameBufferProperties &properties,
 
     int err = eglGetError();
     if (_context && err == EGL_SUCCESS) {
-#ifdef HAVE_X11
+#ifdef USE_X11
       if (!display || _visual)
 #endif
       {
@@ -262,7 +262,7 @@ choose_pixel_format(const FrameBufferProperties &properties,
       << get_egl_error_string(err) << "\n";
     _fbconfig = 0;
     _context = 0;
-#ifdef HAVE_X11
+#ifdef USE_X11
     _visual = 0;
 #endif
   }
@@ -306,7 +306,7 @@ egl_is_at_least_version(int major_version, int minor_version) const {
  */
 void eglGraphicsStateGuardian::
 gl_flush() const {
-#ifdef HAVE_X11
+#ifdef USE_X11
   // This call requires synchronization with X.
   LightReMutexHolder holder(eglGraphicsPipe::_x_mutex);
 #endif
@@ -318,7 +318,7 @@ gl_flush() const {
  */
 GLenum eglGraphicsStateGuardian::
 gl_get_error() const {
-#ifdef HAVE_X11
+#ifdef USE_X11
   // This call requires synchronization with X.
   LightReMutexHolder holder(eglGraphicsPipe::_x_mutex);
 #endif

+ 2 - 2
panda/src/egldisplay/eglGraphicsStateGuardian.h

@@ -17,7 +17,7 @@
 #include "pandabase.h"
 #include "eglGraphicsPipe.h"
 
-#ifdef HAVE_X11
+#ifdef USE_X11
 #include "get_x11.h"
 #endif
 
@@ -55,7 +55,7 @@ public:
   EGLContext _share_context;
   EGLContext _context;
   EGLDisplay _egl_display;
-#ifdef HAVE_X11
+#ifdef USE_X11
   XVisualInfo *_visual = nullptr;
 #endif
   EGLConfig _fbconfig;

+ 2 - 2
panda/src/egldisplay/eglGraphicsWindow.cxx

@@ -13,7 +13,7 @@
 
 #include "eglGraphicsWindow.h"
 
-#ifdef HAVE_X11
+#ifdef USE_X11
 
 #include "eglGraphicsStateGuardian.h"
 #include "config_egldisplay.h"
@@ -279,4 +279,4 @@ open_window() {
   return true;
 }
 
-#endif  // HAVE_X11
+#endif  // USE_X11

+ 2 - 2
panda/src/egldisplay/eglGraphicsWindow.h

@@ -16,7 +16,7 @@
 
 #include "pandabase.h"
 
-#ifdef HAVE_X11
+#ifdef USE_X11
 
 #include "eglGraphicsPipe.h"
 #include "x11GraphicsWindow.h"
@@ -67,6 +67,6 @@ private:
 
 #include "eglGraphicsWindow.I"
 
-#endif  // HAVE_X11
+#endif  // USE_X11
 
 #endif

+ 4 - 0
panda/src/glxdisplay/CMakeLists.txt

@@ -28,6 +28,10 @@ add_component_library(p3glxdisplay SYMBOL BUILDING_PANDA_GLXDISPLAY
   ${P3GLXDISPLAY_HEADERS} ${P3GLXDISPLAY_SOURCES})
 target_link_libraries(p3glxdisplay p3glgsg p3x11display)
 
+if(HAVE_X11)
+  target_compile_definitions(p3glxdisplay PRIVATE USE_X11)
+endif()
+
 if(NOT BUILD_METALIBS)
   install(TARGETS p3glxdisplay EXPORT OpenGL COMPONENT OpenGL DESTINATION ${CMAKE_INSTALL_LIBDIR})
 endif()

+ 40 - 7
panda/src/gobj/geomPrimitive.cxx

@@ -542,16 +542,49 @@ offset_vertices(int offset, int begin_row, int end_row) {
 
     consider_elevate_index_type(cdata, max_vertex + offset);
 
-    GeomVertexRewriter index(do_modify_vertices(cdata), 0);
-    index.set_row_unsafe(begin_row);
-    for (int j = begin_row; j < end_row; ++j) {
-      int vertex = index.get_data1i();
-      if (vertex != strip_cut_index) {
-        index.set_data1i(vertex + offset);
+    {
+      GeomVertexArrayDataHandle handle(cdata->_vertices.get_write_pointer(),
+                                       Thread::get_current_thread());
+
+      unsigned char *ptr = handle.get_write_pointer();
+      switch (cdata->_index_type) {
+      case GeomEnums::NT_uint8:
+        for (int i = begin_row; i < end_row; ++i) {
+          uint8_t &v = ((uint8_t *)ptr)[i];
+          if (v != 0xff) {
+            v += offset;
+          }
+        }
+        break;
+
+      case GeomEnums::NT_uint16:
+        for (int i = begin_row; i < end_row; ++i) {
+          uint16_t &v = ((uint16_t *)ptr)[i];
+          if (v != 0xffff) {
+            v += offset;
+          }
+        }
+        break;
+
+      case GeomEnums::NT_uint32:
+        for (int i = begin_row; i < end_row; ++i) {
+          uint32_t &v = ((uint32_t *)ptr)[i];
+          if (v != 0xffffffff) {
+            v += offset;
+          }
+        }
+        break;
+
+      default:
+        nassert_raise("unsupported index type");
+        break;
       }
     }
 
-  } else {
+    cdata->_modified = Geom::get_next_modified();
+    cdata->_got_minmax = false;
+  }
+  else {
     // The supplied values cover all vertices, so we don't need to make it
     // indexed.
     CDWriter cdata(_cycler, true);

+ 8 - 0
panda/src/gobj/texturePeeker.I

@@ -56,3 +56,11 @@ INLINE bool TexturePeeker::
 has_pixel(int x, int y) const {
   return x >= 0 && y >= 0 && x < _x_size && y < _y_size;
 }
+
+/**
+ * Returns whether a given coordinate is inside of the texture dimensions.
+ */
+INLINE bool TexturePeeker::
+has_pixel(int x, int y, int z) const {
+  return x >= 0 && y >= 0 && z >= 0 && x < _x_size && y < _y_size && z < _z_size;
+}

+ 113 - 73
panda/src/gobj/texturePeeker.cxx

@@ -74,56 +74,42 @@ static double get_signed_int_i(const unsigned char *&p) {
  */
 TexturePeeker::
 TexturePeeker(Texture *tex, Texture::CData *cdata) {
-  if (cdata->_texture_type == Texture::TT_cube_map) {
-    // Cube map texture.  We'll need to map from (u, v, w) to (u, v) within
-    // the appropriate page, where w indicates the page.
-
-    // TODO: handle cube maps.
-    return;
-
-  } else {
-    // Regular 1-d, 2-d, or 3-d texture.  The coordinates map directly.
-    // Simple ram images are possible if it is a 2-d texture.
-    if (tex->do_has_ram_image(cdata) && cdata->_ram_image_compression == Texture::CM_off) {
-      // Get the regular RAM image if it is available.
-      _image = tex->do_get_ram_image(cdata);
-      _x_size = cdata->_x_size;
-      _y_size = cdata->_y_size;
-      _z_size = cdata->_z_size;
-      _component_width = cdata->_component_width;
-      _num_components = cdata->_num_components;
-      _format = cdata->_format;
-      _component_type = cdata->_component_type;
-
-    } else if (!cdata->_simple_ram_image._image.empty()) {
-      // Get the simple RAM image if *that* is available.
-      _image = cdata->_simple_ram_image._image;
-      _x_size = cdata->_simple_x_size;
-      _y_size = cdata->_simple_y_size;
-      _z_size = 1;
-
-      _component_width = 1;
-      _num_components = 4;
-      _format = Texture::F_rgba;
-      _component_type = Texture::T_unsigned_byte;
-
-    } else {
-      // Failing that, reload and get the uncompressed RAM image.
-      _image = tex->do_get_uncompressed_ram_image(cdata);
-      _x_size = cdata->_x_size;
-      _y_size = cdata->_y_size;
-      _z_size = cdata->_z_size;
-      _component_width = cdata->_component_width;
-      _num_components = cdata->_num_components;
-      _format = cdata->_format;
-      _component_type = cdata->_component_type;
-    }
+  // Simple ram images are possible if it is a 2-d texture.
+  if (tex->do_has_ram_image(cdata) && cdata->_ram_image_compression == Texture::CM_off) {
+    // Get the regular RAM image if it is available.
+    _image = tex->do_get_ram_image(cdata);
+    _x_size = cdata->_x_size;
+    _y_size = cdata->_y_size;
+    _z_size = cdata->_z_size;
+    _pixel_width = cdata->_component_width * cdata->_num_components;
+    _format = cdata->_format;
+    _component_type = cdata->_component_type;
+  }
+  else if (!cdata->_simple_ram_image._image.empty()) {
+    // Get the simple RAM image if *that* is available.
+    _image = cdata->_simple_ram_image._image;
+    _x_size = cdata->_simple_x_size;
+    _y_size = cdata->_simple_y_size;
+    _z_size = 1;
+
+    _pixel_width = 4;
+    _format = Texture::F_rgba;
+    _component_type = Texture::T_unsigned_byte;
+  }
+  else {
+    // Failing that, reload and get the uncompressed RAM image.
+    _image = tex->do_get_uncompressed_ram_image(cdata);
+    _x_size = cdata->_x_size;
+    _y_size = cdata->_y_size;
+    _z_size = cdata->_z_size;
+    _pixel_width = cdata->_component_width * cdata->_num_components;
+    _format = cdata->_format;
+    _component_type = cdata->_component_type;
   }
 
   if (_image.is_null()) {
     return;
   }
-  _pixel_width = _component_width * _num_components;
 
   if (Texture::is_integer(_format)) {
     switch (_component_type) {
@@ -291,8 +277,10 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
     _image.clear();
     return;
   }
-}
 
+  _is_cube = (cdata->_texture_type == Texture::TT_cube_map ||
+              cdata->_texture_type == Texture::TT_cube_map_array);
+}
 
 /**
  * Fills "color" with the RGBA color of the texel at point (u, v).
@@ -304,22 +292,95 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) {
  */
 void TexturePeeker::
 lookup(LColor &color, PN_stdfloat u, PN_stdfloat v) const {
-  int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size;
-  int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size;
-  fetch_pixel(color, x, y);
+  if (!_is_cube) {
+    int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size;
+    int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size;
+    fetch_pixel(color, x, y);
+  }
+  else {
+    lookup(color, u, v, 0);
+  }
+}
+
+/**
+ * Fills "color" with the RGBA color of the texel at point (u, v, w).
+ *
+ * The texel color is determined via nearest-point sampling (no filtering of
+ * adjacent pixels), regardless of the filter type associated with the
+ * texture.  u, v, and w will wrap around regardless of the texture's wrap
+ * mode.
+ */
+void TexturePeeker::
+lookup(LColor &color, PN_stdfloat u, PN_stdfloat v, PN_stdfloat w) const {
+  if (!_is_cube) {
+    int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size;
+    int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size;
+    int z = int((w - cfloor(w)) * (PN_stdfloat)_z_size) % _z_size;
+
+    nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size &&
+             z >= 0 && z < _z_size);
+    const unsigned char *p = _image.p() + (z * _x_size * _y_size + y * _x_size + x) * _pixel_width;
+
+    (*_get_texel)(color, p, _get_component);
+  }
+  else {
+    PN_stdfloat absu = fabs(u),
+                absv = fabs(v),
+                absw = fabs(w);
+    PN_stdfloat magnitude;
+    PN_stdfloat u2d, v2d;
+    int z;
+
+    // The following was pulled from:
+    // https://www.gamedev.net/forums/topic/687535-implementing-a-cube-map-lookup-function/
+    if (absw >= absu && absw >= absv) {
+      z = 4 + (w < 0.0);
+      magnitude = 0.5 / absw;
+      u2d = w < 0.0 ? -u : u;
+      v2d = -v;
+    }
+    else if (absv >= absu) {
+      z = 2 + (v < 0.0);
+      magnitude = 0.5 / absv;
+      u2d = u;
+      v2d = v < 0.0 ? -w : w;
+    }
+    else {
+      z = 0 + (u < 0.0);
+      magnitude = 0.5 / absu;
+      u2d = u < 0.0 ? w : -w;
+      v2d = -v;
+    }
+    u2d = u2d * magnitude + 0.5;
+    v2d = v2d * magnitude + 0.5;
+
+    int x = int((u2d - cfloor(u2d)) * (PN_stdfloat)_x_size) % _x_size;
+    int y = int((v2d - cfloor(v2d)) * (PN_stdfloat)_y_size) % _y_size;
+    fetch_pixel(color, x, y, z);
+  }
 }
 
 /**
- *  Works like TexturePeeker::lookup(), but instead uv-coordinates integer
- *  coordinates are used.
+ * Works like TexturePeeker::lookup(), but instead uv-coordinates integer
+ * coordinates are used.
  */
 void TexturePeeker::
-fetch_pixel(LColor& color, int x, int y) const {
+fetch_pixel(LColor &color, int x, int y) const {
   nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size);
   const unsigned char *p = _image.p() + (y * _x_size + x) * _pixel_width;
   (*_get_texel)(color, p, _get_component);
 }
 
+/**
+ * Works like TexturePeeker::lookup(), but instead uv-coordinates integer
+ * coordinates are used.
+ */
+void TexturePeeker::
+fetch_pixel(LColor &color, int x, int y, int z) const {
+  nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size && z >= 0 && z < _z_size);
+  const unsigned char *p = _image.p() + ((z * _y_size + y) * _x_size + x) * _pixel_width;
+  (*_get_texel)(color, p, _get_component);
+}
 
 /**
  * Performs a bilinear lookup to retrieve the color value stored at the uv
@@ -370,27 +431,6 @@ lookup_bilinear(LColor &color, PN_stdfloat u, PN_stdfloat v) const {
   return true;
 }
 
-/**
- * Fills "color" with the RGBA color of the texel at point (u, v, w).
- *
- * The texel color is determined via nearest-point sampling (no filtering of
- * adjacent pixels), regardless of the filter type associated with the
- * texture.  u, v, and w will wrap around regardless of the texture's wrap
- * mode.
- */
-void TexturePeeker::
-lookup(LColor &color, PN_stdfloat u, PN_stdfloat v, PN_stdfloat w) const {
-  int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size;
-  int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size;
-  int z = int((w - cfloor(w)) * (PN_stdfloat)_z_size) % _z_size;
-
-  nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size &&
-           z >= 0 && z < _z_size);
-  const unsigned char *p = _image.p() + (z * _x_size * _y_size + y * _x_size + x) * _pixel_width;
-
-  (*_get_texel)(color, p, _get_component);
-}
-
 /**
  * Fills "color" with the average RGBA color of the texels within the
  * rectangle defined by the specified coordinate range.

+ 4 - 2
panda/src/gobj/texturePeeker.h

@@ -37,9 +37,11 @@ PUBLISHED:
   INLINE int get_z_size() const;
 
   INLINE bool has_pixel(int x, int y) const;
+  INLINE bool has_pixel(int x, int y, int z) const;
   void lookup(LColor &color, PN_stdfloat u, PN_stdfloat v) const;
   void lookup(LColor &color, PN_stdfloat u, PN_stdfloat v, PN_stdfloat w) const;
   void fetch_pixel(LColor &color, int x, int y) const;
+  void fetch_pixel(LColor &color, int x, int y, int z) const;
   bool lookup_bilinear(LColor &color, PN_stdfloat u, PN_stdfloat v) const;
   void filter_rect(LColor &color,
                    PN_stdfloat min_u, PN_stdfloat min_v,
@@ -86,8 +88,8 @@ private:
   int _x_size;
   int _y_size;
   int _z_size;
-  int _component_width;
-  int _num_components;
+  int _is_cube;
+  int _unused1;
   int _pixel_width;
   Texture::Format _format;
   Texture::ComponentType _component_type;

+ 7 - 0
panda/src/pgraph/config_pgraph.cxx

@@ -375,6 +375,13 @@ ConfigVariableBool allow_live_flatten
           "only has an effect when Panda is not compiled for a release "
           "build."));
 
+ConfigVariableBool filled_wireframe_apply_shader
+("filled-wireframe-apply-shader", false,
+ PRC_DESC("Set this true to apply any shader configured on nodes onto the "
+          "filled wireframe overlay.  The wireframe color is multiplied with "
+          "the result of the fragment shader.  This is helpful when the shader "
+          "alters the position of the vertices and makes the overlay wrong."));
+
 /**
  * Initializes the library.  This must be called at least once before any of
  * the functions or classes in this library can be used.  Normally it will be

+ 2 - 0
panda/src/pgraph/config_pgraph.h

@@ -74,6 +74,8 @@ extern ConfigVariableString default_model_extension;
 
 extern ConfigVariableBool allow_live_flatten;
 
+extern ConfigVariableBool filled_wireframe_apply_shader;
+
 extern EXPCL_PANDA_PGRAPH void init_libpgraph();
 
 #endif

+ 32 - 6
panda/src/pgraph/cullResult.cxx

@@ -28,6 +28,7 @@
 #include "config_pgraph.h"
 #include "depthOffsetAttrib.h"
 #include "colorBlendAttrib.h"
+#include "shaderAttrib.h"
 
 TypeHandle CullResult::_type_handle;
 
@@ -133,7 +134,9 @@ add_object(CullableObject *object, const CullTraverser *traverser) {
   if (object->_state->get_attrib(rmode)) {
     if (rmode->get_mode() == RenderModeAttrib::M_filled_wireframe) {
       CullableObject *wireframe_part = new CullableObject(*object);
-      wireframe_part->_state = get_wireframe_overlay_state(rmode);
+      const ShaderAttrib *shader = nullptr;
+      object->_state->get_attrib(shader);
+      wireframe_part->_state = get_wireframe_overlay_state(rmode, shader);
 
       if (wireframe_part->munge_geom
           (_gsg, _gsg->get_geom_munger(wireframe_part->_state, current_thread),
@@ -521,13 +524,36 @@ get_wireframe_filled_state() {
  */
 CPT(RenderState) CullResult::
 get_wireframe_overlay_state(const RenderModeAttrib *rmode) {
-  return RenderState::make(
+  return get_wireframe_overlay_state(rmode, nullptr);
+}
+
+/**
+ * Returns a RenderState that renders only the wireframe part of an
+ * M_filled_wireframe model.
+ * If a shader attrib is provided, a constant color is used in ColorBlendAttrib
+ * to emulate the flat color.
+ */
+CPT(RenderState) CullResult::
+get_wireframe_overlay_state(const RenderModeAttrib *rmode, const ShaderAttrib *shader) {
+  CPT(RenderState) state = RenderState::make(
     DepthOffsetAttrib::make(1, 0, 0.99999f),
-    ColorAttrib::make_flat(rmode->get_wireframe_color()),
-    ColorBlendAttrib::make(ColorBlendAttrib::M_add,
-                           ColorBlendAttrib::O_incoming_alpha,
-                           ColorBlendAttrib::O_one_minus_incoming_alpha),
     RenderModeAttrib::make(RenderModeAttrib::M_wireframe,
                            rmode->get_thickness(),
                            rmode->get_perspective()));
+  if (filled_wireframe_apply_shader) {
+    state = state->add_attrib(ColorBlendAttrib::make(ColorBlendAttrib::M_add,
+                                                     ColorBlendAttrib::O_zero,
+                                                     ColorBlendAttrib::O_constant_color,
+                                                     ColorBlendAttrib::M_add,
+                                                     ColorBlendAttrib::O_one,
+                                                     ColorBlendAttrib::O_one_minus_incoming_alpha,
+                                                     rmode->get_wireframe_color()));
+    state = state->add_attrib(shader);
+  } else {
+    state = state->add_attrib(ColorBlendAttrib::make(ColorBlendAttrib::M_add,
+                                                     ColorBlendAttrib::O_incoming_alpha,
+                                                     ColorBlendAttrib::O_one_minus_incoming_alpha));
+    state = state->add_attrib(ColorAttrib::make_flat(rmode->get_wireframe_color()));
+  }
+  return state;
 }

+ 1 - 0
panda/src/pgraph/cullResult.h

@@ -78,6 +78,7 @@ private:
   static const RenderState *get_dual_opaque_state();
   static const RenderState *get_wireframe_filled_state();
   static CPT(RenderState) get_wireframe_overlay_state(const RenderModeAttrib *rmode);
+  static CPT(RenderState) get_wireframe_overlay_state(const RenderModeAttrib *rmode, const ShaderAttrib *shader);
 
   GraphicsStateGuardianBase *_gsg;
   PStatCollector _draw_region_pcollector;

+ 1 - 0
samples/shader-terrain/main.py

@@ -20,6 +20,7 @@ class ShaderTerrainDemo(ShowBase):
             textures-power-2 none
             gl-coordinate-system default
             window-title Panda3D ShaderTerrainMesh Demo
+            filled-wireframe-apply-shader true
 
             # As an optimization, set this to the maximum number of cameras
             # or lights that will be rendering the terrain at any given time.

+ 62 - 0
tests/gobj/test_geom_primitives.py

@@ -101,3 +101,65 @@ def test_geom_linestrips_adjacency():
         3, 4, 5, 6,
         4, 5, 6, 6,
     )
+
+
+def test_geom_linestrips_offset_indexed():
+    prim = core.GeomLinestrips(core.GeomEnums.UH_static)
+    prim.add_vertex(0)
+    prim.add_vertex(1)
+    prim.close_primitive()
+    prim.add_vertex(1)
+    prim.add_vertex(2)
+    prim.add_vertex(3)
+    prim.close_primitive()
+    prim.add_vertex(3)
+    prim.add_vertex(4)
+    prim.add_vertex(5)
+    prim.add_vertex(6)
+    prim.close_primitive()
+
+    prim.offset_vertices(100)
+    verts = prim.get_vertex_list()
+    cut = prim.strip_cut_index
+    assert tuple(verts) == (
+        100, 101,
+        cut,
+        101, 102, 103,
+        cut,
+        103, 104, 105, 106,
+    )
+
+    prim.offset_vertices(-100)
+    verts = prim.get_vertex_list()
+    cut = prim.strip_cut_index
+    assert tuple(verts) == (
+        0, 1,
+        cut,
+        1, 2, 3,
+        cut,
+        3, 4, 5, 6,
+    )
+
+    prim.offset_vertices(100, 4, 9)
+    verts = prim.get_vertex_list()
+    cut = prim.strip_cut_index
+    assert tuple(verts) == (
+        0, 1,
+        cut,
+        1, 102, 103,
+        cut,
+        103, 104, 5, 6,
+    )
+
+    # Automatically upgrade to uint32
+    prim.offset_vertices(100000)
+    assert prim.index_type == core.GeomEnums.NT_uint32
+    verts = prim.get_vertex_list()
+    cut = prim.strip_cut_index
+    assert tuple(verts) == (
+        100000, 100001,
+        cut,
+        100001, 100102, 100103,
+        cut,
+        100103, 100104, 100005, 100006,
+    )

+ 48 - 0
tests/gobj/test_texture_peek.py

@@ -158,3 +158,51 @@ def test_texture_peek_int_i():
     col = LColor()
     peeker.fetch_pixel(col, 0, 0)
     assert col == (minval, -1, 0, maxval)
+
+
+def test_texture_peek_cube():
+    maxval = 255
+    data_list = []
+    for z in range(6):
+        for y in range(3):
+            for x in range(3):
+                data_list += [z, y, x, maxval]
+    data = array('B', data_list)
+    tex = Texture("")
+    tex.setup_cube_map(3, Texture.T_unsigned_byte, Texture.F_rgba8i)
+    tex.set_ram_image(data)
+    peeker = tex.peek()
+    assert peeker.has_pixel(0, 0)
+    assert peeker.has_pixel(0, 0, 0)
+
+    # If no z is specified, face 0 is used by default
+    col = LColor()
+    peeker.fetch_pixel(col, 1, 2)
+    assert col == (1, 2, 0, maxval)
+
+    # Now try each face
+    for faceidx in range(6):
+        col = LColor()
+        peeker.fetch_pixel(col, 0, 0, faceidx)
+        assert col == (0, 0, faceidx, maxval)
+
+    # Try some vector lookups.
+    def lookup(*vec):
+        col = LColor()
+        peeker.lookup(col, *vec)
+        return col
+    assert lookup(1, 0, 0) == (1, 1, 0, maxval)
+    assert lookup(-1, 0, 0) == (1, 1, 1, maxval)
+    assert lookup(0, 1, 0) == (1, 1, 2, maxval)
+    assert lookup(0, -1, 0) == (1, 1, 3, maxval)
+    assert lookup(0, 0, 1) == (1, 1, 4, maxval)
+    assert lookup(0, 0, -1) == (1, 1, 5, maxval)
+
+    # Magnitude shouldn't matter
+    assert lookup(0, 2, 0) == (1, 1, 2, maxval)
+    assert lookup(0, 0, -0.5) == (1, 1, 5, maxval)
+
+    # Sample in corner (slight bias to disambiguate which face is selected)
+    assert lookup(1.00001, 1, 1) == (0, 0, 0, maxval)
+    assert lookup(1.00001, 1, 0) == (1, 0, 0, maxval)
+    assert lookup(1, 1.00001, 0) == (2, 1, 2, maxval)

+ 22 - 0
tests/showbase/test_PythonUtil.py

@@ -157,3 +157,25 @@ def test_weighted_choice():
     # When subtracting that number by each weight, it will reach 0
     # by the time it hits 'item6' in the iteration.
     assert item == items[5]
+
+
+def test_serial():
+    gen = PythonUtil.SerialNumGen()
+    assert gen.next() == 0
+    assert next(gen) == 1
+    assert next(gen) == 2
+    assert gen.next() == 3
+
+
+def test_alphabet_counter():
+    counter = PythonUtil.AlphabetCounter()
+    assert next(counter) == 'A'
+    assert counter.next() == 'B'
+    assert counter.next() == 'C'
+    assert next(counter) == 'D'
+
+    for i in range(26 - 4):
+        next(counter)
+
+    assert next(counter) == 'AA'
+    assert next(counter) == 'AB'