Finish Build Command

NOTE: This exercise assumes you have completed the previous exercise.

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_octopus_workload_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 script imports.

    import library "app_service_assert_util.ssl";
    import library "app_service_console_util.ssl";
    import library "app_service_main_util.ssl";
    import library "type_file_path_algorithms.ssl";
    import library "type_float32_array_util.ssl";
    import library "type_image_channel_extract_util.ssl";
    import library "type_image_format_util.ssl";
    import library "type_image_util.ssl";
    import library "type_int32_array_util.ssl";
    import library "type_render3d_util.ssl";
    import library "type_service_enumeration_util.ssl";
    import library "type_str_list_util.ssl";
    import library "app_octopus_workload_terrain_classes.ssl";
    
  3. Replace everything in the script from the imports to the bottom of the script file, including the function Execute, with the following:

    (Do not replace the header at the top of the script and do not replace the library declaration!)

    Copy Text To Clipboard

    import library "app_service_assert_util.ssl";
    import library "app_service_console_util.ssl";
    import library "app_service_main_util.ssl";
    import library "type_file_path_algorithms.ssl";
    import library "type_float32_array_util.ssl";
    import library "type_image_channel_extract_util.ssl";
    import library "type_image_format_util.ssl";
    import library "type_image_util.ssl";
    import library "type_int32_array_util.ssl";
    import library "type_render3d_util.ssl";
    import library "type_service_enumeration_util.ssl";
    import library "type_str_list_util.ssl";
    
    import library "app_octopus_workload_terrain_classes.ssl";
    import library "app_octopus_workload_terrain_cpu_image_processing_util.ssl";
    import library "app_octopus_workload_terrain_gpu_image_processing_util.ssl";
    import library "app_octopus_workload_terrain_report_util.ssl";
    
    ///////////////////////////////////////////////////////////////////////////////
    // 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( FileNode p_oData, CommandPresentationModuleInfo p_oInfo, StrList p_slMessages )
    {
    //////////////////////////////////////////////////
    // Declare and initialize build objects.
    //////////////////////////////////////////////////
    
    WorkloadNode a_oWorkload = (WorkloadNode)p_oData.GetParent();
    
    bool a_bDebug = a_oWorkload.Debug;
    int a_nDebugMaxChunksX = 4;
    int a_nDebugMaxChunksY = 4;
    
    auto BuildInputs a_oInputs;
    auto BuildOutputs a_oOutputs;
    
    a_oInputs.m_slMessages = p_slMessages;
    a_oInputs.m_oData = p_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( p_oData.FilePath );
    a_oSrcDataPath.ResolveToModel( p_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( Model.Filename );
    a_oProjectPath.RemoveFileName();
    a_oProjectPath.AppendPath( a_oInputs.m_sBuildFolder );
    
    Application.CreateDirectory( a_oProjectPath.GetPath() );
    a_oInputs.m_slMessages.Add( "Created directory: " + a_oProjectPath.GetPath() );
    
    auto FilePath a_oArraysPath = new FilePath( Model.Filename );
    a_oArraysPath.RemoveFileName();
    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( Model.Filename );
    a_oMapsPath.RemoveFileName();
    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( Model.Filename );
    a_oAnalysisPath.RemoveFileName();
    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() );
    
    //////////////////////////////////////////////////
    // 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;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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 =
    LibAppOctopusWorkloadTerrainCpuImage.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;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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;
    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 =
    LibAppOctopusWorkloadTerrainGpuImage.CreateElevationTiles(
    a_oInputs, a_oElevationProgram, a_oSrcTiles, a_oElevationTiles );
    
    if( !( a_bCreateElevationTiles ) )
    {
    TerminateBuild(
    a_oInputs, "CreateElevationTiles( ... )", a_oElevationProgram );
    return false;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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 = LibAppOctopusWorkloadTerrainCpuImage.CreateMap(
    a_oInputs, a_oElevationTiles, a_oElevationMap );
    
    if( !( a_bCreateElevationMap ) )
    {
    TerminateBuild(
    a_oInputs, "LibAppOctopusWorkloadTerrainCpuImage.CreateMap( ... )", a_oElevationProgram );
    return false;
    }
    
    a_oInputs.m_sElevationMapFilePath = "elevation_map.png";
    LibAppOctopusWorkloadTerrainCpuImage.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;
    
    // 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 =
    LibAppOctopusWorkloadTerrainGpuImage.CreateAspectTiles(
    a_oInputs, a_oAspectProgram, a_oSrcTiles, a_oAspectTiles );
    
    if( !( a_bCreateAspectTiles ) )
    {
    TerminateBuild(
    a_oInputs, "CreateAspectTiles( ... )", a_oAspectProgram );
    return false;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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 = LibAppOctopusWorkloadTerrainCpuImage.CreateMap(
    a_oInputs, a_oAspectTiles, a_oAspectMap );
    
    if( !( a_bCreateAspectMap ) )
    {
    TerminateBuild(
    a_oInputs, "LibAppOctopusWorkloadTerrainCpuImage.CreateMap( ... )", a_oAspectProgram );
    return false;
    }
    
    a_oInputs.m_sAspectMapFilePath = "aspect_map.png";
    LibAppOctopusWorkloadTerrainCpuImage.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;
    
    // Set the GLSL compiler version.
    a_oSlopeProgram.ShadingLanguageVersion =
    a_oInputs.m_eMinGlslVersion;
    
    // Get the minimum and maximum
    // slope angle values for each slice.
    LibAppOctopusWorkloadTerrainCpuImage.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 =
    LibAppOctopusWorkloadTerrainGpuImage.CreateSlopeTiles(
    a_oInputs, a_oSlopeProgram, a_oSrcTiles, a_oSlopeTiles );
    
    if( !( a_bCreateSlopeTiles ) )
    {
    TerminateBuild(
    a_oInputs, "CreateSlopeTiles( ... )", a_oSlopeProgram );
    return false;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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 = LibAppOctopusWorkloadTerrainCpuImage.CreateMap(
    a_oInputs, a_oSlopeTiles, a_oSlopeMap );
    
    if( !( a_bCreateSlopeMap ) )
    {
    TerminateBuild(
    a_oInputs, "LibAppOctopusWorkloadTerrainCpuImage.CreateMap( ... )", a_oSlopeProgram );
    return false;
    }
    
    a_oInputs.m_sSlopeMapFilePath = "slope_map.png";
    LibAppOctopusWorkloadTerrainCpuImage.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;
    
    // 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 =
    LibAppOctopusWorkloadTerrainGpuImage.CreateOpennessTiles(
    a_oInputs, a_oOpennessProgram, a_oSrcTiles, a_oOpennessTiles );
    
    if( !( a_bCreateOpennessTiles ) )
    {
    TerminateBuild(
    a_oInputs, "CreateOpennessTiles( ... )", a_oOpennessProgram );
    return false;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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 = LibAppOctopusWorkloadTerrainCpuImage.CreateMap(
    a_oInputs, a_oOpennessTiles, a_oOpennessMap );
    
    if( !( a_bCreateOpennessMap ) )
    {
    TerminateBuild(
    a_oInputs, "LibAppOctopusWorkloadTerrainCpuImage.CreateMap( ... )", a_oOpennessProgram );
    return false;
    }
    
    a_oInputs.m_sOpennessMapFilePath = "openness_map.png";
    LibAppOctopusWorkloadTerrainCpuImage.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;
    
    // 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 =
    LibAppOctopusWorkloadTerrainGpuImage.CreateSurfaceTiles(
    a_oInputs, a_oSurfaceProgram, a_oSrcTiles, a_oSurfaceTiles );
    
    if( !( a_bCreateSurfaceTiles ) )
    {
    TerminateBuild(
    a_oInputs, "CreateSurfaceTiles( ... )", a_oSurfaceProgram );
    return false;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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;
    LibAppOctopusWorkloadTerrainCpuImage.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 = LibAppOctopusWorkloadTerrainCpuImage.CreateMap(
    a_oInputs, a_oSurfaceTilesQuantized, a_oSurfaceMap );
    
    if( !( a_bCreateSurfaceMap ) )
    {
    TerminateBuild(
    a_oInputs, "LibAppOctopusWorkloadTerrainCpuImage.CreateMap( ... )", a_oSurfaceProgram );
    return false;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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;
    
    // 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 =
    LibAppOctopusWorkloadTerrainGpuImage.CreateSumTiles(
    a_oInputs, a_oSumProgram, a_oSrcTiles, a_oSumTiles );
    
    if( !( a_bCreateSumTiles ) )
    {
    TerminateBuild(
    a_oInputs, "CreateSumTiles( ... )", a_oSumProgram );
    return false;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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;
    LibAppOctopusWorkloadTerrainCpuImage.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 = LibAppOctopusWorkloadTerrainCpuImage.CreateMap(
    a_oInputs, a_oSumTilesQuantized, a_oSumMap );
    
    if( !( a_bCreateSumMap ) )
    {
    TerminateBuild(
    a_oInputs, "LibAppOctopusWorkloadTerrainCpuImage.CreateMap( ... )", a_oSumProgram );
    return false;
    }
    
    LibAppOctopusWorkloadTerrainCpuImage.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.
    //////////////////////////////////////////////////
    
    LibAppOctopusWorkloadTerrainReport.GenerateReport( a_oInputs, a_oOutputs );
    
    //////////////////////////////////////////////////
    // Finish
    //////////////////////////////////////////////////
    
    a_oInputs.m_slMessages.AddBlank();
    a_oInputs.m_slMessages.Add( "Build completed." );
    
    p_slMessages.Add( "Processed elevation data for: " + p_oData.Name );
    
    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 is executed when the Octopus app build command remotely calls this function.

First, we have a few critical settings for debug that are currently enabled. This allows you to work on a small subset of the entire GEOTIFF if you need to debug (or if you only want to analyze a small area). Next, we instantiate build objects and verify the rendering device has GLSL #version 430 at minimum. We need GLSL #version 430 because this code will run compute shaders, and this is the minimum version of GLSL in which compute shader support is guaranteed.

Then we create the project directories so that we can write data to disk. We need to create them now because otherwise they won't exist, and then our disk writes will fail.

Next, we compute information about which parts of the GEOTIFF are overlapped by the region chosen by the user (for example: if this function is in debug mode). We also clamp the values to 0,0 and a_nMaxChunksX,a_nMaxChunksY to prevent bad read coordinates while we process the GEOTIFF. At the end, we check that the number of chunks requested by the user is not greater than the number of array texture slices supported by the hardware. At present, we're just sort of handling this in a rough way. In future iterations, we might find a way to handle this more elegantly.

After that, we build an array texture from the GEOTIFF tiles the user wishes to evaluate. In debug mode, this array texture might be fairly small. When processing the entire GEOTIFF, the array texture might have more than 1500 slices, and it could have a height of hundreds of thousands of pixels.

Once we have built the array texture, we run compute shaders that generate the analysis images from the elevation data.

Test Code Changes

  1. Return to the running Octopus app.
  2. Select Desktop » Refresh Scripts from the main menu. ( ALT + D + R )

    The application displays script compiler messages in the output window:

    Start loading scripts
    Done loading scripts; 10 loaded in 1.29 ms; avg 0.09
    

    If there are any script compiler errors, undo your changes in the text editor, go back to the previous step, and follow the instructions again. Here is an example of what error messages might look like:

    Start loading scripts
    D:\release6\scripts\app_shell_util.ssl(1770) : error: newline in constant
    Done loading scripts; 10 loaded in 1.29 ms; avg 0.09
    

    This exercise is complete. Please proceed to the next exercise.