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); + } + }; + +} }