diff --git a/src/osgEarthDrivers/engine_corey/CMakeLists.txt b/src/osgEarthDrivers/engine_corey/CMakeLists.txt
new file mode 100644
index 0000000000..5488ff56a9
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/CMakeLists.txt
@@ -0,0 +1,73 @@
+
+set(TARGET_GLSL
+ Corey.vert.glsl
+ Corey.elevation.glsl
+ Corey.gs.glsl
+ Corey.ImageLayer.glsl
+ Corey.NormalMap.glsl
+ Corey.Morphing.glsl
+ Corey.Tessellation.glsl
+ Corey.SDK.glsl
+ Corey.vert.GL4.glsl
+ Corey.ImageLayer.GL4.glsl
+ Corey.NormalMap.GL4.glsl
+ Corey.Tessellation.GL4.glsl
+ Corey.SDK.GL4.glsl
+ Corey.GL4.glsl)
+
+set(TARGET_IN
+ Shaders.cpp.in)
+
+set(SHADERS_CPP "${CMAKE_CURRENT_BINARY_DIR}/AutoGenShaders.cpp")
+
+configure_shaders(
+ Shaders.cpp.in
+ ${SHADERS_CPP}
+ ${TARGET_GLSL} )
+
+SET(TARGET_LIBRARIES_VARS OSG_LIBRARY OSGDB_LIBRARY OSGUTIL_LIBRARY OSGTEXT_LIBRARY OPENTHREADS_LIBRARY OPENGL_gl_LIBRARY)
+
+SET(TARGET_SRC
+ CreateTileImplementation.cpp
+ DrawState.cpp
+ DrawTileCommand.cpp
+ CoreyTerrainEngineNode.cpp
+ CoreyTerrainEngineDriver.cpp
+ LayerDrawable.cpp
+ SelectionInfo.cpp
+ TerrainCuller.cpp
+ TerrainRenderData.cpp
+ TileGeometry.cpp
+ TileNode.cpp
+ ${SHADERS_CPP}
+)
+
+SET(TARGET_H
+ Common
+ CoreyTerrainEngineNode
+ CreateTileImplementation
+ DrawState
+ DrawTileCommand
+ EngineData
+ LayerDrawable
+ RenderBindings
+ SelectionInfo
+ Shaders
+ TerrainCuller
+ TerrainRenderData
+ TileRenderModel
+ TileGeometry
+ TileNode
+)
+
+IF(TRACY_FOUND)
+ INCLUDE_DIRECTORIES(${TRACY_INCLUDE_DIR})
+ LIST(APPEND TARGET_LIBRARIES_VARS TRACY_LIBRARY )
+ENDIF(TRACY_FOUND)
+
+setup_plugin(osgearth_engine_corey)
+
+# to install public driver includes:
+SET(LIB_NAME engine_corey)
+SET(LIB_PUBLIC_HEADERS ${TARGET_H})
+INCLUDE(ModuleInstallOsgEarthDriverIncludes OPTIONAL)
diff --git a/src/osgEarthDrivers/engine_corey/Common b/src/osgEarthDrivers/engine_corey/Common
new file mode 100644
index 0000000000..dc2c600888
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Common
@@ -0,0 +1,27 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#ifndef OSGEARTH_DRIVERS_Corey_TERRAIN_ENGINE_COMMON_H
+#define OSGEARTH_DRIVERS_Corey_TERRAIN_ENGINE_COMMON_H 1
+
+#include
+
+#define ARENA_LOAD_TILE "oe.rex.loadtile"
+#define ARENA_CREATE_CHILD "oe.rex.createchild"
+
+#endif // OSGEARTH_DRIVERS_Corey_TERRAIN_ENGINE_COMMON_H
diff --git a/src/osgEarthDrivers/engine_corey/Corey.GL4.glsl b/src/osgEarthDrivers/engine_corey/Corey.GL4.glsl
new file mode 100644
index 0000000000..b2e3152925
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.GL4.glsl
@@ -0,0 +1,41 @@
+#define MAX_NUM_SHARED_SAMPLERS 16
+
+struct oe_rex_Shared {
+ vec2 morphConstants[49];
+ float padding[2];
+};
+struct oe_rex_Tile {
+ vec4 tileKey;
+ mat4 modelViewMatrix;
+ mat4 colorMat;
+ mat4 parentMat;
+ mat4 elevMat;
+ mat4 normalMat;
+ mat4 landcoverMat;
+ mat4 sharedMat[MAX_NUM_SHARED_SAMPLERS];
+ int colorIndex;
+ int parentIndex;
+ int elevIndex;
+ int normalIndex;
+ int landcoverIndex;
+ int sharedIndex[MAX_NUM_SHARED_SAMPLERS];
+ int drawOrder;
+ float padding[2];
+};
+layout(binding = 29, std430) readonly buffer RexTextureArena {
+ uint64_t oe_terrain_tex[];
+};
+layout(binding = 30, std430) readonly buffer RexSharedDataBuffer {
+ oe_rex_Shared oe_shared;
+};
+layout(binding = 31, std430) readonly buffer RexTileBuffer {
+ oe_rex_Tile oe_tile[];
+};
+
+#if defined(VP_STAGE_VERTEX) || defined(VP_STAGE_TESSEVALUATION)
+flat out int oe_tileID;
+#elif defined(VP_STAGE_FRAGMENT)
+flat in int oe_tileID;
+#else
+int oe_tileID;
+#endif
diff --git a/src/osgEarthDrivers/engine_corey/Corey.ImageLayer.GL4.glsl b/src/osgEarthDrivers/engine_corey/Corey.ImageLayer.GL4.glsl
new file mode 100644
index 0000000000..c60380bbc5
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.ImageLayer.GL4.glsl
@@ -0,0 +1,143 @@
+#pragma include RexEngine.GL4.glsl
+#pragma vp_name Corey Engine - ImageLayer/VS
+#pragma vp_function oe_rex_imageLayer_VS, vertex_view, 0.4
+
+// Stage globals
+vec4 oe_layer_tilec;
+
+// outputs
+out vec2 oe_color_uv;
+out vec2 oe_parent_uv;
+flat out uint64_t oe_color_handle;
+flat out uint64_t oe_parent_handle;
+flat out int oe_draw_order;
+
+void oe_rex_imageLayer_VS(inout vec4 vertexView)
+{
+ oe_color_uv = (oe_tile[oe_tileID].colorMat * oe_layer_tilec).st;
+ int colorIndex = oe_tile[oe_tileID].colorIndex;
+ oe_color_handle = (colorIndex >= 0) ? oe_terrain_tex[colorIndex] : 0;
+
+ oe_parent_uv = (oe_tile[oe_tileID].parentMat * oe_layer_tilec).st;
+ int parentIndex = oe_tile[oe_tileID].parentIndex;
+ oe_parent_handle = (parentIndex >= 0) ? oe_terrain_tex[parentIndex] : 0;
+
+ oe_draw_order = oe_tile[oe_tileID].drawOrder;
+}
+
+
+[break]
+#pragma include RexEngine.GL4.glsl
+
+#pragma vp_name Corey Engine - Fragment
+#pragma vp_function oe_rex_imageLayer_FS, fragment_coloring, 0.5
+
+#pragma import_defines(OE_TERRAIN_RENDER_IMAGERY)
+#pragma import_defines(OE_TERRAIN_MORPH_IMAGERY)
+#pragma import_defines(OE_TERRAIN_BLEND_IMAGERY)
+#pragma import_defines(OE_TERRAIN_CAST_SHADOWS)
+#pragma import_defines(OE_IS_PICK_CAMERA)
+#pragma import_defines(OE_IS_SHADOW_CAMERA)
+#pragma import_defines(OE_IS_DEPTH_CAMERA)
+
+//uniform sampler2D oe_layer_tex;
+uniform int oe_layer_uid;
+uniform int oe_layer_order;
+
+#ifdef OE_TERRAIN_MORPH_IMAGERY
+in vec2 oe_parent_uv;
+flat in uint64_t oe_parent_handle;
+in float oe_rex_morphFactor;
+#endif
+
+// inputs
+in vec2 oe_color_uv;
+flat in uint64_t oe_color_handle;
+in vec4 oe_layer_tilec;
+in float oe_layer_opacity;
+flat in int oe_terrain_vertexMarker;
+flat in int oe_draw_order;
+
+#define VERTEX_VISIBLE 1
+#define VERTEX_BOUNDARY 2
+#define VERTEX_HAS_ELEVATION 4
+#define VERTEX_SKIRT 8
+
+void oe_rex_imageLayer_FS(inout vec4 color)
+{
+ // if the provoking vertex is marked for discard, skip it:
+ if ((oe_terrain_vertexMarker & VERTEX_VISIBLE) == 0)
+ {
+ discard;
+ return;
+ }
+
+ // If this is a shadow camera and the terrain doesn't cast shadows, no render:
+#if defined(OE_IS_SHADOW_CAMERA) && !defined(OE_TERRAIN_CAST_SHADOWS)
+ discard;
+ return;
+#endif
+
+ // If this is a depth-only camera, skip terrain skirt geometry:
+#if defined(OE_IS_DEPTH_CAMERA)
+ if ((oe_terrain_vertexMarker & VERTEX_SKIRT) != 0)
+ {
+ discard;
+ return;
+ }
+#endif // OE_IS_DEPTH_CAMERA
+
+ // if this is a picking camera, reset the color to all zeros:
+#ifdef OE_IS_PICK_CAMERA
+ color = vec4(0);
+ return;
+#endif
+
+ // If imagery rendering is disabled, we're done:
+#ifndef OE_TERRAIN_RENDER_IMAGERY
+ return;
+#endif
+
+ // whether this layer contains texel color (UID<0 means no texture)
+ bool isTexelLayer = oe_color_handle > 0UL;
+
+ vec4 texel = color;
+
+ if (isTexelLayer)
+ {
+ texel = texture(sampler2D(oe_color_handle), oe_color_uv);
+
+#ifdef OE_TERRAIN_MORPH_IMAGERY
+
+ if (oe_parent_handle != 0UL)
+ {
+ // sample the parent texture and blend for the morphing.
+ // We have to clamp oe_rex_morphFactor here even though it's clamped in the
+ // vertex shader. Reason unknown.
+ vec4 texelParent = texture(sampler2D(oe_parent_handle), oe_parent_uv);
+ texel = mix(texel, texelParent, clamp(oe_rex_morphFactor, 0.0, 1.0));
+ }
+
+#endif
+
+ // intergrate the layer opacity:
+ texel.a = texel.a * oe_layer_opacity;
+ color.a = 1.0;
+ }
+
+#ifdef OE_TERRAIN_BLEND_IMAGERY
+ // If this is a first image layer, blend with the incoming terrain color.
+ // Otherwise, apply directly and let GL blending do the rest.
+ if (isTexelLayer && (oe_draw_order == 0))
+ {
+ color.rgb = texel.rgb*texel.a + color.rgb*(1.0 - texel.a);
+ }
+ else
+ {
+ color = texel;
+ }
+#else
+ // No blending? The output is just the texel value.
+ color = texel;
+#endif // OE_TERRAIN_BLEND_IMAGERY
+}
diff --git a/src/osgEarthDrivers/engine_corey/Corey.ImageLayer.glsl b/src/osgEarthDrivers/engine_corey/Corey.ImageLayer.glsl
new file mode 100644
index 0000000000..693715eb25
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.ImageLayer.glsl
@@ -0,0 +1,143 @@
+#pragma vp_name Corey Engine - ImageLayer/VS
+#pragma vp_entryPoint oe_rex_imageLayer_VS
+#pragma vp_location vertex_view
+#pragma vp_order 0.4
+
+// Stage globals
+vec4 oe_layer_tilec;
+vec2 oe_layer_texc;
+vec2 oe_layer_texcParent;
+
+uniform mat4 oe_layer_texMatrix;
+uniform mat4 oe_layer_texParentMatrix;
+
+void oe_rex_imageLayer_VS(inout vec4 vertexView)
+{
+ // calculate the texture coordinates:
+ oe_layer_texc = (oe_layer_texMatrix * oe_layer_tilec).st;
+ oe_layer_texcParent = (oe_layer_texParentMatrix * oe_layer_tilec).st;
+}
+
+
+[break]
+
+#pragma vp_name Corey Engine - Fragment
+#pragma vp_entryPoint oe_rex_imageLayer_FS
+#pragma vp_location fragment_coloring
+#pragma vp_order 0.5
+
+#pragma import_defines(OE_TERRAIN_RENDER_IMAGERY)
+#pragma import_defines(OE_TERRAIN_MORPH_IMAGERY)
+#pragma import_defines(OE_TERRAIN_BLEND_IMAGERY)
+#pragma import_defines(OE_TERRAIN_CAST_SHADOWS)
+#pragma import_defines(OE_IS_PICK_CAMERA)
+#pragma import_defines(OE_IS_SHADOW_CAMERA)
+#pragma import_defines(OE_IS_DEPTH_CAMERA)
+
+uniform sampler2D oe_layer_tex;
+uniform int oe_layer_uid;
+uniform int oe_layer_order;
+
+#ifdef OE_TERRAIN_MORPH_IMAGERY
+uniform sampler2D oe_layer_texParent;
+uniform float oe_layer_texParentExists;
+in vec2 oe_layer_texcParent;
+in float oe_rex_morphFactor;
+#endif
+
+in vec2 oe_layer_texc;
+in vec4 oe_layer_tilec;
+in float oe_layer_opacity;
+
+// Vertex Markers:
+#define VERTEX_VISIBLE 1
+#define VERTEX_BOUNDARY 2
+#define VERTEX_HAS_ELEVATION 4
+#define VERTEX_SKIRT 8
+flat in int oe_terrain_vertexMarker;
+
+void oe_rex_imageLayer_FS(inout vec4 color)
+{
+ // if the provoking vertex is marked for discard, skip it:
+ if ((oe_terrain_vertexMarker & VERTEX_VISIBLE) == 0)
+ {
+ discard;
+ return;
+ }
+
+ // If this is a shadow camera and the terrain doesn't cast shadows, no render:
+#if defined(OE_IS_SHADOW_CAMERA) && !defined(OE_TERRAIN_CAST_SHADOWS)
+ discard;
+ return;
+#endif
+
+ // If this is a depth-only camera, skip terrain skirt geometry:
+#if defined(OE_IS_DEPTH_CAMERA)
+ if ((oe_terrain_vertexMarker & VERTEX_SKIRT) != 0)
+ {
+ discard;
+ return;
+ }
+#endif // OE_IS_DEPTH_CAMERA
+
+ // if this is a picking camera, reset the color to all zeros:
+#ifdef OE_IS_PICK_CAMERA
+ color = vec4(0);
+#else
+
+ // If imagery rendering is disabled, we're done:
+#ifndef OE_TERRAIN_RENDER_IMAGERY
+ return;
+#endif
+
+ // whether this layer contains texel color (UID<0 means no texture)
+ bool isTexelLayer = oe_layer_uid >= 0;
+
+ // whether this is the first layer to render:
+ bool isFirstLayer = oe_layer_order == 0;
+
+ vec4 texel = color;
+
+ if (isTexelLayer)
+ {
+ texel = texture(oe_layer_tex, oe_layer_texc);
+
+#ifdef OE_TERRAIN_MORPH_IMAGERY
+ // sample the main texture:
+
+ // sample the parent texture:
+ vec4 texelParent = texture(oe_layer_texParent, oe_layer_texcParent);
+
+ // if the parent texture does not exist, use the current texture with alpha=0 as the parent
+ // so we can "fade in" an image layer that starts at LOD > 0:
+ texelParent = mix(vec4(texel.rgb, 0.0), texelParent, oe_layer_texParentExists);
+
+ // Resolve the final texel color.
+ // We have to clamp oe_rex_morphFactor here even though it's clamped in the
+ // vertex shader. Reason unknown.
+ texel = mix(texel, texelParent, clamp(oe_rex_morphFactor, 0.0, 1.0));
+#endif
+
+ // intergrate thelayer opacity:
+ texel.a = texel.a * oe_layer_opacity;
+ color.a = 1.0;
+ }
+
+#ifdef OE_TERRAIN_BLEND_IMAGERY
+ // If this is a first image layer, blend with the incoming terrain color.
+ // Otherwise, apply directly and let GL blending do the rest.
+ if (isTexelLayer && isFirstLayer)
+ {
+ color.rgb = texel.rgb*texel.a + color.rgb*(1.0 - texel.a);
+ }
+ else
+ {
+ color = texel;
+ }
+#else
+ // No blending? The output is just the texel value.
+ color = texel;
+#endif // OE_TERRAIN_BLEND_IMAGERY
+
+#endif // OE_IS_PICK_CAMERA
+}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/engine_corey/Corey.Morphing.glsl b/src/osgEarthDrivers/engine_corey/Corey.Morphing.glsl
new file mode 100644
index 0000000000..c4303eea82
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.Morphing.glsl
@@ -0,0 +1,99 @@
+#pragma vp_name Corey Engine - Morphing
+#pragma vp_entryPoint oe_rex_morph
+#pragma vp_location vertex_model
+#pragma vp_order 0.5
+
+#pragma import_defines(OE_TERRAIN_MORPH_GEOMETRY)
+#pragma import_defines(OE_TERRAIN_RENDER_ELEVATION)
+#pragma import_defines(OE_IS_DEPTH_CAMERA)
+#pragma import_defines(OE_TILE_SIZE)
+
+out vec3 vp_Normal;
+out vec4 oe_layer_tilec;
+out float oe_rex_morphFactor;
+flat out int oe_terrain_vertexMarker;
+
+uniform vec2 oe_tile_morph;
+
+uniform vec2 oe_tile_elevTexelCoeff;
+
+#ifdef OE_IS_DEPTH_CAMERA
+uniform mat4 oe_shadowToPrimaryMatrix;
+#endif
+
+// SDK functions:
+float oe_terrain_getElevation(in vec2 uv);
+
+// Vertex Markers:
+#define VERTEX_VISIBLE 1
+#define VERTEX_BOUNDARY 2
+#define VERTEX_HAS_ELEVATION 4
+#define VERTEX_SKIRT 8
+#define VERTEX_CONSTRAINT 16
+
+
+void moveToConstraint(in vec4 vertex, in vec4 layer_tilec, out vec4 newVertex, out vec4 new_layer_tilec)
+{
+ newVertex = vertex;
+ new_layer_tilec = layer_tilec;
+}
+
+// Compute a morphing factor based on model-space inputs:
+float oe_rex_ComputeMorphFactor(in vec4 position, in vec3 up)
+{
+ // Find the "would be" position of the vertex (the position the vertex would
+ // assume with no morphing)
+ vec4 wouldBePosition = position;
+
+#ifdef OE_TERRAIN_RENDER_ELEVATION
+ float elev = oe_terrain_getElevation( oe_layer_tilec.st );
+ wouldBePosition.xyz += up*elev;
+#endif
+
+ vec4 wouldBePositionView = gl_ModelViewMatrix * wouldBePosition;
+
+#ifdef OE_IS_DEPTH_CAMERA
+ // For a depth camera, we have to compute the morphed position
+ // from the perspective of the primary camera so they match up:
+ wouldBePositionView = oe_shadowToPrimaryMatrix * wouldBePositionView;
+#endif
+
+ float fDistanceToEye = length(wouldBePositionView.xyz); // or just -z.
+ float fMorphLerpK = 1.0 - clamp( oe_tile_morph[0] - fDistanceToEye * oe_tile_morph[1], 0.0, 1.0 );
+ return fMorphLerpK;
+}
+
+void oe_rex_morph(inout vec4 vertexModel)
+{
+ // compute the morphing factor to send down the pipe.
+ // we need this even if vertex-morphing is off since we use it for
+ // other things (like image blending)
+ if ((oe_terrain_vertexMarker & VERTEX_CONSTRAINT) == 0)
+ {
+ oe_rex_morphFactor = oe_rex_ComputeMorphFactor(vertexModel, vp_Normal);
+
+#ifdef OE_TERRAIN_MORPH_GEOMETRY
+ vec4 neighborVertexModel = vec4(gl_MultiTexCoord1.xyz, 1.0);
+ vec3 neighborNormal = gl_MultiTexCoord2.xyz;
+
+ const float halfSize = (0.5*OE_TILE_SIZE)-0.5;
+ const float twoOverHalfSize = 2.0/(OE_TILE_SIZE-1.0);
+ // Either 0 if point should not be morphed (in (x, y)), or the
+ // delta to the neighbor point.
+ vec2 fractionalPart = fract(oe_layer_tilec.st * halfSize) * twoOverHalfSize;
+ vec4 neighbor_tilec = oe_layer_tilec;
+ neighbor_tilec.st = clamp(oe_layer_tilec.st - fractionalPart, 0.0, 1.0);
+
+ // morph the vertex:
+ vertexModel.xyz = mix(vertexModel.xyz, neighborVertexModel.xyz, oe_rex_morphFactor);
+
+ // morph the normal:
+ vp_Normal = normalize(mix(vp_Normal, neighborNormal, oe_rex_morphFactor));
+ oe_layer_tilec.st = mix(oe_layer_tilec.st, neighbor_tilec.st, oe_rex_morphFactor);
+#endif
+ }
+ else
+ {
+ oe_rex_morphFactor = 0.0;
+ }
+}
diff --git a/src/osgEarthDrivers/engine_corey/Corey.NormalMap.GL4.glsl b/src/osgEarthDrivers/engine_corey/Corey.NormalMap.GL4.glsl
new file mode 100644
index 0000000000..e6f60e2a7a
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.NormalMap.GL4.glsl
@@ -0,0 +1,77 @@
+#pragma include RexEngine.GL4.glsl
+#pragma vp_function oe_rex_normalMapVS, vertex_view, 0.5
+
+#pragma import_defines(OE_TERRAIN_RENDER_NORMAL_MAP)
+
+//out vec3 oe_normal_binormal;
+
+#ifdef OE_TERRAIN_RENDER_NORMAL_MAP
+vec2 oe_terrain_getNormalCoords();
+flat out uint64_t oe_normal_handle;
+out vec2 oe_normal_uv;
+#endif
+
+void oe_rex_normalMapVS(inout vec4 unused)
+{
+#ifdef OE_TERRAIN_RENDER_NORMAL_MAP
+ oe_normal_handle = 0;
+ int index = oe_tile[oe_tileID].normalIndex;
+ if (index >= 0)
+ {
+ oe_normal_uv = oe_terrain_getNormalCoords();
+ oe_normal_handle = oe_terrain_tex[index];
+ }
+#endif
+}
+
+
+[break]
+#pragma include RexEngine.GL4.glsl
+#pragma vp_function oe_rex_normalMapFS, fragment_coloring, 0.1
+
+#pragma import_defines(OE_TERRAIN_RENDER_NORMAL_MAP)
+#pragma import_defines(OE_DEBUG_NORMALS)
+#pragma import_defines(OE_DEBUG_CURVATURE)
+
+in vec3 vp_Normal;
+in vec3 oe_UpVectorView;
+
+vec4 oe_terrain_getNormalAndCurvature(in uint64_t, in vec2); // SDK
+
+#ifdef OE_TERRAIN_RENDER_NORMAL_MAP
+flat in uint64_t oe_normal_handle;
+in vec2 oe_normal_uv;
+#endif
+
+// stage global
+mat3 oe_normalMapTBN;
+
+void oe_rex_normalMapFS(inout vec4 color)
+{
+ vp_Normal = oe_UpVectorView;
+
+ vec3 binormal = normalize(gl_NormalMatrix * vec3(0, 1, 0));
+ vec3 tangent = normalize(cross(binormal, oe_UpVectorView));
+ oe_normalMapTBN = mat3(tangent, binormal, oe_UpVectorView);
+
+#ifdef OE_TERRAIN_RENDER_NORMAL_MAP
+ if (oe_normal_handle > 0)
+ {
+ vec4 N = oe_terrain_getNormalAndCurvature(oe_normal_handle, oe_normal_uv);
+ vp_Normal = normalize( oe_normalMapTBN*N.xyz );
+ }
+#endif
+
+#ifdef OE_DEBUG_CURVATURE
+ // visualize curvature quantized:
+ color.rgba = vec4(0, 0, 0, 1);
+ float curvature = N.w;
+ if (curvature > 0.0) color.r = curvature;
+ if (curvature < 0.0) color.g = -curvature;
+#endif
+
+#ifdef OE_DEBUG_NORMALS
+ // visualize normals:
+ color.rgb = (N.xyz + 1.0)*0.5;
+#endif
+}
diff --git a/src/osgEarthDrivers/engine_corey/Corey.NormalMap.glsl b/src/osgEarthDrivers/engine_corey/Corey.NormalMap.glsl
new file mode 100644
index 0000000000..9bbec09e4b
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.NormalMap.glsl
@@ -0,0 +1,88 @@
+#pragma vp_entryPoint oe_rex_normalMapVS
+#pragma vp_location vertex_view
+#pragma vp_order 0.5
+
+#pragma import_defines(OE_TERRAIN_RENDER_NORMAL_MAP)
+
+uniform mat4 oe_tile_normalTexMatrix;
+uniform vec2 oe_tile_elevTexelCoeff;
+
+uniform mat4 oe_tile_elevationTexMatrix;
+
+// stage globals
+out vec4 oe_layer_tilec;
+out vec2 oe_normal_uv;
+out vec3 oe_normal_binormal;
+
+void oe_rex_normalMapVS(inout vec4 unused)
+{
+#ifndef OE_TERRAIN_RENDER_NORMAL_MAP
+ return;
+#endif
+
+ // calculate the sampling coordinates for the normal texture
+ //oe_normalMapCoords = (oe_tile_normalTexMatrix * oe_layer_tilec).st;
+
+ //oe_normalMapCoords = oe_layer_tilec.st
+ // * oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[0][0]
+ // + oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[3].st
+ // + oe_tile_elevTexelCoeff.y;
+
+ oe_normal_uv = oe_layer_tilec.st
+ * oe_tile_elevTexelCoeff.x * oe_tile_elevationTexMatrix[0][0]
+ + oe_tile_elevTexelCoeff.x * oe_tile_elevationTexMatrix[3].st
+ + oe_tile_elevTexelCoeff.y;
+
+ // send the bi-normal to the fragment shader
+ oe_normal_binormal = normalize(gl_NormalMatrix * vec3(0, 1, 0));
+}
+
+
+[break]
+
+#pragma vp_entryPoint oe_rex_normalMapFS
+#pragma vp_location fragment_coloring
+#pragma vp_order 0.1
+
+#pragma import_defines(OE_TERRAIN_RENDER_NORMAL_MAP)
+#pragma import_defines(OE_DEBUG_NORMALS)
+#pragma import_defines(OE_DEBUG_CURVATURE)
+
+// import terrain SDK
+vec4 oe_terrain_getNormalAndCurvature(in vec2);
+
+uniform sampler2D oe_tile_normalTex;
+
+in vec3 vp_Normal;
+in vec3 oe_UpVectorView;
+in vec2 oe_normal_uv;
+in vec3 oe_normal_binormal;
+
+// global
+mat3 oe_normalMapTBN;
+
+void oe_rex_normalMapFS(inout vec4 color)
+{
+#ifndef OE_TERRAIN_RENDER_NORMAL_MAP
+ return;
+#endif
+
+ vec4 N = oe_terrain_getNormalAndCurvature(oe_normal_uv);
+
+ vec3 tangent = normalize(cross(oe_normal_binormal, oe_UpVectorView));
+ oe_normalMapTBN = mat3(tangent, oe_normal_binormal, oe_UpVectorView);
+ vp_Normal = normalize(oe_normalMapTBN*N.xyz);
+
+#ifdef OE_DEBUG_CURVATURE
+ // visualize curvature quantized:
+ color.rgba = vec4(0, 0, 0, 1);
+ float curvature = N.w;
+ if (curvature > 0.0) color.r = curvature;
+ if (curvature < 0.0) color.g = -curvature;
+#endif
+
+#ifdef OE_DEBUG_NORMALS
+ // visualize normals:
+ color.rgb = (N.xyz + 1.0)*0.5;
+#endif
+}
\ No newline at end of file
diff --git a/src/osgEarthDrivers/engine_corey/Corey.NormalMap.vert.glsl b/src/osgEarthDrivers/engine_corey/Corey.NormalMap.vert.glsl
new file mode 100644
index 0000000000..bf8b27124f
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.NormalMap.vert.glsl
@@ -0,0 +1,35 @@
+#version $GLSL_VERSION_STR
+$GLSL_DEFAULT_PRECISION_FLOAT
+
+#pragma vp_entryPoint oe_normalMapVertex
+#pragma vp_location vertex_view
+#pragma vp_order 0.5
+
+#pragma import_defines(OE_TERRAIN_RENDER_NORMAL_MAP)
+
+uniform mat4 oe_tile_normalTexMatrix;
+uniform vec2 oe_tile_elevTexelCoeff;
+
+// stage globals
+vec4 oe_layer_tilec;
+
+out vec2 oe_normalMapCoords;
+out vec3 oe_normalMapBinormal;
+
+void oe_normalMapVertex(inout vec4 unused)
+{
+#ifndef OE_TERRAIN_RENDER_NORMAL_MAP
+ return;
+#endif
+
+ // calculate the sampling coordinates for the normal texture
+ //oe_normalMapCoords = (oe_tile_normalTexMatrix * oe_layer_tilec).st;
+
+ oe_normalMapCoords = oe_layer_tilec.st
+ * oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[0][0]
+ + oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[3].st
+ + oe_tile_elevTexelCoeff.y;
+
+ // send the bi-normal to the fragment shader
+ oe_normalMapBinormal = normalize(gl_NormalMatrix * vec3(0,1,0));
+}
diff --git a/src/osgEarthDrivers/engine_corey/Corey.SDK.GL4.glsl b/src/osgEarthDrivers/engine_corey/Corey.SDK.GL4.glsl
new file mode 100644
index 0000000000..12f5353226
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.SDK.GL4.glsl
@@ -0,0 +1,151 @@
+#pragma include RexEngine.GL4.glsl
+#pragma vp_name Rex Terrain SDK
+/**
+ * SDK functions for the Rex engine.
+ * Declare and call these from any shader that runs on the terrain.
+ */
+
+// Stage global
+vec4 oe_layer_tilec;
+vec4 oe_tile_key;
+
+#if !defined(VP_STAGE_FRAGMENT)
+
+uniform vec2 oe_tile_elevTexelCoeff;
+
+// Gets the coordinate to use for elevation sampling.
+vec2 oe_terrain_getElevationCoord(in vec2 uv)
+{
+ return uv
+ * oe_tile_elevTexelCoeff.x * oe_tile[oe_tileID].elevMat[0][0] // scale
+ + oe_tile_elevTexelCoeff.x * oe_tile[oe_tileID].elevMat[3].st // bias
+ + oe_tile_elevTexelCoeff.y;
+}
+
+// Gets the handle to use for elevation sampling
+uint64_t oe_terrain_getElevationHandle()
+{
+ int index = oe_tile[oe_tileID].elevIndex;
+ return (index >= 0) ? oe_terrain_tex[index] : 0;
+}
+
+// Sample the elevation data at a UV tile coordinate.
+float oe_terrain_getElevation(in vec2 uv)
+{
+ // Texel-level scale and bias allow us to sample the elevation texture
+ // on texel center instead of edge.
+ int index = oe_tile[oe_tileID].elevIndex;
+ if (index >= 0)
+ {
+ vec2 uv_scaledBiased = oe_terrain_getElevationCoord(uv);
+ return texture(sampler2D(oe_terrain_tex[index]), uv_scaledBiased).r;
+ }
+ return 0.0;
+}
+
+// Read the elevation at the build-in tile coordinates (convenience)
+float oe_terrain_getElevation()
+{
+ return oe_terrain_getElevation(oe_layer_tilec.st);
+}
+
+// Read the normal vector and curvature at resolved UV tile coordinates.
+vec4 oe_terrain_getNormalAndCurvature(in vec2 uv_scaledBiased)
+{
+ int index = oe_tile[oe_tileID].normalIndex;
+ if (index >= 0)
+ {
+ vec4 n = texture(sampler2D(oe_terrain_tex[index]), uv_scaledBiased);
+ n.xyz = n.xyz * 2.0 - 1.0;
+ float curv = n.z;
+ n.z = 1.0 - abs(n.x) - abs(n.y);
+ // unnecessary since Z is never < 0:
+ //float t = clamp(-n.z, 0, 1);
+ //n.x += (n.x > 0)? -t : t;
+ //n.y += (n.y > 0)? -t : t;
+ return vec4(normalize(n.xyz), curv);
+ }
+ else return vec4(0.0, 0.0, 1.0, 0.0);
+}
+
+vec4 oe_terrain_getNormalAndCurvature()
+{
+ vec2 uv_scaledBiased = oe_layer_tilec.st
+ * oe_tile_elevTexelCoeff.x * oe_tile[oe_tileID].normalMat[0][0]
+ + oe_tile_elevTexelCoeff.x * oe_tile[oe_tileID].normalMat[3].st
+ + oe_tile_elevTexelCoeff.y;
+
+ return oe_terrain_getNormalAndCurvature(uv_scaledBiased);
+}
+
+uint64_t oe_terrain_getNormalHandle()
+{
+ int index = oe_tile[oe_tileID].normalIndex;
+ return (index >= 0) ? oe_terrain_tex[index] : 0;
+}
+
+vec2 oe_terrain_getNormalCoords()
+{
+ return oe_layer_tilec.st
+ * oe_tile_elevTexelCoeff.x * oe_tile[oe_tileID].normalMat[0][0]
+ + oe_tile_elevTexelCoeff.x * oe_tile[oe_tileID].normalMat[3].st
+ + oe_tile_elevTexelCoeff.y;
+}
+
+#endif // !VP_STAGE_FRAGMENT
+
+vec4 oe_terrain_getNormalAndCurvature(in uint64_t handle, in vec2 uv)
+{
+ vec4 n = texture(sampler2D(handle), uv);
+ n.xyz = n.xyz*2.0 - 1.0;
+ float curv = n.z;
+ n.z = 1.0 - abs(n.x) - abs(n.y);
+ // unnecessary since Z is never < 0:
+ //float t = clamp(-n.z, 0, 1);
+ //n.x += (n.x > 0)? -t : t;
+ //n.y += (n.y > 0)? -t : t;
+ return vec4(normalize(n.xyz), curv);
+}
+
+/**
+ * Scales repeating texture coordinate such that they are [0..1]
+ * at a specific reference tile LOD.
+ */
+vec2 oe_terrain_scaleCoordsToRefLOD(in vec2 tc, in float refLOD)
+{
+ float dL = oe_tile_key.z - refLOD;
+ float factor = exp2(dL);
+ float invFactor = 1.0/factor;
+ vec2 result = tc * vec2(invFactor);
+
+ vec2 a = floor(oe_tile_key.xy * invFactor);
+ vec2 b = a * factor;
+ vec2 c = b + factor;
+
+ float m = floor(clamp(factor,0.0,1.0)); // if factor>=1.0
+ result += m*(oe_tile_key.xy-b)/(c-b);
+
+ return result;
+}
+
+/**
+ * Scales repeating texture coordinate such that they are [0..1]
+ * at a specific reference tile LOD.
+ */
+vec4 oe_terrain_scaleCoordsAndTileKeyToRefLOD(in vec2 tc, in float refLOD)
+{
+ float dL = oe_tile_key.z - refLOD;
+ float factor = exp2(dL);
+ float invFactor = 1.0 / factor;
+ vec2 result = tc * vec2(invFactor);
+
+ vec2 a = floor(oe_tile_key.xy * invFactor);
+ vec2 b = a * factor;
+ vec2 c = b + factor;
+
+ float m = floor(clamp(factor, 0.0, 1.0)); // if factor>=1.0
+ result += m * (oe_tile_key.xy - b) / (c - b);
+
+ return vec4(result, a);
+}
+
diff --git a/src/osgEarthDrivers/engine_corey/Corey.SDK.glsl b/src/osgEarthDrivers/engine_corey/Corey.SDK.glsl
new file mode 100644
index 0000000000..d4b40c143d
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.SDK.glsl
@@ -0,0 +1,109 @@
+#pragma vp_name Rex Terrain SDK
+
+#pragma import_defines(OE_MESA_23_WORKAROUND)
+#ifdef OE_MESA_23_WORKAROUND
+#define TILE_COORDS gl_MultiTexCoord0
+#else
+#define TILE_COORDS oe_layer_tilec
+#endif
+
+uniform sampler2D oe_tile_elevationTex;
+uniform mat4 oe_tile_elevationTexMatrix;
+uniform sampler2D oe_tile_normalTex;
+uniform mat4 oe_tile_normalTexMatrix;
+
+// SDK functions for the Rex engine.
+// Declare and call these from any shader that runs on the terrain.
+
+// uniforms from terrain engine
+uniform vec2 oe_tile_elevTexelCoeff;
+
+// Stage global
+vec4 oe_layer_tilec;
+vec4 oe_tile_key;
+
+// Sample the elevation data at a UV tile coordinate.
+float oe_terrain_getElevation(in vec2 uv)
+{
+ // Texel-level scale and bias allow us to sample the elevation texture
+ // on texel center instead of edge.
+ vec2 uv_scaledBiased = uv
+ * oe_tile_elevTexelCoeff.x * oe_tile_elevationTexMatrix[0][0] // scale
+ + oe_tile_elevTexelCoeff.x * oe_tile_elevationTexMatrix[3].st // bias
+ + oe_tile_elevTexelCoeff.y;
+
+ return texture(oe_tile_elevationTex, uv_scaledBiased).r;
+}
+
+// Read the elevation at the build-in tile coordinates (convenience)
+float oe_terrain_getElevation()
+{
+ return oe_terrain_getElevation(TILE_COORDS.st);
+}
+
+// Read the normal vector and curvature at resolved UV tile coordinates.
+vec4 oe_terrain_getNormalAndCurvature(in vec2 uv_scaledBiased)
+{
+ vec4 n = texture(oe_tile_normalTex, uv_scaledBiased);
+ n.xyz = n.xyz*2.0-1.0;
+ float curv = n.z;
+ n.z = 1.0 - abs(n.x) - abs(n.y);
+ // unnecessary since Z is never < 0:
+ //float t = clamp(-n.z, 0, 1);
+ //n.x += (n.x > 0)? -t : t;
+ //n.y += (n.y > 0)? -t : t;
+ return vec4(normalize(n.xyz), curv);
+}
+
+vec4 oe_terrain_getNormalAndCurvature()
+{
+ vec2 uv_scaledBiased = TILE_COORDS.st
+ * oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[0][0]
+ + oe_tile_elevTexelCoeff.x * oe_tile_normalTexMatrix[3].st
+ + oe_tile_elevTexelCoeff.y;
+
+ return oe_terrain_getNormalAndCurvature(uv_scaledBiased);
+}
+
+/**
+ * Scales repeating texture coordinate such that they are [0..1]
+ * at a specific reference tile LOD.
+ */
+vec2 oe_terrain_scaleCoordsToRefLOD(in vec2 tc, in float refLOD)
+{
+ float dL = oe_tile_key.z - refLOD;
+ float factor = exp2(dL);
+ float invFactor = 1.0/factor;
+ vec2 result = tc * vec2(invFactor);
+
+ vec2 a = floor(oe_tile_key.xy * invFactor);
+ vec2 b = a * factor;
+ vec2 c = b + factor;
+
+ float m = floor(clamp(factor,0.0,1.0)); // if factor>=1.0
+ result += m*(oe_tile_key.xy-b)/(c-b);
+
+ return result;
+}
+
+/**
+ * Scales repeating texture coordinate such that they are [0..1]
+ * at a specific reference tile LOD.
+ */
+vec4 oe_terrain_scaleCoordsAndTileKeyToRefLOD(in vec2 tc, in float refLOD)
+{
+ float dL = oe_tile_key.z - refLOD;
+ float factor = exp2(dL);
+ float invFactor = 1.0 / factor;
+ vec2 result = tc * vec2(invFactor);
+
+ vec2 a = floor(oe_tile_key.xy * invFactor);
+ vec2 b = a * factor;
+ vec2 c = b + factor;
+
+ float m = floor(clamp(factor, 0.0, 1.0)); // if factor>=1.0
+ result += m * (oe_tile_key.xy - b) / (c - b);
+
+ return vec4(result, a);
+}
+
diff --git a/src/osgEarthDrivers/engine_corey/Corey.Tessellation.GL4.glsl b/src/osgEarthDrivers/engine_corey/Corey.Tessellation.GL4.glsl
new file mode 100644
index 0000000000..70be2df948
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.Tessellation.GL4.glsl
@@ -0,0 +1,121 @@
+#pragma vp_name Corey Engine TCS
+#pragma vp_entryPoint oe_rex_TCS
+#pragma vp_location tess_control
+#pragma vp_order last
+
+#pragma include RexEngine.GL4.glsl
+
+layout(vertices=3) out;
+
+uniform float oe_terrain_tess;
+uniform float oe_terrain_tess_range;
+
+#pragma oe_use_shared_layer(LIFEMAP_TEX, LIFEMAP_MAT)
+
+varying vec4 oe_layer_tilec;
+varying vec4 vp_Vertex;
+varying vec3 vp_Normal;
+
+void VP_LoadVertex(in int);
+float oe_terrain_getElevation();
+
+float remap_unit(in float value, in float lo, in float hi)
+{
+ return clamp((value - lo) / (hi - lo), 0.0, 1.0);
+}
+
+// note: we are in MODEL space
+void oe_rex_TCS()
+{
+ if (gl_InvocationID == 0)
+ {
+ // iterator backward so we end up loading vertex 0
+ float d[3];
+ vec3 v[3];
+ mat4 mvm = oe_tile[oe_tileID].modelViewMatrix;
+ for (int i = 2; i >= 0; --i)
+ {
+ VP_LoadVertex(i);
+ v[i] = (mvm * (vp_Vertex + vec4(vp_Normal * oe_terrain_getElevation(), 0.0))).xyz;
+ d[i] = 1.0;
+#ifdef LIFEMAP_TEX
+ d[i] = texture(LIFEMAP_TEX, (LIFEMAP_MAT *oe_layer_tilec).st).r; // more rugged = more tessellated
+#endif
+ d[i] = oe_terrain_tess * d[i];
+ }
+
+ float max_dist = oe_terrain_tess_range;
+ float min_dist = oe_terrain_tess_range / 6.0;
+
+ vec3 m12 = 0.5*(v[1] + v[2]);
+ vec3 m20 = 0.5*(v[2] + v[0]);
+ vec3 m01 = 0.5*(v[0] + v[1]);
+
+ float f12 = remap_unit(-m12.z, max_dist, min_dist);
+ float f20 = remap_unit(-m20.z, max_dist, min_dist);
+ float f01 = remap_unit(-m01.z, max_dist, min_dist);
+
+ float e0 = max(1.0, max(d[1], d[2]) * f12);
+ float e1 = max(1.0, max(d[2], d[0]) * f20);
+ float e2 = max(1.0, max(d[0], d[1]) * f01);
+
+ float e3 = max(e0, max(e1, e2));
+
+ gl_TessLevelOuter[0] = e0;
+ gl_TessLevelOuter[1] = e1;
+ gl_TessLevelOuter[2] = e2;
+ gl_TessLevelInner[0] = e3;
+ }
+}
+
+
+[break]
+#pragma vp_name Corey Engine TES
+#pragma vp_entryPoint oe_rex_TES
+#pragma vp_location tess_eval
+
+// osgEarth terrain is always CCW winding
+layout(triangles, equal_spacing, ccw) in;
+
+// Internal helpers:
+void VP_Interpolate3();
+void VP_EmitVertex();
+
+float VP_Interpolate3(float a, float b, float c)
+{
+ return dot(gl_TessCoord.xyz, vec3(a,b,c));
+}
+
+vec2 VP_Interpolate3(vec2 a, vec2 b, vec2 c)
+{
+ return vec2(
+ dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
+ dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)));
+}
+
+vec3 VP_Interpolate3(vec3 a, vec3 b, vec3 c)
+{
+ return vec3(
+ dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
+ dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
+ dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)));
+}
+
+vec4 VP_Interpolate3(vec4 a, vec4 b, vec4 c)
+{
+ return vec4(
+ dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
+ dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
+ dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)),
+ dot(gl_TessCoord.xyz, vec3(a.w,b.w,c.w)));
+}
+
+varying vec3 oe_UpVectorView;
+varying vec3 vp_Normal;
+
+void oe_rex_TES()
+{
+ VP_Interpolate3();
+ VP_EmitVertex();
+}
+
diff --git a/src/osgEarthDrivers/engine_corey/Corey.Tessellation.glsl b/src/osgEarthDrivers/engine_corey/Corey.Tessellation.glsl
new file mode 100644
index 0000000000..f797ece5af
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.Tessellation.glsl
@@ -0,0 +1,111 @@
+#pragma vp_name Corey Engine TCS
+#pragma vp_function oe_rex_TCS, tess_control, last
+
+layout(vertices=3) out;
+
+uniform float oe_terrain_tess;
+uniform float oe_terrain_tess_range;
+
+#pragma oe_use_shared_layer(LIFEMAP_TEX, LIFEMAP_MAT)
+
+varying vec4 oe_layer_tilec;
+varying vec4 vp_Vertex;
+varying vec3 vp_Normal;
+
+void VP_LoadVertex(in int);
+float oe_terrain_getElevation();
+
+float remap_unit(in float value, in float lo, in float hi)
+{
+ return clamp((value - lo) / (hi - lo), 0.0, 1.0);
+}
+
+void oe_rex_TCS()
+{
+ if (gl_InvocationID == 0)
+ {
+ // iterator backward so we end up loading vertex 0
+ float d[3];
+ vec3 v[3];
+ for (int i = 2; i >= 0; --i)
+ {
+ VP_LoadVertex(i);
+ v[i] = (gl_ModelViewMatrix * (vp_Vertex + vec4(vp_Normal * oe_terrain_getElevation(), 0.0))).xyz;
+ d[i] = 1.0;
+#ifdef LIFEMAP_TEX
+ d[i] = texture(LIFEMAP_TEX, (LIFEMAP_MAT * oe_layer_tilec).st).r; // more rugged = more tessellated
+#endif
+ d[i] = oe_terrain_tess * d[i];
+ }
+
+ float max_dist = oe_terrain_tess_range;
+ float min_dist = oe_terrain_tess_range / 6.0;
+
+ vec3 m12 = 0.5*(v[1] + v[2]);
+ vec3 m20 = 0.5*(v[2] + v[0]);
+ vec3 m01 = 0.5*(v[0] + v[1]);
+
+ float f12 = remap_unit(-m12.z, max_dist, min_dist);
+ float f20 = remap_unit(-m20.z, max_dist, min_dist);
+ float f01 = remap_unit(-m01.z, max_dist, min_dist);
+
+ float e0 = max(1.0, max(d[1], d[2]) * f12);
+ float e1 = max(1.0, max(d[2], d[0]) * f20);
+ float e2 = max(1.0, max(d[0], d[1]) * f01);
+
+ float e3 = max(e0, max(e1, e2));
+
+ gl_TessLevelOuter[0] = e0;
+ gl_TessLevelOuter[1] = e1;
+ gl_TessLevelOuter[2] = e2;
+ gl_TessLevelInner[0] = e3;
+ }
+}
+
+
+[break]
+#pragma vp_name Corey Engine TES
+#pragma vp_function oe_rex_TES, tess_eval
+
+// osgEarth terrain is always CCW winding
+layout(triangles, equal_spacing, ccw) in;
+
+// Internal helpers:
+void VP_Interpolate3();
+void VP_EmitVertex();
+
+float VP_Interpolate3(float a, float b, float c)
+{
+ return dot(gl_TessCoord.xyz, vec3(a,b,c));
+}
+
+vec2 VP_Interpolate3(vec2 a, vec2 b, vec2 c)
+{
+ return vec2(
+ dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
+ dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)));
+}
+
+vec3 VP_Interpolate3(vec3 a, vec3 b, vec3 c)
+{
+ return vec3(
+ dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
+ dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
+ dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)));
+}
+
+vec4 VP_Interpolate3(vec4 a, vec4 b, vec4 c)
+{
+ return vec4(
+ dot(gl_TessCoord.xyz, vec3(a.x,b.x,c.x)),
+ dot(gl_TessCoord.xyz, vec3(a.y,b.y,c.y)),
+ dot(gl_TessCoord.xyz, vec3(a.z,b.z,c.z)),
+ dot(gl_TessCoord.xyz, vec3(a.w,b.w,c.w)));
+}
+
+void oe_rex_TES()
+{
+ VP_Interpolate3();
+ VP_EmitVertex();
+}
+
diff --git a/src/osgEarthDrivers/engine_corey/Corey.elevation.glsl b/src/osgEarthDrivers/engine_corey/Corey.elevation.glsl
new file mode 100644
index 0000000000..626459d3d8
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.elevation.glsl
@@ -0,0 +1,37 @@
+#pragma vp_name Corey Engine - Elevation
+#pragma vp_function oe_rex_applyElevation, vertex_view, 0.1
+
+#pragma import_defines(OE_TERRAIN_RENDER_ELEVATION)
+
+// Vertex Markers:
+#define VERTEX_VISIBLE 1
+#define VERTEX_BOUNDARY 2
+#define VERTEX_HAS_ELEVATION 4
+#define VERTEX_SKIRT 8
+#define VERTEX_CONSTRAINT 16
+
+// stage
+out vec4 oe_layer_tilec;
+out vec3 oe_UpVectorView;
+flat out int oe_terrain_vertexMarker;
+
+uniform float oe_terrain_altitude;
+
+// SDK functions:
+float oe_terrain_getElevation();
+
+void oe_rex_applyElevation(inout vec4 vertex)
+{
+#ifdef OE_TERRAIN_RENDER_ELEVATION
+
+ bool elevate =
+ ((oe_terrain_vertexMarker & VERTEX_VISIBLE) != 0) &&
+ ((oe_terrain_vertexMarker & VERTEX_HAS_ELEVATION) == 0);
+
+ float elev = elevate ? oe_terrain_getElevation() : 0.0;
+
+ vertex.xyz += oe_UpVectorView * elev;
+#endif
+
+ vertex.xyz += oe_UpVectorView * oe_terrain_altitude;
+}
diff --git a/src/osgEarthDrivers/engine_corey/Corey.gs.glsl b/src/osgEarthDrivers/engine_corey/Corey.gs.glsl
new file mode 100644
index 0000000000..a07d665f5b
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.gs.glsl
@@ -0,0 +1,20 @@
+#pragma vp_name Corey Engine - GS
+#pragma vp_entryPoint oe_rex_GS
+#pragma vp_location geometry
+
+layout(triangles)in;
+layout(triangle_strip, max_vertices=3) out;
+
+void VP_LoadVertex(in int);
+void VP_EmitModelVertex();
+
+void oe_rex_GS(void)
+{
+ for(int i=0; i < 3; ++i )
+ {
+ VP_LoadVertex(i);
+ gl_Position = gl_in[i].gl_Position;
+ VP_EmitModelVertex();
+ }
+ EndPrimitive();
+}
diff --git a/src/osgEarthDrivers/engine_corey/Corey.vert.GL4.glsl b/src/osgEarthDrivers/engine_corey/Corey.vert.GL4.glsl
new file mode 100644
index 0000000000..1b413c1540
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.vert.GL4.glsl
@@ -0,0 +1,152 @@
+#pragma include RexEngine.GL4.glsl
+#pragma vp_name Corey Custom M2V Transform
+#pragma vp_function oe_XformModelToView, vertex_transform_model_to_view
+
+out vec4 vp_Vertex;
+out vec3 vp_Normal;
+
+void oe_XformModelToView()
+{
+ mat4 mvm = oe_tile[oe_tileID].modelViewMatrix;
+ vp_Vertex = mvm * vp_Vertex;
+ vp_Normal = normalize(mat3(mvm) * vp_Normal);
+}
+
+[break]
+#pragma include RexEngine.GL4.glsl
+#pragma vp_name Corey Engine - Init Model Space
+#pragma vp_function oe_rex_init_model, vertex_model, first
+
+#pragma import_defines(OE_TERRAIN_RENDER_ELEVATION)
+#pragma import_defines(OE_TERRAIN_MORPH_GEOMETRY)
+#pragma import_defines(OE_TERRAIN_MORPH_IMAGERY)
+#pragma import_defines(OE_IS_SHADOW_CAMERA)
+#pragma import_defines(OE_TILE_SIZE)
+
+// SDK functions:
+float oe_terrain_getElevation(in vec2 uv);
+
+// attributes
+layout(location = 0) in vec3 a_position;
+layout(location = 1) in vec3 a_normal;
+layout(location = 2) in vec3 a_uv;
+layout(location = 3) in vec3 a_neighbor;
+layout(location = 4) in vec3 a_neighborNormal;
+
+#define VERTEX_CONSTRAINT 16
+
+// uniforms
+uniform vec4 oe_terrain_color;
+#ifdef OE_IS_SHADOW_CAMERA
+uniform mat4 oe_shadowToPrimaryMatrix;
+#endif
+
+// model stage only
+flat out vec4 oe_tile_key;
+
+out vec3 vp_Normal;
+out vec4 vp_Vertex;
+out vec3 vp_VertexView;
+out vec4 vp_Color;
+
+//flat out mat4 oe_tile_mvm;
+out vec4 oe_layer_tilec;
+
+out vec3 oe_UpVectorView;
+out float oe_rex_morphFactor;
+out vec4 oe_terrain_tessLevel;
+flat out int oe_terrain_vertexMarker;
+
+void oe_rex_morph_model();
+
+void oe_rex_init_model(inout vec4 out_model_vertex)
+{
+ vp_Vertex = vec4(a_position, 1);
+ vp_Normal = a_normal;
+
+ // instance ID from the DrawElementsIndirect cmd
+ oe_tileID = gl_DrawID;
+
+ // Color of the underlying map geometry (untextured)
+ vp_Color = oe_terrain_color;
+
+ // initialize:
+ oe_rex_morphFactor = 0.0;
+
+ // Default tessellation level (where applicable)
+ oe_terrain_tessLevel = vec4(1);
+
+ // assign vertex marker flags
+ oe_terrain_vertexMarker = int(a_uv.z);
+
+ // extract tile UV (global data)
+ oe_layer_tilec = vec4(a_uv.xy, 0, 1);
+
+ // the tile key
+ oe_tile_key = oe_tile[oe_tileID].tileKey;
+
+#if defined(OE_TERRAIN_MORPH_GEOMETRY) || defined(OE_TERRAIN_MORPH_IMAGERY)
+ if ((oe_terrain_vertexMarker & VERTEX_CONSTRAINT) == 0)
+ oe_rex_morph_model();
+#endif
+
+ // assign to output.
+ out_model_vertex = vp_Vertex;
+}
+
+// Uses neighbor data to morph across LODs
+void oe_rex_morph_model()
+{
+ mat4 mvm = oe_tile[oe_tileID].modelViewMatrix;
+
+ // Compute the morphing factor. We need the distance to
+ // the "final" vertex (elevation applied) to compute it
+
+#ifdef OE_TERRAIN_RENDER_ELEVATION
+ float elev = oe_terrain_getElevation(oe_layer_tilec.st);
+ vec4 vertex_view = mvm * vec4(a_position + vp_Normal * elev, 1);
+#else
+ vec4 vertex_view = mvm * vec4(a_position, 1);
+#endif
+
+#ifdef OE_IS_SHADOW_CAMERA
+ // For a depth camera, we have to compute the morphed position
+ // from the perspective of the primary camera so they match up:
+ vertex_view = oe_shadowToPrimaryMatrix * vertex_view;
+#endif
+
+ int lod = int(oe_tile_key.z);
+ float dist_to_eye = length(vertex_view.xyz);
+ vec2 mc = oe_shared.morphConstants[lod];
+ oe_rex_morphFactor = 1.0 - clamp(mc[0] - dist_to_eye * mc[1], 0.0, 1.0);
+
+ // if just morphing imagery, we're done - otherwise we also need
+ // to morph the vertex/normal/uv:
+
+#ifdef OE_TERRAIN_MORPH_GEOMETRY
+ // Compute the delta to the neighbor point.
+ const float halfSize = (0.5*OE_TILE_SIZE) - 0.5;
+ const float twoOverHalfSize = 2.0 / (OE_TILE_SIZE - 1.0);
+ vec2 fractionalPart = fract(oe_layer_tilec.st * halfSize) * twoOverHalfSize;
+ vec2 neighbor_tilec = clamp(oe_layer_tilec.st - fractionalPart, 0.0, 1.0);
+
+ // morph the vertex, normal, and uvs.
+ vp_Vertex.xyz = mix(a_position, a_neighbor, oe_rex_morphFactor);
+ vp_Normal = normalize(mix(vp_Normal, a_neighborNormal, oe_rex_morphFactor));
+ oe_layer_tilec.st = mix(oe_layer_tilec.st, neighbor_tilec, oe_rex_morphFactor);
+#endif
+}
+
+[break]
+#pragma include RexEngine.GL4.glsl
+#pragma vp_name Corey Engine - Init View Space
+#pragma vp_function oe_rex_init_view, vertex_view, first
+
+// outputs
+out vec3 vp_Normal;
+out vec3 oe_UpVectorView;
+
+void oe_rex_init_view(inout vec4 ignore)
+{
+ oe_UpVectorView = vp_Normal;
+}
diff --git a/src/osgEarthDrivers/engine_corey/Corey.vert.glsl b/src/osgEarthDrivers/engine_corey/Corey.vert.glsl
new file mode 100644
index 0000000000..0fc2645ba8
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Corey.vert.glsl
@@ -0,0 +1,59 @@
+#pragma vp_name Corey Engine - Init Model Space
+#pragma vp_function oe_rex_init_model, vertex_model, first
+
+// uniforms
+uniform vec4 oe_terrain_color;
+uniform vec4 oe_tile_key_u;
+
+// outputs
+out vec4 vp_Color;
+out vec4 oe_layer_tilec;
+out vec4 oe_terrain_tessLevel;
+out float oe_rex_morphFactor;
+flat out int oe_terrain_vertexMarker;
+
+// stage globals
+vec4 oe_tile_key;
+
+void oe_rex_init_model(inout vec4 vertexModel)
+{
+ // Texture coordinate for the tile (always 0..1)
+ oe_layer_tilec = gl_MultiTexCoord0;
+
+ // Extract the vertex type marker
+ //oe_terrain_vertexMarker = int(oe_layer_tilec.z);
+
+ // Doing this instead of the commented-out line above is a workaround
+ // for the Mesa driver bug described here:
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/10482
+ oe_terrain_vertexMarker = int(gl_MultiTexCoord0.z);
+
+ // Color of the underlying map geometry (untextured)
+ vp_Color = oe_terrain_color;
+
+ // initialize:
+ oe_rex_morphFactor = 0.0;
+
+ // tile key
+ oe_tile_key = oe_tile_key_u;
+
+ // Default tessellation level (where applicable)
+ oe_terrain_tessLevel = vec4(1);
+}
+
+
+[break]
+#pragma vp_name Corey Engine - Init View Space
+#pragma vp_function oe_rex_init_view, vertex_view, first
+
+// outputs
+out vec3 vp_Normal;
+out vec3 oe_UpVectorView;
+
+void oe_rex_init_view(inout vec4 vert_view)
+{
+ // "up" vector at this vertex in view space, which we will later
+ // need in order to elevate the terrain. vp_Normal can change later
+ // but UpVectorView will stay the same.
+ oe_UpVectorView = vp_Normal;
+}
diff --git a/src/osgEarthDrivers/engine_corey/CoreyTerrainEngineDriver.cpp b/src/osgEarthDrivers/engine_corey/CoreyTerrainEngineDriver.cpp
new file mode 100644
index 0000000000..e20b7fa4ee
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/CoreyTerrainEngineDriver.cpp
@@ -0,0 +1,67 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#include "CoreyTerrainEngineNode"
+#include
+#include
+#include
+#include
+#include
+
+#define LC "[engine_corey] "
+
+namespace osgEarth { namespace Corey
+{
+ /**
+ * osgEarth driver for the Rex terrain engine.
+ */
+ class CoreyTerrainEngineDriver : public osgDB::ReaderWriter
+ {
+ public:
+ CoreyTerrainEngineDriver()
+ {
+ //nop
+ }
+
+ virtual const char* className() const
+ {
+ return "osgEarth Corey Terrain Engine";
+ }
+
+ virtual bool acceptsExtension(const std::string& extension) const
+ {
+ return osgDB::equalCaseInsensitive( extension, "osgearth_engine_corey" );
+ }
+
+ virtual ReadResult readObject(const std::string& uri, const Options* options) const
+ {
+ if ( "osgearth_engine_corey" == osgDB::getFileExtension( uri ) )
+ {
+ OE_INFO << LC << "Activated!" << std::endl;
+ return ReadResult( new CoreyTerrainEngineNode() );
+ }
+ else
+ {
+ return ReadResult::FILE_NOT_HANDLED;
+ }
+ }
+ };
+
+ REGISTER_OSGPLUGIN(osgearth_engine_corey, CoreyTerrainEngineDriver);
+
+} } // namespace osgEarth::Corey
diff --git a/src/osgEarthDrivers/engine_corey/CoreyTerrainEngineNode b/src/osgEarthDrivers/engine_corey/CoreyTerrainEngineNode
new file mode 100644
index 0000000000..182cf01161
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/CoreyTerrainEngineNode
@@ -0,0 +1,187 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "EngineData"
+#include "RenderBindings"
+#include "SelectionInfo"
+#include
+#include
+
+namespace osgEarth { namespace Corey
+{
+ using namespace osgEarth;
+
+ class CoreyTerrainEngineNode : public osgEarth::TerrainEngineNode
+ {
+ public:
+ CoreyTerrainEngineNode();
+
+ protected:
+ virtual ~CoreyTerrainEngineNode();
+
+ public:
+
+ //! Forces regeneration of tiles in the given region.
+ void invalidateRegion(
+ const GeoExtent& extent,
+ unsigned minLevel,
+ unsigned maxLevel) override;
+
+ //! Forces regeneration of tiles in the given region for one layer.
+ void invalidateRegion(
+ const std::vector layers,
+ const GeoExtent& extent,
+ unsigned minLevel,
+ unsigned maxLevel) override;
+
+ const TerrainEngineRequirements& getRequirements() const override {
+ return _require;
+ }
+
+ //! Access the stateset used to render the entire terrain.
+ osg::StateSet* getTerrainStateSet() override;
+
+ //! Get the stateset used to render the terrain surface.
+ osg::StateSet* getSurfaceStateSet() override;
+
+ //! Unique identifier of this engine instance
+ UID getUID() const override { return _uid; }
+
+ //! Generate a standalone tile geometry
+ osg::Node* createStandaloneTile(
+ const TerrainTileModel* model,
+ int createTileFlags,
+ unsigned referenceLOD,
+ const TileKey& subRegion) override;
+
+ //! Shutdown the engine and any running services
+ void shutdown() override;
+
+ //! Name of the job arena used to load terrain tiles
+ std::string getJobArenaName() const override;
+
+ //! Number of resident terrain tiles
+ unsigned getNumResidentTiles() const override;
+
+ public: // osg::Node
+
+ void traverse(osg::NodeVisitor& nv) override;
+
+ osg::BoundingSphere computeBound() const override;
+
+ void resizeGLObjectBuffers(unsigned maxSize) override;
+
+ void releaseGLObjects(osg::State* state) const override;
+
+ public: // MapCallback adapter functions
+
+ void onMapModelChanged( const MapModelChange& change ); // not virtual!
+
+ protected: // TerrainEngineNode protected
+
+ virtual void onSetMap() override;
+
+ virtual void updateTextureCombining() override { updateState(); }
+
+ virtual void dirtyState() override;
+
+ virtual void dirtyTerrainOptions() override;
+
+ private:
+
+ void update_traverse(osg::NodeVisitor& nv);
+ void cull_traverse(osg::NodeVisitor& nv);
+
+ //! Reloads all the tiles in the terrain due to a data model change
+ void refresh(bool force =false);
+
+ //! Various methods that trigger when the Map model changes.
+ void addLayer(Layer* layer);
+ void addSurfaceLayer( Layer* layer );
+ void removeImageLayer( ImageLayer* layerRemoved );
+ void addElevationLayer(Layer* layer);
+ void removeElevationLayer(Layer* layerRemoved );
+ void moveElevationLayer(Layer* layerMoved );
+
+ //! refresh the statesets of the terrain and the imagelayer tile surface
+ void updateState();
+
+ //! one-time allocation of render units for the terrain
+ void setupRenderBindings();
+
+ //! Adds a Layer to the cachedLayerExtents vector.
+ void cacheLayerExtentInMapSRS(Layer* layer);
+
+ //! Recompute all cached layer extents
+ void cacheAllLayerExtentsInMapSRS();
+
+ private:
+ UID _uid;
+ bool _batchUpdateInProgress = false;
+ bool _refreshRequired = false;
+ bool _stateUpdateRequired = false;
+ bool _renderModelUpdateRequired = false;
+ bool _morphTerrainSupported = true;
+ bool _morphingSupported = true;
+
+ TerrainEngineRequirements _require;
+ EngineData _data;
+
+ osg::ref_ptr _terrain;
+ osg::ref_ptr _pager;
+ osg::ref_ptr _surfaceSS;
+ osg::ref_ptr _imageLayerSS;
+
+ // Data that we maintain across frames on a per-camera basis.
+ using PersistentDataTable = vector_map<
+ osg::Camera*,
+ TerrainRenderData::PersistentData>;
+
+ Mutexed _persistent;
+
+ unsigned _frameLastUpdated = 0u;
+ FrameClock _clock;
+ std::atomic_bool _updatedThisFrame = { false };
+ UID _ppUID = 0;
+
+ // keys of tiles that require an update
+ // This is an ordered set in order to preserve LOD ordering.
+ mutable std::set _staleTiles;
+ mutable std::mutex _staleTiles_mutex;
+
+ using LoadFunction = std::function()>;
+ mutable std::list>> _loadQueue;
+ mutable std::mutex _queue_mutex;
+
+ void merge(TerrainTileModel*);
+ };
+
+} } // namespace osgEarth::Corey
diff --git a/src/osgEarthDrivers/engine_corey/CoreyTerrainEngineNode.cpp b/src/osgEarthDrivers/engine_corey/CoreyTerrainEngineNode.cpp
new file mode 100644
index 0000000000..549c2f69d8
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/CoreyTerrainEngineNode.cpp
@@ -0,0 +1,1709 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#include "CoreyTerrainEngineNode"
+#include "Shaders"
+#include "TerrainCuller"
+#include "TileGeometry"
+#include "CreateTileImplementation"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include // for getenv
+
+#define LC "[CoreyTerrainEngineNode] "
+
+using namespace osgEarth::Corey;
+using namespace osgEarth;
+
+#define DEFAULT_MAX_LOD 19u
+
+#define TERRAIN_JOB_POOL "oe.terrain"
+
+//------------------------------------------------------------------------
+
+namespace
+{
+ // adapter that lets CoreyTerrainEngineNode listen to Map events
+ struct CoreyTerrainEngineNodeMapCallbackProxy : public MapCallback
+ {
+ CoreyTerrainEngineNodeMapCallbackProxy(CoreyTerrainEngineNode* node) : _node(node) { }
+ osg::observer_ptr _node;
+
+ void onMapModelChanged(const MapModelChange& change) override {
+ osg::ref_ptr node;
+ if (_node.lock(node))
+ node->onMapModelChanged(change);
+ }
+ };
+
+
+ /**
+ * Run this visitor whenever you remove a layer, so that each
+ * TileNode can update its render model and get rid of passes
+ * that no longer exist.
+ */
+ struct PurgeOrphanedLayers : public osg::NodeVisitor
+ {
+ const Map* _map;
+ const RenderBindings& _bindings;
+ unsigned _count;
+
+ PurgeOrphanedLayers(const Map* map, RenderBindings& bindings) : _map(map), _bindings(bindings), _count(0u)
+ {
+ setTraversalMode(TRAVERSE_ALL_CHILDREN);
+ setNodeMaskOverride(~0);
+ }
+
+ void apply(osg::Node& node)
+ {
+ TileNode* tileNode = dynamic_cast(&node);
+ if (tileNode)
+ {
+ apply(*tileNode);
+ }
+ traverse(node);
+ }
+
+ void apply(TileNode& tileNode)
+ {
+ TileRenderModel& model = tileNode._renderModel;
+
+ for (int p = 0; p < model._passes.size(); ++p)
+ {
+ RenderingPass& pass = model._passes[p];
+
+ // if the map doesn't contain a layer with a matching UID,
+ // or if the layer is now disabled, remove it from the render model.
+ Layer* layer = _map->getLayerByUID(pass.sourceUID());
+ if (layer == nullptr || layer->isOpen() == false)
+ {
+ model._passes.erase(model._passes.begin() + p);
+ --p;
+ _count++;
+ }
+ }
+
+#if 0
+ // For shared samplers we need to refresh the list if one of them
+ // goes inactive (as is the case when removing a shared layer)
+ tileNode.refreshSharedSamplers(_bindings);
+#endif
+ }
+ };
+
+ class TerrainPagedNode : public PagedNode2
+ {
+ public:
+ TerrainPagedNode(const TileKey& key) : _key(key) { }
+ TileKey _key;
+ bool _dirty = false;
+ };
+}
+
+//------------------------------------------------------------------------
+
+CoreyTerrainEngineNode::CoreyTerrainEngineNode() :
+ TerrainEngineNode()
+{
+ // activate update traversals for this node.
+ ADJUST_UPDATE_TRAV_COUNT(this, +1);
+
+ // Necessary for pager object data
+ // Note: Do not change this value. Apps depend on it to
+ // detect being inside a terrain traversal.
+ this->setName("corey");
+
+ // unique ID for this engine:
+ _uid = osgEarth::createUID();
+
+ // Corey engine does NOT require elevation textures
+ _require.fullDataAtFirstLod = true;
+ _require.tileMesh = true;
+ _require.elevationTextures = true;
+ _require.normalTextures = true;
+ _require.landCoverTextures = false;
+
+ // static shaders.
+ osg::StateSet* stateset = getOrCreateStateSet();
+ stateset->setName("Terrain node");
+
+ _surfaceSS = new osg::StateSet();
+ _surfaceSS->setName("Terrain surface");
+
+ _imageLayerSS = new osg::StateSet();
+ _imageLayerSS->setName("Terrain image layer");
+
+ _updatedThisFrame = false;
+}
+
+CoreyTerrainEngineNode::~CoreyTerrainEngineNode()
+{
+ if (_ppUID > 0)
+ Registry::instance()->getShaderFactory()->removePreProcessorCallback(_ppUID);
+}
+
+void
+CoreyTerrainEngineNode::resizeGLObjectBuffers(unsigned maxSize)
+{
+ TerrainEngineNode::resizeGLObjectBuffers(maxSize);
+
+ getStateSet()->resizeGLObjectBuffers(maxSize);
+ _surfaceSS->resizeGLObjectBuffers(maxSize);
+ _imageLayerSS->resizeGLObjectBuffers(maxSize);
+
+ // TODO: where should this live? MapNode?
+ LayerVector layers;
+ getMap()->getLayers(layers);
+ for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i)
+ {
+ if ((*i)->getStateSet()) {
+ (*i)->getStateSet()->resizeGLObjectBuffers(maxSize);
+ }
+ }
+}
+
+void
+CoreyTerrainEngineNode::releaseGLObjects(osg::State* state) const
+{
+ if (_imageLayerSS.valid())
+ _imageLayerSS->releaseGLObjects(state);
+
+ if (_surfaceSS.valid())
+ _surfaceSS->releaseGLObjects(state);
+
+ // release the LayerDrawables
+ for (auto& p : _persistent)
+ {
+ for (auto& d : p.second._drawables)
+ {
+ d.second->releaseGLObjects(state);
+ }
+ }
+
+ if (_data.textures.valid())
+ {
+ _data.textures->releaseGLObjects(state);
+ }
+
+ TerrainEngineNode::releaseGLObjects(state);
+}
+
+void
+CoreyTerrainEngineNode::shutdown()
+{
+ TerrainEngineNode::shutdown();
+}
+
+std::string
+CoreyTerrainEngineNode::getJobArenaName() const
+{
+ return TERRAIN_JOB_POOL;
+}
+
+unsigned
+CoreyTerrainEngineNode::getNumResidentTiles() const
+{
+ return _terrain->getNumTrackedNodes();
+}
+
+void
+CoreyTerrainEngineNode::onSetMap()
+{
+ OE_SOFT_ASSERT_AND_RETURN(_map.valid(), void());
+
+ int maxSize = std::min((int)getOptions().getMaxTextureSize(), Registry::instance()->getMaxTextureSize());
+
+ // Populate the engine data structure
+ _data.map = _map;
+ _data.options = getOptions();
+ _data.clock = &_clock;
+ _data.renderBindings = {};
+ _data.selectionInfo = {};
+
+ _data.textures = new TextureArena();
+ _data.textures->setName("Corey Terrain Engine");
+ _data.textures->setBindingPoint(29); // TODO
+ _data.textures->setAutoRelease(true);
+ _data.textures->setMaxTextureSize(maxSize);
+
+ // callbacks for the scene graph.
+ _data.tileNodeCullCallback = new TileNodeCuller(_data);
+ _data.tileDrawableCullCallback = new TileDrawableCuller(_data);
+
+
+
+ osg::observer_ptr engine_weak = this;
+
+ auto load_tile = [engine_weak](const TileKey& key, ProgressCallback* progress) -> osg::ref_ptr
+ {
+ osg::ref_ptr tilenode;
+ osg::ref_ptr engine;
+ if (engine_weak.lock(engine))
+ {
+ osg::ref_ptr model = engine->_tileModelDB.getOrCreate(key,
+ [&](const TileKey& key) {
+ return engine->_tileModelFactory->createTileModel(
+ engine->_data.map.get(), key, {}, engine->_require, progress);
+ });
+
+ if (model.valid() && model->mesh.valid())
+ {
+ tilenode = new TileNode(key);
+ auto parent = engine->_data.getTileNode(key.createParentKey());
+ auto parentData = parent.valid() ? parent->_fullDataModel.get() : nullptr;
+ tilenode->set(model.get(), parentData, engine->_data);
+
+ engine->_data.storeTileNode(tilenode);
+ }
+ }
+ return tilenode;
+ };
+
+ _terrain = new PagingManager(TERRAIN_JOB_POOL);
+ addChild(_terrain);
+
+ _pager = new SimplePager(_map.get(), _map->getProfile());
+ _pager->setName("oe.corey");
+ _pager->setMaxLevel(19u);
+ _pager->setAdditive(false);
+ _pager->setUsePayloadBoundsForChildren(true);
+ _pager->setCreateNodeFunction(load_tile);
+ _pager->setCreatePagedNodeFunction([](const TileKey& key) { return new TerrainPagedNode(key); });
+ _terrain->addChild(_pager);
+
+ _morphingSupported = true;
+ auto options = getOptions();
+ if (options.getLODMethod() ==LODMethod::SCREEN_SPACE)
+ {
+ OE_INFO << LC << "LOD method = pixel size; pixel tile size = " << options.getTilePixelSize() << std::endl;
+
+ // force morphing off for PSOS mode
+ _morphingSupported = false;
+ }
+
+#if 0
+ // There is an (apparent) bug in the mesa 23.1.4 driver that causes display artifacts
+ // when doing morphing; this code will attempt to detect that condition and disable
+ // the offending code until we can find a workaround.
+ auto gl_version_str = Registry::capabilities().getVersion();
+ auto mesa_pos = gl_version_str.find("Mesa");
+ if (mesa_pos != std::string::npos)
+ {
+ auto ver = gl_version_str.substr(mesa_pos + 5);
+ Version driver_version = parseVersion(ver.c_str());
+ if (driver_version.greaterThanOrEqualTo(23, 1, 4))
+ {
+ _morphingSupported = false;
+ getOrCreateStateSet()->setDefine("OE_MESA_23_WORKAROUND");
+ }
+ }
+#endif
+
+ // morphing imagery LODs requires we bind parent textures to their own unit.
+ if (options.getMorphImagery() && _morphingSupported)
+ {
+ _require.parentTextures = true;
+ }
+
+ // Terrain morphing doesn't work in projected maps:
+ if (_map->getSRS()->isProjected())
+ {
+ _morphTerrainSupported = false;
+ }
+
+ // Check for normals debugging.
+ if (::getenv("OSGEARTH_DEBUG_NORMALS"))
+ getOrCreateStateSet()->setDefine("OE_DEBUG_NORMALS");
+ else
+ if (getStateSet()) getStateSet()->removeDefine("OE_DEBUG_NORMALS");
+
+ // check for normal map generation (required for lighting).
+ _require.normalTextures = (options.getUseNormalMaps() == true);
+
+ // don't know how to set this up so just do it
+ // always off in corey
+ //_require.landCoverTextures = (options.getUseLandCover() == true);
+
+ // ensure we get full coverage at the first LOD.
+ _require.fullDataAtFirstLod = true;
+
+ // Loader concurrency (size of the thread pool)
+ unsigned concurrency = options.getConcurrency();
+ const char* concurrency_str = ::getenv("OSGEARTH_TERRAIN_CONCURRENCY");
+ if (concurrency_str)
+ concurrency = Strings::as(concurrency_str, concurrency);
+ jobs::get_pool(TERRAIN_JOB_POOL)->set_concurrency(concurrency);
+
+ // Initialize the core render bindings.
+ setupRenderBindings();
+
+ // install a layer callback for processing further map actions:
+ _map->addMapCallback(new CoreyTerrainEngineNodeMapCallbackProxy(this));
+
+ // Prime with existing layers:
+ _batchUpdateInProgress = true;
+
+ LayerVector layers;
+ _map->getLayers(layers);
+ for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i)
+ addLayer(i->get());
+
+ _batchUpdateInProgress = false;
+
+ // Calculate the LOD morphing parameters:
+ unsigned maxLOD = options.getMaxLOD();
+
+ _data.selectionInfo.initialize(
+ 0u, // always zero, not the terrain options firstLOD
+ maxLOD,
+ _map->getProfile(),
+ options.getMinTileRangeFactor(),
+ true); // restrict polar subdivision for geographic maps
+
+ TerrainResources* res = getResources();
+ for (unsigned lod = 0; lod <= maxLOD; ++lod)
+ res->setVisibilityRangeHint(lod, _data.selectionInfo.getLOD(lod)._visibilityRange);
+
+ // set up the initial graph
+ refresh();
+
+ // now that we have a map, set up to recompute the bounds
+ dirtyBound();
+
+ // preprocess shaders to parse the "oe_use_shared_layer" directive
+ // for shared layer samplers
+ if (_ppUID > 0)
+ {
+ Registry::instance()->getShaderFactory()->removePreProcessorCallback(_ppUID);
+ }
+
+ _ppUID = Registry::instance()->getShaderFactory()->addPreProcessorCallback(
+ this,
+ [](std::string& source, osg::Referenced* host)
+ {
+ CoreyTerrainEngineNode* engine = dynamic_cast(host);
+ if (!engine) return;
+
+ std::string line;
+ std::vector tokens;
+
+ while (ShaderLoader::getPragmaValueAsTokens(
+ source,
+ "#pragma oe_use_shared_layer",
+ line,
+ tokens))
+ {
+ if (tokens.size() == 2)
+ {
+ std::ostringstream buf;
+
+ if (GLUtils::useNVGL())
+ {
+ ShadersGL4 sh;
+ std::string incStrGL4 = ShaderLoader::load(sh.ENGINE_TYPES, sh);
+ if (source.find(incStrGL4) == std::string::npos)
+ {
+ buf << incStrGL4 << "\n";
+ }
+
+ // find the shared index.
+ int index = -1;
+ const RenderBindings& bindings = engine->_data.renderBindings;
+ for (int i = SamplerBinding::SHARED; i < (int)bindings.size() && index < 0; ++i)
+ {
+ if (bindings[i].samplerName == tokens[0])
+ {
+ index = i - SamplerBinding::SHARED;
+ }
+ }
+ if (index < 0)
+ {
+ OE_WARN << LC << "Cannot find a shared sampler binding for " << tokens[0] << std::endl;
+ Strings::replaceIn(source, line, "// error, no matching sampler binding");
+ continue;
+ }
+
+ buf << "#define " << tokens[0] << "_HANDLE oe_terrain_tex[oe_tile[oe_tileID].sharedIndex[" << index << "]]\n"
+ << "#define " << tokens[0] << " sampler2D(" << tokens[0] << "_HANDLE)\n"
+ << "#define " << tokens[1] << " oe_tile[oe_tileID].sharedMat[" << index << "]\n";
+ }
+ else
+ {
+ buf << "uniform sampler2D " << tokens[0] << ";\n"
+ << "uniform mat4 " << tokens[1] << ";\n";
+ }
+
+ Strings::replaceIn(source, line, buf.str());
+ }
+ else
+ {
+ Strings::replaceIn(source, line, "// error, missing token(s)");
+ }
+ }
+ }
+ );
+
+ // finally, fire up the pager.
+ _pager->build();
+}
+
+
+osg::BoundingSphere
+CoreyTerrainEngineNode::computeBound() const
+{
+ return TerrainEngineNode::computeBound();
+}
+
+namespace
+{
+ struct Invalidater : public osg::NodeVisitor
+ {
+ GeoExtent extent;
+ unsigned minLevel = 0, maxLevel = 99;
+ std::function(const TileKey&)> getTileModel;
+ CreateTileManifest manifest;
+
+ Invalidater() {
+ setTraversalMode(TRAVERSE_ALL_CHILDREN);
+ setNodeMaskOverride(~0);
+ }
+ void apply(osg::Node& node)
+ {
+ bool keep_going = true;
+ TileNode* tilenode = dynamic_cast(&node);
+
+ if (tilenode &&
+ tilenode->_key.getLOD() >= minLevel &&
+ tilenode->_key.getLOD() <= maxLevel &&
+ (!extent.isValid() || tilenode->_key.getExtent().intersects(extent)))
+ {
+ tilenode->_dirty = true;
+ }
+ else
+ {
+ traverse(node);
+ }
+ }
+ };
+}
+
+void
+CoreyTerrainEngineNode::invalidateRegion(
+ const GeoExtent& extent,
+ unsigned minLevel,
+ unsigned maxLevel)
+{
+ _tileModelDB.clear();
+
+ Invalidater v;
+ v.extent = extent;
+ v.minLevel = minLevel;
+ v.maxLevel = maxLevel;
+
+ v.getTileModel = [&](const TileKey& key)
+ {
+ return _tileModelDB.getOrCreate(key,
+ [&](const TileKey& key) {
+ return _tileModelFactory->createTileModel(_map.get(), key, {}, _require, nullptr);
+ });
+ };
+
+ v.manifest = {};
+
+ this->accept(v);
+}
+
+void
+CoreyTerrainEngineNode::invalidateRegion(
+ const std::vector layers,
+ const GeoExtent& extent,
+ unsigned minLevel,
+ unsigned maxLevel)
+{
+ invalidateRegion(extent, minLevel, maxLevel);
+ return;
+
+ _tileModelDB.clear();
+
+ Invalidater v;
+ v.extent = extent;
+ v.minLevel = minLevel;
+ v.maxLevel = maxLevel;
+
+ v.getTileModel = [&](const TileKey& key)
+ {
+ CreateTileManifest manifest;
+ for (auto& layer : layers)
+ manifest.insert(layer);
+
+ return _tileModelDB.getOrCreate(key,
+ [&](const TileKey& key) {
+ return _tileModelFactory->createTileModel(_map.get(), key, manifest, _require, nullptr);
+ });
+ };
+
+ v.manifest = {};
+
+ this->accept(v);
+}
+
+void
+CoreyTerrainEngineNode::refresh(bool forceDirty)
+{
+ OE_SOFT_ASSERT_AND_RETURN("NOT IMPLEMENTED - TODO", void());
+
+ if (_batchUpdateInProgress)
+ {
+ _refreshRequired = true;
+ }
+ else
+ {
+ _refreshRequired = false;
+#if 0
+
+ if (_terrain.valid())
+ {
+ _terrain->releaseGLObjects();
+ _terrain->removeChildren(0, _terrain->getNumChildren());
+ }
+
+ //// clear the loader:
+ //_merger->clear();
+
+ //// clear out the tile registry:
+ //if (_tiles)
+ //{
+ // _tiles->releaseAll(nullptr);
+ //}
+
+ // scrub the geometry pool:
+ _geometryPool->clear();
+
+ // Build the first level of the terrain.
+ // Collect the tile keys comprising the root tiles of the terrain.
+ std::vector keys;
+ getMap()->getProfile()->getAllKeysAtLOD(getOptions().getFirstLOD(), keys);
+
+ // create a root node for each root tile key.
+ OE_INFO << LC << "Creating " << keys.size() << " root keys." << std::endl;
+
+ // We need to take a self-ref here to ensure that the TileNode's data loader
+ // can use its observer_ptr back to the terrain engine.
+ this->ref();
+
+ // Load all the root key tiles.
+ jobs::context context;
+ context.group = jobs::jobgroup::create();
+ context.pool = jobs::get_pool(ARENA_LOAD_TILE);
+
+ for (unsigned i = 0; i < keys.size(); ++i)
+ {
+ TileNode* tileNode = new TileNode(
+ keys[i],
+ nullptr, // parent
+ _EngineData.get(),
+ nullptr); // progress
+
+ // Root nodes never expire
+ tileNode->setDoNotExpire(true);
+
+ // Add it to the scene graph
+ _terrain->addChild(tileNode);
+
+ // Post-add initialization:
+ tileNode->initializeData();
+
+ // And load the tile's data
+ jobs::dispatch([tileNode]() { tileNode->loadSync(); }, context);
+
+ OE_DEBUG << " - " << (i + 1) << "/" << keys.size() << " : " << keys[i].str() << std::endl;
+ }
+
+ // wait for all loadSync calls to complete
+ context.group->join();
+
+ // release the self-ref.
+ this->unref_nodelete();
+#endif
+
+ // Set up the state sets.
+ updateState();
+ }
+}
+
+osg::StateSet*
+CoreyTerrainEngineNode::getTerrainStateSet()
+{
+ OE_SOFT_ASSERT_AND_RETURN(_terrain.valid(), nullptr);
+ return _terrain->getOrCreateStateSet();
+}
+
+osg::StateSet*
+CoreyTerrainEngineNode::getSurfaceStateSet()
+{
+ return _surfaceSS.get();
+}
+
+void
+CoreyTerrainEngineNode::setupRenderBindings()
+{
+ // Release any pre-existing bindings:
+ for (unsigned i = 0; i < _data.renderBindings.size(); ++i)
+ {
+ SamplerBinding& b = _data.renderBindings[i];
+ if (b.isActive())
+ {
+ getResources()->releaseTextureImageUnit(b.unit);
+ }
+ }
+ _data.renderBindings.clear();
+
+ // "SHARED" is the start of shared layers, so we always want the bindings
+ // vector to be at least that size.
+ _data.renderBindings.resize(SamplerBinding::SHARED);
+
+ SamplerBinding& color = _data.renderBindings[SamplerBinding::COLOR];
+ color.usage = SamplerBinding::COLOR;
+ color.samplerName = "oe_layer_tex";
+ color.matrixName = "oe_layer_texMatrix";
+ color.defaultTexture = new osg::Texture2D(ImageUtils::createEmptyImage(1, 1));
+ color.defaultTexture->setName("terrain default color");
+
+ if (!GLUtils::useNVGL())
+ getResources()->reserveTextureImageUnit(color.unit, "Terrain Color");
+
+ if (_require.elevationTextures)
+ {
+ SamplerBinding& elevation = _data.renderBindings[SamplerBinding::ELEVATION];
+ elevation.usage = SamplerBinding::ELEVATION;
+ elevation.samplerName = "oe_tile_elevationTex";
+ elevation.matrixName = "oe_tile_elevationTexMatrix";
+ elevation.defaultTexture = osgEarth::createEmptyElevationTexture();
+ elevation.defaultTexture->setName("terrain default elevation");
+
+ if (!GLUtils::useNVGL())
+ getResources()->reserveTextureImageUnit(elevation.unit, "Terrain Elevation");
+ }
+
+ if (_require.normalTextures)
+ {
+ SamplerBinding& normal = _data.renderBindings[SamplerBinding::NORMAL];
+ normal.usage = SamplerBinding::NORMAL;
+ normal.samplerName = "oe_tile_normalTex";
+ normal.matrixName = "oe_tile_normalTexMatrix";
+ normal.defaultTexture = osgEarth::createEmptyNormalMapTexture();
+ normal.defaultTexture->setName("terrain default normalmap");
+
+ if (!GLUtils::useNVGL())
+ getResources()->reserveTextureImageUnit(normal.unit, "Terrain Normals");
+ }
+
+ if (_require.parentTextures)
+ {
+ SamplerBinding& colorParent = _data.renderBindings[SamplerBinding::COLOR_PARENT];
+ colorParent.usage = SamplerBinding::COLOR_PARENT;
+ colorParent.samplerName = "oe_layer_texParent";
+ colorParent.matrixName = "oe_layer_texParentMatrix";
+
+ if (!GLUtils::useNVGL())
+ getResources()->reserveTextureImageUnit(colorParent.unit, "Terrain Parent Color");
+ }
+
+ if (_require.landCoverTextures)
+ {
+ SamplerBinding& landCover = _data.renderBindings[SamplerBinding::LANDCOVER];
+ landCover.usage = SamplerBinding::LANDCOVER;
+ landCover.samplerName = "oe_tile_landCoverTex";
+ landCover.matrixName = "oe_tile_landCoverTexMatrix";
+ landCover.defaultTexture = LandCover::createEmptyTexture();
+ landCover.defaultTexture->setName("terrain default landcover");
+ getOrCreateStateSet()->setDefine("OE_LANDCOVER_TEX", landCover.samplerName);
+ getOrCreateStateSet()->setDefine("OE_LANDCOVER_TEX_MATRIX", landCover.matrixName);
+
+ if (!GLUtils::useNVGL())
+ getResources()->reserveTextureImageUnit(landCover.unit, "Terrain Land Cover");
+ }
+
+ // Apply a default, empty texture to each render binding.
+ if (!GLUtils::useNVGL())
+ {
+ OE_DEBUG << LC << "Render Bindings:\n";
+ for (unsigned i = 0; i < _data.renderBindings.size(); ++i)
+ {
+ SamplerBinding& b = _data.renderBindings[i];
+ if (b.isActive())
+ {
+ _terrain->getOrCreateStateSet()->addUniform(new osg::Uniform(b.samplerName.c_str(), b.unit));
+ _terrain->getOrCreateStateSet()->setTextureAttribute(b.unit, b.defaultTexture);
+ OE_DEBUG << LC << " > Bound \"" << b.samplerName << "\" to unit " << b.unit << "\n";
+ }
+ }
+ }
+}
+
+void
+CoreyTerrainEngineNode::dirtyState()
+{
+ // TODO: perhaps defer this until the next update traversal so we don't
+ // reinitialize the state multiple times unnecessarily.
+ updateState();
+}
+
+void
+CoreyTerrainEngineNode::dirtyTerrainOptions()
+{
+ TerrainEngineNode::dirtyTerrainOptions();
+
+ auto options = getOptions();
+
+ if (_data.textures.valid())
+ {
+ _data.textures->setMaxTextureSize(options.getMaxTextureSize());
+ }
+
+ //_tiles->setNotifyNeighbors(options.getNormalizeEdges() == true);
+
+ //_merger->setMergesPerFrame(options.getMergesPerFrame());
+
+ //jobs::get_pool(ARENA_LOAD_TILE)->set_concurrency(options.getConcurrency());
+
+ getSurfaceStateSet()->getOrCreateUniform(
+ "oe_terrain_tess", osg::Uniform::FLOAT)->set(options.getTessellationLevel());
+
+ getSurfaceStateSet()->getOrCreateUniform(
+ "oe_terrain_tess_range", osg::Uniform::FLOAT)->set(options.getTessellationRange());
+
+ _pager->setLODMethod(options.getLODMethod());
+
+ if (_pager->getLODMethod() == LODMethod::SCREEN_SPACE)
+ {
+ _pager->setMinPixels(options.getTilePixelSize());
+ }
+}
+
+void
+CoreyTerrainEngineNode::cacheAllLayerExtentsInMapSRS()
+{
+ // Only call during update
+ LayerVector layers;
+ getMap()->getLayers(layers);
+ for (LayerVector::const_iterator i = layers.begin();
+ i != layers.end();
+ ++i)
+ {
+ cacheLayerExtentInMapSRS(i->get());
+ }
+}
+
+void
+CoreyTerrainEngineNode::cull_traverse(osg::NodeVisitor& nv)
+{
+ OE_PROFILING_ZONE;
+
+ auto* cv = static_cast(&nv);
+
+ // fetch the persistent data associated with this traversal.
+ _persistent.lock();
+ TerrainRenderData::PersistentData& pd = _persistent[cv->getCurrentCamera()];
+ pd._lastCull = *nv.getFrameStamp();
+ _persistent.unlock();
+
+ // Prepare the culler:
+ auto& cullData = _data.cullData;
+ cullData.reset(cv, pd, _data);
+
+ // Assemble the terrain drawables:
+ TerrainEngineNode::traverse(nv);
+
+ // If we're using geometry pooling, optimize the drawable forf shared state
+ // by sorting the draw commands.
+ // Skip if using GL4/indirect rendering. Actually seems to hurt?
+ // TODO: benchmark this further to see whether it's worthwhile
+ if (!GLUtils::useNVGL())
+ //getEngineData()->getGeometryPool()->isEnabled())
+ {
+ cullData._renderData.sortDrawCommands();
+ }
+
+ // The common stateset for the terrain group:
+ cv->pushStateSet(_terrain->getOrCreateStateSet());
+
+ // Push all the layers to draw on to the cull visitor in the order in which
+ // they appear in the map.
+ LayerDrawable* lastLayer = nullptr;
+ unsigned order = 0;
+ bool surfaceStateSetPushed = false;
+ bool imageLayerStateSetPushed = false;
+ int layersDrawn = 0;
+ unsigned surfaceDrawOrder = 0;
+
+ std::vector patchLayers;
+
+ for (auto layerDrawable : cullData._renderData._layerList)
+ {
+ if (layerDrawable->_tiles.empty() == false &&
+ layerDrawable->_patchLayer)
+ {
+ patchLayers.push_back(layerDrawable);
+ }
+ }
+
+ for (auto layerDrawable : cullData._renderData._layerList)
+ {
+ if (!layerDrawable->_tiles.empty())
+ {
+ // skip patch layers for now
+ if (layerDrawable->_patchLayer)
+ continue;
+
+ lastLayer = layerDrawable;
+
+ // if this is a RENDERTYPE_TERRAIN_SURFACE, we need to activate either the
+ // default surface state set or the image layer state set.
+ if (layerDrawable->_renderType == Layer::RENDERTYPE_TERRAIN_SURFACE)
+ {
+ layerDrawable->_surfaceDrawOrder = surfaceDrawOrder++;
+
+ if (!surfaceStateSetPushed)
+ {
+ cv->pushStateSet(_surfaceSS.get());
+ surfaceStateSetPushed = true;
+ }
+
+ if (layerDrawable->_imageLayer || layerDrawable->_layer == nullptr)
+ {
+ if (!imageLayerStateSetPushed)
+ {
+ cv->pushStateSet(_imageLayerSS.get());
+ imageLayerStateSetPushed = true;
+ }
+ }
+ else
+ {
+ if (imageLayerStateSetPushed)
+ {
+ cv->popStateSet();
+ imageLayerStateSetPushed = false;
+ }
+ }
+ }
+
+ else
+ {
+ if (imageLayerStateSetPushed)
+ {
+ cv->popStateSet();
+ imageLayerStateSetPushed = false;
+ }
+ if (surfaceStateSetPushed)
+ {
+ cv->popStateSet();
+ surfaceStateSetPushed = false;
+ }
+ }
+
+ if (layerDrawable->_layer)
+ {
+ layerDrawable->_layer->apply(layerDrawable, cv);
+ }
+ else
+ {
+ layerDrawable->accept(*cv);
+ }
+
+ ++layersDrawn;
+ }
+ }
+
+ // clear out the statesets:
+ if (imageLayerStateSetPushed)
+ {
+ cv->popStateSet();
+ imageLayerStateSetPushed = false;
+ }
+
+ if (surfaceStateSetPushed)
+ {
+ cv->popStateSet();
+ surfaceStateSetPushed = false;
+ }
+
+ // patch layers go last
+ for (auto layerDrawable : patchLayers)
+ {
+ lastLayer = layerDrawable;
+
+ TileBatch batch(nullptr);
+ for (auto& tile : layerDrawable->_tiles)
+ batch._tiles.push_back(&tile);
+
+ if (layerDrawable->_patchLayer->getStateSet())
+ cv->pushStateSet(layerDrawable->_patchLayer->getStateSet());
+
+ if (layerDrawable->_patchLayer->getCullCallback()) // backwards compat
+ layerDrawable->_patchLayer->apply(layerDrawable, cv);
+ else
+ layerDrawable->_patchLayer->cull(batch, *cv);
+
+ if (layerDrawable->_patchLayer->getStateSet())
+ cv->popStateSet();
+ }
+
+ // The last layer to render must clear up the OSG state,
+ // otherwise it will be corrupt and can lead to crashing.
+ if (lastLayer)
+ {
+ lastLayer->_clearOsgState = true;
+ }
+
+ // pop the common terrain state set
+ cv->popStateSet();
+
+ // If the culler found any orphaned data, we need to update the render model
+ // during the next update cycle.
+ if (cullData._orphanedPassesDetected > 0u)
+ {
+ _renderModelUpdateRequired = true;
+ OE_DEBUG << LC << "Detected " << cullData._orphanedPassesDetected << " orphaned rendering passes\n";
+ }
+
+ // Finally, if the cull traversal spotted any stale tiles, add them to the
+ // consolidated stale tiles list. We will process that set during the next
+ // update traversal.
+ if (cullData._staleTiles.size() > 0)
+ {
+ std::lock_guard lock(_staleTiles_mutex);
+ _staleTiles.insert(cullData._staleTiles.begin(), cullData._staleTiles.end());
+ }
+ cullData._staleTiles.clear();
+}
+
+void
+CoreyTerrainEngineNode::update_traverse(osg::NodeVisitor& nv)
+{
+ if (_renderModelUpdateRequired)
+ {
+ PurgeOrphanedLayers visitor(getMap(), _data.renderBindings);
+ _terrain->accept(visitor);
+ _renderModelUpdateRequired = false;
+ }
+
+ // Called once on the first update pass to ensure that all existing
+ // layers have their extents cached properly
+ if (_data.cachedLayerExtentsComputeRequired)
+ {
+ cacheAllLayerExtentsInMapSRS();
+ _data.cachedLayerExtentsComputeRequired = false;
+ }
+ else
+ {
+ // Update the cached layer extents as necessary.
+ osg::ref_ptr layer;
+ for (auto& layerExtent : _data.cachedLayerExtents)
+ {
+ layerExtent.second._layer.lock(layer);
+ if (layer.valid() && layer->getRevision() > layerExtent.second._revision)
+ {
+ layerExtent.second._extent = _map->getProfile()->clampAndTransformExtent(layer->getExtent());
+ layerExtent.second._revision = layer->getRevision();
+ }
+ }
+ }
+
+ // Call update() on all open layers
+ LayerVector layers;
+ _map->getOpenLayers(layers);
+ for (auto& layer : layers)
+ {
+ layer->update(nv);
+ }
+
+ // check on the persistent data cache
+ _persistent.lock();
+ const osg::FrameStamp* fs = nv.getFrameStamp();
+ for (auto iter : _persistent)
+ {
+ if (fs->getFrameNumber() - iter.second._lastCull.getFrameNumber() > 60)
+ {
+ _persistent.erase(iter.first);
+ OE_DEBUG << LC << "Releasing orphaned view data" << std::endl;
+ break;
+ }
+ }
+ _persistent.unlock();
+
+ // traverse the texture arena since it's not in the scene graph.
+ if (_data.textures.valid())
+ {
+ _data.textures->update(nv);
+ }
+
+ // Queue update requests for any stale tiles.
+ {
+ std::lock_guard lock(_staleTiles_mutex);
+
+ osg::observer_ptr weak_this = this;
+
+ for (auto& key : _staleTiles)
+ {
+ jobs::context c;
+ c.pool = jobs::get_pool(TERRAIN_JOB_POOL);
+ c.name = "update tilenode " + key.str();
+ float lod = key.getLOD();
+ c.priority = [lod]() { return lod; };
+
+ auto update = [weak_this, key](Cancelable& c)
+ {
+ osg::ref_ptr result;
+ osg::ref_ptr engine;
+ if (weak_this.lock(engine))
+ {
+ auto tilenode = engine->_data.getTileNode(key);
+ if (tilenode.valid() && tilenode->_partialDataModel.valid())
+ {
+ // make a shallow copy of the current partial data model,
+ // i.e. the last model that the tile loaded.
+ osg::ref_ptr model_to_update =
+ new TerrainTileModel(*tilenode->_partialDataModel.get());
+
+ //OE_INFO << LC << "Updating: " << key.str() << std::endl;
+ bool updated = false;
+ result = engine->_tileModelDB.createAndStore(key, [&](const TileKey& key)
+ {
+ updated = engine->_tileModelFactory->updateTileModel(
+ model_to_update, engine->_data.map, {}, engine->_require, nullptr);
+ return model_to_update.get();
+ });
+ }
+ }
+ return result;
+ };
+
+ _loadQueue.emplace_back(jobs::dispatch(update, c));
+ }
+ _staleTiles.clear();
+ }
+
+ // process the merge queue
+ unsigned count = std::max(4u, getOptions().getMergesPerFrame());
+
+ for(auto iter = _loadQueue.begin(); iter != _loadQueue.end() && count > 0u;)
+ {
+ auto& result = *iter;
+ if (result.available())
+ {
+ merge(result.value().get());
+ iter = _loadQueue.erase(iter);
+ --count;
+ }
+ else if (result.canceled())
+ {
+ OE_INFO << LC << "Canceled tile load" << std::endl;
+ iter = _loadQueue.erase(iter);
+ }
+ else
+ {
+ break; // so we keep everything in order!!
+
+ //OE_INFO << LC << "Waiting on tile load" << std::endl;
+ //++iter;
+ }
+ }
+}
+
+void
+CoreyTerrainEngineNode::traverse(osg::NodeVisitor& nv)
+{
+ if (nv.getVisitorType() == nv.UPDATE_VISITOR)
+ {
+ if (!_updatedThisFrame.exchange(true))
+ {
+ _clock.update();
+ update_traverse(nv);
+ TerrainEngineNode::traverse(nv);
+ }
+ }
+
+#if 1
+ else if (nv.getVisitorType() == nv.CULL_VISITOR)
+ {
+ _updatedThisFrame.exchange(false);
+ _clock.cull();
+ cull_traverse(nv);
+ }
+#endif
+ else
+ {
+ TerrainEngineNode::traverse(nv);
+ }
+}
+
+void
+CoreyTerrainEngineNode::onMapModelChanged(const MapModelChange& change)
+{
+ if (change.getAction() == MapModelChange::BEGIN_BATCH_UPDATE)
+ {
+ _batchUpdateInProgress = true;
+ }
+
+ else if (change.getAction() == MapModelChange::END_BATCH_UPDATE)
+ {
+ _batchUpdateInProgress = false;
+
+ if (_refreshRequired)
+ refresh();
+
+ if (_stateUpdateRequired)
+ updateState();
+ }
+
+ else
+ {
+
+ // dispatch the change handler
+ if (change.getLayer())
+ {
+ // then apply the actual change:
+ switch (change.getAction())
+ {
+ case MapModelChange::ADD_LAYER:
+ case MapModelChange::OPEN_LAYER:
+ addLayer(change.getLayer());
+ break;
+
+ case MapModelChange::REMOVE_LAYER:
+ case MapModelChange::CLOSE_LAYER:
+ if (change.getImageLayer())
+ removeImageLayer(change.getImageLayer());
+ else if (change.getElevationLayer() || change.getConstraintLayer())
+ removeElevationLayer(change.getLayer());
+ break;
+
+ case MapModelChange::MOVE_LAYER:
+ if (change.getElevationLayer())
+ moveElevationLayer(change.getElevationLayer());
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+void
+CoreyTerrainEngineNode::cacheLayerExtentInMapSRS(Layer* layer)
+{
+ OE_SOFT_ASSERT_AND_RETURN(layer != nullptr, void());
+
+ // Store the layer's extent in the map's SRS:
+ LayerExtent& le = _data.cachedLayerExtents[layer->getUID()];
+ le._layer = layer;
+ le._extent = getMap()->getProfile()->clampAndTransformExtent(layer->getExtent());
+}
+
+void
+CoreyTerrainEngineNode::addLayer(Layer* layer)
+{
+ if (layer)
+ {
+ if (layer->isOpen())
+ {
+ if (layer->getRenderType() == Layer::RENDERTYPE_TERRAIN_SURFACE)
+ addSurfaceLayer(layer);
+ else if (dynamic_cast(layer) || dynamic_cast(layer))
+ addElevationLayer(layer);
+ }
+
+ cacheLayerExtentInMapSRS(layer);
+ }
+}
+
+void
+CoreyTerrainEngineNode::addSurfaceLayer(Layer* layer)
+{
+ if (layer && layer->isOpen())
+ {
+ ImageLayer* imageLayer = dynamic_cast(layer);
+ if (imageLayer)
+ {
+ // for a shared layer, allocate a shared image unit if necessary.
+ if (imageLayer->isShared())
+ {
+ if (!imageLayer->sharedImageUnit().isSet() && !GLUtils::useNVGL())
+ {
+ int temp;
+ if (getResources()->reserveTextureImageUnit(temp, imageLayer->getName().c_str()))
+ {
+ imageLayer->sharedImageUnit() = temp;
+ //OE_INFO << LC << "Image unit " << temp << " assigned to shared layer " << imageLayer->getName() << std::endl;
+ }
+ else
+ {
+ OE_WARN << LC << "Insufficient GPU image units to share layer " << imageLayer->getName() << std::endl;
+ }
+ }
+
+ // Build a sampler binding for the shared layer.
+ if (imageLayer->sharedImageUnit().isSet() || GLUtils::useNVGL())
+ {
+ // Find the next empty SHARED slot:
+ unsigned newIndex = SamplerBinding::SHARED;
+ while (_data.renderBindings[newIndex].isActive())
+ ++newIndex;
+
+ // Put the new binding there:
+ SamplerBinding& newBinding = _data.renderBindings[newIndex];
+ newBinding.usage = SamplerBinding::SHARED;
+ newBinding.sourceUID = imageLayer->getUID();
+ newBinding.unit = imageLayer->sharedImageUnit().get();
+ newBinding.samplerName = imageLayer->getSharedTextureUniformName();
+ newBinding.matrixName = imageLayer->getSharedTextureMatrixUniformName();
+
+ OE_INFO << LC
+ << "Shared Layer \"" << imageLayer->getName() << "\" : sampler=\"" << newBinding.samplerName << "\", "
+ << "matrix=\"" << newBinding.matrixName << "\", "
+ << "unit=" << newBinding.unit << "\n";
+
+ // Install an empty texture for this binding at the top of the graph, so that
+ // a texture is always defined even when the data source supplies no real data.
+ if (newBinding.isActive() && !GLUtils::useNVGL())
+ {
+ osg::ref_ptr tex;
+ if (osg::Image* emptyImage = imageLayer->getEmptyImage())
+ {
+ if (emptyImage->r() > 1)
+ {
+ tex = ImageUtils::makeTexture2DArray(emptyImage);
+ }
+ else
+ {
+ tex = new osg::Texture2D(emptyImage);
+ }
+ }
+ else
+ {
+ tex = new osg::Texture2D(ImageUtils::createEmptyImage(1, 1));
+ }
+ tex->setName("default:" + imageLayer->getName());
+ tex->setUnRefImageDataAfterApply(Registry::instance()->unRefImageDataAfterApply().get());
+ _terrain->getOrCreateStateSet()->addUniform(new osg::Uniform(newBinding.samplerName.c_str(), newBinding.unit));
+ _terrain->getOrCreateStateSet()->setTextureAttribute(newBinding.unit, tex.get(), 1);
+ OE_INFO << LC << "Bound shared sampler " << newBinding.samplerName << " to unit " << newBinding.unit << std::endl;
+ }
+ }
+ }
+ }
+
+ else
+ {
+ // non-image tile layer.
+ }
+
+ if (_terrain)
+ {
+ // Update the existing render models, and trigger a data reload.
+ // Later we can limit the reload to an update of only the new data.
+ std::vector layers;
+ layers.push_back(layer);
+ invalidateRegion(layers, GeoExtent::INVALID, 0u, INT_MAX);
+ }
+
+ updateState();
+ }
+}
+
+
+void
+CoreyTerrainEngineNode::removeImageLayer(ImageLayer* layerRemoved)
+{
+ if (layerRemoved)
+ {
+ // release its layer drawable
+ _persistent.scoped_lock([&]() {
+ for (auto& e : _persistent)
+ e.second._drawables.erase(layerRemoved);
+ });
+
+ // for a shared layer, release the shared image unit.
+ if (layerRemoved->isOpen() && layerRemoved->isShared())
+ {
+ if (layerRemoved->sharedImageUnit().isSet())
+ {
+ getResources()->releaseTextureImageUnit(*layerRemoved->sharedImageUnit());
+ layerRemoved->sharedImageUnit().unset();
+ }
+
+ // Remove from RenderBindings (mark as unused)
+ for (unsigned i = 0; i < _data.renderBindings.size(); ++i)
+ {
+ SamplerBinding& binding = _data.renderBindings[i];
+ if (binding.isActive() && binding.sourceUID == layerRemoved->getUID())
+ {
+ OE_INFO << LC << "Binding (" << binding.samplerName << " unit " << binding.unit << ") cleared\n";
+ binding.usage.clear();
+ binding.unit = -1;
+
+ // Request an update to reset the shared sampler in the scene graph
+ // GW: running this anyway below (PurgeOrphanedLayers), so no need..?
+ _renderModelUpdateRequired = true;
+ }
+ }
+ }
+
+ updateState();
+ }
+
+ if (_terrain)
+ {
+ // Run the update visitor, which will clean out any rendering passes
+ // associated with the layer we just removed. This would happen
+ // automatically during cull/update anyway, but it's more efficient
+ // to do it all at once.
+ PurgeOrphanedLayers updater(getMap(), _data.renderBindings);
+ _terrain->accept(updater);
+ }
+
+ //OE_INFO << LC << " Updated " << updater._count << " tiles\n";
+}
+
+void
+CoreyTerrainEngineNode::addElevationLayer(Layer* layer)
+{
+ if (layer && layer->isOpen())
+ {
+ std::vector layers;
+ layers.push_back(layer);
+ invalidateRegion(layers, GeoExtent::INVALID, 0u, INT_MAX);
+ }
+}
+
+void
+CoreyTerrainEngineNode::removeElevationLayer(Layer* layer)
+{
+ // only need to refresh is the elevation layer is visible.
+ if (layer)
+ {
+ std::vector layers;
+ layers.push_back(layer);
+ invalidateRegion(layers, GeoExtent::INVALID, 0u, INT_MAX);
+ }
+}
+
+void
+CoreyTerrainEngineNode::moveElevationLayer(Layer* layer)
+{
+ if (layer && layer->isOpen())
+ {
+ std::vector layers;
+ layers.push_back(layer);
+ invalidateRegion(layers, GeoExtent::INVALID, 0u, INT_MAX);
+ }
+}
+
+// Generates the main shader code for rendering the terrain.
+void
+CoreyTerrainEngineNode::updateState()
+{
+ if (_batchUpdateInProgress)
+ {
+ _stateUpdateRequired = true;
+ }
+ else
+ {
+ // Load up the appropriate shader package:
+ CoreyShaders& shaders = CoreyShadersFactory::get(GLUtils::useNVGL());
+
+ auto options = getOptions();
+
+ auto* terrainSS = _terrain->getOrCreateStateSet();
+
+ // State that affects any terrain layer (surface, patch, other)
+ // AND compute shaders
+ {
+ // activate standard mix blending.
+ terrainSS->setAttributeAndModes(
+ new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA),
+ osg::StateAttribute::ON);
+
+ VirtualProgram* terrainVP = VirtualProgram::getOrCreate(terrainSS);
+ shaders.load(terrainVP, shaders.sdk());
+
+ // GL4 rendering?
+ if (GLUtils::useNVGL())
+ {
+ terrainSS->setDefine("OE_USE_GL4");
+ }
+
+ // vertex-dimension of each standard terrain tile.
+ terrainSS->setDefine("OE_TILE_SIZE",
+ std::to_string(options.getTileSize()));
+
+ // uniform that conveys the layer UID to the shaders; necessary
+ // for per-layer branching (like color filters)
+ // UID -1 => no image layer (no texture)
+ terrainSS->addUniform(new osg::Uniform(
+ "oe_layer_uid", (int)-1));
+
+ // uniform that conveys the render order, since the shaders
+ // need to know which is the first layer in order to blend properly
+ terrainSS->addUniform(new osg::Uniform(
+ "oe_layer_order", (int)0));
+
+ if (_require.elevationTextures)
+ {
+ // Compute an elevation texture sampling scale/bias so we sample elevation data on center
+ // instead of on edge (as we do with color, etc.)
+ float bias = 0.5; // getEngineData()->getUseTextureBorder() ? 1.5 : 0.5;
+ float size = (float)ELEVATION_TILE_SIZE;
+
+ terrainSS->addUniform(new osg::Uniform(
+ "oe_tile_elevTexelCoeff",
+ osg::Vec2f((size - (2.0 * bias)) / size, bias / size)));
+ }
+ }
+
+
+ // State that affects surface layers only:
+ {
+ // required for multipass tile rendering to work
+ _surfaceSS->setAttributeAndModes(
+ new osg::Depth(osg::Depth::LEQUAL, 0, 1, true));
+
+ // backface culling on
+ _surfaceSS->setAttributeAndModes(
+ new osg::CullFace(), osg::StateAttribute::ON);
+
+ // untextured terrain skin color
+ _surfaceSS->addUniform(new osg::Uniform(
+ "oe_terrain_color", options.getColor()));
+
+ // vertical offset of the terrain verts (cloud layer e.g.)
+ _surfaceSS->addUniform(new osg::Uniform(
+ "oe_terrain_altitude", (float)0.0f));
+
+ // exists do you can override it from above
+ _surfaceSS->setDefine("OE_TERRAIN_RENDER_IMAGERY");
+
+ // RENDERTYPE_TERRAIN_SURFACE shaders
+ VirtualProgram* surfaceVP = VirtualProgram::getOrCreate(_surfaceSS.get());
+ shaders.load(surfaceVP, shaders.vert());
+ shaders.load(surfaceVP, shaders.elevation());
+ shaders.load(surfaceVP, shaders.normal_map());
+
+ // GPU tessellation:
+ if (options.getGPUTessellation())
+ {
+ shaders.load(surfaceVP, shaders.tessellation());
+
+ // Default tess level
+ _surfaceSS->addUniform(new osg::Uniform("oe_terrain_tess", options.getTessellationLevel()));
+ _surfaceSS->addUniform(new osg::Uniform("oe_terrain_tess_range", options.getTessellationRange()));
+
+#ifdef HAVE_PATCH_PARAMETER
+ // backwards compatibility
+ _surfaceSS->setAttributeAndModes(new osg::PatchParameter(3));
+#endif
+ }
+
+ // Elevation
+ // Corey does NOT elevate the terrain in the shader
+ if (_require.elevationTextures)
+ {
+ _surfaceSS->setDefine("OE_TERRAIN_RENDER_ELEVATION");
+ }
+
+ // Normal mapping
+ if (_require.normalTextures)
+ {
+ _surfaceSS->setDefine("OE_TERRAIN_RENDER_NORMAL_MAP");
+ }
+
+ // Imagery blending
+ if (options.getEnableBlending())
+ {
+ _surfaceSS->setDefine("OE_TERRAIN_BLEND_IMAGERY");
+ }
+
+ // Compressed normal maps
+ if (options.getCompressNormalMaps())
+ {
+ _surfaceSS->setDefine("OE_COMPRESSED_NORMAL_MAP");
+ }
+
+ // Morphing (imagery and terrain)
+ if (_morphingSupported)
+ {
+ if ((options.getMorphTerrain() && _morphTerrainSupported) ||
+ (options.getMorphImagery()))
+ {
+ // GL4 morphing is built into another shader (vert.GL4.glsl)
+ if (!GLUtils::useNVGL())
+ shaders.load(surfaceVP, shaders.morphing());
+
+ if ((options.getMorphTerrain() && _morphTerrainSupported))
+ {
+ _surfaceSS->setDefine("OE_TERRAIN_MORPH_GEOMETRY");
+ }
+ if (options.getMorphImagery())
+ {
+ _surfaceSS->setDefine("OE_TERRAIN_MORPH_IMAGERY");
+ }
+ }
+ }
+
+ // Shadowing
+ if (options.getCastShadows())
+ {
+ _surfaceSS->setDefine("OE_TERRAIN_CAST_SHADOWS");
+ }
+
+ // special object ID that denotes the terrain surface.
+ _surfaceSS->addUniform(new osg::Uniform(
+ Registry::objectIndex()->getObjectIDUniformName().c_str(),
+ OSGEARTH_OBJECTID_TERRAIN));
+ }
+
+ // STATE for image layers
+ VirtualProgram* vp = VirtualProgram::getOrCreate(_imageLayerSS.get());
+ shaders.load(vp, shaders.imagelayer());
+
+ // The above shader will integrate opacity itself.
+ _imageLayerSS->setDefine("OE_SELF_MANAGE_LAYER_OPACITY");
+
+ _stateUpdateRequired = false;
+ }
+}
+
+osg::Node*
+CoreyTerrainEngineNode::createStandaloneTile(
+ const TerrainTileModel* model,
+ int createTileFlags,
+ unsigned referenceLOD,
+ const TileKey& subRegion)
+{
+ return nullptr;
+ //CreateTileImplementation impl;
+ //return impl.createTile(getEngineData(), model, createTileFlags, referenceLOD, subRegion);
+}
+
+void
+CoreyTerrainEngineNode::merge(TerrainTileModel* model)
+{
+ if (model)
+ {
+ //OE_INFO << "Merging " << model->key.str() << std::endl;
+
+ auto tilenode = _data.getTileNode(model->key);
+ if (tilenode.valid())
+ {
+ auto parent = _data.getTileNode(tilenode->_key.createParentKey());
+ auto parentData = parent.valid() ? parent->_fullDataModel.get() : nullptr;
+ tilenode->set(model, parentData, _data);
+ }
+ }
+}
+
+#if 0
+void
+CoreyTerrainEngineNode::refreshTileNode(const TileKey& key)
+{
+ // look up the tile node:
+ osg::ref_ptr tilenode;
+ {
+ std::lock_guard lock(_EngineData->tilesMutex);
+ auto i = _EngineData->tiles.find(key);
+ if (i != _EngineData->tiles.end())
+ {
+ tilenode = i->second;
+ }
+ }
+
+ if (!tilenode)
+ return;
+
+ OE_SOFT_ASSERT_AND_RETURN(tilenode->_partialDataModel.valid(), void());
+
+ auto model = tilenode->_partialDataModel;
+
+ // make a shallow copy of the current partial data model,
+ // i.e. the last model that the tile loaded.
+ osg::ref_ptr model_to_update =
+ new TerrainTileModel(*model.get());
+
+ OE_INFO << LC << "Updating: " << key.str() << std::endl;
+ bool updated = false;
+ _tileModelDB.createAndStore(key, [&](const TileKey& key)
+ {
+ updated = _tileModelFactory->updateTileModel(
+ model_to_update, _EngineData->getMap(), {}, _require, nullptr);
+ return model_to_update.get();
+ });
+
+ if (updated)
+ {
+ osg::observer_ptr tilenode_weak;
+ auto merge_function = [this, tilenode_weak, model_to_update]()
+ {
+ osg::ref_ptr tilenode;
+ if (tilenode_weak.lock(tilenode))
+ {
+ osg::ref_ptr parent;
+
+ auto parentKey = tilenode->_key.createParentKey();
+ if (parentKey.valid())
+ {
+ std::lock_guard lock(_EngineData->tilesMutex);
+ parent = _EngineData->tiles[parentKey];
+ if (!parent.valid())
+ _EngineData->tiles.erase(parentKey);
+ }
+
+ auto parentData = parent.valid() ? parent->_fullDataModel.get() : nullptr;
+ tilenode->set(model_to_update.get(), parentData, _EngineData.get());
+ }
+ };
+
+ std::lock_guard lock(_mergeQueue_mutex);
+ _mergeQueue.emplace(std::move(merge_function));
+ }
+
+ tilenode->_dirty = false;
+}
+#endif
diff --git a/src/osgEarthDrivers/engine_corey/CreateTileImplementation b/src/osgEarthDrivers/engine_corey/CreateTileImplementation
new file mode 100644
index 0000000000..af91265cfc
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/CreateTileImplementation
@@ -0,0 +1,54 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#if 0
+#ifndef OSGEARTH_Corey_CREATE_TILE_IMPLEMENTATION_H
+#define OSGEARTH_Corey_CREATE_TILE_IMPLEMENTATION_H
+
+#include "Common"
+#include
+
+namespace osg {
+ class Node;
+}
+
+namespace osgEarth {
+ class TerrainTileModel;
+ class TileKey;
+}
+
+namespace osgEarth { namespace Corey
+{
+ class EngineData;
+
+ class /* internal */ CreateTileImplementation
+ {
+ public:
+ osg::Node* createTile(
+ EngineData* context,
+ const TerrainTileModel* model,
+ int flags,
+ unsigned referenceLOD,
+ const TileKey& area,
+ Cancelable* progress = nullptr);
+ };
+
+} }
+
+#endif // OSGEARTH_Corey_CREATE_TILE_IMPLEMENTATION_H
+#endif
\ No newline at end of file
diff --git a/src/osgEarthDrivers/engine_corey/CreateTileImplementation.cpp b/src/osgEarthDrivers/engine_corey/CreateTileImplementation.cpp
new file mode 100644
index 0000000000..0d371c0d9b
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/CreateTileImplementation.cpp
@@ -0,0 +1,213 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#if 0
+#include "CreateTileImplementation"
+#include "EngineData"
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace osgEarth;
+using namespace osgEarth::Util;
+using namespace osgEarth::Corey;
+
+#undef LC
+#define LC "[Corey::CreateTileImpl] "
+
+osg::Node*
+CreateTileImplementation::createTile(
+ EngineData* context,
+ const TerrainTileModel* model,
+ int flags,
+ unsigned referenceLOD,
+ const TileKey& area,
+ Cancelable* progress)
+{
+ if (model == nullptr)
+ {
+ OE_WARN << LC << "Illegal: createTile(NULL)" << std::endl;
+ return 0L;
+ }
+
+ // Verify that we have a map:
+ osg::ref_ptr map = context->getMap();
+ if (!map.valid())
+ {
+ return nullptr;
+ }
+
+ // Dimension of each tile in vertices
+ unsigned tileSize = context->options().getTileSize();
+ TileKey rootkey = area.valid() ? area : model->key;
+ const SpatialReference* srs = rootkey.getExtent().getSRS();
+
+ // Will hold keys at reference lod to check
+ std::vector keys;
+
+ // Recurse down through tile hierarchy checking for masks at each level.
+ // If a given tilekey doesn't have any masks then we don't have to check children.
+ std::stack keyStack;
+ keyStack.push(rootkey);
+ while (!keyStack.empty())
+ {
+ TileKey key = keyStack.top();
+ keyStack.pop();
+
+ if (key.getLOD() < referenceLOD)
+ {
+ keyStack.push(key.createChildKey(0));
+ keyStack.push(key.createChildKey(1));
+ keyStack.push(key.createChildKey(2));
+ keyStack.push(key.createChildKey(3));
+ }
+ else
+ {
+ keys.push_back(key);
+ }
+ }
+
+ if (keys.empty())
+ return 0L;
+
+ bool include_constrained = (flags & TerrainEngineNode::CREATE_TILE_INCLUDE_TILES_WITH_MASKS) != 0;
+ bool include_unconstrained = (flags & TerrainEngineNode::CREATE_TILE_INCLUDE_TILES_WITHOUT_MASKS) != 0;
+
+ // group to hold all the tiles
+ osg::ref_ptr group;
+
+ for (std::vector::const_iterator subkey = keys.begin(); subkey != keys.end(); ++subkey)
+ {
+ osg::ref_ptr sharedGeom;
+
+ context->getGeometryPool()->getPooledGeometry(
+ *subkey,
+ tileSize,
+ map.get(),
+ context->options(),
+ sharedGeom,
+ progress);
+
+ if (progress && progress->canceled())
+ {
+ return nullptr;
+ }
+
+ if (sharedGeom.valid() == false &&
+ include_constrained == true &&
+ include_unconstrained == false)
+ {
+ // This means that we found a constrained tile that was completely
+ // masked out - all triangles were removed. If we are ONLY returning
+ // constrained tiles, make an empty group for it to mark its
+ // existance.
+ if (!group.valid())
+ group = new osg::Group();
+
+ osg::Group* empty_tile_group = new osg::Group();
+ osg::UserDataContainer* udc = empty_tile_group->getOrCreateUserDataContainer();
+ udc->setUserValue("tile_key", subkey->str());
+ group->addChild(empty_tile_group);
+ }
+
+ else if (
+ sharedGeom.valid() &&
+ !sharedGeom->empty() &&
+ (
+ (include_constrained && sharedGeom->hasConstraints()) ||
+ (include_unconstrained && !sharedGeom->hasConstraints())
+ ))
+ {
+ // This means we got some geometry.
+ if (!group.valid())
+ group = new osg::Group();
+
+ osg::ref_ptr drawable = sharedGeom.get();
+
+ osg::UserDataContainer* udc = drawable->getOrCreateUserDataContainer();
+ udc->setUserValue("tile_key", subkey->str());
+
+ osg::ref_ptr geom = sharedGeom->makeOsgGeometry();
+ drawable = geom.get();
+
+ drawable->setUserDataContainer(udc);
+
+ // Burn elevation data into the vertex list
+ if (model->elevation.texture)
+ {
+ // Clone the vertex array since it's shared and we're going to alter it
+ geom->setVertexArray(osg::clone(geom->getVertexArray(), osg::CopyOp::DEEP_COPY_ALL));
+
+ // Apply the elevation model to the verts, noting that the texture coordinate
+ // runs [0..1] across the tile and the normal is the up vector at each vertex.
+ osg::Vec3Array* verts = dynamic_cast(geom->getVertexArray());
+ osg::Vec3Array* ups = dynamic_cast(geom->getNormalArray());
+ osg::Vec3Array* tileCoords = dynamic_cast(geom->getTexCoordArray(0));
+
+ const osg::HeightField* hf = model->elevation.heightField;
+ const osg::Matrix& hfmatrix = model->elevation.matrix;
+
+ // Tile coords must be transformed into the local tile's space
+ // for elevation grid lookup:
+ osg::Matrix scaleBias;
+ subkey->getExtent().createScaleBias(model->key.getExtent(), scaleBias);
+
+ // Apply elevation to each vertex.
+ for (unsigned i = 0; i < verts->size(); ++i)
+ {
+ osg::Vec3& vert = (*verts)[i];
+ osg::Vec3& up = (*ups)[i];
+ osg::Vec3& tileCoord = (*tileCoords)[i];
+
+ // Skip verts on a masking boundary since their elevations are hard-wired.
+ if ((VERTEX_HAS_ELEVATION & (int)tileCoord.z()) == 0) // if VERTEX_HAS_ELEVATION bit not set
+ {
+ osg::Vec3d n = osg::Vec3d(tileCoord.x(), tileCoord.y(), 0);
+ n = n * scaleBias;
+ n = n * hfmatrix;
+
+ float z = HeightFieldUtils::getHeightAtNormalizedLocation(hf, n.x(), n.y());
+ if (z != NO_DATA_VALUE)
+ {
+ vert += up * z;
+ }
+ }
+ }
+
+ verts->dirty();
+ }
+
+ // Establish a local reference frame for the tile:
+ GeoPoint centroid = subkey->getExtent().getCentroid();
+
+ osg::Matrix local2world;
+ centroid.createLocalToWorld(local2world);
+
+ osg::MatrixTransform* xform = new osg::MatrixTransform(local2world);
+ xform->addChild(drawable.get());
+
+ group->addChild(xform);
+ }
+ }
+
+ return group.release();
+}
+#endif
\ No newline at end of file
diff --git a/src/osgEarthDrivers/engine_corey/DrawState b/src/osgEarthDrivers/engine_corey/DrawState
new file mode 100644
index 0000000000..d5203b93b4
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/DrawState
@@ -0,0 +1,127 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#ifndef OSGEARTH_Corey_TERRAIN_DRAW_STATE_H
+#define OSGEARTH_Corey_TERRAIN_DRAW_STATE_H 1
+
+#include "RenderBindings"
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace Corey
+{
+ /**
+ * Tracks the state of a single sampler through the draw process,
+ * to prevent redundant OpenGL texture binding and matrix uniform sets.
+ */
+ struct SamplerState
+ {
+ std::string _name; // Sampler name
+ optional _texture; // Texture currently bound
+ optional _matrix; // Matrix that is currently set
+ GLint _matrixUL = -1; // Matrix uniform location
+
+ void clear() {
+ _texture.clear();
+ _matrix.clear();
+ }
+ };
+
+ /**
+ * Tracks the state of all samplers used in render a tile,
+ * to prevent redundant OpenGL binds.
+ */
+ struct TileSamplerState
+ {
+ std::vector _samplers;
+
+ void clear() {
+ for (unsigned i = 0; i<_samplers.size(); ++i)
+ _samplers[i].clear();
+ }
+ };
+
+ struct ProgramState
+ {
+ const osg::Program::PerContextProgram* _pcp = nullptr;
+
+ // uniform locations
+ GLint _tileKeyUL = -1;
+ GLint _parentTextureExistsUL = -1;
+ GLint _layerUidUL = -1;
+ GLint _layerOrderUL = -1;
+ GLint _morphConstantsUL = -1;
+
+ optional _morphConstants;
+ optional _parentTextureExists;
+ optional _layerOrder;
+
+ TileSamplerState _samplerState;
+
+ void reset();
+
+ void init(
+ const osg::Program::PerContextProgram* pcp,
+ const RenderBindings* bindings);
+ };
+
+ /**
+ * Tracks the state of terrain drawing settings in a single frame,
+ * to prevent redundant OpenGL calls.
+ */
+ struct DrawState
+ {
+ using Ptr = std::shared_ptr;
+ static DrawState::Ptr create();
+
+ using ProgramStates = std::unordered_map;
+ ProgramStates _perProgramStates;
+
+ const RenderBindings* _bindings = nullptr;
+ osg::BoundingSphere _bs;
+ osg::BoundingBox _box;
+
+ ProgramState& getProgramState(osg::RenderInfo& ri)
+ {
+ auto pcp = ri.getState()->getLastAppliedProgramObject();
+ ProgramState& programState = _perProgramStates[pcp];
+ if (programState._pcp == nullptr)
+ {
+ programState.init(pcp, _bindings);
+ }
+ return programState;
+ }
+
+ void resetAll(osg::RenderInfo& ri)
+ {
+ _perProgramStates.clear();
+ }
+ };
+
+} } // namespace
+
+#endif // OSGEARTH_Corey_TERRAIN_DRAW_STATE_H
diff --git a/src/osgEarthDrivers/engine_corey/DrawState.cpp b/src/osgEarthDrivers/engine_corey/DrawState.cpp
new file mode 100644
index 0000000000..7ede648a73
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/DrawState.cpp
@@ -0,0 +1,77 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#include "DrawState"
+
+using namespace osgEarth::Corey;
+
+#undef LC
+#define LC "[DrawState] "
+
+DrawState::Ptr
+DrawState::create()
+{
+ return std::make_shared();
+}
+
+void
+ProgramState::init(
+ const osg::Program::PerContextProgram* pcp,
+ const RenderBindings* bindings)
+{
+ _pcp = pcp;
+
+ // Size the sampler states property:
+ _samplerState._samplers.resize(bindings->size());
+
+ // for each sampler binding, initialize its state tracking structure
+ // and resolve its matrix uniform location:
+ for (unsigned i = 0; i < bindings->size(); ++i)
+ {
+ const SamplerBinding& binding = (*bindings)[i];
+ _samplerState._samplers[i]._name = binding.samplerName;
+ if (_pcp)
+ {
+ _samplerState._samplers[i]._matrixUL = _pcp->getUniformLocation(
+ osg::Uniform::getNameID(binding.matrixName));
+ }
+ }
+
+ // resolve all the other uniform locations:
+ if (_pcp)
+ {
+ _tileKeyUL = _pcp->getUniformLocation(osg::Uniform::getNameID("oe_tile_key_u"));
+ _parentTextureExistsUL = _pcp->getUniformLocation(osg::Uniform::getNameID("oe_layer_texParentExists"));
+ _layerUidUL = _pcp->getUniformLocation(osg::Uniform::getNameID("oe_layer_uid"));
+ _layerOrderUL = _pcp->getUniformLocation(osg::Uniform::getNameID("oe_layer_order"));
+ _morphConstantsUL = _pcp->getUniformLocation(osg::Uniform::getNameID("oe_tile_morph"));
+ }
+
+ // Reset all optional states
+ reset();
+}
+
+void
+ProgramState::reset()
+{
+ //_elevTexelCoeff.clear();
+ _morphConstants.clear();
+ _parentTextureExists.clear();
+ _layerOrder.clear();
+ _samplerState.clear();
+}
diff --git a/src/osgEarthDrivers/engine_corey/DrawTileCommand b/src/osgEarthDrivers/engine_corey/DrawTileCommand
new file mode 100644
index 0000000000..56596c4009
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/DrawTileCommand
@@ -0,0 +1,195 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#pragma once
+
+#include "TileRenderModel"
+#include "DrawState"
+#include "TileGeometry"
+#include
+#include
+#include
+#include
+#include
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace Corey
+{
+ /**
+ * All data necessary to draw a single terrain tile.
+ */
+ struct DrawTileCommand : public osgEarth::TileState
+ {
+ // Tile key
+ const TileKey* _key;
+
+ // True if this tile intersects the extent of its source layer.
+ // We can achieve some optimizations by not rendering tiles outside
+ // of the layer extents in many cases, but we don't know until it's
+ // time to render.
+ bool _intersectsLayerExtent;
+
+ // ModelView matrix to apply before rendering this tile
+ osg::Matrix _modelViewMatrix;
+ osg::Matrix _localToWorld;
+
+ // Samplers that are common across all rendering passes
+ const Samplers* _commonSamplers;
+
+ // Samplers specific to one rendering pass
+ const Samplers* _colorSamplers;
+
+ // Tile geometry, if present (ref_ptr necessary?)
+ osg::ref_ptr _geom;
+
+ //TileDrawable* _tile;
+
+ // Tile key value to push to uniform just before drawing
+ osg::Vec4f _keyValue;
+
+ // Data revision # of the tile
+ unsigned _tileRevision;
+
+ // Coefficient used for tile vertex morphing
+ osg::Vec2f _morphConstants;
+
+ // Custom draw callback to call instead of rendering _geom
+ TileRenderer* _drawCallback;
+
+ // When drawing _geom, whether to render as GL_PATCHES
+ // instead of GL_TRIANGLES (for patch layers)
+ bool _drawPatch;
+
+ // Distance from camera to center of tile
+ float _range;
+
+ // Start and end morphing distances for this tile
+ float _morphStartRange, _morphEndRange;
+
+ // Layer order for this tile. i.e., if this is zero, then this command
+ // is drawing a tile for the first LayerDrawable.
+ int _layerOrder;
+
+ // Order in which this tile appears within the layer
+ int _sequence;
+
+ // True if the tile has a custom constrainted mesh
+ bool _hasConstraints;
+
+ // Renders the tile.
+ inline void draw(osg::RenderInfo& ri) const {
+ _geom->draw(ri);
+ }
+
+ // Less than operator will ensure that tiles are sorted near to far
+ // to minimze overdraw.
+ bool operator < (const DrawTileCommand& rhs) const
+ {
+ // using LOD seems to be slightly faster than using range, probably
+ // because we also get nice grouping of shared geometry tiles that way.
+ if (_key->getLOD() > rhs._key->getLOD()) return true;
+ if (_key->getLOD() < rhs._key->getLOD()) return false;
+ return _geom.get() < rhs._geom.get();
+ }
+
+ bool operator == (const DrawTileCommand& rhs) const
+ {
+ return
+ // same tile
+ _key == rhs._key &&
+ // same revision
+ _tileRevision == rhs._tileRevision &&
+ // same matrix
+ _modelViewMatrix == rhs._modelViewMatrix &&
+ // same underlying geometry pointer - this is important
+ // if we're doing bindless NV rendering and have to
+ // use a different GPU buffer address.
+ _geom.get() == rhs._geom.get();
+ }
+
+ DrawTileCommand() :
+ _commonSamplers(0L),
+ _colorSamplers(0L),
+ _geom(0L),
+ _drawCallback(0L),
+ _drawPatch(false),
+ _range(0.0f),
+ _layerOrder(INT_MAX),
+ _sequence(0),
+ _hasConstraints(false) { }
+
+ DrawTileCommand(DrawTileCommand&&) = default;
+ DrawTileCommand& operator = (const DrawTileCommand&) = default;
+ DrawTileCommand& operator = (DrawTileCommand&&) = default;
+
+ void accept(osg::PrimitiveFunctor& functor) const;
+ void accept(osg::PrimitiveIndexFunctor& functor) const;
+
+ public: // TileState
+
+ const TileKey& getKey() const override {
+ return *_key;
+ }
+
+ int getRevision() const override {
+ return _tileRevision;
+ }
+
+ int getSequence() const override {
+ return _sequence;
+ }
+
+ const osg::BoundingBox& getBBox() const override {
+ return _geom->getBoundingBox();
+ }
+
+ const osg::Matrix& getModelViewMatrix() const override {
+ return _modelViewMatrix;
+ }
+
+ const osg::Matrixd& getLocalToWorld() const override {
+ return _localToWorld;
+ }
+
+ float getMorphStartRange() const override {
+ return _morphStartRange;
+ }
+
+ float getMorphEndRange() const override {
+ return _morphEndRange;
+ }
+
+ //! Apply the GL state for this tile (all samplers and uniforms)
+ //! in preparation for rendering or computation
+ bool apply(
+ osg::RenderInfo& ri,
+ void* implData) const override;
+
+ void debug(
+ osg::RenderInfo& ri,
+ void* implData) const override;
+ };
+
+ /**
+ * Ordered list of tile drawing commands.
+ * List is faster than vector here (benchmarked)
+ */
+ typedef std::vector DrawTileCommands;
+
+} } // namespace
diff --git a/src/osgEarthDrivers/engine_corey/DrawTileCommand.cpp b/src/osgEarthDrivers/engine_corey/DrawTileCommand.cpp
new file mode 100644
index 0000000000..4412639105
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/DrawTileCommand.cpp
@@ -0,0 +1,244 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#include "DrawTileCommand"
+#include
+
+using namespace osgEarth::Corey;
+
+#undef LC
+#define LC "[DrawTileCommand] "
+
+bool
+DrawTileCommand::apply(osg::RenderInfo& ri, void* implData) const
+{
+ DrawState& ds = *static_cast(implData);
+ ProgramState& pps = ds.getProgramState(ri);
+
+ osg::State& state = *ri.getState();
+ osg::GLExtensions* ext = state.get();
+
+ // Tile key encoding, if the uniform is required.
+ if (pps._tileKeyUL >= 0)
+ {
+ ext->glUniform4fv(pps._tileKeyUL, 1, _keyValue.ptr());
+ }
+
+ // Apply the layer draw order for this tile so we can blend correctly:
+ if (pps._layerOrderUL >= 0 && !pps._layerOrder.isSetTo(_layerOrder))
+ {
+ ext->glUniform1i(pps._layerOrderUL, (GLint)_layerOrder);
+ pps._layerOrder = _layerOrder;
+ }
+
+ // Morphing constants for this LOD
+ if (pps._morphConstantsUL >= 0 && !pps._morphConstants.isSetTo(_morphConstants))
+ {
+ ext->glUniform2fv(pps._morphConstantsUL, 1, _morphConstants.ptr());
+ pps._morphConstants = _morphConstants;
+ }
+
+ // MVM for this tile:
+ state.applyModelViewMatrix(_modelViewMatrix);
+
+ // MVM uniforms for GL3 core:
+ if (state.getUseModelViewAndProjectionUniforms())
+ {
+ state.applyModelViewAndProjectionUniformsIfRequired();
+ }
+
+ // Apply samplers for this tile draw:
+ unsigned s = 0;
+
+ if (_colorSamplers)
+ {
+ for (s = SamplerBinding::COLOR; s <= SamplerBinding::COLOR_PARENT; ++s)
+ {
+ const Sampler& sampler = (*_colorSamplers)[s];
+ SamplerState& samplerState = pps._samplerState._samplers[s];
+
+ if (sampler._texture &&
+ !samplerState._texture.isSetTo(sampler._texture))
+ {
+ if (!sampler._texture->dataLoaded())
+ return false;
+
+ state.setActiveTextureUnit((*ds._bindings)[s].unit);
+ sampler._texture->osgTexture()->apply(state);
+ samplerState._texture = sampler._texture;
+ }
+
+ if (samplerState._matrixUL >= 0 && !samplerState._matrix.isSetTo(sampler._matrix))
+ {
+ ext->glUniformMatrix4fv(samplerState._matrixUL, 1, GL_FALSE, sampler._matrix.ptr());
+ samplerState._matrix = sampler._matrix;
+ }
+
+ // Need a special uniform for color parents.
+ if (s == SamplerBinding::COLOR_PARENT)
+ {
+ if (pps._parentTextureExistsUL >= 0 && !pps._parentTextureExists.isSetTo(sampler._texture.get() != 0L))
+ {
+ ext->glUniform1f(pps._parentTextureExistsUL, sampler._texture ? 1.0f : 0.0f);
+ pps._parentTextureExists = sampler._texture != nullptr;
+ }
+ }
+ }
+ }
+
+ if (_commonSamplers)
+ {
+ for (; s < _commonSamplers->size(); ++s)
+ {
+ const Sampler& sampler = (*_commonSamplers)[s];
+ SamplerState& samplerState = pps._samplerState._samplers[s];
+
+ if (sampler._texture && !samplerState._texture.isSetTo(sampler._texture))
+ {
+ state.setActiveTextureUnit((*ds._bindings)[s].unit);
+ sampler._texture->osgTexture()->apply(state);
+ samplerState._texture = sampler._texture;
+ }
+
+ if (samplerState._matrixUL >= 0 && !samplerState._matrix.isSetTo(sampler._matrix))
+ {
+ ext->glUniformMatrix4fv(samplerState._matrixUL, 1, GL_FALSE, sampler._matrix.ptr());
+ samplerState._matrix = sampler._matrix;
+ }
+ }
+ }
+
+ return true;
+}
+
+void
+DrawTileCommand::debug(
+ osg::RenderInfo& ri,
+ void* implData) const
+{
+ DrawState& ds = *static_cast(implData);
+ ProgramState& pps = ds.getProgramState(ri);
+
+ // Tile key encoding, if the uniform is required.
+ OE_INFO << "\nKey " << _key->str() << std::endl;
+
+ if (pps._tileKeyUL >= 0)
+ OE_INFO << " tileKey UL = " << pps._tileKeyUL << ", value = " << _keyValue[0] << ", " << _keyValue[1] << ", " << _keyValue[2] << ", " << _keyValue[3] << std::endl;
+
+ if (pps._layerOrderUL >= 0)
+ OE_INFO << " layerOrder UL = " << pps._layerOrderUL << ", value = " << _layerOrder << std::endl;
+
+ //if (pps._elevTexelCoeffUL >= 0)
+ // OE_INFO << " _elevTexelCoeff UL = " << pps._elevTexelCoeffUL << ", value = " << (*pps._elevTexelCoeff)[0] << ", " << (*pps._elevTexelCoeff)[1] << std::endl;
+
+ if (pps._morphConstantsUL >= 0 )
+ OE_INFO << " _morphConstantsUL UL = " << pps._morphConstantsUL << ", value = " << (*pps._morphConstants)[0] << ", " << (*pps._morphConstants)[1] << std::endl;
+
+ OE_INFO << " samplers:" << std::endl;
+ unsigned s = 0;
+ if (_commonSamplers)
+ {
+ for (; s < _commonSamplers->size(); ++s)
+ {
+ const Sampler& sampler = (*_commonSamplers)[s];
+ SamplerState& samplerState = pps._samplerState._samplers[s];
+
+ //TODO: Check the _matrixUL first, and if it's not set, don't apply the texture
+ // because it's not used either.
+
+ if (samplerState._matrixUL >= 0)
+ {
+ OE_INFO << " name = " << samplerState._name << ", mUL = " << samplerState._matrixUL
+ << ", scale = " << sampler._matrix(0,0) << std::endl;
+
+#if 0
+ if (samplerState._texture.isSet())
+ {
+ osg::ref_ptr c = osg::clone(
+ samplerState._texture.get()->getImage(0),
+ osg::CopyOp::DEEP_COPY_ALL);
+
+ ImageUtils::PixelReader r(c.get());
+ ImageUtils::PixelWriter w(c.get());
+ ImageUtils::ImageIterator i(r);
+ i.forEachPixel([&]() {
+ osg::Vec4f p;
+ r(p, i.s(), i.t());
+ p.a() = 1.0f;
+ w(p, i.s(), i.t());
+ });
+
+ osgDB::writeImageFile(
+ *c.get(),
+ Stringify() << "out/" << samplerState._name
+ << "." << ri.getState()->getFrameStamp()->getFrameNumber()
+ << ".png");
+ }
+#endif
+ }
+ }
+ }
+}
+
+#if 0
+void
+DrawTileCommand::draw(osg::RenderInfo& ri) const
+{
+ OE_SOFT_ASSERT_AND_RETURN(_geom.valid(), void());
+
+ auto cid = GLUtils::getSharedContextID(*ri.getState());
+ _geom->_ptype[cid] = _drawPatch ? GL_PATCHES : _geom->drawElements->getMode();
+ _geom->draw(ri);
+}
+#endif
+
+#if 0
+void
+DrawTileCommand::visit(osg::RenderInfo& ri) const
+{
+ if (_drawCallback)
+ {
+ PatchLayer::DrawContext tileData;
+
+ tileData._key = _key;
+ tileData._revision = _tileRevision;
+ //tileData._geomBBox = &_geom->getBoundingBox();
+ tileData._tileBBox = &_tile->getBoundingBox();
+ tileData._modelViewMatrix = _modelViewMatrix.get();
+ _drawCallback->visitTile(ri, tileData);
+ }
+}
+#endif
+
+#if 1
+void DrawTileCommand::accept(osg::PrimitiveFunctor& functor) const
+{
+ if (_geom.valid() && _geom->supports(functor))
+ {
+ _geom->accept(functor);
+ }
+}
+
+void DrawTileCommand::accept(osg::PrimitiveIndexFunctor& functor) const
+{
+ if (_geom.valid() && _geom->supports(functor))
+ {
+ _geom->accept(functor);
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/osgEarthDrivers/engine_corey/EngineData b/src/osgEarthDrivers/engine_corey/EngineData
new file mode 100644
index 0000000000..490957e2e7
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/EngineData
@@ -0,0 +1,83 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#pragma once
+
+#include "Common"
+#include "TileNode"
+#include "RenderBindings"
+#include "TerrainCuller"
+#include "SelectionInfo"
+
+#include
+#include
+#include
+
+#include
+
+using namespace osgEarth;
+
+namespace osgEarth {
+ class FrameClock;
+}
+
+namespace osgEarth
+{
+ namespace Corey
+ {
+ class SelectionInfo;
+
+ struct EngineData
+ {
+ CullData cullData; // TODO: per camera, please
+
+ inline osg::ref_ptr getTileNode(const TileKey& key) const {
+ osg::ref_ptr result;
+ std::lock_guard lock(tiles_mutex);
+ auto i = tiles.find(key);
+ if (i != tiles.end()) {
+ i->second.lock(result);
+ if (!result.valid())
+ tiles.erase(key);
+ }
+ return result;
+ }
+
+ inline void storeTileNode(TileNode* node) {
+ OE_SOFT_ASSERT_AND_RETURN(node, void());
+ std::lock_guard lock(tiles_mutex);
+ tiles[node->_key] = node;
+ }
+
+ osg::ref_ptr tileNodeCullCallback;
+ osg::ref_ptr tileDrawableCullCallback;
+ mutable std::unordered_map> tiles;
+ mutable std::mutex tiles_mutex;
+ osg::ref_ptr map;
+ TerrainOptionsAPI options;
+ RenderBindings renderBindings;
+ SelectionInfo selectionInfo;
+ FrameClock* clock = nullptr;
+ osg::ref_ptr textures;
+
+ // extents of each layer, in the Map's SRS. UID = vector index (for speed)
+ LayerExtentMap cachedLayerExtents;
+ bool cachedLayerExtentsComputeRequired = true;
+ };
+ }
+}
diff --git a/src/osgEarthDrivers/engine_corey/LayerDrawable b/src/osgEarthDrivers/engine_corey/LayerDrawable
new file mode 100644
index 0000000000..04f2bd8010
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/LayerDrawable
@@ -0,0 +1,206 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#pragma once
+
+#include "DrawTileCommand"
+#include "DrawState"
+#include "TileRenderModel"
+
+#include
+#include
+#include
+
+#include
+
+using namespace osgEarth;
+
+namespace osgEarth { namespace Corey
+{
+ class TerrainRenderData;
+ struct EngineData;
+
+ /**
+ * Drawable for single "Layer" i.e. rendering pass.
+ * It is important that LayerDrawables be rendered in the order in which
+ * they appear. Since all LayerDrawables share a common bounds, this
+ * should happen automatically, but let's keep an eye out for trouble.
+ */
+ class LayerDrawable : public osg::Drawable
+ {
+ public:
+ LayerDrawable();
+
+ // The colletion of tiles to render for this layer
+ DrawTileCommands _tiles;
+
+ // Determines whether to use the default surface shader program
+ Layer::RenderType _renderType = Layer::RENDERTYPE_TERRAIN_SURFACE;
+
+ // Pointer back to the actual Map layer, if there is one
+ const Layer* _layer = nullptr;
+
+ // If _layer is a VisibleLayer, this will be set as well, otherwise nullptr
+ const VisibleLayer* _visibleLayer = nullptr;
+
+ // If _layer is an ImageLayer, this will be set as well, otherwise nullptr
+ const ImageLayer* _imageLayer = nullptr;
+
+ // If _layer is a PatchLayer, this will be set, otherwise nullptr
+ const PatchLayer* _patchLayer = nullptr;
+
+ // Layer render order, which is pushed into a Uniform at render time.
+ // This value is assigned at cull time by CoreyTerrainEngineNode.
+ int _drawOrder = 0;
+
+ // Layer render order (only terrain surface layers)
+ int _surfaceDrawOrder = 0;
+
+ // The last layer to render will have this flag set, which will
+ // prompt the render to dirty the osg::State to prevent corruption.
+ // This flag is set at cull time by CoreyTerrainEngineNode.
+ bool _clearOsgState = false;
+
+ // Reference the terrain-wide state
+ DrawState::Ptr _drawState;
+
+ // Whether to render this layer.
+ bool _draw = true;
+
+ // shared context
+ EngineData* _data = nullptr;
+
+ public: // osg::Drawable
+
+ // All LayerDrawables share the common terrain bounds.
+ osg::BoundingSphere computeBound() const override { return _drawState->_bs; }
+ osg::BoundingBox computeBoundingBox() const override { return _drawState->_box; }
+ bool supports(const osg::PrimitiveFunctor&) const override { return true; }
+ void accept(osg::PrimitiveFunctor& functor) const override;
+ bool supports(const osg::PrimitiveIndexFunctor&) const override { return true; }
+ void accept(osg::PrimitiveIndexFunctor&) const override;
+
+ void accept(osg::NodeVisitor& nv) override {
+ osg::Drawable::accept(nv);
+ }
+
+ protected:
+
+ // overriden to prevent OSG from releasing GL objects on an attached stateset.
+ virtual ~LayerDrawable();
+
+ private:
+ };
+
+
+ /**
+ * Drawable for single "Layer" i.e. rendering pass.
+ * It is important that LayerDrawables be rendered in the order in which
+ * they appear. Since all LayerDrawables share a common bounds, this
+ * should happen automatically, but let's keep an eye out for trouble.
+ */
+ class LayerDrawableGL3 : public LayerDrawable
+ {
+ public:
+ LayerDrawableGL3();
+
+ public: // osg::Drawable
+
+ void drawImplementation(osg::RenderInfo& ri) const override;
+
+ protected:
+
+ // overriden to prevent OSG from releasing GL objects on an attached stateset.
+ virtual ~LayerDrawableGL3();
+ };
+
+
+ /**
+ * Drawable for single "Layer" i.e. rendering pass.
+ * It is important that LayerDrawables be rendered in the order in which
+ * they appear. Since all LayerDrawables share a common bounds, this
+ * should happen automatically, but let's keep an eye out for trouble.
+ */
+ class LayerDrawableNVGL : public LayerDrawable
+ {
+ public:
+ LayerDrawableNVGL();
+
+ public: // osg::Drawable
+
+ void drawImplementation(osg::RenderInfo& ri) const override;
+ void releaseGLObjects(osg::State*) const override;
+ void resizeGLObjectBuffers(unsigned size) override;
+ void accept(osg::NodeVisitor& nv) override;
+
+ protected:
+
+ virtual ~LayerDrawableNVGL();
+
+ private:
+
+ // GL objects maintained entirely on a per-State/GC basis.
+ // Since this is a Drawable, the data here will differ per view
+ // and per camera, and therefore cannot be shared across graphics
+ // contexts no matter what.
+ struct GLObjects : public PerStateGLObjects
+ {
+ // ssbo containing globally shared constant data
+ GLBuffer::Ptr shared;
+
+ // ssbo containing per-tile render info
+ GLBuffer::Ptr tiles;
+
+ // VAO for rendering with bindless buffers
+ GLVAO::Ptr vao;
+
+ // bindless draw commands
+ GLBuffer::Ptr commands;
+
+ osg::GLExtensions* ext;
+
+ void(GL_APIENTRY * glMultiDrawElementsIndirectBindlessNV)
+ (GLenum, GLenum, const GLvoid*, GLsizei, GLsizei, GLint);
+
+ void(GL_APIENTRY * glVertexAttribFormat)
+ (GLuint, GLint, GLenum, GLboolean, GLuint);
+ };
+
+ struct RenderState
+ {
+ RenderState();
+
+ bool dirty;
+
+ DrawTileCommands tiles;
+ std::vector tilebuf;
+
+ std::vector commands;
+ osg::buffered_object globjects;
+ };
+ mutable RenderState _rs;
+
+ void refreshRenderState();
+ };
+
+
+ // Straight list of LayerDrawables.
+ using LayerDrawableList = std::vector>;
+ using LayerDrawableMap = std::unordered_map>;
+
+} } // namespace
diff --git a/src/osgEarthDrivers/engine_corey/LayerDrawable.cpp b/src/osgEarthDrivers/engine_corey/LayerDrawable.cpp
new file mode 100644
index 0000000000..19cae2c1aa
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/LayerDrawable.cpp
@@ -0,0 +1,570 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#include "LayerDrawable"
+#include "TerrainRenderData"
+#include "SelectionInfo"
+#include "EngineData"
+#include
+#include
+#include
+
+using namespace osgEarth::Corey;
+
+#undef LC
+#define LC "[LayerDrawable] "
+
+#define COPY_MAT4F(FROM,TO) ::memcpy((TO), (FROM).ptr(), 16*sizeof(float))
+
+#ifndef GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV
+#define GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV 0x8F1E
+#define GL_ELEMENT_ARRAY_UNIFIED_NV 0x8F1F
+#endif
+
+// Uncomment this to reset all buffer base index bindings after rendering.
+// It's unlikely this is necessary, but it's here just we find otherwise.
+//#define RESET_BUFFER_BASE_BINDINGS
+
+
+LayerDrawable::LayerDrawable()
+{
+ // Since we refresh the render state in the CULL traversal, we must
+ // set the variance to dynamic to prevent overlap with DRAW
+ //TODO: Check this.
+ setDataVariance(DYNAMIC);
+ setUseDisplayList(false);
+ setUseVertexBufferObjects(true);
+ _tiles.reserve(128);
+}
+
+LayerDrawable::~LayerDrawable()
+{
+ // Drawable's DTOR will release GL objects on any attached stateset;
+ // we don't want that because our Layer stateset is shared and re-usable.
+ // So detach it before OSG has a chance to do so.
+ setStateSet(nullptr);
+}
+
+void
+LayerDrawable::accept(osg::PrimitiveFunctor& functor) const
+{
+ for (auto& tile : _tiles)
+ tile.accept(functor);
+}
+
+void
+LayerDrawable::accept(osg::PrimitiveIndexFunctor& functor) const
+{
+ for (auto& tile : _tiles)
+ tile.accept(functor);
+}
+
+//.........................................................
+
+LayerDrawableGL3::LayerDrawableGL3() :
+ LayerDrawable()
+{
+ setName("LayerDrawableGL3");
+}
+
+LayerDrawableGL3::~LayerDrawableGL3()
+{
+ //nop
+}
+
+void
+LayerDrawableGL3::drawImplementation(osg::RenderInfo& ri) const
+{
+ const char* zone = _layer ? _layer->getName().c_str() : className();
+ OE_GL_ZONE_NAMED(zone);
+
+ if (_patchLayer && _patchLayer->getRenderer())
+ {
+ TileBatch batch(_drawState.get());
+ batch._tiles.reserve(_tiles.size());
+ for (auto& tile : _tiles)
+ batch._tiles.push_back(&tile);
+
+ _patchLayer->getRenderer()->draw(ri, batch);
+ }
+ else
+ {
+ ProgramState& pps = _drawState->getProgramState(ri);
+
+ if (pps._layerUidUL >= 0)
+ {
+ osg::GLExtensions* ext = ri.getState()->get();
+ GLint uid = _layer ? (GLint)_layer->getUID() : (GLint)-1;
+ ext->glUniform1i(pps._layerUidUL, uid);
+ }
+
+ // Draw all tile commands that intersect their respective layer's extent;
+ // AND draw all tiles in the first layer regardless. This will prevent
+ // holes from appearing in the terrain skin because of the "first LOD"
+ // empty 1x1 tiles generated by the TerrainTileModelFactory.
+ for (auto& tile : _tiles)
+ {
+ if (tile._intersectsLayerExtent || _drawOrder == 0)
+ {
+ if (tile.apply(ri, _drawState.get()))
+ {
+ tile.draw(ri);
+ }
+ }
+ }
+ }
+
+ LayerDrawable::drawImplementation(ri);
+}
+
+//.........................................................
+
+LayerDrawableNVGL::LayerDrawableNVGL() :
+ LayerDrawable()
+{
+ setName("LayerDrawableNVGL");
+
+ // Make sure our structures are aligned to 16 bytes
+ OE_HARD_ASSERT(sizeof(GL4GlobalData) % 16 == 0);
+ OE_HARD_ASSERT(sizeof(GL4Tile) % 16 == 0);
+}
+LayerDrawableNVGL::~LayerDrawableNVGL()
+{
+ //nop
+}
+
+void
+LayerDrawableNVGL::accept(osg::NodeVisitor& nv)
+{
+ if (nv.getVisitorType() == nv.CULL_VISITOR)
+ {
+ refreshRenderState();
+ }
+
+ osg::Drawable::accept(nv);
+}
+
+void
+LayerDrawableNVGL::refreshRenderState()
+{
+ OE_PROFILING_ZONE;
+
+ // NOTE: LayerDrawable exists on a per-camera basis
+ // ...BUT in a multithreading mode, we will probably need
+ // to double-buffer some things. TODO.
+
+ for (int i = 0; i < _tiles.size(); ++i)
+ {
+ // We must draw all tiles in the first layer to prevent holes in the terrain.
+ // For subsequent layers, we only need to draw tiles that intersect their
+ // respective source layers' extents.
+ if (_drawOrder > 0 && !_tiles[i]._intersectsLayerExtent)
+ {
+ _tiles[i] = _tiles[_tiles.size() - 1];
+ _tiles.resize(_tiles.size() - 1); // don't worry, won't reallocate
+ --i;
+ }
+ }
+
+ if (_tiles != _rs.tiles)
+ {
+ // Next assemble the TileBuffer structures
+ if (_rs.tilebuf.size() < _tiles.size())
+ {
+ _rs.tilebuf.resize(_tiles.size());
+ }
+
+ TextureArena* textures = _data->textures;
+
+ unsigned tile_num = 0;
+
+ _rs.tiles.clear();
+
+ for (auto& tile : _tiles)
+ {
+ GL4Tile& buf = _rs.tilebuf[tile_num++];
+
+ // main MVM (double to float is OK)
+ for (int i = 0; i < 16; ++i)
+ buf.modelViewMatrix[i] = tile._modelViewMatrix.ptr()[i];
+
+ // Tile key encoding
+ for (int i = 0; i < 4; ++i)
+ buf.tileKey[i] = tile._keyValue[i];
+
+ // Color sampler and matrix:
+ buf.colorIndex = -1;
+ buf.parentIndex = -1;
+ if (tile._colorSamplers != nullptr)
+ {
+ const Sampler& color = (*tile._colorSamplers)[SamplerBinding::COLOR];
+ if (color._texture != nullptr)
+ {
+ buf.colorIndex = textures->add(color._texture);
+ COPY_MAT4F(color._matrix, buf.colorMat);
+ }
+
+ const Sampler& parent = (*tile._colorSamplers)[SamplerBinding::COLOR_PARENT];
+ if (parent._texture != nullptr)
+ {
+ buf.parentIndex = textures->add(parent._texture);
+ COPY_MAT4F(parent._matrix, buf.parentMat);
+ }
+ }
+
+ // Elevation sampler:
+ buf.elevIndex = -1;
+ if (tile._commonSamplers != nullptr /* && is elevation active */)
+ {
+ const Sampler& s = (*tile._commonSamplers)[SamplerBinding::ELEVATION];
+ if (s._texture)
+ {
+ s._texture->compress() = false;
+ s._texture->mipmap() = false;
+ s._texture->keepImage() = true; // never discard.. we use it elsewhere
+ buf.elevIndex = textures->add(s._texture);
+ COPY_MAT4F(s._matrix, buf.elevMat);
+ }
+ }
+
+ // Normal sampler:
+ buf.normalIndex = -1;
+ if (tile._commonSamplers != nullptr /* && is normalmapping active */)
+ {
+ const Sampler& s = (*tile._commonSamplers)[SamplerBinding::NORMAL];
+ if (s._texture)
+ {
+ s._texture->compress() = false;
+ s._texture->mipmap() = true;
+ s._texture->maxAnisotropy() = 1.0f;
+ buf.normalIndex = textures->add(s._texture);
+ COPY_MAT4F(s._matrix, buf.normalMat);
+ }
+ }
+
+ // LandCover sampler:
+ if (_data->options.getUseLandCover() == true)
+ {
+ buf.landcoverIndex = -1;
+ if (tile._commonSamplers != nullptr /* && is normalmapping active */)
+ {
+ const Sampler& s = (*tile._commonSamplers)[SamplerBinding::LANDCOVER];
+ if (s._texture)
+ {
+ s._texture->compress() = false;
+ s._texture->mipmap() = false;
+ s._texture->maxAnisotropy() = 1.0f;
+ buf.landcoverIndex = textures->add(s._texture);
+ COPY_MAT4F(s._matrix, buf.landcoverMat);
+ }
+ }
+ }
+
+ // Other common samplers.
+ if (tile._commonSamplers != nullptr)
+ {
+ for (unsigned i = SamplerBinding::SHARED; i < tile._commonSamplers->size(); ++i)
+ {
+ const Sampler& s = (*tile._commonSamplers)[i];
+ if (s._texture)
+ {
+ int k = i - SamplerBinding::SHARED;
+ if (k < MAX_NUM_SHARED_SAMPLERS)
+ {
+ s._texture->compress() = false;
+ s._texture->mipmap() = true;
+ //s._arena_texture->_maxAnisotropy = 4.0f;
+ buf.sharedIndex[k] = textures->add(s._texture);
+ COPY_MAT4F(s._matrix, buf.sharedMat[k]);
+ }
+ else
+ {
+ OE_WARN << LC << "Exceeded number of shared samplers" << std::endl;
+ }
+ }
+ }
+ }
+
+ // stuck the layer order here (for now...later, hide it elsewhere)
+ buf.drawOrder = _surfaceDrawOrder;
+ }
+
+ _rs.tiles.swap(_tiles); // hopefully uses std::move
+
+ _tiles.clear();
+
+ // This will trigger a GPU upload on the next draw
+ _rs.dirty = true;
+ }
+}
+
+LayerDrawableNVGL::RenderState::RenderState()
+{
+ globjects.resize(64);
+}
+
+void
+LayerDrawableNVGL::drawImplementation(osg::RenderInfo& ri) const
+{
+ // work around the GDP makOsg hack (ViewerBase.cpp:894)
+ //ri.getState()->getGraphicsContext()->makeCurrent();
+
+ // Research on glMultiDrawElementsIndirectBindlessNV:
+ // https://github.com/ychding11/HelloWorld/wiki/Modern-GPU-Driven-Rendering--%28How-to-draw-fast%29
+ // https://on-demand.gputechconf.com/siggraph/2014/presentation/SG4117-OpenGL-Scene-Rendering-Techniques.pdf
+ // https://developer.download.nvidia.com/opengl/tutorials/bindless_graphics.pdf
+
+ osg::State& state = *ri.getState();
+
+ GLObjects& gl = GLObjects::get(_rs.globjects, state);
+
+ if (_rs.tiles.empty())
+ return;
+
+ bool renderTerrainSurface = (_patchLayer == nullptr);
+
+ if (gl.tiles == nullptr || !gl.tiles->valid())
+ {
+ auto cid = GLUtils::getSharedContextID(state);
+ gl.ext = osg::GLExtensions::Get(cid, true);
+
+ gl.tiles = GLBuffer::create(GL_SHADER_STORAGE_BUFFER, state);
+ gl.tiles->bind();
+ gl.tiles->debugLabel("Terrain geometry", "Tiles SSBO");
+
+ // preallocate space for a bunch of tiles (just for fun)
+ gl.tiles->bufferData(
+ 512 * sizeof(GL4Tile),
+ nullptr,
+ GL_DYNAMIC_DRAW);
+
+ gl.tiles->unbind();
+ }
+
+ if (renderTerrainSurface)
+ {
+ if (gl.commands == nullptr || !gl.commands->valid())
+ {
+ gl.commands = GLBuffer::create(GL_DRAW_INDIRECT_BUFFER, state);
+ gl.commands->bind();
+ gl.commands->debugLabel("Terrain geometry", "GL_DRAW_INDIRECT_BUFFER");
+ // preallocate space for a bunch of draw commands (just for fun)
+ gl.commands->bufferData(
+ 512 * sizeof(DrawElementsIndirectBindlessCommandNV),
+ nullptr,
+ GL_DYNAMIC_DRAW);
+ gl.commands->unbind();
+
+ osg::setGLExtensionFuncPtr(gl.glMultiDrawElementsIndirectBindlessNV, "glMultiDrawElementsIndirectBindlessNV");
+ OE_HARD_ASSERT(gl.glMultiDrawElementsIndirectBindlessNV != nullptr);
+
+ // OSG bug: glVertexAttribFormat is mapped to the wrong function :( so
+ // we have to look it up fresh.
+ osg::setGLExtensionFuncPtr(gl.glVertexAttribFormat, "glVertexAttribFormat");
+ OE_HARD_ASSERT(gl.glVertexAttribFormat != nullptr);
+
+ // Needed for core profile
+ void(GL_APIENTRY * glEnableClientState_)(GLenum);
+ osg::setGLExtensionFuncPtr(glEnableClientState_, "glEnableClientState");
+ OE_HARD_ASSERT(glEnableClientState_ != nullptr);
+
+ // Set up a VAO that we'll use to render with bindless NV.
+ gl.vao = GLVAO::create(state);
+
+ // Start recording
+ gl.vao->bind();
+
+ // after the bind, please
+ gl.vao->debugLabel("Terrain geometry", "VAO");
+
+ // set up the VAO for NVIDIA bindless buffers
+ glEnableClientState_(GL_VERTEX_ATTRIB_ARRAY_UNIFIED_NV);
+ glEnableClientState_(GL_ELEMENT_ARRAY_UNIFIED_NV);
+
+ // Record the format for each of the attributes in GL4Vertex
+ const GLuint offsets[5] = {
+ offsetof(GL4Vertex, position),
+ offsetof(GL4Vertex, normal),
+ offsetof(GL4Vertex, uv),
+ offsetof(GL4Vertex, neighborPosition),
+ offsetof(GL4Vertex, neighborNormal)
+ };
+
+ for (unsigned location = 0; location < 5; ++location)
+ {
+ gl.glVertexAttribFormat(location, 3, GL_FLOAT, GL_FALSE, offsets[location]);
+ gl.ext->glVertexAttribBinding(location, 0);
+ gl.ext->glEnableVertexAttribArray(location);
+ gl.ext->glBindVertexBuffer(location, 0, offsets[location], sizeof(GL4Vertex));
+ }
+
+ // Finish recording
+ gl.vao->unbind();
+ }
+
+ if (gl.shared == nullptr || !gl.shared->valid())
+ {
+ GL4GlobalData buf;
+
+ // Encode morphing constants, one per LOD
+ const SelectionInfo& info = _data->selectionInfo;
+ for (unsigned lod = 0; lod < info.getNumLODs(); ++lod)
+ {
+ float end = info.getLOD(lod)._morphEnd;
+ float start = info.getLOD(lod)._morphStart;
+ float one_over_end_minus_start = 1.0f / (end - start);
+ buf.morphConstants[(2 * lod) + 0] = end * one_over_end_minus_start;
+ buf.morphConstants[(2 * lod) + 1] = one_over_end_minus_start;
+ }
+
+ gl.shared = GLBuffer::create(GL_SHADER_STORAGE_BUFFER, state);
+
+ gl.shared->bind();
+ gl.shared->debugLabel("Terrain geometry", "Global data SSBO");
+ gl.shared->bufferStorage(sizeof(GL4GlobalData), &buf, 0); // permanent
+ gl.shared->unbind();
+ }
+ }
+
+ if (_rs.dirty)
+ {
+ // The CULL traversal determined that the tile set changed,
+ // so we need to re-upload the tile buffer and we need to
+ // rebuild the command list.
+
+ _rs.dirty = false;
+
+ // TODO: implement double/triple buffering so OSG multi-threading modes
+ // will not overlap and corrupt the buffers
+
+ // Update the tile data buffer:
+ gl.tiles->uploadData(
+ _rs.tiles.size() * sizeof(GL4Tile),
+ _rs.tilebuf.data());
+
+ if (renderTerrainSurface)
+ {
+ // Reconstruct and upload the command list:
+ _rs.commands.clear();
+ for (auto& tile : _rs.tiles)
+ {
+ TileGeometry* geom = tile._geom.get();
+ auto& command = geom->getOrCreateNVGLCommand(state);
+ _rs.commands.push_back(command);
+ }
+
+ gl.commands->uploadData(_rs.commands);
+
+ OE_SOFT_ASSERT(_rs.commands.size() == _rs.tiles.size());
+ }
+ }
+
+ // Apply the the texture arena:
+ //if (state.getLastAppliedAttribute(OE_TEXTURE_ARENA_SA_TYPE_ID) != _context->textures())
+ {
+ _data->textures->apply(state);
+ state.haveAppliedAttribute(_data->textures);
+ }
+
+ // Bind the tiles data to its layout(binding=X) in the shader.
+ gl.tiles->bindBufferBase(31);
+
+ if (renderTerrainSurface)
+ {
+ // Bind the command buffer for rendering.
+ gl.commands->bind();
+
+ // Bind the shared data to its layout(binding=X) in the shader.
+ gl.shared->bindBufferBase(30);
+
+ gl.vao->bind();
+
+ GLenum primitive_type =
+ _data->options.getGPUTessellation() == true ?
+ GL_PATCHES : GL_TRIANGLES;
+
+ GLenum element_type = GL_UNSIGNED_INT;
+ //sizeof(DrawElementsBase::value_type) == sizeof(short) ?
+ //GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;
+
+ gl.glMultiDrawElementsIndirectBindlessNV(
+ primitive_type,
+ element_type,
+ nullptr,
+ _rs.commands.size(),
+ sizeof(DrawElementsIndirectBindlessCommandNV),
+ 1);
+
+ gl.vao->unbind();
+
+ gl.commands->unbind();
+
+#ifdef RESET_BUFFER_BASE_BINDINGS
+ gl.ext->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 30, 0); // unbind shared data
+#endif
+ }
+
+ else if (_patchLayer && _patchLayer->getRenderer())
+ {
+ // If it's a patch layer, the layer does its own rendering
+ // TODO: pass along the fact that we're using GL4 so that
+ // the patch layer doesn't actually APPLY each DrawTileCommand!
+ TileBatch batch(_drawState.get());
+ batch._tiles.reserve(_rs.tiles.size());
+ for (auto& tile : _rs.tiles)
+ {
+ batch._tiles.push_back(&tile);
+ }
+
+ _patchLayer->getRenderer()->draw(ri, batch);
+ }
+
+#ifdef RESET_BUFFER_BASE_BINDINGS
+ gl.ext->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 31, 0); // tiles data
+ gl.ext->glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 29, 0); // texture arena
+#endif
+
+ LayerDrawable::drawImplementation(ri);
+}
+
+void
+LayerDrawableNVGL::releaseGLObjects(osg::State* state) const
+{
+ if (state)
+ {
+ GLObjects& gl = GLObjects::get(_rs.globjects, *state);
+ gl.shared = nullptr;
+ gl.tiles = nullptr;
+ gl.commands = nullptr;
+ gl.vao = nullptr;
+ }
+ else
+ {
+ _rs.globjects.setAllElementsTo(GLObjects());
+ }
+
+ _rs.dirty = true;
+
+ LayerDrawable::releaseGLObjects(state);
+}
+
+void
+LayerDrawableNVGL::resizeGLObjectBuffers(unsigned size)
+{
+ LayerDrawable::resizeGLObjectBuffers(size);
+}
diff --git a/src/osgEarthDrivers/engine_corey/RenderBindings b/src/osgEarthDrivers/engine_corey/RenderBindings
new file mode 100644
index 0000000000..58b44ce92b
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/RenderBindings
@@ -0,0 +1,71 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#pragma once
+
+#include "Common"
+#include
+#include
+#include
+#include
+
+namespace osgEarth
+{
+ namespace Corey
+ {
+ using namespace osgEarth;
+ using namespace osgEarth::Util;
+
+ /**
+ * Defines the usage information for a single texture sampler.
+ */
+ class SamplerBinding
+ {
+ public:
+ enum Usage
+ {
+ COLOR = 0,
+ COLOR_PARENT = 1,
+ ELEVATION = 2,
+ NORMAL = 3,
+ LANDCOVER = 4,
+ SHARED = 5 // non-core shared layers start at this index
+ };
+
+ int unit = -1;
+ optional sourceUID;
+ optional usage;
+ std::string samplerName;
+ std::string matrixName;
+ osg::ref_ptr defaultTexture;
+
+ inline bool isActive() const {
+ return unit >= 0 || usage.isSet() || sourceUID.isSet();
+ }
+ };
+
+ /**
+ * Array of render bindings. This array is always indexed by the
+ * SamplerBinding::USAGE itself. So for example, RenderBindings[0] is always
+ * the COLOR usage, [2] is the ELEVATION usage, etc. Shared layer bindings
+ * (i.e., custom samplers) start at index SHARED and go up from there.
+ */
+ using RenderBindings = AutoArray;
+
+ }
+}
diff --git a/src/osgEarthDrivers/engine_corey/SelectionInfo b/src/osgEarthDrivers/engine_corey/SelectionInfo
new file mode 100644
index 0000000000..7c3ea8e6e7
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/SelectionInfo
@@ -0,0 +1,81 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#ifndef OSGEARTH_DRIVERS_Corey_TERRAIN_ENGINE_SELECTION_INFO
+#define OSGEARTH_DRIVERS_Corey_TERRAIN_ENGINE_SELECTION_INFO 1
+
+#include "Common"
+#include
+#include
+#include
+#include
+
+
+namespace osgEarth { namespace Corey
+{
+ /**
+ * SelectionInfo is a data structure that holds the LOD distance switching
+ * information for the terrain, to support paging and LOD morphing.
+ * This is calculated once when the terrain is first created.
+ */
+ class SelectionInfo
+ {
+ public:
+ struct LOD
+ {
+ double _visibilityRange;
+ double _morphStart;
+ double _morphEnd;
+ unsigned _minValidTY, _maxValidTY;
+ };
+
+ public:
+ SelectionInfo() : _firstLOD(0) { }
+
+ //! Initialize the selection into LODs
+ void initialize(unsigned firstLod, unsigned maxLod, const Profile* profile, double mtrf, bool restrictPolarSubdivision);
+
+ //! Number of LODs
+ unsigned getNumLODs(void) const { return _lods.size(); }
+
+ //! Visibility and morphing information for a specific LOD
+ const LOD& getLOD(unsigned lod) const;
+
+ //! Fetch the effective visibility range and morphing range for a key
+ void get(const TileKey& key, float& out_range, float& out_startMorphRange, float& out_endMorphRange) const;
+
+ //! Get just the visibility range for a TileKey.
+ //! inlined for speed (SL measured)
+ inline float getRange(const TileKey& key) const {
+ const LOD& lod = _lods[key.getLOD()];
+ if (key.getTileY() >= lod._minValidTY && key.getTileY() <= lod._maxValidTY)
+ {
+ return lod._visibilityRange;
+ }
+ return 0.0f;
+ }
+
+ private:
+ std::vector _lods;
+ unsigned _firstLOD;
+ static const double _morphStartRatio;
+ };
+
+} } // namespace
+
+#endif
diff --git a/src/osgEarthDrivers/engine_corey/SelectionInfo.cpp b/src/osgEarthDrivers/engine_corey/SelectionInfo.cpp
new file mode 100644
index 0000000000..fcd431a7b5
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/SelectionInfo.cpp
@@ -0,0 +1,144 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#include "SelectionInfo"
+#include
+
+using namespace osgEarth::Corey;
+using namespace osgEarth;
+
+#define LC "[SelectionInfo] "
+
+const double SelectionInfo::_morphStartRatio = 0.66;
+
+const SelectionInfo::LOD&
+SelectionInfo::getLOD(unsigned lod) const
+{
+ static SelectionInfo::LOD s_dummy;
+
+ if (lod-_firstLOD >= _lods.size())
+ {
+ // note, this can happen if firstLOD() is set
+ OE_DEBUG << LC <<"Index out of bounds"<getSRS() != nullptr && profile->getSRS()->valid(), void());
+
+ if (getNumLODs() > 0)
+ {
+ OE_INFO << LC <<"Error: Selection Information already initialized"< maxLod)
+ {
+ OE_INFO << LC <<"Error: Inconsistent First and Max LODs"<getNumTiles(lod, tx, ty);
+ TileKey key(lod, tx/2, ty/2, profile);
+ GeoExtent e = key.getExtent();
+ GeoCircle c = e.computeBoundingGeoCircle();
+ double range = c.getRadius() * mtrf * 2.0 * (1.0/1.405);
+ _lods[lod]._visibilityRange = range;
+ _lods[lod]._minValidTY = 0;
+ _lods[lod]._maxValidTY = 0xFFFFFFFF;
+ }
+
+ double metersPerEquatorialDegree = (profile->getSRS()->getEllipsoid().getRadiusEquator() * 2.0 * osg::PI) / 360.0;
+
+ double prevPos = 0.0;
+
+ for (int lod=(int)(numLods-1); lod>=0; --lod)
+ {
+ double span = _lods[lod]._visibilityRange - prevPos;
+
+ _lods[lod]._morphEnd = _lods[lod]._visibilityRange;
+ _lods[lod]._morphStart = prevPos + span*_morphStartRatio;
+ //prevPos = _lods[i]._morphStart; // original value
+ prevPos = _lods[lod]._morphEnd;
+
+ // Calc the maximum valid TY (to avoid over-subdivision at the poles)
+ // In a geographic map, this will effectively limit the maximum LOD
+ // progressively starting at about +/- 72 degrees latitude.
+ int startLOD = 6;
+ if (restrictPolarSubdivision && lod >= startLOD && profile->getSRS()->isGeographic())
+ {
+ const double startAR = 0.1; // minimum allowable aspect ratio at startLOD
+ const double endAR = 0.4; // minimum allowable aspect ratio at maxLOD
+ double lodT = (double)(lod-startLOD)/(double)(numLods-1);
+ double minAR = startAR + (endAR-startAR)*lodT;
+
+ unsigned tx, ty;
+ profile->getNumTiles(lod, tx, ty);
+ for(int y=(int)ty/2; y>=0; --y)
+ {
+ TileKey k(lod, 0, y, profile);
+ const GeoExtent& e = k.getExtent();
+ double width_m = e.width(Units::METERS);
+ double height_m = e.height(Units::METERS);
+ if (width_m/height_m < minAR)
+ {
+ _lods[lod]._minValidTY = std::min(y+1, (int)(ty-1));
+ _lods[lod]._maxValidTY = (ty-1)-_lods[lod]._minValidTY;
+ break;
+ }
+ }
+ }
+ }
+}
+
+void
+SelectionInfo::get(const TileKey& key,
+ float& out_range,
+ float& out_startMorphRange,
+ float& out_endMorphRange) const
+{
+ out_range = 0.0f;
+ out_startMorphRange = 0.0f;
+ out_endMorphRange = 0.0f;
+
+ if (key.getLOD() < _lods.size())
+ {
+ const LOD& lod = _lods[key.getLOD()];
+
+ if (key.getTileY() >= lod._minValidTY && key.getTileY() <= lod._maxValidTY)
+ {
+ out_range = lod._visibilityRange;
+ out_startMorphRange = lod._morphStart;
+ out_endMorphRange = lod._morphEnd;
+ }
+ }
+}
diff --git a/src/osgEarthDrivers/engine_corey/Shaders b/src/osgEarthDrivers/engine_corey/Shaders
new file mode 100644
index 0000000000..d379025f6e
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Shaders
@@ -0,0 +1,94 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2012 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#include
+
+namespace osgEarth { namespace Corey
+{
+ struct CoreyShaders : public osgEarth::Util::ShaderPackage
+ {
+ virtual std::string vert() = 0;
+ virtual std::string elevation() = 0;
+ virtual std::string geom() = 0;
+ virtual std::string tessellation() = 0;
+ virtual std::string normal_map() = 0;
+ virtual std::string morphing() = 0;
+ virtual std::string imagelayer() = 0;
+ virtual std::string sdk() = 0;
+ virtual std::string types() = 0;
+ };
+
+ struct ShadersGL3 : public CoreyShaders
+ {
+ ShadersGL3();
+
+ std::string
+ ENGINE_VERT,
+ ENGINE_ELEVATION,
+ ENGINE_GEOM,
+ ENGINE_TESSELLATION,
+ ENGINE_NORMAL_MAP,
+ ENGINE_MORPHING,
+ ENGINE_IMAGELAYER,
+ ENGINE_SDK;
+
+ virtual std::string vert() { return ENGINE_VERT; }
+ virtual std::string elevation() { return ENGINE_ELEVATION; }
+ virtual std::string geom() { return ENGINE_GEOM; }
+ virtual std::string tessellation() { return ENGINE_TESSELLATION; }
+ virtual std::string normal_map() { return ENGINE_NORMAL_MAP; }
+ virtual std::string morphing() { return ENGINE_MORPHING; }
+ virtual std::string imagelayer() { return ENGINE_IMAGELAYER; }
+ virtual std::string sdk() { return ENGINE_SDK; }
+ virtual std::string types() { return ""; }
+ };
+
+ struct ShadersGL4 : public CoreyShaders
+ {
+ ShadersGL4();
+
+ std::string
+ ENGINE_VERT,
+ ENGINE_ELEVATION,
+ ENGINE_GEOM,
+ ENGINE_TESSELLATION,
+ ENGINE_NORMAL_MAP,
+ ENGINE_IMAGELAYER,
+ ENGINE_SDK,
+ ENGINE_TYPES;
+
+ virtual std::string vert() { return ENGINE_VERT; }
+ virtual std::string elevation() { return ENGINE_ELEVATION; }
+ virtual std::string geom() { return ENGINE_GEOM; }
+ virtual std::string tessellation() { return ENGINE_TESSELLATION; }
+ virtual std::string normal_map() { return ENGINE_NORMAL_MAP; }
+ virtual std::string morphing() { return ""; }
+ virtual std::string imagelayer() { return ENGINE_IMAGELAYER; }
+ virtual std::string sdk() { return ENGINE_SDK; }
+ virtual std::string types() { return ENGINE_TYPES; }
+ };
+
+ struct CoreyShadersFactory
+ {
+ static ShadersGL3 s_gl3;
+ static ShadersGL4 s_gl4;
+ static CoreyShaders& get(bool use_gl4) { return use_gl4 ? (CoreyShaders&)s_gl4 : (CoreyShaders&)s_gl3; }
+ };
+
+} } // namespace osgEarth::Corey
diff --git a/src/osgEarthDrivers/engine_corey/Shaders.cpp.in b/src/osgEarthDrivers/engine_corey/Shaders.cpp.in
new file mode 100644
index 0000000000..754b1a5b35
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/Shaders.cpp.in
@@ -0,0 +1,59 @@
+// ***DO NOT EDIT THIS FILE - IT IS AUTOMATICALLY GENERATED BY CMAKE***
+
+#include
+
+using namespace osgEarth::Corey;
+
+ShadersGL3::ShadersGL3()
+{
+ ENGINE_VERT = "Corey.vert.glsl";
+ _sources[ENGINE_VERT] = @Corey.vert.glsl@;
+
+ ENGINE_ELEVATION = "Corey.elevation.glsl";
+ _sources[ENGINE_ELEVATION] = @Corey.elevation.glsl@;
+
+ ENGINE_MORPHING = "Corey.Morphing.glsl";
+ _sources[ENGINE_MORPHING] = @Corey.Morphing.glsl@;
+
+ ENGINE_IMAGELAYER = "Corey.ImageLayer.glsl";
+ _sources[ENGINE_IMAGELAYER] = @Corey.ImageLayer.glsl@;
+
+ ENGINE_NORMAL_MAP = "Corey.NormalMap.glsl";
+ _sources[ENGINE_NORMAL_MAP] = @Corey.NormalMap.glsl@;
+
+ ENGINE_GEOM = "Corey.gs.glsl";
+ _sources[ENGINE_GEOM] = @Corey.gs.glsl@;
+
+ ENGINE_TESSELLATION = "Corey.Tessellation.glsl";
+ _sources[ENGINE_TESSELLATION] = @Corey.Tessellation.glsl@;
+
+ ENGINE_SDK = "Corey.SDK.glsl";
+ _sources[ENGINE_SDK] = @Corey.SDK.glsl@;
+}
+
+ShadersGL4::ShadersGL4()
+{
+ ENGINE_VERT = "Corey.vert.GL4.glsl";
+ _sources[ENGINE_VERT] = @Corey.vert.GL4.glsl@;
+
+ ENGINE_ELEVATION = "Corey.elevation.glsl";
+ _sources[ENGINE_ELEVATION] = @Corey.elevation.glsl@;
+
+ ENGINE_IMAGELAYER = "Corey.ImageLayer.GL4.glsl";
+ _sources[ENGINE_IMAGELAYER] = @Corey.ImageLayer.GL4.glsl@;
+
+ ENGINE_NORMAL_MAP = "Corey.NormalMap.GL4.glsl";
+ _sources[ENGINE_NORMAL_MAP] = @Corey.NormalMap.GL4.glsl@;
+
+ ENGINE_TESSELLATION = "Corey.Tessellation.GL4.glsl";
+ _sources[ENGINE_TESSELLATION] = @Corey.Tessellation.GL4.glsl@;
+
+ ENGINE_SDK = "Corey.SDK.GL4.glsl";
+ _sources[ENGINE_SDK] = @Corey.SDK.GL4.glsl@;
+
+ ENGINE_TYPES = "Corey.GL4.glsl";
+ _sources[ENGINE_TYPES] = @Corey.GL4.glsl@;
+}
+
+ShadersGL3 CoreyShadersFactory::s_gl3;
+ShadersGL4 CoreyShadersFactory::s_gl4;
\ No newline at end of file
diff --git a/src/osgEarthDrivers/engine_corey/TerrainCuller b/src/osgEarthDrivers/engine_corey/TerrainCuller
new file mode 100644
index 0000000000..cac51af01f
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/TerrainCuller
@@ -0,0 +1,102 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#pragma once
+
+#include "TerrainRenderData"
+#include
+
+using namespace osgEarth;
+
+namespace osgEarth
+{
+ class Layer;
+ class PatchLayer;
+
+ namespace Corey
+ {
+ class TileNode;
+ struct EngineData;
+
+ struct LayerExtent
+ {
+ LayerExtent() : _revision(-1) { }
+ osg::observer_ptr _layer;
+ Revision _revision;
+ GeoExtent _extent;
+ };
+ using LayerExtentMap = std::unordered_map;
+
+
+ struct CullData
+ {
+ int _mapRevision = 0;
+ EngineData* _data;
+ std::stack _tileNodeStack;
+ DrawTileCommand* _firstDrawCommandForTile;
+ unsigned _orphanedPassesDetected;
+ LayerExtentMap* _layerExtents;
+ std::vector _patchLayers;
+ double _lastTimeVisited = 0.0;
+ bool _acceptSurfaceNodes = true;
+ TerrainRenderData _renderData;
+ bool _isSpy = false;
+ std::list _staleTiles;
+
+ void reset(
+ osgUtil::CullVisitor* cv,
+ TerrainRenderData::PersistentData& pd,
+ EngineData& data);
+
+ DrawTileCommand* addDrawCommand(
+ UID sourceUID,
+ const TileRenderModel* model,
+ const RenderingPass* pass,
+ TileNode* node,
+ osgUtil::CullVisitor* cv);
+ };
+
+ /**
+ * Custom culling code for a TileNode that primarily handles patch layers.
+ */
+ class TileNodeCuller : public osg::Callback
+ {
+ public:
+ EngineData* _data;
+ TileNodeCuller(EngineData& data) : _data(&data) { }
+
+ // osg::Callback
+ bool run(osg::Object* object, osg::Object* data) override;
+ };
+
+ /**
+ * Custom culling code a the tile drawable that handles shared geometries,
+ * draw commands, and NVGL support
+ */
+ struct TileDrawableCuller : public osg::DrawableCullCallback
+ {
+ public:
+ EngineData* _data;
+ TileDrawableCuller(EngineData& data) : _data(&data) { }
+
+ // osg::Callback
+ bool cull(osg::NodeVisitor* nv, osg::Drawable* drawable, osg::RenderInfo* renderInfo) const override;
+ };
+
+ }
+} // namespace
diff --git a/src/osgEarthDrivers/engine_corey/TerrainCuller.cpp b/src/osgEarthDrivers/engine_corey/TerrainCuller.cpp
new file mode 100644
index 0000000000..1738ab6454
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/TerrainCuller.cpp
@@ -0,0 +1,338 @@
+
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#include "TerrainCuller"
+#include "TileNode"
+#include "SelectionInfo"
+#include "EngineData"
+
+#include
+#include
+#include
+
+#define LC "[TerrainCuller] "
+
+using namespace osgEarth::Corey;
+
+
+namespace
+{
+ // hack to get access to createOrReuseMatrix
+ struct CullVisitorEx : public osgUtil::CullVisitor {
+ inline osg::RefMatrix* createOrReuseMatrixEx(const osg::Matrix& value) {
+ return createOrReuseMatrix(value);
+ }
+ };
+}
+
+
+void
+CullData::reset(osgUtil::CullVisitor* cv, TerrainRenderData::PersistentData& pd, EngineData& data)
+{
+ auto& map = data.map;
+ _mapRevision = map->getDataModelRevision();
+ _data = &data;
+ _orphanedPassesDetected = 0u;
+ _layerExtents = &data.cachedLayerExtents;
+ //bool temp;
+ //_isSpy = _cv->getUserValue("osgEarth.Spy", temp);
+ _patchLayers.clear();
+ _lastTimeVisited = osg::Timer::instance()->tick();
+ _staleTiles.clear();
+
+ // skip surface nodes is this is a shadow camera and shadowing is disabled.
+ _acceptSurfaceNodes =
+ CameraUtils::isShadowCamera(cv->getCurrentCamera()) == false ||
+ data.options.getCastShadows() == true;
+
+ unsigned frameNum = cv->getFrameStamp() ? cv->getFrameStamp()->getFrameNumber() : 0u;
+
+ _renderData.reset(frameNum, pd, cv, data);
+}
+
+DrawTileCommand*
+CullData::addDrawCommand(UID uid, const TileRenderModel* model, const RenderingPass* pass, TileNode* tilenode, osgUtil::CullVisitor* cv)
+{
+ if (tilenode->getBound().valid() == false)
+ return nullptr;
+
+ const RenderBindings& bindings = _data->renderBindings;
+
+ // add a new Draw command to the appropriate layer
+ LayerDrawable* drawable = _renderData.layer(uid);
+ if (drawable)
+ {
+ // Layer marked for drawing?
+ if (drawable->_draw)
+ {
+ drawable->_tiles.emplace_back();
+ DrawTileCommand& tile = drawable->_tiles.back();
+ tile._intersectsLayerExtent = true;
+
+ // If the tile is outside the layer extent, we MAY or MAY NOT need to
+ // actually render it. Mark it as such.
+ if (drawable->_layer)
+ {
+ const LayerExtent& le = (*_layerExtents)[drawable->_layer->getUID()];
+ if (le._extent.isValid() && !le._extent.intersects(tilenode->getKey().getExtent(), false))
+ {
+ tile._intersectsLayerExtent = false;
+ }
+ }
+
+ // install everything we need in the Draw Command:
+ tile._colorSamplers = pass ? &(pass->samplers()) : nullptr;
+ tile._commonSamplers = &model->_commonSamplers;
+ tile._localToWorld = tilenode->_xform->getMatrix();
+ tile._modelViewMatrix = *cv->getModelViewMatrix();
+ tile._keyValue = tilenode->_tileKeyValue;
+ tile._geom = tilenode->_drawable;
+ tile._morphConstants = tilenode->_morphConstants;
+ tile._key = &tilenode->getKey();
+ tile._tileRevision = tilenode->_fullDataModel->mapRevision;
+
+ // note: _xform->getInverseMatrix() is cached internally
+ osg::Vec3 center_local = tilenode->_xform->getBound().center() * tilenode->_xform->getInverseMatrix();
+ tile._range = cv->getDistanceToViewPoint(center_local, true);
+
+ tile._layerOrder = drawable->_drawOrder;
+
+ // assign the draw sequence:
+ tile._sequence = drawable->_tiles.size();
+
+ return &drawable->_tiles.back();
+ }
+ }
+ else if (pass)
+ {
+ // The pass exists but it's layer is not in the render data draw list.
+ // This means that the layer is no longer in the map. Detect and record
+ // this information so we can run a cleanup visitor later on.
+ ++_orphanedPassesDetected;
+ }
+ else
+ {
+ OE_WARN << "Added nothing for a UID -1 draw command" << std::endl;
+ }
+
+ return nullptr;
+}
+
+
+bool
+TileNodeCuller::run(osg::Object* object, osg::Object* visitor)
+{
+ auto* tilenode = static_cast(object);
+ auto* cv = dynamic_cast(visitor);
+ auto& cullData = _data->cullData;
+
+ if (cv->isCulled(tilenode->getBound()))
+ return false;
+
+ // handle patch layers:
+ if (cullData._renderData.patchLayers().size() > 0)
+ {
+ const RenderBindings& bindings = cullData._data->renderBindings;
+ TileRenderModel& renderModel = tilenode->_renderModel;
+
+ // Render patch layers if applicable.
+ cullData._patchLayers.clear();
+
+ osg::BoundingBox buffer(0, 0, 0, 0, 0, 0);
+
+ for (auto& patchLayer : cullData._renderData.patchLayers())
+ {
+ // is the layer accepting this key?
+ if (patchLayer->getAcceptCallback() != nullptr &&
+ !patchLayer->getAcceptCallback()->acceptKey(tilenode->getKey()))
+ {
+ continue;
+ }
+
+ // is the tile in visible range?
+ float range = cv->getDistanceToViewPoint(tilenode->getBound().center(), true) - tilenode->getBound().radius();
+ if (patchLayer->getMaxVisibleRange() < range)
+ {
+ continue;
+ }
+
+ buffer.expandBy(patchLayer->getBuffer());
+
+ cullData._patchLayers.push_back(patchLayer.get());
+ }
+
+ if (cullData._patchLayers.size() > 0)
+ {
+ // push the surface matrix:
+ auto cs = static_cast(cv);
+ osg::ref_ptr matrix = cs->createOrReuseMatrixEx(*cv->getModelViewMatrix());
+ tilenode->_xform->computeLocalToWorldMatrix(*matrix.get(), cv);
+ cv->pushModelViewMatrix(matrix.get(), tilenode->_xform->getReferenceFrame());
+
+ // adjust the tile bounding box to account for the patch layer buffer.
+ auto bbox = tilenode->_drawable->getBoundingBox();
+ bbox._min += buffer._min, bbox._max += buffer._max;
+
+ if (!cv->isCulled(bbox))
+ {
+ float range, morphStart, morphEnd;
+ _data->selectionInfo.get(tilenode->getKey(), range, morphStart, morphEnd);
+
+ // Add the draw commands:
+ for (auto patchLayer : cullData._patchLayers)
+ {
+ DrawTileCommand* cmd = cullData.addDrawCommand(patchLayer->getUID(), &renderModel, nullptr, tilenode, cv);
+ if (cmd)
+ {
+ cmd->_drawPatch = true;
+ cmd->_drawCallback = patchLayer->getRenderer();
+ cmd->_morphStartRange = morphStart;
+ cmd->_morphEndRange = morphEnd;
+ }
+ }
+ }
+
+ cv->popModelViewMatrix();
+ }
+ }
+
+ // check for a stale data model and request an update if necessary.
+ if (tilenode->_dirty)
+ //tilenode->_fullDataModel->revision != cullData._mapRevision)
+ {
+ // submit an update request.
+ cullData._staleTiles.emplace_back(tilenode->_key);
+ tilenode->_dirty = false;
+ }
+
+ // store this tilenode and traverse the children.
+ cullData._tileNodeStack.push(tilenode);
+ bool ok = traverse(object, visitor);
+ cullData._tileNodeStack.pop();
+
+ return ok;
+}
+
+
+bool
+TileDrawableCuller::cull(osg::NodeVisitor* visitor, osg::Drawable* node, osg::RenderInfo* renderInfo) const
+{
+ auto& cullData = _data->cullData;
+
+ // skip surface nodes if this is a shadow camera and self-shadowing is disabled;
+ // this will only render patch layers in the shader camera (see TileNode)
+ if (!cullData._acceptSurfaceNodes)
+ return true;
+
+ auto* cv = dynamic_cast(visitor);
+ auto& drawable = *static_cast(node);
+
+ auto& bbox = drawable.getBoundingBox();
+ if (drawable.isCullingActive() && cv->isCulled(bbox))
+ return true;
+
+ float center_range = cv->getDistanceToViewPoint(drawable.getBound().center(), true);
+ float node_radius = drawable.getBound().radius();
+ float near_range = center_range - node_radius;
+ float far_range = center_range + node_radius;
+
+#if 0
+ if (!_isSpy)
+ {
+ node.setLastFramePassedCull(_context->getClock()->getFrame());
+ }
+#endif
+
+ // find the drawable's parent tilenode:
+ TileNode& tilenode = *cullData._tileNodeStack.top();
+
+ TileRenderModel& renderModel = tilenode._renderModel;
+
+ int order = 0;
+ unsigned count = 0;
+ DrawTileCommand* first_cmd = nullptr;
+ auto lod = tilenode.getKey().getLOD();
+
+ // First go through any legit rendering pass data in the Tile and
+ // and add a DrawCommand for each.
+ for (unsigned p = 0; p < renderModel._passes.size(); ++p)
+ {
+ const RenderingPass& pass = renderModel._passes[p];
+
+ if (pass.tileLayer())
+ {
+ if (pass.tileLayer()->getVisible() == false ||
+ pass.tileLayer()->getMaxLevel() < lod ||
+ pass.tileLayer()->getMinLevel() > lod ||
+ pass.tileLayer()->getMaxVisibleRange() < near_range ||
+ pass.tileLayer()->getMinVisibleRange() > far_range)
+ {
+ continue;
+ }
+ }
+ else if (pass.visibleLayer())
+ {
+ if (pass.visibleLayer()->getVisible() == false ||
+ pass.visibleLayer()->getMaxVisibleRange() < near_range ||
+ pass.visibleLayer()->getMinVisibleRange() > far_range)
+ {
+ continue;
+ }
+ }
+
+ DrawTileCommand* cmd = cullData.addDrawCommand(pass.sourceUID(), &renderModel, &pass, &tilenode, cv);
+
+ if (cmd)
+ {
+ if (first_cmd == nullptr)
+ {
+ first_cmd = cmd;
+ }
+ else if (cmd->_layerOrder < first_cmd->_layerOrder)
+ {
+ first_cmd = cmd;
+ }
+ }
+ }
+
+ // If the culler added no draw commands for this tile... we still need
+ // to draw something or else there will be a hole! So draw a blank tile.
+ // UID = -1 is the special UID code for a blank.
+ if (first_cmd == nullptr)
+ {
+ DrawTileCommand* cmd = cullData.addDrawCommand(-1, &renderModel, nullptr, &tilenode, cv);
+ if (cmd)
+ {
+ first_cmd = cmd;
+ }
+ }
+
+ // Set the layer order of the first draw command for this tile to zero,
+ // to support proper terrain blending.
+ if (first_cmd)
+ {
+ first_cmd->_layerOrder = 0;
+ }
+
+ // update our bounds (using the tilenode's world-space bounds)
+ cullData._renderData._drawState->_bs.expandBy(tilenode.getBound());
+ cullData._renderData._drawState->_box.expandBy(cullData._renderData._drawState->_bs);
+
+ return true;
+}
diff --git a/src/osgEarthDrivers/engine_corey/TerrainRenderData b/src/osgEarthDrivers/engine_corey/TerrainRenderData
new file mode 100644
index 0000000000..86bfcd7408
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/TerrainRenderData
@@ -0,0 +1,83 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#pragma once
+
+#include "DrawState"
+#include "LayerDrawable"
+#include
+
+using namespace osgEarth;
+
+namespace osgEarth
+{
+ namespace Corey
+ {
+ struct EngineData;
+
+ //! Lookup table of each layer's layerdrawable
+ using LayerDrawableTable = std::unordered_map<
+ const Layer*,
+ osg::ref_ptr>;
+
+ /**
+ * Main data structure assembled by the TerrainCuller that contains
+ * everything necessary to render one frame of the terrain.
+ */
+ class TerrainRenderData
+ {
+ public:
+ struct PersistentData
+ {
+ osg::FrameStamp _lastCull;
+ LayerDrawableTable _drawables;
+ };
+
+ /** Set up the map layers before culling the terrain */
+ void reset(
+ unsigned frameNum,
+ PersistentData& pd,
+ osgUtil::CullVisitor* cv,
+ EngineData& data);
+
+ /** Optimize for best state sharing (when using geometry pooling). Returns total tile count. */
+ unsigned sortDrawCommands();
+
+ /** Add a Drawable for a layer. Add these in the order you wish to render them. */
+ LayerDrawable* addLayerDrawable(const Layer*);
+
+ /** Look up a LayerDrawable by its source layer UID. */
+ LayerDrawable* layer(UID uid) { return _layersByUID[uid]; }
+
+ // Draw state shared by all layers during one frame.
+ DrawState::Ptr _drawState;
+
+ // Layers of type RENDERTYPE_TERRAIN_PATCH
+ PatchLayerVector& patchLayers() { return _patchLayers; }
+
+ // transient:
+ // ref_ptr's not necessary b/c of refs in _drawables above
+ std::unordered_map _layersByUID;
+ std::vector> _layerList;
+ PatchLayerVector _patchLayers; // Layers of type RENDERTYPE_TERRAIN_PATCH
+ PersistentData* _persistent = nullptr;
+ EngineData* _data = nullptr;
+ };
+
+ }
+} // namespace
diff --git a/src/osgEarthDrivers/engine_corey/TerrainRenderData.cpp b/src/osgEarthDrivers/engine_corey/TerrainRenderData.cpp
new file mode 100644
index 0000000000..6bbf8c6799
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/TerrainRenderData.cpp
@@ -0,0 +1,188 @@
+
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#include "TerrainRenderData"
+#include "TileNode"
+#include "EngineData"
+#include
+#include
+
+using namespace osgEarth::Corey;
+
+#undef LC
+#define LC "[TerrainRenderData] "
+
+
+unsigned
+TerrainRenderData::sortDrawCommands()
+{
+ unsigned total = 0;
+ for(auto layer : _layerList)
+ {
+ //TODO: review and benchmark list vs. vector vs. unsorted here.
+ DrawTileCommands& tiles = layer->_tiles;
+ std::sort(tiles.begin(), tiles.end());
+ total += tiles.size();
+
+ // re-sequence them after sorting
+ int seq = 0;
+ for (auto& tile : tiles)
+ tile._sequence = seq++;
+ }
+ return total;
+}
+
+void
+TerrainRenderData::reset(
+ unsigned frameNum,
+ PersistentData& persistent,
+ osgUtil::CullVisitor* cv,
+ EngineData& data)
+{
+ _data = &data;
+ _persistent = &persistent;
+
+ // Create a new State object to track sampler and uniform settings
+ _drawState = DrawState::create();
+ _drawState->_bindings = &data.renderBindings;
+
+ _layersByUID.clear();
+ _layerList.clear();
+ _patchLayers.clear();
+
+ // Is this a depth camera? Because if it is, we don't need any color layers.
+ const osg::Camera* cam = cv->getCurrentCamera();
+ bool isDepthCamera = CameraUtils::isDepthCamera(cam);
+
+ // Make a drawable for each rendering pass (i.e. each render-able map layer).
+ LayerVector layers;
+ _data->map->getLayers(layers);
+
+ for (auto& layer : layers)
+ {
+ if (layer->isOpen())
+ {
+ bool render =
+ (layer->getRenderType() == Layer::RENDERTYPE_TERRAIN_SURFACE) ||
+ (layer->getRenderType() == Layer::RENDERTYPE_TERRAIN_PATCH);
+
+ if ( render )
+ {
+ // If this is an image layer, check the enabled/visible states.
+ VisibleLayer* visLayer = dynamic_cast(layer.get());
+ if (visLayer)
+ {
+ // Check the visibility flag as well as the cull mask
+ render = visLayer->getVisible() && ((cv->getTraversalMask() & visLayer->getMask()) != 0);
+ }
+
+ if (render)
+ {
+ if (layer->getRenderType() == Layer::RENDERTYPE_TERRAIN_SURFACE)
+ {
+ LayerDrawable* ld = addLayerDrawable(layer);
+
+ // If the current camera is depth-only, leave this layer in the set
+ // but mark it as no-draw. We keep it in the set so the culler doesn't
+ // inadvertently think it's an orphaned layer.
+ if (isDepthCamera)
+ {
+ ld->_draw = false;
+ }
+ }
+
+ else // if (layer->getRenderType() == Layer::RENDERTYPE_TERRAIN_PATCH)
+ {
+ PatchLayer* patchLayer = static_cast(layer.get()); // asumption!
+
+ if (patchLayer->getAcceptCallback() != nullptr &&
+ patchLayer->getAcceptCallback()->acceptLayer(*cv, cv->getCurrentCamera()))
+ {
+ patchLayers().push_back(dynamic_cast(layer.get()));
+ addLayerDrawable(layer);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Include a "blank" layer for missing data. We should only need this if
+ // no layers get rendered at all.
+ addLayerDrawable(nullptr);
+}
+
+LayerDrawable*
+TerrainRenderData::addLayerDrawable(
+ const Layer* layer)
+{
+ LayerDrawable* drawable = nullptr;
+ bool isNew = false;
+
+ if (GLUtils::useNVGL())
+ {
+ osg::ref_ptr& obj = _persistent->_drawables[layer];
+ if (!obj.valid())
+ {
+ obj = new LayerDrawableNVGL();
+ isNew = true;
+ }
+ drawable = obj.get();
+ }
+ else
+ {
+ drawable = new LayerDrawableGL3();
+ isNew = true;
+ }
+
+ if (isNew)
+ {
+ drawable->_data = _data;
+ drawable->_layer = layer;
+ drawable->_visibleLayer = dynamic_cast(layer);
+ drawable->_imageLayer = dynamic_cast(layer);
+ drawable->_patchLayer = dynamic_cast(layer);
+
+ if (layer)
+ {
+ drawable->setName(layer->getName());
+ drawable->setStateSet(layer->getStateSet());
+ drawable->_renderType = layer->getRenderType();
+ }
+ }
+
+ // reset state:
+ drawable->_tiles.clear();
+ drawable->_clearOsgState = false;
+ drawable->_drawState = _drawState;
+ drawable->dirtyBound();
+ drawable->_drawOrder = _layerList.size();
+ _layerList.push_back(drawable); // ref taken
+
+ if (layer)
+ {
+ _layersByUID[layer->getUID()] = drawable;
+ }
+ else
+ {
+ _layersByUID[-1] = drawable;
+ }
+
+ return drawable;
+}
diff --git a/src/osgEarthDrivers/engine_corey/TileGeometry b/src/osgEarthDrivers/engine_corey/TileGeometry
new file mode 100644
index 0000000000..6ccd064734
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/TileGeometry
@@ -0,0 +1,96 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#pragma once
+#include "Common"
+#include "TileRenderModel"
+#include
+#include
+
+namespace osgEarth
+{
+ namespace Corey
+ {
+ using namespace osgEarth;
+
+ using DrawElementsBase = osg::DrawElementsUShort;
+
+ struct DrawElementsGL4Data : osg::Referenced
+ {
+ // Shareable because they are static and bindless
+ struct GLObjects : public BindlessShareableGLObjects {
+ GLBuffer::Ptr _ebo; // bindless buffer
+ };
+ mutable osg::buffered_object _globjects;
+
+ void releaseGLObjects(osg::State* state) const {
+ if (state)
+ GLObjects::get(_globjects, *state)._ebo = nullptr;
+ }
+
+ void resizeGLObjectBuffers(unsigned size) {
+ if (size > _globjects.size())
+ _globjects.resize(size);
+ }
+ };
+
+ class TileGeometry : public osg::Geometry
+ {
+ public:
+ TileGeometry();
+
+ //! Make a SharedGeometry from a TileMesh
+ static TileGeometry* create(const TileMesh& mesh);
+
+ //! Whether this tile has constraints; if not, it can be shared
+ bool hasConstraints = false;
+
+ //! Vertex array for NVGL rendering
+ std::vector verts;
+
+ //! Mode of the SharedDrawElements primitiveset (cached)
+ GLenum mode = GL_TRIANGLES;
+
+ //! Generate the NVGL command for this geometry
+ const DrawElementsIndirectBindlessCommandNV& getOrCreateNVGLCommand(osg::State& state);
+
+ public: // osg::Drawable
+
+ void resizeGLObjectBuffers(unsigned int maxSize) override;
+ void releaseGLObjects(osg::State* state) const override;
+
+ private:
+ virtual ~TileGeometry()
+ {
+ releaseGLObjects(nullptr);
+ }
+
+ // Shareable because they are static and bindless
+ struct GLObjects : public BindlessShareableGLObjects
+ {
+ DrawElementsIndirectBindlessCommandNV _command;
+ GLBuffer::Ptr _vbo;
+ };
+ mutable osg::buffered_object _globjects;
+
+ osg::ref_ptr _drawElementsData;
+
+ friend struct DrawTileCommand;
+ };
+ }
+}
diff --git a/src/osgEarthDrivers/engine_corey/TileGeometry.cpp b/src/osgEarthDrivers/engine_corey/TileGeometry.cpp
new file mode 100644
index 0000000000..9bf8ef189e
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/TileGeometry.cpp
@@ -0,0 +1,180 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#include "TileGeometry"
+
+using namespace osgEarth;
+using namespace osgEarth::Corey;
+
+TileGeometry*
+TileGeometry::create(const TileMesh& mesh)
+{
+ OE_SOFT_ASSERT_AND_RETURN(mesh.verts.valid(), {});
+ OE_SOFT_ASSERT_AND_RETURN(mesh.normals.valid(), {});
+ OE_SOFT_ASSERT_AND_RETURN(mesh.uvs.valid(), {});
+ OE_SOFT_ASSERT_AND_RETURN(mesh.indices.valid(), {});
+
+ auto geom = new TileGeometry();
+ geom->setVertexArray(mesh.verts);
+ geom->setNormalArray(mesh.normals);
+ geom->setTexCoordArray(0, mesh.uvs);
+ if (mesh.vert_neighbors.valid())
+ geom->setTexCoordArray(1, mesh.vert_neighbors);
+ if (mesh.normal_neighbors.valid())
+ geom->setTexCoordArray(2, mesh.normal_neighbors);
+
+ if (mesh.indices.valid())
+ {
+ geom->addPrimitiveSet(mesh.indices);
+
+ // later we will see about sharing these
+ geom->_drawElementsData = new DrawElementsGL4Data();
+ }
+
+ geom->hasConstraints = mesh.hasConstraints;
+
+ // if we are using GL4, create the GL4 tile model.
+ if (/*using GL4*/true)
+ {
+ unsigned size = geom->getVertexArray()->getNumElements();
+ geom->verts.reserve(size);
+
+ for (unsigned i = 0; i < size; ++i)
+ {
+ GL4Vertex v;
+
+ v.position = (*mesh.verts)[i];
+ v.normal = (*mesh.normals)[i];
+ v.uv = (*mesh.uvs)[i];
+ if (mesh.vert_neighbors.valid())
+ v.neighborPosition = (*mesh.vert_neighbors)[i];
+ if (mesh.normal_neighbors.valid())
+ v.neighborNormal = (*mesh.normal_neighbors)[i];
+
+ geom->verts.emplace_back(std::move(v));
+ }
+ }
+
+ return geom;
+}
+
+TileGeometry::TileGeometry() : osg::Geometry()
+{
+ _supportsVertexBufferObjects = true;
+ setSupportsDisplayList(false);
+ setUseDisplayList(false);
+ setUseVertexBufferObjects(true);
+}
+
+const DrawElementsIndirectBindlessCommandNV&
+TileGeometry::getOrCreateNVGLCommand(osg::State& state)
+{
+ OE_SOFT_ASSERT(verts.size() > 0);
+ OE_SOFT_ASSERT(getNumPrimitiveSets() == 1);
+
+ bool dirty = false;
+
+ // first the drawelements
+ auto* prim = static_cast(getPrimitiveSet(0));
+ auto& de = DrawElementsGL4Data::GLObjects::get(_drawElementsData->_globjects, state);
+
+ if (de._ebo == nullptr || !de._ebo->valid())
+ {
+ de._ebo = GLBuffer::create_shared(GL_ELEMENT_ARRAY_BUFFER_ARB, state);
+ de._ebo->bind();
+ de._ebo->debugLabel("Terrain geometry", "Shared EBO");
+ de._ebo->bufferStorage(prim->getTotalDataSize(), prim->getDataPointer(), 0);
+ de._ebo->unbind();
+
+ dirty = true;
+ }
+
+ auto& gs = GLObjects::get(_globjects, state);
+
+ if (gs._vbo == nullptr || !gs._vbo->valid())
+ {
+ // supply a "size hint" for unconstrained tiles to the GLBuffer so it can try to re-use
+ GLsizei size = verts.size() * sizeof(GL4Vertex);
+ if (hasConstraints)
+ gs._vbo = GLBuffer::create_shared(GL_ARRAY_BUFFER_ARB, state);
+ else
+ gs._vbo = GLBuffer::create_shared(GL_ARRAY_BUFFER_ARB, state, size);
+
+ gs._vbo->bind();
+ gs._vbo->debugLabel("Terrain geometry", "Shared VBO");
+ gs._vbo->bufferStorage(size, verts.data());
+ gs._vbo->unbind();
+
+ dirty = true;
+ }
+
+ // make them resident in each context separately
+ de._ebo->makeResident(state);
+ gs._vbo->makeResident(state);
+
+ OE_SOFT_ASSERT(de._ebo->address() != 0);
+ OE_SOFT_ASSERT(de._ebo->size() > 0);
+
+ OE_SOFT_ASSERT(gs._vbo->address() != 0);
+ OE_SOFT_ASSERT(gs._vbo->size() > 0);
+
+ if (dirty)
+ {
+ gs._command.cmd.count = prim->getNumIndices(); // size();
+ gs._command.cmd.instanceCount = 1;
+ gs._command.cmd.firstIndex = 0;
+ gs._command.cmd.baseVertex = 0;
+ gs._command.cmd.baseInstance = 0;
+
+ gs._command.reserved = 0;
+
+ gs._command.indexBuffer.index = 0;
+ gs._command.indexBuffer.reserved = 0;
+ gs._command.indexBuffer.address = de._ebo->address();
+ gs._command.indexBuffer.length = de._ebo->size();
+
+ gs._command.vertexBuffer.index = 0;
+ gs._command.vertexBuffer.reserved = 0;
+ gs._command.vertexBuffer.address = gs._vbo->address();
+ gs._command.vertexBuffer.length = gs._vbo->size();
+ }
+
+ return gs._command;
+}
+
+void TileGeometry::resizeGLObjectBuffers(unsigned int maxSize)
+{
+ osg::Geometry::resizeGLObjectBuffers(maxSize);
+
+ if (maxSize > _globjects.size())
+ _globjects.resize(maxSize);
+}
+
+void TileGeometry::releaseGLObjects(osg::State* state) const
+{
+ osg::Geometry::releaseGLObjects(state);
+
+ if (state)
+ {
+ auto& gl = GLObjects::get(_globjects, *state);
+ gl._vbo = nullptr;
+ }
+
+ // Do nothing if state is nullptr!
+ // Let nature take its course and let the GLObjectPool deal with it
+}
diff --git a/src/osgEarthDrivers/engine_corey/TileNode b/src/osgEarthDrivers/engine_corey/TileNode
new file mode 100644
index 0000000000..3862bdebff
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/TileNode
@@ -0,0 +1,64 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#pragma once
+
+#include "Common"
+#include "TileRenderModel"
+#include "TileGeometry"
+
+#include
+#include
+
+namespace osgEarth
+{
+ namespace Corey
+ {
+ struct EngineData;
+
+ class TileNode : public osg::Group, public osgEarth::TerrainTile
+ {
+ public:
+ TileNode(const TileKey& key);
+
+ //! Sets a new (partial) data model for this tile.
+ //! @param model The new (potentially partial) data model to install
+ //! @param parent_model The full data model of the parent file, which this tile
+ //! can use to inherit data not in its own data model.
+ //! @param ctx The engine context
+ void set(TerrainTileModel* model, const TerrainTileModel* parent_model, EngineData& data);
+
+ public:
+ TileKey _key;
+ osg::MatrixTransform* _xform = nullptr;
+ TileGeometry* _drawable = nullptr;
+ osg::ref_ptr _partialDataModel, _fullDataModel;
+ TileRenderModel _renderModel;
+ osg::Vec4f _tileKeyValue;
+ osg::Vec2f _morphConstants;
+ bool _dirty = false;
+
+ public: // TerrainTile interface
+ const TileKey& getKey() const override { return _key; }
+
+ private:
+ void updateSamplers(const TileRenderModel* parentRenderModel, EngineData& data);
+ };
+ }
+}
+
diff --git a/src/osgEarthDrivers/engine_corey/TileNode.cpp b/src/osgEarthDrivers/engine_corey/TileNode.cpp
new file mode 100644
index 0000000000..2f5fc27816
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/TileNode.cpp
@@ -0,0 +1,467 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+* Copyright 2008-2014 Pelican Mapping
+* http://osgearth.org
+*
+* osgEarth is free software; you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation; either version 2 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see
+*/
+#include "TileNode"
+#include "EngineData"
+#include "SelectionInfo"
+#include "TerrainCuller"
+
+using namespace osgEarth::Corey;
+using namespace osgEarth;
+
+#define LC "[TileNode] "
+
+namespace
+{
+ // Scale and bias matrices, one for each TileKey quadrant.
+ const osg::Matrixf scaleBias[4] =
+ {
+ osg::Matrixf(0.5f,0,0,0, 0,0.5f,0,0, 0,0,1.0f,0, 0.0f,0.5f,0,1.0f),
+ osg::Matrixf(0.5f,0,0,0, 0,0.5f,0,0, 0,0,1.0f,0, 0.5f,0.5f,0,1.0f),
+ osg::Matrixf(0.5f,0,0,0, 0,0.5f,0,0, 0,0,1.0f,0, 0.0f,0.0f,0,1.0f),
+ osg::Matrixf(0.5f,0,0,0, 0,0.5f,0,0, 0,0,1.0f,0, 0.5f,0.0f,0,1.0f)
+ };
+
+ TerrainTileModel* combine(TerrainTileModel* child, const TerrainTileModel* parent)
+ {
+ OE_SOFT_ASSERT_AND_RETURN(child, nullptr);
+
+ if (!parent)
+ return child;
+
+ auto lod = child->key.getLOD();
+ int quadrant = child->key.getQuadrant();
+
+ TerrainTileModel::ColorLayer::Vector colorLayersToInherit;
+
+ // Look for parent color layers that don't exist in the child.
+ // For each one, copy its data model and apply the scale/bias matrix.
+ unsigned original_num_color_layers = child->colorLayers.size();
+ for (auto& parent_layer : parent->colorLayers)
+ {
+ if (parent_layer.tileLayer.valid() && parent_layer.tileLayer->getMaxLevel() >= lod)
+ {
+ bool found = false;
+ for(unsigned i=0; i< original_num_color_layers; ++i)
+ {
+ auto& child_layer = child->colorLayers[i];
+ if (child_layer.layer.valid() && child_layer.layer->getUID() == parent_layer.layer->getUID())
+ {
+ // found a match; carry on.
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ child->colorLayers.emplace_back(parent_layer);
+ auto& new_color_layer = child->colorLayers.back();
+ new_color_layer.matrix.preMult(scaleBias[quadrant]);
+ }
+ }
+ }
+
+ if (child->normalMap.texture == nullptr && parent->normalMap.texture != nullptr)
+ {
+ child->normalMap = parent->normalMap;
+ child->normalMap.matrix.preMult(scaleBias[quadrant]);
+ }
+
+ if (child->elevation.texture == nullptr && parent->elevation.texture != nullptr)
+ {
+ child->elevation = parent->elevation;
+ child->elevation.matrix.preMult(scaleBias[quadrant]);
+ }
+
+ // todo, landcover IF we need it. Hopefully never.
+
+ return child;
+ }
+}
+
+
+TileNode::TileNode(const TileKey& key) :
+ _key(key)
+{
+ //nop
+}
+
+void
+TileNode::set(TerrainTileModel* new_model, const TerrainTileModel* parent_model, EngineData& data)
+{
+ OE_SOFT_ASSERT_AND_RETURN(new_model, void()); // require the data model and context
+ OE_SOFT_ASSERT_AND_RETURN(new_model->mesh.verts.valid(), void()); // require valid mesh
+
+ bool newMesh =
+ !_partialDataModel.valid() ||
+ _partialDataModel->mesh.revision != new_model->mesh.revision;
+
+ _partialDataModel = new_model;
+
+ // Fills in any missing data in the new data model with data inherited
+ // from the parent data model, if available.
+ _fullDataModel = combine(new_model, parent_model);
+
+ // todo: populate the full model based on parent data --
+ // -- maybe do this in the caller and pass them both in --
+ // -- then TileNode doesn't need to know about its parent
+
+ auto& mesh = _fullDataModel->mesh;
+
+ if (!_xform)
+ {
+ _xform = new osg::MatrixTransform();
+ _xform->addChild(_drawable);
+ addChild(_xform);
+ this->setCullCallback(data.tileNodeCullCallback.get());
+
+ // Encode the tile key in a uniform. Note! The X and Y components are presented
+ // modulo 2^16 form so they don't overrun single-precision space.
+ unsigned tw, th;
+ _key.getProfile()->getNumTiles(_key.getLOD(), tw, th);
+ double x = (double)_key.getTileX();
+ double y = (double)(th - _key.getTileY() - 1);
+ _tileKeyValue.set((float)(x - tw / 2), (float)(y - th / 2), (float)_key.getLOD(), -1.0f);
+
+ // initialize all the per-tile uniforms the shaders will need:
+ float range, morphStart, morphEnd;
+ data.selectionInfo.get(_key, range, morphStart, morphEnd);
+
+ float one_over_end_minus_start = 1.0f / (morphEnd - morphStart);
+ _morphConstants.set(morphEnd * one_over_end_minus_start, one_over_end_minus_start);
+ }
+
+ if (newMesh)
+ {
+ _drawable = TileGeometry::create(mesh);
+ _drawable->setCullCallback(data.tileDrawableCullCallback.get());
+ _xform->removeChildren(0, 1);
+ _xform->addChild(_drawable);
+ _xform->setMatrix(mesh.localToWorld);
+ }
+
+ // update the samplers based on the new data model.
+ updateSamplers(nullptr, data);
+}
+
+void
+TileNode::updateSamplers(const TileRenderModel* parentRenderModel, EngineData& data)
+{
+ bool newElevationData = false;
+ const RenderBindings& bindings = data.renderBindings;
+ RenderingPasses& myPasses = _renderModel._passes;
+ vector_set uidsLoaded;
+
+#if 0
+ // if terrain constraints are in play, regenerate the tile's geometry.
+ // this could be kinda slow, but meh, if you are adding and removing
+ // constraints, frame drops are not a big concern
+ if (manifest.includesConstraints())
+ {
+ // todo: progress callback here? I believe progress gets
+ // checked before merge() anyway.
+ createGeometry(nullptr);
+ }
+#endif
+
+ auto* model = _fullDataModel.get();
+
+ // First deal with the rendering passes (for color data):
+ const SamplerBinding& color = bindings[SamplerBinding::COLOR];
+ if (color.isActive())
+ {
+ // loop over all the layers included in the new data model and
+ // add them to our render model (or update them if they already exist)
+ for (const auto& colorLayer : model->colorLayers)
+ {
+ auto& layer = colorLayer.layer;
+ if (!layer.valid())
+ continue;
+
+ // Look up the parent pass in case we need it
+ RenderingPass* pass = _renderModel.getPass(layer->getUID());
+
+ const RenderingPass* parentPass = parentRenderModel ? parentRenderModel->getPass(layer->getUID()) : nullptr;
+
+ // ImageLayer?
+ ImageLayer* imageLayer = dynamic_cast(layer.get());
+ if (imageLayer)
+ {
+ bool isNewPass = (pass == nullptr);
+ if (isNewPass)
+ {
+ // Pass didn't exist here, so add it now.
+ pass = &_renderModel.addPass();
+ pass->setLayer(layer);
+ }
+
+ pass->setSampler(SamplerBinding::COLOR, colorLayer.texture, colorLayer.matrix, colorLayer.revision);
+
+ // If this is a new rendering pass, just copy the color into the color-parent.
+ if (isNewPass && bindings[SamplerBinding::COLOR_PARENT].isActive())
+ {
+ pass->sampler(SamplerBinding::COLOR_PARENT) = pass->sampler(SamplerBinding::COLOR);
+ }
+
+#if 0
+ // check to see if this data requires an image update traversal.
+ if (_imageUpdatesActive == false)
+ {
+ _imageUpdatesActive = colorLayer.texture->needsUpdates();
+ }
+#endif
+
+#if 0
+ if (imageLayer->getAsyncLoading())
+ {
+ if (parentPass)
+ {
+ pass->inheritFrom(*parentPass, scaleBias[_key.getQuadrant()]);
+
+ if (bindings[SamplerBinding::COLOR_PARENT].isActive())
+ {
+ Sampler& colorParent = pass->sampler(SamplerBinding::COLOR_PARENT);
+ colorParent._texture = parentPass->sampler(SamplerBinding::COLOR)._texture;
+ colorParent._matrix = parentPass->sampler(SamplerBinding::COLOR)._matrix;
+ colorParent._matrix.preMult(scaleBias[_key.getQuadrant()]);
+ }
+ }
+ else
+ {
+ // note: this can happen with an async layer load
+ OE_DEBUG << "no parent pass in my pass. key=" << model->key.str() << std::endl;
+ }
+
+ // check whether it's actually a futuretexture.
+ // if it's not, it is likely an empty texture and we'll ignore it
+ if (dynamic_cast(colorLayer.texture->osgTexture().get()))
+ {
+ pass->sampler(SamplerBinding::COLOR)._futureTexture = colorLayer.texture;
+ }
+
+ // require an update pass to process the future texture
+ _imageUpdatesActive = true;
+ }
+#endif
+
+ uidsLoaded.insert(pass->sourceUID());
+ }
+
+ else // non-image color layer (like splatting, e.g.)
+ {
+ if (!pass)
+ {
+ pass = &_renderModel.addPass();
+ pass->setLayer(colorLayer.layer.get());
+ }
+
+ uidsLoaded.insert(pass->sourceUID());
+ }
+ }
+
+#if 0
+ // Next loop over all the passes that we OWN, we asked for, but we didn't get.
+ // That means they no longer exist at this LOD, and we need to convert them
+ // into inherited samplers (or delete them entirely)
+ for (int p = 0; p < myPasses.size(); ++p)
+ {
+ RenderingPass& myPass = myPasses[p];
+
+ if (myPass.ownsTexture() &&
+ manifest.includes(myPass.layer()) &&
+ !uidsLoaded.contains(myPass.sourceUID()))
+ {
+ OE_DEBUG << LC << "Releasing orphaned layer " << myPass.layer()->getName() << std::endl;
+
+ bool deletePass = true;
+
+ if (parentRenderModel)
+ {
+ const RenderingPass* parentPass = parentRenderModel->getPass(myPass.sourceUID());
+ if (parentPass)
+ {
+ myPass.inheritFrom(*parentPass, scaleBias[_key.getQuadrant()]);
+ deletePass = false;
+ }
+ }
+
+ if (deletePass)
+ {
+ myPasses.erase(myPasses.begin() + p);
+ --p;
+ }
+ }
+ }
+#endif
+ }
+
+ // Elevation data:
+ const SamplerBinding& elevation = bindings[SamplerBinding::ELEVATION];
+ if (elevation.isActive() && model->elevation.texture)
+ {
+ _renderModel.setCommonSampler(SamplerBinding::ELEVATION,
+ model->elevation.texture, model->elevation.matrix, model->elevation.revision);
+
+ //updateElevationRaster();
+ newElevationData = true;
+ }
+ else
+ {
+ _renderModel.clearCommonSampler(SamplerBinding::ELEVATION);
+ }
+
+#if 0
+ else if (
+ manifest.includesElevation() &&
+ _renderModel._CommonSamplers[SamplerBinding::ELEVATION].ownsTexture())
+ {
+ // We OWN elevation data, requested new data, and didn't get any.
+ // That means it disappeared and we need to delete what we have.
+ inheritCommonSampler(SamplerBinding::ELEVATION);
+
+ updateElevationRaster();
+
+ newElevationData = true;
+ }
+#endif
+
+
+ // Normals:
+ const SamplerBinding& normals = bindings[SamplerBinding::NORMAL];
+ if (normals.isActive() && model->normalMap.texture)
+ {
+ _renderModel.setCommonSampler(SamplerBinding::NORMAL,
+ model->normalMap.texture, model->normalMap.matrix, model->normalMap.revision);
+ //updateNormalMap();
+ }
+ else
+ {
+ _renderModel.clearCommonSampler(SamplerBinding::NORMAL);
+ }
+
+#if 0
+ // If we OWN normal data, requested new data, and didn't get any,
+ // that means it disappeared and we need to delete what we have:
+ else if (
+ manifest.includesElevation() && // not a typo, check for elevation
+ _renderModel._CommonSamplers[SamplerBinding::NORMAL].ownsTexture())
+ {
+ inheritCommonSampler(SamplerBinding::NORMAL);
+ updateNormalMap();
+ }
+#endif
+
+
+ // Land Cover:
+ const SamplerBinding& landCover = bindings[SamplerBinding::LANDCOVER];
+ if (landCover.isActive() && model->landCover.texture)
+ {
+ _renderModel.setCommonSampler(SamplerBinding::LANDCOVER,
+ model->landCover.texture, model->landCover.matrix, model->landCover.revision);
+ }
+ else
+ {
+ _renderModel.clearCommonSampler(SamplerBinding::LANDCOVER);
+ }
+
+#if 0
+ else if (
+ manifest.includesLandCover() &&
+ _renderModel._CommonSamplers[SamplerBinding::LANDCOVER].ownsTexture())
+ {
+ // We OWN landcover data, requested new data, and didn't get any.
+ // That means it disappeared and we need to delete what we have.
+ inheritCommonSampler(SamplerBinding::LANDCOVER);
+ }
+#endif
+
+
+
+ // Other Shared Layers:
+ uidsLoaded.clear();
+
+ for (unsigned index : model->sharedLayerIndices)
+ {
+ auto& sharedLayer = model->colorLayers[index];
+ // locate the shared binding corresponding to this layer:
+ UID uid = sharedLayer.layer->getUID();
+ unsigned bindingIndex = INT_MAX;
+ for (unsigned i = SamplerBinding::SHARED; i < bindings.size() && bindingIndex == INT_MAX; ++i)
+ {
+ if (bindings[i].isActive() && bindings[i].sourceUID.isSetTo(uid))
+ {
+ bindingIndex = i;
+ }
+ }
+
+ if (sharedLayer.texture && bindingIndex < INT_MAX)
+ {
+ _renderModel.setCommonSampler(bindingIndex, sharedLayer.texture, sharedLayer.revision);
+ uidsLoaded.insert(uid);
+ }
+ else
+ {
+ _renderModel.clearCommonSampler(bindingIndex);
+ }
+ }
+
+
+
+#if 0
+ // Look for shared layers we need to remove because we own them,
+ // requested them, and didn't get updates for them:
+ for (unsigned i = SamplerBinding::SHARED; i < bindings.size(); ++i)
+ {
+ if (bindings[i].isActive() &&
+ manifest.includes(bindings[i].sourceUID().get()) &&
+ !uidsLoaded.contains(bindings[i].sourceUID().get()))
+ {
+ inheritCommonSampler(i);
+ }
+ }
+
+ // Propagate changes we made down to this tile's children.
+ if (_childrenReady)
+ {
+ for (int i = 0; i < 4; ++i)
+ {
+ TileNode* child = getSubTile(i);
+ if (child)
+ {
+ child->refreshInheritedData(this, bindings);
+ }
+ }
+ }
+
+ if (newElevationData)
+ {
+ _context->getEngine()->getTerrain()->notifyTileUpdate(getKey(), this);
+ }
+#endif
+
+
+ //if (newElevationData)
+ //{
+ // auto updateElevation = [&](SharedGeometry& geom, const osg::Matrix& matrix)
+ // {
+ // };
+
+ // TypedNodeVisitor visitor(updateElevation);
+ // this->accept(visitor);
+ //}
+}
diff --git a/src/osgEarthDrivers/engine_corey/TileRenderModel b/src/osgEarthDrivers/engine_corey/TileRenderModel
new file mode 100644
index 0000000000..3123c8e63c
--- /dev/null
+++ b/src/osgEarthDrivers/engine_corey/TileRenderModel
@@ -0,0 +1,375 @@
+/* -*-c++-*- */
+/* osgEarth - Geospatial SDK for OpenSceneGraph
+ * Copyright 2008-2014 Pelican Mapping
+ * http://osgearth.org
+ *
+ * osgEarth is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see
+ */
+#pragma once
+
+#include "Common"
+#include "RenderBindings"
+#include
+#include // for AutoArray
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace osgEarth { namespace Corey
+{
+ using namespace osgEarth::Util;
+
+
+ // **** NVGL rendering support ****
+
+#define MAX_NUM_SHARED_SAMPLERS 16
+
+ // TODO: consider making this a simple UBO instead
+ // since it's constant data
+ struct GL4GlobalData // align to 16 bytes (std430)
+ {
+ float morphConstants[98];
+ float padding2[2];
+ };
+
+ struct GL4Tile // align to 16 bytes (std430)
+ {
+ GLfloat tileKey[4];
+ GLfloat modelViewMatrix[4 * 4];
+ GLfloat colorMat[4 * 4];
+ GLfloat parentMat[4 * 4];
+ GLfloat elevMat[4 * 4];
+ GLfloat normalMat[4 * 4];
+ GLfloat landcoverMat[4 * 4];
+ GLfloat sharedMat[MAX_NUM_SHARED_SAMPLERS][4 * 4];
+ GLint colorIndex;
+ GLint parentIndex;
+ GLint elevIndex;
+ GLint normalIndex;
+ GLint landcoverIndex;
+ GLint sharedIndex[MAX_NUM_SHARED_SAMPLERS];
+ GLint drawOrder; // todo: stuff this somewhere else?
+ float padding[2];
+ };
+
+ struct GL4Vertex
+ {
+ osg::Vec3f position;
+ osg::Vec3f normal;
+ osg::Vec3f uv;
+ osg::Vec3f neighborPosition;
+ osg::Vec3f neighborNormal;
+ };
+
+ /**
+ * A single texture and its matrix.
+ * This corresponds to a single SamplerBinding. If the texture matrix
+ * is non-identity, that means the sampler inherits the texture from
+ * another sampler higher up in the scene graph.
+ */
+ class Sampler
+ {
+ public:
+ //! Construct
+ Sampler()
+ : _revision(0u) { }
+
+ //! Construct
+ Sampler(const Sampler& rhs) :
+ // no _futuretexture
+ _texture(rhs._texture),
+ _matrix(rhs._matrix),
+ _revision(rhs._revision)
+ {
+ //nop
+ }
+
+ // pointer to the sampler's active texture data
+ Texture::Ptr _texture;
+
+ // scale and bias matrix for accessing the texture - will be non-identity
+ // if the texture is inherited from an ancestor tile
+ osg::Matrixf _matrix;
+
+ // point to a texture whose image is still being loaded asynchronously
+ Texture::Ptr _futureTexture;
+
+ // revision of the data in this sampler (taken from its source layer)
+ unsigned _revision;
+
+ //! True is this sampler is the rightful owner of _texture.
+ inline bool ownsTexture() const {
+ return _texture && _matrix.isIdentity();
+ }
+
+ //! True is this sampler is NOT the rightful owner of _texture.
+ inline bool inheritsTexture() const {
+ return !_texture || !_matrix.isIdentity();
+ }
+
+ //! Revision of the data model use to initialize this sampler.
+ inline unsigned revision() const {
+ return _revision;
+ }
+
+ //! Set this sampler's matrix to inherit
+ inline void inheritFrom(const Sampler& rhs, const osg::Matrixf& scaleBias) {
+ _texture = rhs._texture;
+ _matrix = rhs._matrix;
+ _revision = rhs._revision;
+ // do NOT copy and overwrite _futureTexture!
+ _matrix.preMult(scaleBias);
+ }
+ };
+ typedef AutoArray Samplers;
+
+ /**
+ * A single rendering pass for color data.
+ * Samplers (one per RenderBinding) specific to one rendering pass of a tile.
+ * These are just the COLOR and COLOR_PARENT samplers.
+ */
+ class RenderingPass
+ {
+ public:
+ RenderingPass() :
+ _sourceUID(-1),
+ _samplers(SamplerBinding::COLOR_PARENT+1),
+ _visibleLayer(0L),
+ _tileLayer(0L)
+ { }
+
+ RenderingPass(const RenderingPass& rhs) :
+ _sourceUID(rhs._sourceUID),
+ _samplers(rhs._samplers),
+ _layer(rhs._layer),
+ _visibleLayer(rhs._visibleLayer),
+ _tileLayer(rhs._tileLayer)
+ {
+ //nop
+ }
+
+ ~RenderingPass()
+ {
+ releaseGLObjects(nullptr);
+ }
+
+ // UID of the layer corresponding to this rendering pass
+ UID sourceUID() const { return _sourceUID; }
+
+ // the COLOR and COLOR_PARENT (optional) samplers for this rendering pass
+ Samplers& samplers() { return _samplers; }
+ const Samplers& samplers() const { return _samplers; }
+
+ Sampler& sampler(int binding) { return _samplers[binding]; }
+ const Sampler& sampler(int binding) const { return _samplers[binding]; }
+
+ const Layer* layer() const { return _layer.get(); }
+ const VisibleLayer* visibleLayer() const { return _visibleLayer; }
+ const TileLayer* tileLayer() const { return _tileLayer; }
+
+ // whether the color sampler in this rendering pass are native
+ // or inherited from another tile
+ bool ownsTexture() const { return _samplers[SamplerBinding::COLOR].ownsTexture(); }
+ bool inheritsTexture() const { return !ownsTexture(); }
+
+ void inheritFrom(const RenderingPass& rhs, const osg::Matrix& scaleBias) {
+ _sourceUID = rhs._sourceUID;
+ _samplers = rhs._samplers;
+ _layer = rhs._layer;
+ _visibleLayer = rhs._visibleLayer;
+ _tileLayer = rhs._tileLayer;
+
+ for (unsigned s = 0; s < samplers().size(); ++s)
+ sampler(s)._matrix.preMult(scaleBias);
+ }
+
+ void releaseGLObjects(osg::State* state) const
+ {
+ for (unsigned s = 0; s < _samplers.size(); ++s)
+ {
+ const Sampler& sampler = _samplers[s];
+ if (sampler.ownsTexture())
+ sampler._texture->releaseGLObjects(state);
+ if (sampler._futureTexture)
+ sampler._futureTexture->releaseGLObjects(state);
+ }
+ }
+
+ void resizeGLObjectBuffers(unsigned size)
+ {
+ for (unsigned s = 0; s < _samplers.size(); ++s)
+ {
+ const Sampler& sampler = _samplers[s];
+ if (sampler.ownsTexture())
+ sampler._texture->resizeGLObjectBuffers(size);
+ if (sampler._futureTexture)
+ sampler._futureTexture->resizeGLObjectBuffers(size);
+ }
+ }
+
+ void setLayer(const Layer* layer) {
+ _layer = layer;
+ if (layer) {
+ _visibleLayer = dynamic_cast(layer);
+ _tileLayer = dynamic_cast(layer);
+ _sourceUID = layer->getUID();
+ for (unsigned s = 0; s<_samplers.size(); ++s) {
+ _samplers[s]._revision = layer->getRevision();
+ }
+ }
+ }
+
+ void setSampler(
+ SamplerBinding::Usage binding,
+ Texture::Ptr texture,
+ const osg::Matrix& matrix,
+ int sourceRevision)
+ {
+ Sampler& sampler = _samplers[binding];
+ sampler._texture = texture;
+ sampler._matrix = matrix;
+ sampler._revision = sourceRevision;
+ }
+
+ private:
+ /** UID of the layer responsible for this rendering pass (usually an ImageLayer) */
+ UID _sourceUID;
+
+ /** Samplers specific to this rendering pass (COLOR, COLOR_PARENT) */
+ Samplers _samplers;
+
+ /** Layer respsonible for this rendering pass */
+ osg::ref_ptr _layer;
+
+ /** VisibleLayer responsible for this rendering pass (is _layer is a VisibleLayer) */
+ const VisibleLayer* _visibleLayer;
+
+ /** VisibleLayer responsible for this rendering pass (is _layer is a TileLayer) */
+ const TileLayer* _tileLayer;
+
+ friend class TileRenderModel;
+ };
+
+ /**
+ * Unordered collection of rendering passes.
+ */
+ typedef std::vector RenderingPasses;
+
+ /**
+ * Everything necessary to render a single terrain tile.
+ * Corey renders the terrain in multiple passes, one pass for each visible layer.
+ */
+ class TileRenderModel
+ {
+ public:
+ /** Samplers that are bound for every rendering pass (elevation, normal map, etc.) */
+ Samplers _commonSamplers;
+
+ /** Samplers bound for each visible layer (color) */
+ RenderingPasses _passes;
+
+ DrawElementsIndirectBindlessCommandNV _command;
+
+ /** Add a new rendering pass to the end of the list. */
+ RenderingPass& addPass()
+ {
+ _passes.resize(_passes.size()+1);
+ return _passes.back();
+ }
+
+ RenderingPass& copyPass(const RenderingPass& rhs)
+ {
+ _passes.push_back(rhs);
+ return _passes.back();
+ }
+
+ /** Look up a rendering pass by the corresponding layer ID */
+ const RenderingPass* getPass(UID uid) const
+ {
+ for (unsigned i = 0; i < _passes.size(); ++i) {
+ if (_passes[i].sourceUID() == uid)
+ return &_passes[i];
+ }
+ return 0L;
+ }
+
+ /** Look up a rendering pass by the corresponding layer ID */
+ RenderingPass* getPass(UID uid)
+ {
+ for (unsigned i = 0; i < _passes.size(); ++i) {
+ if (_passes[i].sourceUID() == uid)
+ return &_passes[i];
+ }
+ return 0L;
+ }
+
+ void setCommonSampler(
+ unsigned binding,
+ Texture::Ptr texture,
+ int sourceRevision)
+ {
+ Sampler& sampler = _commonSamplers[binding];
+ sampler._texture = texture;
+ sampler._matrix.makeIdentity();
+ sampler._revision = sourceRevision;
+ }
+
+ void setCommonSampler(
+ unsigned binding,
+ Texture::Ptr texture,
+ const osg::Matrixf& matrix,
+ int sourceRevision)
+ {
+ Sampler& sampler = _commonSamplers[binding];
+ sampler._texture = texture;
+ sampler._matrix = matrix;
+ sampler._revision = sourceRevision;
+ }
+
+ void clearCommonSampler(unsigned binding)
+ {
+ Sampler& sampler = _commonSamplers[binding];
+ sampler._texture = nullptr;
+ sampler._matrix.makeIdentity();
+ sampler._revision = 0;
+ }
+
+ /** Deallocate GPU objects associated with this model */
+ void releaseGLObjects(osg::State* state) const
+ {
+ for (unsigned s = 0; s< _commonSamplers.size(); ++s)
+ if (_commonSamplers[s].ownsTexture())
+ _commonSamplers[s]._texture->releaseGLObjects(state);
+
+ for (unsigned p = 0; p<_passes.size(); ++p)
+ _passes[p].releaseGLObjects(state);
+ }
+
+ /** Resize GL buffers associated with this model */
+ void resizeGLObjectBuffers(unsigned size)
+ {
+ for (unsigned s = 0; s< _commonSamplers.size(); ++s)
+ if (_commonSamplers[s].ownsTexture())
+ _commonSamplers[s]._texture->resizeGLObjectBuffers(size);
+
+ for (unsigned p = 0; p<_passes.size(); ++p)
+ _passes[p].resizeGLObjectBuffers(size);
+ }
+ };
+
+} }