Finish Service Implementation

In this exercise, you'll learn to create a new script library in which you'll implement some CPU compute code that executes part of the compute workload.

Implement Functions

  1. Go to the text editor and find the script app_service_workload_analyze_terrain_util.ssl.

    Instead of copying each function in, one-by-one, in this exercise we'll copy in the entire script source code and then review the functions in the next section.

  2. Find the function named Execute.

    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function bool Execute( CommandPresentationModuleInfo commandInfo )
    {
       auto Str a_oName;
       GetName( a_oName );
    
       auto StrList a_slMessages;
       LibAppServiceBuild.GenerateServiceHeader(
          a_oName.Value, a_slMessages );
    
       a_slMessages.Add( "Executed service: " + a_oName.Value );
    
       LibAppServiceBuild.Out( a_slMessages );
    
       return true;
    }
    
  3. Replace the entire Execute function with the following.

    Copy Text To Clipboard

    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function void TerminateBuild( BuildInputs p_oInputs, string p_sFail, Program p_oProgram )
    {
       p_oInputs.m_oDevice.DeleteProgram( p_oProgram.GetHandle() );
    
       string a_sMessage = "Error. Call to " + p_sFail + " failed!";
       p_oInputs.m_slMessages.Add( a_sMessage );
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function bool Execute( CommandPresentationModuleInfo commandInfo )
    {
       auto Str a_oName;
       GetName( a_oName );
    
       //////////////////////////////////////////////////
       // Initialize source data and messages.
       //////////////////////////////////////////////////
    
       auto StrList a_slMessages;
       LibAppServiceBuild.GenerateServiceHeader(
          a_oName.Value, a_slMessages );
    
       auto FilePath a_oElevationDataPath = new FilePath(
          Application.GetApplicationDocumentsPath() );
       a_oElevationDataPath.AppendPath( "Library" );
       a_oElevationDataPath.AppendPath( "Textures" );
       a_oElevationDataPath.AppendPath( "Elevation" );
       a_oElevationDataPath.AppendPathWithFile( "USGS_13_n49w122.tif" );
    
       auto FileNode a_oData;
       a_oData.Name = a_oElevationDataPath.GetFileName();
       a_oData.FilePath = a_oElevationDataPath.GetPath();
    
       //////////////////////////////////////////////////
       // Declare and initialize build objects.
       //////////////////////////////////////////////////
    
       bool a_bDebug = true;
       int a_nDebugMaxChunksX = 4;
       int a_nDebugMaxChunksY = 4;
    
       auto BuildInputs a_oInputs;
       auto BuildOutputs a_oOutputs;
    
       a_oInputs.m_slMessages = a_slMessages;
       a_oInputs.m_oData = a_oData;
       a_oInputs.m_eMinGlslVersion = Enum.ShadingLanguageVersion_430();
    
       Render3D a_oAccel = Application.GetAccelerate3D();
       auto RenderInfo a_oRenderInfo = new RenderInfo( a_oAccel );
    
       // Initialize build object pointers.
       a_oInputs.m_oDevice = a_oAccel;
       a_oInputs.m_oRenderInfo = a_oRenderInfo;
    
       // Initialize build object pointers.
       a_oInputs.m_oModel = Model;
    
       auto FilePath a_oSrcDataPath = new FilePath( a_oData.FilePath );
       a_oSrcDataPath.ResolveToModel( a_oData );
    
       // This scale factor works for most everything.
       // https://sciencing.com/convert-distances-degrees-meters-7858322.phpl
       // Each degree of latitude and longitude covers this many meters.
       // This number might need to be adjusted for elevation
       // data of different resolutions. Ex: we are working
       // on 10 meter GEOTIFF data now, but will this still
       // work for 1 meter GEOTIFF data?
       a_oInputs.m_nScaleFactor = 111139;
    
       // Set the source data path and build folder.
       a_oInputs.m_oSourcePath.SetPath( a_oSrcDataPath.GetPath() );
       a_oInputs.m_sBuildFolder = a_oSrcDataPath.GetFileNameNoExtension();
    
       //////////////////////////////////////////////////
       // Create the paths to the arrays, maps, and analysis.
       //////////////////////////////////////////////////
    
       // These are directories where we output the
       // imagery generated by the build process.
       // Project = contains the following sub-folders:
       // Arrays = array textures
       // Maps  = 2D overview textures
       // Analysis = HTML report
    
       auto FilePath a_oProjectPath = new FilePath( a_oElevationDataPath.GetFilePath() );
       a_oProjectPath.AppendPath( a_oInputs.m_sBuildFolder );
       //Console.Out( a_oProjectPath.GetPath() );
    
       Application.CreateDirectory( a_oProjectPath.GetPath() );
       a_oInputs.m_slMessages.Add( "Created directory: " + a_oProjectPath.GetPath() );
    
       auto FilePath a_oArraysPath = new FilePath( a_oElevationDataPath.GetFilePath() );
       a_oArraysPath.AppendPath( a_oInputs.m_sBuildFolder );
       a_oArraysPath.AppendPath( "Arrays" );
       a_oInputs.m_oArraysPath.SetPath( a_oArraysPath.GetPath() );
    
       Application.CreateDirectory( a_oArraysPath.GetPath() );
       a_oInputs.m_slMessages.Add( "Created directory: " + a_oArraysPath.GetPath() );
       //Console.Out( a_oArraysPath.GetPath() );
    
       auto FilePath a_oMapsPath = new FilePath( a_oElevationDataPath.GetFilePath() );
       a_oMapsPath.AppendPath( a_oInputs.m_sBuildFolder );
       a_oMapsPath.AppendPath( "Maps" );
       a_oInputs.m_oMapsPath.SetPath( a_oMapsPath.GetPath() );
       a_oInputs.m_slMessages.Add( "Created directory: " + a_oMapsPath.GetPath() );
       //Console.Out( a_oMapsPath.GetPath() );
    
       Application.CreateDirectory( a_oMapsPath.GetPath() );
    
       auto FilePath a_oAnalysisPath = new FilePath( a_oElevationDataPath.GetFilePath() );
       a_oAnalysisPath.AppendPath( a_oInputs.m_sBuildFolder );
       a_oAnalysisPath.AppendPath( "Analysis" );
       a_oInputs.m_oAnalysisPath.SetPath( a_oAnalysisPath.GetPath() );
       a_oInputs.m_slMessages.Add( "Created directory: " + a_oAnalysisPath.GetPath() );
       //Console.Out( a_oAnalysisPath.GetPath() );
    
       Application.CreateDirectory( a_oAnalysisPath.GetPath() );
    
       auto FilePath a_oImagesPath = new FilePath( a_oAnalysisPath.GetPath() );
       a_oImagesPath.AppendPath( "Images" );
       a_oInputs.m_oImagesPath.SetPath( a_oImagesPath.GetPath() );
       a_oInputs.m_slMessages.Add( "Created directory: " + a_oImagesPath.GetPath() );
       //Console.Out( a_oImagesPath.GetPath() );
    
       Application.CreateDirectory( a_oImagesPath.GetPath() );
    
       //return false;
    
       //////////////////////////////////////////////////
       // Compute area overlap information about the terrain.
       //////////////////////////////////////////////////
    
       // Load metadata.
       PluginDomainGIS.LoadGeotiffInfo(
          a_oInputs.m_oSourcePath.GetPath(), a_oInputs.m_oGdalImageInfo );
    
       int a_nMaxChunksX = a_oInputs.m_oGdalImageInfo.GetChunkCountX();
       int a_nMaxChunksY = a_oInputs.m_oGdalImageInfo.GetChunkCountY();
    
       // By default, we'll process the entire GEOTIFF.
       a_oInputs.m_oLens.X = 0;
       a_oInputs.m_oLens.Y = 0;
       a_oInputs.m_oLens.Z = a_nMaxChunksX;
       a_oInputs.m_oLens.W = a_nMaxChunksY;
    
       // If you want to debug...
       // The values here must be between 0 and a_nMaxChunksX
       // and 0 and a_nMaxChunksY. .Z and .W must be at least
       // 1 greater than X and Y.
       if( a_bDebug )
       {
          a_oInputs.m_oLens.X = 0;
          a_oInputs.m_oLens.Y = 0;
          a_oInputs.m_oLens.Z = a_nDebugMaxChunksX;
          a_oInputs.m_oLens.W = a_nDebugMaxChunksY;
       }
    
       // Clamp the values below to make sure we are
       // totally in range. Otherwise we might extract
       // data that is out of range. We set the
       // correct max values for .Z and .W in the code above.
       a_oInputs.m_oLens.X = Math.ClampInt32(
          a_oInputs.m_oLens.X, 0, a_nMaxChunksX - 1 );
       a_oInputs.m_oLens.Y = Math.ClampInt32(
          a_oInputs.m_oLens.Y, 0, a_nMaxChunksY - 1 );
       a_oInputs.m_oLens.Z = Math.ClampInt32(
          a_oInputs.m_oLens.Z, 0, a_nMaxChunksX );
       a_oInputs.m_oLens.W = Math.ClampInt32(
          a_oInputs.m_oLens.W, 0, a_nMaxChunksY );
    
       /*
       Console.Out( a_oInputs.m_oLens.X );
       Console.Out( a_oInputs.m_oLens.Y );
       Console.Out( a_oInputs.m_oLens.Z );
       Console.Out( a_oInputs.m_oLens.W );
       */
    
       // https://opengl.gpuinfo.org/displaycapability.php?name=GL_MAX_ARRAY_TEXTURE_LAYERS
       // It looks like the vast majority of modern GPUs
       // support array textures with up to 2048 slices (layers).
       // Check how many array texture slices (layers)
       // are supported and make sure we don't exceed
       // that limit.
       auto Int32Array a_aiDims;
       int GL_MAX_ARRAY_TEXTURE_LAYERS = 0x88FF; // From glext.h...
       a_oInputs.m_oDevice.QueryDeviceParameterInt(
          GL_MAX_ARRAY_TEXTURE_LAYERS, a_aiDims );
       int a_nMaxSlices = a_aiDims.GetFirst();
    
       int a_nSliceCount = a_oInputs.m_oLens.Z * a_oInputs.m_oLens.W;
       if( a_nSliceCount > a_nMaxSlices )
       {
          string a_sMessage = "Maximum supported array slices on your GPU is " + a_nMaxSlices + ". " +
             "You requested " + a_nSliceCount + " slices.";
          a_oInputs.m_slMessages.Add( a_sMessage );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.ComputeTerrainInfo( a_oInputs );
    
       //////////////////////////////////////////////////
       // Generate FP32 tiles from the area of interest.
       //////////////////////////////////////////////////
    
       uint64 a_uStartCreateArrayTexture = Time.GetTickCountUint64();
    
       // We're going to feed this data to the compute shaders.
    
       // Build an array texture that contains a single
       // slice for each tile. This texture is basically
       // like a film-strip and it allows us to send
       // all the data to the GPU at once, whereupon
       // we can run whatever shaders we like. We'll
       // use this to generate all the other images we need.
       int a_nChunkDimX = a_oInputs.m_oGdalImageInfo.GetChunkDimX();
       int a_nChunkDimY = a_oInputs.m_oGdalImageInfo.GetChunkDimY();
    
       //Console.Out( "a_nSliceCount " + a_nSliceCount );
    
       a_oInputs.m_slMessages.Add(
          "Building array texture of dimensions " +
          a_nChunkDimX + "x" + a_nChunkDimY + " with " + a_nSliceCount + " slices." );
    
       auto Image a_oSrcTiles;
       bool a_bAlloc = a_oSrcTiles.AllocateBuffer(
          a_nChunkDimX, a_nChunkDimY * a_nSliceCount, Enum.IPF_FP32() );
       if( !( a_bAlloc ) )
       {
          a_oInputs.m_slMessages.Add(
             "Error. Source data allocation failed! Requested: " +
             a_nChunkDimX + "x" + a_nChunkDimY * a_nSliceCount + " pixels." );
          //Console.Alert( a_oInputs.m_slMessages.GetLast() );
          return false;
       }
       else
       {
          a_oInputs.m_slMessages.Add(
             "Source data allocation succeeded! Requested: " +
             a_nChunkDimX + "x" + a_nChunkDimY * a_nSliceCount + " pixels." );
       }
    
       bool a_bOpen = PluginDomainGIS.OpenGdalDataset(
          a_oInputs.m_oSourcePath.GetPath(), a_oInputs.m_oGdalDataset );
    
       if( !( a_bOpen ) )
       {
          string a_sMessage = "Unable to open elevation data: " +
             a_oInputs.m_oSourcePath.GetPath();
          a_oInputs.m_slMessages.Add( a_sMessage );
          return false;
       }
    
       bool a_bAnalysis =
          LibTerrainAnalysisCpuImage.BuildArrayTextureFromGeotiff(
             a_oInputs, a_oSrcTiles );
    
       if( !( a_bAnalysis ) )
       {
          string a_sMessage =
             "Error. Call to BuildArrayTextureFromGeotiff() failed!";
          a_oInputs.m_slMessages.Add( a_sMessage );
          //Console.Alert( a_sMessage );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.SaveTiles(
          a_oInputs, a_oSrcTiles, "elevation_tiles.image" );
    
       if( !( a_oInputs.m_afMinMaxElevation.GetCount() ) )
       {
          string a_sMessage =
             "Error. a_oInputs.m_afMinMaxElevation is empty!";
          a_oInputs.m_slMessages.Add( a_sMessage );
          //Console.Alert( a_sMessage );
          return false;
       }
    
       uint64 a_uEndCreateArrayTexture = Time.GetTickCountUint64();
       uint a_uArrayTextureExecTime =
          a_uEndCreateArrayTexture - a_uStartCreateArrayTexture;
    
       a_oInputs.m_slMessages.Add(
          "Created Array Texture From GEOTIFF In: " + a_uArrayTextureExecTime / 1000 + " seconds." );
    
       //LibFloat32Array.Out( a_oInputs.m_afMinMaxElevation );
    
       //////////////////////////////////////////////////
       // Finalize map coverage details.
       //////////////////////////////////////////////////
    
       a_oInputs.m_viMapInfo.X = a_oInputs.m_viDstRange.Z;
       a_oInputs.m_viMapInfo.Y = a_oInputs.m_viDstRange.W;
       a_oInputs.m_viMapInfo.Z = a_nChunkDimX;
       a_oInputs.m_viMapInfo.W = a_nChunkDimY;
       a_oInputs.m_iMaxBatchSize = 500; // Only affects openness shader...
    
       //////////////////////////////////////////////////
       // Generate and save rgba versions of the elevation data.
       //////////////////////////////////////////////////
    
       uint64 a_uStartCreateRgbaTextures = Time.GetTickCountUint64();
    
       // Declare a Program that we will use to do work.
       auto Program a_oElevationProgram;
       a_oElevationProgram.Name = "QuantizeElevationComputeShader";
       a_oElevationProgram.PrintCompileStatus = false;
       int a_nDestroyProgram = -1;
    
       // Set the GLSL compiler version.
       a_oElevationProgram.ShadingLanguageVersion =
          a_oInputs.m_eMinGlslVersion;
    
       auto Image a_oElevationTiles = new Image(
          a_oSrcTiles.Width, a_oSrcTiles.Height, Enum.IPF_8888_ARGB() );
    
       bool a_bCreateElevationTiles =
          LibTerrainAnalysisGpuImage.CreateElevationTiles(
             a_oInputs, a_oElevationProgram, a_oSrcTiles, a_oElevationTiles );
    
       if( !( a_bCreateElevationTiles ) )
       {
          TerminateBuild(
             a_oInputs, "CreateElevationTiles( ... )", a_oElevationProgram );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.SaveTiles(
          a_oInputs, a_oElevationTiles, "elevation_tiles.png" );
       a_oOutputs.m_apArrayImages.Refers( a_oElevationTiles );
    
       // Save an overview map of the elevation tiles.
       auto Image a_oElevationMap;
       bool a_bCreateElevationMap = LibTerrainAnalysisCpuImage.CreateMap(
          a_oInputs, a_oElevationTiles, a_oElevationMap );
    
       if( !( a_bCreateElevationMap ) )
       {
          TerminateBuild(
             a_oInputs, "LibTerrainAnalysisCpuImage.CreateMap( ... )", a_oElevationProgram );
          return false;
       }
    
       a_oInputs.m_sElevationMapFilePath = "elevation_map.png";
       LibTerrainAnalysisCpuImage.SaveMap(
          a_oInputs, a_oElevationMap, a_oInputs.m_sElevationMapFilePath );
       a_oOutputs.m_apMapImages.Refers( a_oElevationMap );
    
       // Destroy Program.
       a_nDestroyProgram = a_oElevationProgram.Delete( a_oRenderInfo );
       a_oInputs.m_slMessages.Add( "Destroyed shader program at ID: <" + a_nDestroyProgram + ">." );
    
       uint64 a_uEndCreateRgbaTextures = Time.GetTickCountUint64();
       uint a_uRgbaTextureExecTime =
          a_uEndCreateRgbaTextures - a_uStartCreateRgbaTextures;
    
       a_oInputs.m_slMessages.Add(
          "Created RGBA Textures In: " + a_uRgbaTextureExecTime / 1000 + " seconds." );
    
       //////////////////////////////////////////////////
       // Generate and save aspect map images. This shows slope aspect n-e-s-w...
       //////////////////////////////////////////////////
    
       uint64 a_uStartCreateAspectTextures = Time.GetTickCountUint64();
    
       auto Program a_oAspectProgram;
       a_oAspectProgram.Name = "AspectComputeShader";
       a_oAspectProgram.PrintCompileStatus = false;
    
       // Set the GLSL compiler version.
       a_oAspectProgram.ShadingLanguageVersion =
          a_oInputs.m_eMinGlslVersion;
    
       auto Image a_oAspectTiles = new Image(
          a_oSrcTiles.Width, a_oSrcTiles.Height, Enum.IPF_8888_ARGB() );
    
       bool a_bCreateAspectTiles =
          LibTerrainAnalysisGpuImage.CreateAspectTiles(
             a_oInputs, a_oAspectProgram, a_oSrcTiles, a_oAspectTiles );
    
       if( !( a_bCreateAspectTiles ) )
       {
          TerminateBuild(
             a_oInputs, "CreateAspectTiles( ... )", a_oAspectProgram );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.SaveTiles(
          a_oInputs, a_oAspectTiles, "aspect_tiles.png" );
       a_oOutputs.m_apArrayImages.Refers( a_oAspectTiles );
    
       // Save an overview map of the normal tiles.
       auto Image a_oAspectMap;
       bool a_bCreateAspectMap = LibTerrainAnalysisCpuImage.CreateMap(
          a_oInputs, a_oAspectTiles, a_oAspectMap );
    
       if( !( a_bCreateAspectMap ) )
       {
          TerminateBuild(
             a_oInputs, "LibTerrainAnalysisCpuImage.CreateMap( ... )", a_oAspectProgram );
          return false;
       }
    
       a_oInputs.m_sAspectMapFilePath = "aspect_map.png";
       LibTerrainAnalysisCpuImage.SaveMap(
          a_oInputs, a_oAspectMap, a_oInputs.m_sAspectMapFilePath );
       a_oOutputs.m_apMapImages.Refers( a_oAspectMap );
    
       // Destroy Program.
       a_nDestroyProgram = a_oAspectProgram.Delete( a_oRenderInfo );
       a_oInputs.m_slMessages.Add( "Destroyed shader program at ID: <" + a_nDestroyProgram + ">." );
    
       uint64 a_uEndCreateAspectTextures = Time.GetTickCountUint64();
       uint a_uAspectTextureExecTime =
          a_uEndCreateAspectTextures - a_uStartCreateAspectTextures;
    
       a_oInputs.m_slMessages.Add(
          "Created Aspect Textures In: " + a_uAspectTextureExecTime / 1000 + " seconds." );
    
       //////////////////////////////////////////////////
       // Generate and save slope angle images. This shows slope steepness.
       //////////////////////////////////////////////////
    
       uint64 a_uStartCreateSlopeTextures = Time.GetTickCountUint64();
    
       auto Program a_oSlopeProgram;
       a_oSlopeProgram.Name = "SlopeComputeShader";
       a_oSlopeProgram.PrintCompileStatus = false;
    
       // Set the GLSL compiler version.
       a_oSlopeProgram.ShadingLanguageVersion =
          a_oInputs.m_eMinGlslVersion;
    
       // Get the minimum and maximum
       // slope angle values for each slice.
       LibTerrainAnalysisCpuImage.GetMinMaxZFromNormals(
          a_oAspectTiles, a_oInputs.m_afMinMaxNormals );
    
       //LibFloat32Array.Out( a_oInputs.m_afMinMaxNormals );
       //return;
    
       auto Image a_oSlopeTiles = new Image(
          a_oSrcTiles.Width, a_oSrcTiles.Height, Enum.IPF_8888_ARGB() );
    
       bool a_bCreateSlopeTiles =
          LibTerrainAnalysisGpuImage.CreateSlopeTiles(
             a_oInputs, a_oSlopeProgram, a_oSrcTiles, a_oSlopeTiles );
    
       if( !( a_bCreateSlopeTiles ) )
       {
          TerminateBuild(
             a_oInputs, "CreateSlopeTiles( ... )", a_oSlopeProgram );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.SaveTiles(
          a_oInputs, a_oSlopeTiles, "slope_tiles.png" );
       a_oOutputs.m_apArrayImages.Refers( a_oSlopeTiles );
    
       // Save an overview map of the slope angle map tiles.
       auto Image a_oSlopeMap;
       bool a_bCreateSlopeMap = LibTerrainAnalysisCpuImage.CreateMap(
          a_oInputs, a_oSlopeTiles, a_oSlopeMap );
    
       if( !( a_bCreateSlopeMap ) )
       {
          TerminateBuild(
             a_oInputs, "LibTerrainAnalysisCpuImage.CreateMap( ... )", a_oSlopeProgram );
          return false;
       }
    
       a_oInputs.m_sSlopeMapFilePath = "slope_map.png";
       LibTerrainAnalysisCpuImage.SaveMap(
          a_oInputs, a_oSlopeMap, a_oInputs.m_sSlopeMapFilePath );
       a_oOutputs.m_apMapImages.Refers( a_oSlopeMap );
    
       // Destroy Program.
       a_nDestroyProgram = a_oSlopeProgram.Delete( a_oRenderInfo );
       a_oInputs.m_slMessages.Add( "Destroyed shader program at ID: <" + a_nDestroyProgram + ">." );
    
       uint64 a_uEndCreateSlopeTextures = Time.GetTickCountUint64();
       uint a_uSlopeTextureExecTime =
          a_uEndCreateSlopeTextures - a_uStartCreateSlopeTextures;
    
       a_oInputs.m_slMessages.Add(
          "Created Slope Textures In: " + a_uSlopeTextureExecTime / 1000 + " seconds." );
    
       //////////////////////////////////////////////////
       // Generate and save topographic openness images.
       //////////////////////////////////////////////////
    
       uint64 a_uStartCreateOpennessTextures = Time.GetTickCountUint64();
    
       auto Program a_oOpennessProgram;
       a_oOpennessProgram.Name = "OpennessComputeShader";
       a_oOpennessProgram.PrintCompileStatus = false;
    
       // Set the GLSL compiler version.
       a_oOpennessProgram.ShadingLanguageVersion =
          a_oInputs.m_eMinGlslVersion;
    
       auto Image a_oOpennessTiles = new Image(
          a_oSrcTiles.Width, a_oSrcTiles.Height, Enum.IPF_8888_ARGB() );
    
       bool a_bCreateOpennessTiles =
          LibTerrainAnalysisGpuImage.CreateOpennessTiles(
             a_oInputs, a_oOpennessProgram, a_oSrcTiles, a_oOpennessTiles );
    
       if( !( a_bCreateOpennessTiles ) )
       {
          TerminateBuild(
             a_oInputs, "CreateOpennessTiles( ... )", a_oOpennessProgram );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.SaveTiles(
          a_oInputs, a_oOpennessTiles, "openness_tiles.png" );
       a_oOutputs.m_apArrayImages.Refers( a_oOpennessTiles );
    
       // Save an overview map of the openness map tiles.
       auto Image a_oOpennessMap;
       bool a_bCreateOpennessMap = LibTerrainAnalysisCpuImage.CreateMap(
          a_oInputs, a_oOpennessTiles, a_oOpennessMap );
    
       if( !( a_bCreateOpennessMap ) )
       {
          TerminateBuild(
             a_oInputs, "LibTerrainAnalysisCpuImage.CreateMap( ... )", a_oOpennessProgram );
          return false;
       }
    
       a_oInputs.m_sOpennessMapFilePath = "openness_map.png";
       LibTerrainAnalysisCpuImage.SaveMap(
          a_oInputs, a_oOpennessMap, a_oInputs.m_sOpennessMapFilePath );
       a_oOutputs.m_apMapImages.Refers( a_oOpennessMap );
    
       // Destroy Program.
       a_nDestroyProgram = a_oOpennessProgram.Delete( a_oRenderInfo );
       a_oInputs.m_slMessages.Add( "Destroyed shader program at ID: <" + a_nDestroyProgram + ">." );
    
       uint64 a_uEndCreateOpennessTextures = Time.GetTickCountUint64();
       uint a_uOpennessTextureExecTime =
          a_uEndCreateOpennessTextures - a_uStartCreateOpennessTextures;
    
       a_oInputs.m_slMessages.Add(
          "Created Openness Textures In: " + a_uOpennessTextureExecTime / 1000 + " seconds." );
    
       //////////////////////////////////////////////////
       // Generate and save surface area images.
       //////////////////////////////////////////////////
    
       uint64 a_uStartCreateSurfaceTextures = Time.GetTickCountUint64();
    
       auto Program a_oSurfaceProgram;
       a_oSurfaceProgram.Name = "SurfaceAreaComputeShader";
       a_oSurfaceProgram.PrintCompileStatus = false;
    
       // Set the GLSL compiler version.
       a_oSurfaceProgram.ShadingLanguageVersion =
          a_oInputs.m_eMinGlslVersion;
    
       auto Image a_oSurfaceTiles = new Image(
          a_oSrcTiles.Width, a_oSrcTiles.Height, Enum.IPF_FP32() );
    
       bool a_bCreateSurfaceTiles =
          LibTerrainAnalysisGpuImage.CreateSurfaceTiles(
            a_oInputs, a_oSurfaceProgram, a_oSrcTiles, a_oSurfaceTiles );
    
       if( !( a_bCreateSurfaceTiles ) )
       {
          TerminateBuild(
             a_oInputs, "CreateSurfaceTiles( ... )", a_oSurfaceProgram );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.SaveTiles(
          a_oInputs, a_oSurfaceTiles, "surface_tiles.image" );
       a_oOutputs.m_oSurfaceTiles = a_oSurfaceTiles;
    
       // This shader doesn't create an RGBA image for us.
       // Quantize and save to disk since as a .PNG we can't look at
       // the .image file directly. This is useful for debugging.
       // This image should be mostly dark, but it will resemble
       // the slope angle image if inverted in PhotoShop(R) or Gimp(R).
       string a_sSurfaceQuantizedFilename = "surface_tiles.png";
       auto FilePath a_oSurfaceQuantizedPath =
          new FilePath( a_oArraysPath.GetPath() );
       a_oSurfaceQuantizedPath.RemoveEndSeparator();
       a_oSurfaceQuantizedPath.AppendPathWithFile(
          a_sSurfaceQuantizedFilename );
    
       auto Image a_oSurfaceTilesQuantized;
       LibTerrainAnalysisCpuImage.ConvertFloat32ToRgba8888(
          a_oInputs,
          a_oSurfaceTiles,
          a_oSurfaceTilesQuantized,
          a_oSurfaceQuantizedPath,
          a_oInputs.m_slMessages );
    
       a_oOutputs.m_apArrayImages.Refers( a_oSurfaceTilesQuantized );
    
       // Save an overview map of the slope angle map tiles.
       auto Image a_oSurfaceMap;
       bool a_bCreateSurfaceMap = LibTerrainAnalysisCpuImage.CreateMap(
          a_oInputs, a_oSurfaceTilesQuantized, a_oSurfaceMap );
    
       if( !( a_bCreateSurfaceMap ) )
       {
          TerminateBuild(
             a_oInputs, "LibTerrainAnalysisCpuImage.CreateMap( ... )", a_oSurfaceProgram );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.SaveMap(
          a_oInputs, a_oSurfaceMap, "surface_map.png" );
       a_oOutputs.m_apMapImages.Refers( a_oSurfaceMap );
    
       // Destroy Program.
       a_nDestroyProgram = a_oSurfaceProgram.Delete( a_oRenderInfo );
       a_oInputs.m_slMessages.Add( "Destroyed shader program at ID: <" + a_nDestroyProgram + ">." );
    
       uint64 a_uEndCreateSurfaceTextures = Time.GetTickCountUint64();
       uint a_uSurfaceTextureExecTime =
          a_uEndCreateSurfaceTextures - a_uStartCreateSurfaceTextures;
    
       a_oInputs.m_slMessages.Add(
          "Created Surface Textures In: " + a_uSurfaceTextureExecTime / 1000 + " seconds." );
    
       //////////////////////////////////////////////////
       // Use prefix-sum shader to sum slope angles.
       //////////////////////////////////////////////////
    
       uint64 a_uStartCreateSumTextures = Time.GetTickCountUint64();
    
       auto Program a_oSumProgram;
       a_oSumProgram.Name = "PrefixSumComputeShader";
       a_oSumProgram.PrintCompileStatus = false;
    
       // Set the GLSL compiler version.
       a_oSumProgram.ShadingLanguageVersion =
          a_oInputs.m_eMinGlslVersion;
    
       auto Image a_oSumTiles = new Image(
          a_oSrcTiles.Width, a_oSrcTiles.Height, Enum.IPF_FP32() );
    
       bool a_bCreateSumTiles =
          LibTerrainAnalysisGpuImage.CreateSumTiles(
             a_oInputs, a_oSumProgram, a_oSrcTiles, a_oSumTiles );
    
       if( !( a_bCreateSumTiles ) )
       {
          TerminateBuild(
             a_oInputs, "CreateSumTiles( ... )", a_oSumProgram );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.SaveTiles(
          a_oInputs, a_oSumTiles, "sum_tiles.image" );
       a_oOutputs.m_oSumTiles = a_oSumTiles;
    
       // This shader doesn't create an RGBA image for us.
       // Quantize and save to disk since as a .PNG we can't look at
       // the .image file directly. This is useful for debugging.
       // This image should be mostly dark, but it will resemble
       // the slope angle image if inverted in PhotoShop(R) or Gimp(R).
       string a_sSumQuantizedFilename = "sum_tiles.png";
       auto FilePath a_oSumQuantizedPath =
          new FilePath( a_oArraysPath.GetPath() );
       a_oSumQuantizedPath.RemoveEndSeparator();
       a_oSumQuantizedPath.AppendPathWithFile(
          a_sSumQuantizedFilename );
    
       auto Image a_oSumTilesQuantized;
       LibTerrainAnalysisCpuImage.ConvertFloat32ToRgba8888(
          a_oInputs,
          a_oSumTiles,
          a_oSumTilesQuantized,
          a_oSumQuantizedPath,
          a_oInputs.m_slMessages );
    
       a_oOutputs.m_apArrayImages.Refers( a_oSumTilesQuantized );
    
       // Save an overview map of the slope angle map tiles.
       auto Image a_oSumMap;
       bool a_bCreateSumMap = LibTerrainAnalysisCpuImage.CreateMap(
          a_oInputs, a_oSumTilesQuantized, a_oSumMap );
    
       if( !( a_bCreateSumMap ) )
       {
          TerminateBuild(
             a_oInputs, "LibTerrainAnalysisCpuImage.CreateMap( ... )", a_oSumProgram );
          return false;
       }
    
       LibTerrainAnalysisCpuImage.SaveMap(
          a_oInputs, a_oSumMap, "sum_map.png" );
       a_oOutputs.m_apMapImages.Refers( a_oSumMap );
    
       // Destroy Program.
       a_nDestroyProgram = a_oSumProgram.Delete( a_oRenderInfo );
       a_oInputs.m_slMessages.Add( "Destroyed shader program at ID: <" + a_nDestroyProgram + ">." );
    
       uint64 a_uEndCreateSumTextures = Time.GetTickCountUint64();
       uint a_uSumTextureExecTime =
          a_uEndCreateSumTextures - a_uStartCreateSumTextures;
    
       a_oInputs.m_slMessages.Add(
          "Created Sum Textures In: " + a_uSumTextureExecTime / 1000 + " seconds." );
    
       //////////////////////////////////////////////////
       // Generate HTML report and open it.
       //////////////////////////////////////////////////
    
       LibTerrainAnalysisGenerateReport.GenerateReport( a_oInputs, a_oOutputs );
    
       //////////////////////////////////////////////////
       // Finish
       //////////////////////////////////////////////////
    
       a_oInputs.m_slMessages.AddBlank();
       a_oInputs.m_slMessages.Add( "Processed elevation data for: " + a_oData.Name );
       a_oInputs.m_slMessages.Add( "Service execution comleted: " + a_oName.Value );
    
       LibAppServiceBuild.Out( a_oInputs.m_slMessages );
    
       //Console.Out( a_oOutputs.m_oReportPath.GetPath() );
       Application.ShellExec( a_oOutputs.m_oReportPath.GetPath() );
    
       return true;
    }
    
  4. Save changes to the script.

Review Functions

Let's review the functions in this library.

Table 1.1. Review Script Contents

Function Description
TerminateBuild This function destroys the <Program> node and prints a build message in case the build terminates.
Execute

This function executes the service.

Table 1.2. In-Depth Discussion

Function Area Description
Initialize source data and messages. Prints a service message header and initializes the input GEOTIFF data to refer to the file we downloaded earlier.
Declare and initialize build objects. Configures debug state, declares build input and output objects, and initializes build input objects with messages, the rendering device, and source data.
Create the paths to the arrays, maps, and analysis. Creates project directories as sub-directories in the folder containing the GEOTIFF source data. The main project directory has the same name as the GEOTIFF, and this directory contains a list of sub-directories for storing array textures, 2D overhead maps textures, and the HTML that contains the report.
Compute area overlap information about the terrain. Determines which chunks of the GEOTIFF have been requested for analysis and makes sure that the requested chunks are within the bounds of the GEOTIFF's chunk structure. If a sub-region is requested, chunks outside the bounds of the GEOTIFF's chunk structure will be clamped to 0 - max chunks X and 0 - max chunks Y. Then the GPUs array texture support is analyzed, and the function generates an error message and returns if the requested number of chunks exceeds the maximum number of array slices supported by the device. Finally, information about the terrain is computed from the final computed region. For example: elevation data spacing might be 1-meter, or it might be 10-meters, and we need to compute and store this information so it can be used by the compute shaders.
Generate FP32 tiles from the area of interest. Generates an array texture from the GEOTIFF. GEOTIFF files are like maps that are subdivided into a 2D grid of chunks. We open the GEOTIFF file and stream the data out of the GEOTIFF and into a 32-bit floating point array texture. For example: if you select a 4x4 region for analysis, the resulting array texture will have 16 slices/layers.
Finalize map coverage details. Populates an <Int32Vector> with information about the range in meters being analyzed, as well as with the number of chunks requested for analysis. We also set the maximum batch size, which refers to the maximum number of array texture slices that can be processed by a compute shader. This value is only used by the compute shader that computes topographic openness because some GPUs don't want to run that shader on large numbers of array slices for some reason.
Generate and save rgba versions of the elevation data. Executes a compute shader that quantizes the floating point elevation data to grayscale so that it is compatible with the human visual system.
Generate and save aspect map images. This shows slope aspect n-e-s-w... Executes a compute shader that generates a normal map. For example: this allows a human analyst to know whether or not a slope is more north-facing than south facing, or more east-facing than west-facing.
Generate and save slope angle images. This shows slope steepness. Executes a compute shader that generates a map indicating steepness. Maximum steepness is shown as white, and flat terrain is shown as black, with varying shades of gray representing varying steepness.
Generate and save topographic openness images. Executes a compute shader that generates a map of topographic openness. This allows a human analyst to evaluate how open or enclosed the terrain is relative to other terrain nearby. It also allows the service to compute relative reductions in openness, which is an important statistic.
Generate and save surface area images. Executes a compute shader that generates a map of surface area. This allows a human analyst to evaluate how much snow the terrain might hold, and gives important clues about whether or not terrain is convoluted.
Use prefix-sum shader to sum slope angles. Executes a compute shader that generates a prefix sum to compute cumulative slope angles. This purpose of this analysis is not disclosed.
Generate HTML report and open it. Generates a small web application that allows a human analyst to click on areas of the map and review the analysis.
Finish Prints build messages to the output window and opens the HTML report.

Test Code Changes

  1. Return to the running Shader app.
  2. Select File » Run Service... from the main menu.

    The software displays the service selection dialog. Since we're dynamically loading this script, using the Desktop » Refresh Scripts command won't work. We need to invoke the service in order to compile the script.

    This is a picture of the service selection dialog.

    If there are script errors, you'll see the following dialog:

    This is a picture of script error dialog.

    You'll also see script compiler errors in the output window:

    D:\release6\scripts\app_service_analyze_terrain_cpu_image_processing_util.ssl(31) : error: undeclared identifier 'a'
    A runtime error ocurred:
    
    Error:       RUNTIME_STATUS_NULL_OBJECT_POINTER
    Function:    ApplicationRunService
    File:        D:\release6\scripts\app_service_file_menu_scripts.ssl
    Line:        1346
    
    Call Stack:
       ApplicationRunService()  app_service_file_menu_scripts.ssl(1346)
    
    Script execution has been stopped.
    

    If there are errors, return to the start of this tutorial and retrace your steps. Otherwise, continue to the next step.

  3. Hit Cancel to dimiss the service selection dialog.

    We'll execute the service in the next exercise.