Implement GPU Image Processing Library

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

Create A New Script Utility Library

  1. Return to the running Shader application.
  2. Select Desktop » Create Script... from the main menu.

    The software presents a wizard that helps you create a new script document.

    This is a picture of the create script wizard.
  3. Set Script Folder to Application Scripts Folder if it is not already set.
  4. Set Script Type to Function Library.
  5. Set Script Author to your name or company name.
  6. Set Script Description to This script contains functions for GPU image processing.

    Copy Text To Clipboard

    This script contains functions for GPU image processing.
  7. Set Library Name to LibTerrainAnalysisGpuImage.

    Copy Text To Clipboard

    LibTerrainAnalysisGpuImage
  8. Set File Name to app_service_analyze_terrain_gpu_image_processing_util.ssl.

    Copy Text To Clipboard

    app_service_analyze_terrain_gpu_image_processing_util.ssl
  9. Click OK or hit ENTER when you are finished.

    The software creates a new script and copies the path to the Windows® clipboard.

Open The Script Library

  1. Return to the text editor and select the option to open a file from disk.
  2. Select CTRL + V to paste the script command library file path (into the place in the dialog where you specify the file to open).
  3. Open the file.

    The script appears in the text editor.

    ///////////////////////////////////////////////////////////////////////////////
    //
    // $author           Scenomics LLC
    // $description      This script contains functions for GPU image processing.
    //
    // Copyright 2023 Scenomics LLC. All Rights Reserved.
    //
    ///////////////////////////////////////////////////////////////////////////////
    
    library LibTerrainAnalysisGpuImage;
    
    import library "app_service_assert_util.ssl";
    import library "app_service_console_util.ssl";
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function void MyFunction()
    {
    }
    

Implement Functions

  1. Find the script imports.

    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.

    import library "app_service_assert_util.ssl";
    import library "app_service_console_util.ssl";
    
  2. Replace everything in the script from the imports to the bottom of the script file, including the function MyFunction, 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_float32_array_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 "import library "app_service_workload_analyze_terrain_classes.ssl";
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function void GetBatchSizes( int p_nArraySlices, int p_nBatchSize, Int32Array p_aiBatchSizes )
    {
       while( p_nArraySlices > p_nBatchSize )
       {
          p_aiBatchSizes.Add( p_nBatchSize );
          p_nArraySlices -= p_nBatchSize;
       }
    
       p_aiBatchSizes.Add( p_nArraySlices );
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function bool CreateElevationTiles(
    
       BuildInputs p_oInputs,
       Program p_oProgram,
       Image p_oSrcTiles,
       Image p_oDstTiles
    
       )
    {
       // Declare a <FilePath> so we
       // can load and execute our shader source.
       string a_sComputeSrc = "avalanche_quantize_elevation_compute_shader.glsl";
    
       auto FilePath a_oShaderPath;
       a_oShaderPath.SetPath( LibAppServiceMain.GetShadersPath() );
       a_oShaderPath.AppendPath( "Avalanche Quantize Elevation" );
       a_oShaderPath.AppendPath( "430" );
       a_oShaderPath.AppendPathWithFile( a_sComputeSrc );
    
       bool a_bBindProgram = LibRender3D.BindComputeProgram(
          p_oInputs.m_oRenderInfo, p_oProgram, a_oShaderPath, p_oInputs.m_slMessages );
       if( !( a_bBindProgram ) )
       {
          return false;
       }
    
       // Set up uniforms.
       p_oInputs.m_slMessages.Add( "Setting shader uniforms..." );
    
       bool a_bSetConstant = false;
    
       a_bSetConstant = LibRender3D.SetFloat32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "format_min", 0.0, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       a_bSetConstant = LibRender3D.SetFloat32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "format_max", 1.0, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       int array_slices = p_oInputs.m_iArraySlices;
       a_bSetConstant = LibRender3D.SetInt32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "array_slices", array_slices, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       // Set up a shader buffer.
       p_oInputs.m_slMessages.Add( "Setting up shader buffer..." );
    
       auto ShaderBufferNode a_oShaderBuffer;
       a_oShaderBuffer.BufferSize = p_oInputs.m_afMinMaxElevation.SizeInBytes();
    
       auto ShaderBufferInfo a_oShaderBufferInfo;
       a_oShaderBufferInfo.m_pAccel = p_oInputs.m_oDevice;
       a_oShaderBufferInfo.m_pRenderInfo = p_oInputs.m_oRenderInfo;
       a_oShaderBufferInfo.m_pBuffer = a_oShaderBuffer;
       a_oShaderBufferInfo.m_pProgram = p_oProgram;
       a_oShaderBufferInfo.m_sBufferName = "data";
       a_oShaderBufferInfo.m_eBufferUsage = Enum.RBU_DynamicDraw();
    
       bool a_bCreatedBuffer = LibRender3D.CreateShaderBuffer(
          a_oShaderBufferInfo, p_oInputs.m_slMessages );
    
       if( !( a_bCreatedBuffer ) )
       {
          return false;
       }
    
       // Update the shader buffer with the min/max values.
       p_oInputs.m_slMessages.Add( "Uploading buffer data data to GPU..." );
       auto Float32Pointer min_max = p_oInputs.m_afMinMaxElevation.GetPointer();
       int a_nCopyDataToBuffer = p_oInputs.m_oDevice.WriteShaderSubBuffer( 0,
          p_oInputs.m_afMinMaxElevation.SizeInBytes(), min_max );
       p_oInputs.m_slMessages.Add( "Copy data to shader buffer: " + a_nCopyDataToBuffer );
    
       // Set up Textures.
       // Create the source Texture object.
       auto Texture a_oSrcTexture =
          LibRender3D.CreateImage2DArrayTexture( "src", p_oSrcTiles, false );
    
       // Create the destination Texture object.
       auto Texture a_oDstTexture =
          LibRender3D.CreateImage2DArrayTexture( "dst", p_oDstTiles, false );
    
       // Bind Textures.
       int a_nTexUnit = 0;
       bool a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oSrcTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       a_nTexUnit = 1;
       a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oDstTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       // Execute Program
       int a_nExecuteStatus = p_oInputs.m_oDevice.ExecuteProgram( true, 8, 8, 1, 32, 32, 1 );
       p_oInputs.m_slMessages.Add( "<Program> Execution status: " + a_nExecuteStatus );
    
       Image a_oSaveImage = (Image)a_oDstTexture.ImageList.GetFirst();
    
       p_oInputs.m_slMessages.Add( "Reading texture back from GPU..." );
       int a_nReadbackStatus = p_oInputs.m_oDevice.ReadTextureBuffer(
          a_oSaveImage, a_oDstTexture, 0 );
       p_oInputs.m_slMessages.Add( "Readback operation status: " +
          a_nReadbackStatus );
    
       // Clean up.
       p_oInputs.m_oDevice.DestroyTexture( a_oSrcTexture );
       p_oInputs.m_oDevice.DestroyTexture( a_oDstTexture );
       p_oInputs.m_oDevice.DestroyShaderBuffer( a_oShaderBuffer.GetParams() );
    
       return true;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function bool CreateAspectTiles(
    
       BuildInputs p_oInputs,
       Program p_oProgram,
       Image p_oSrcTiles,
       Image p_oDstTiles
    
       )
    {
       // Declare a <FilePath> so we
       // can load and execute our shader source.
       string a_sComputeSrc = "avalanche_normal_map_compute_shader.glsl";
    
       auto FilePath a_oShaderPath;
       a_oShaderPath.SetPath( LibAppServiceMain.GetShadersPath() );
       a_oShaderPath.AppendPath( "Avalanche Normal Map" );
       a_oShaderPath.AppendPath( "430" );
       a_oShaderPath.AppendPathWithFile( a_sComputeSrc );
    
       bool a_bBindProgram = LibRender3D.BindComputeProgram(
          p_oInputs.m_oRenderInfo, p_oProgram, a_oShaderPath, p_oInputs.m_slMessages );
       if( !( a_bBindProgram ) )
       {
          return false;
       }
    
       // Set up uniforms.
       p_oInputs.m_slMessages.Add( "Setting shader uniforms..." );
    
       bool a_bSetConstant = false;
    
       a_bSetConstant = LibRender3D.SetFloat32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "region_size_x", 10.0, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       a_bSetConstant = LibRender3D.SetFloat32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "region_size_y", 10.0, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       int array_slices = p_oInputs.m_iArraySlices;
       a_bSetConstant = LibRender3D.SetInt32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "array_slices", array_slices, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       // Set up Textures.
       // Create the source Texture object.
       auto Texture a_oSrcTexture =
          LibRender3D.CreateImage2DArrayTexture( "src", p_oSrcTiles, false );
    
       // Create the destination Texture object.
       auto Texture a_oDstTexture =
          LibRender3D.CreateImage2DArrayTexture( "dst", p_oDstTiles, false );
    
       // Bind Textures.
       int a_nTexUnit = 0;
       bool a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oSrcTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       a_nTexUnit = 1;
       a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oDstTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       // Execute Program
       int a_nExecuteStatus = p_oInputs.m_oDevice.ExecuteProgram( true, 8, 8, 1, 32, 32, 1 );
       p_oInputs.m_slMessages.Add( "Compute Shader Execution status: " + a_nExecuteStatus );
    
       Image a_oSaveImage = ( (Image)a_oDstTexture.ImageList.GetFirst() );
    
       p_oInputs.m_slMessages.Add( "Reading texture back from GPU..." );
       int a_nReadbackStatus = p_oInputs.m_oDevice.ReadTextureBuffer(
          a_oSaveImage, a_oDstTexture, 0 );
       p_oInputs.m_slMessages.Add( "Readback operation status: " +
          a_nReadbackStatus );
    
       // Clean up.
       p_oInputs.m_oDevice.DestroyTexture( a_oSrcTexture );
       p_oInputs.m_oDevice.DestroyTexture( a_oDstTexture );
    
       return true;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function bool CreateSlopeTiles(
    
       BuildInputs p_oInputs,
       Program p_oProgram,
       Image p_oSrcTiles,
       Image p_oDstTiles
    
       )
    {
       // Declare a <FilePath> so we
       // can load and execute our shader source.
       string a_sComputeSrc = "avalanche_slope_map_compute_shader.glsl";
    
       auto FilePath a_oShaderPath;
       a_oShaderPath.SetPath( LibAppServiceMain.GetShadersPath() );
       a_oShaderPath.AppendPath( "Avalanche Slope Map" );
       a_oShaderPath.AppendPath( "430" );
       a_oShaderPath.AppendPathWithFile( a_sComputeSrc );
    
       bool a_bBindProgram = LibRender3D.BindComputeProgram(
          p_oInputs.m_oRenderInfo, p_oProgram, a_oShaderPath, p_oInputs.m_slMessages );
       if( !( a_bBindProgram ) )
       {
          return false;
       }
    
       // Set up uniforms.
       p_oInputs.m_slMessages.Add( "Setting shader uniforms..." );
    
       bool a_bSetConstant = false;
    
       a_bSetConstant = LibRender3D.SetFloat32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "region_size_x", 10.0, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       a_bSetConstant = LibRender3D.SetFloat32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "region_size_y", 10.0, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       // Set up a shader buffer.
       p_oInputs.m_slMessages.Add( "Setting up shader buffer..." );
    
       auto ShaderBufferNode a_oShaderBuffer;
       a_oShaderBuffer.BufferSize = p_oInputs.m_afMinMaxNormals.SizeInBytes();
    
       auto ShaderBufferInfo a_oShaderBufferInfo;
       a_oShaderBufferInfo.m_pAccel = p_oInputs.m_oDevice;
       a_oShaderBufferInfo.m_pRenderInfo = p_oInputs.m_oRenderInfo;
       a_oShaderBufferInfo.m_pBuffer = a_oShaderBuffer;
       a_oShaderBufferInfo.m_pProgram = p_oProgram;
       a_oShaderBufferInfo.m_sBufferName = "data";
       a_oShaderBufferInfo.m_eBufferUsage = Enum.RBU_DynamicDraw();
    
       bool a_bCreatedBuffer = LibRender3D.CreateShaderBuffer(
          a_oShaderBufferInfo, p_oInputs.m_slMessages );
    
       if( !( a_bCreatedBuffer ) )
       {
          return false;
       }
    
       // Update the shader buffer with the min/max values.
       p_oInputs.m_slMessages.Add( "Uploading buffer data data to GPU..." );
       auto Float32Pointer min_max = p_oInputs.m_afMinMaxNormals.GetPointer();
       int a_nCopyDataToBuffer = p_oInputs.m_oDevice.WriteShaderSubBuffer( 0,
          p_oInputs.m_afMinMaxNormals.SizeInBytes(), min_max );
       p_oInputs.m_slMessages.Add( "Copy data to shader buffer: " + a_nCopyDataToBuffer );
    
       int array_slices = p_oInputs.m_iArraySlices;
       a_bSetConstant = LibRender3D.SetInt32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "array_slices", array_slices, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       // Set up Textures.
       // Create the source Texture object.
       auto Texture a_oSrcTexture =
          LibRender3D.CreateImage2DArrayTexture( "src", p_oSrcTiles, false );
    
       // Create the destination Texture object.
       auto Texture a_oDstTexture =
          LibRender3D.CreateImage2DArrayTexture( "dst", p_oDstTiles, false );
    
       // Bind Textures.
       int a_nTexUnit = 0;
       bool a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oSrcTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       a_nTexUnit = 1;
       a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oDstTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       // Execute Program
       int a_nExecuteStatus = p_oInputs.m_oDevice.ExecuteProgram( true, 8, 8, 1, 32, 32, 1 );
       p_oInputs.m_slMessages.Add( "Compute Shader Execution status: " + a_nExecuteStatus );
    
       Image a_oSaveImage = ( (Image)a_oDstTexture.ImageList.GetFirst() );
    
       p_oInputs.m_slMessages.Add( "Reading texture back from GPU..." );
       int a_nReadbackStatus = p_oInputs.m_oDevice.ReadTextureBuffer(
          a_oSaveImage, a_oDstTexture, 0 );
       p_oInputs.m_slMessages.Add( "Readback operation status: " +
          a_nReadbackStatus );
    
       // Clean up.
       p_oInputs.m_oDevice.DestroyTexture( a_oSrcTexture );
       p_oInputs.m_oDevice.DestroyTexture( a_oDstTexture );
       p_oInputs.m_oDevice.DestroyShaderBuffer( a_oShaderBuffer.GetParams() );
    
       return true;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function bool CreateOpennessTiles(
    
       BuildInputs p_oInputs,
       Program p_oProgram,
       Image p_oSrcTiles,
       Image p_oDstTiles
    
       )
    {
       // Declare a <FilePath> so we
       // can load and execute our shader source.
       string a_sComputeSrc = "avalanche_openness_map_compute_shader.glsl";
    
       auto FilePath a_oShaderPath;
       a_oShaderPath.SetPath( LibAppServiceMain.GetShadersPath() );
       a_oShaderPath.AppendPath( "Avalanche Openness Map" );
       a_oShaderPath.AppendPath( "430" );
       a_oShaderPath.AppendPathWithFile( a_sComputeSrc );
    
       bool a_bBindProgram = LibRender3D.BindComputeProgram(
          p_oInputs.m_oRenderInfo, p_oProgram, a_oShaderPath, p_oInputs.m_slMessages );
       if( !( a_bBindProgram ) )
       {
          return false;
       }
    
       // On some Nvidia GPUs, we cannot run this shader on the entire
       // array texture. Because we are able to run this on other GPUs,
       // it seems likely this is a driver bug, but it could be that
       // we have a bug in Scenome. In any case, for Nvidia GPUs, we
       // need to partition the texture and run the shader multiple times.
    
       // For less than 500 slices...
       if( p_oInputs.m_iArraySlices < p_oInputs.m_iMaxBatchSize )
       {
          // If you have a non-Nvidia GPU, then you can run
          // the following code, which runs the shader on the
          // entire array texture, and does not need to subdivide
          // the data into separate chunks.
    
          // Set up uniforms.
          p_oInputs.m_slMessages.Add( "Setting shader uniforms..." );
    
          bool a_bSetConstant = false;
    
          a_bSetConstant = LibRender3D.SetInt32Uniform(
             p_oInputs.m_oDevice, p_oProgram, "radius", 64, p_oInputs.m_slMessages );
          if( !( a_bSetConstant ) )
          {
             return false;
          }
    
          a_bSetConstant = LibRender3D.SetFloat32Uniform(
             p_oInputs.m_oDevice, p_oProgram, "min_angle", 90.0, p_oInputs.m_slMessages );
          if( !( a_bSetConstant ) )
          {
             return false;
          }
    
          a_bSetConstant = LibRender3D.SetFloat32Uniform(
             p_oInputs.m_oDevice, p_oProgram, "max_angle", 0.0, p_oInputs.m_slMessages );
          if( !( a_bSetConstant ) )
          {
             return false;
          }
    
          int array_slices = p_oInputs.m_iArraySlices;
          a_bSetConstant = LibRender3D.SetInt32Uniform(
             p_oInputs.m_oDevice, p_oProgram, "array_slices", array_slices, p_oInputs.m_slMessages );
          if( !( a_bSetConstant ) )
          {
             return false;
          }
    
          // Set up Textures.
          // Create the source Texture object.
          auto Texture a_oSrcTexture =
          LibRender3D.CreateImage2DArrayTexture( "src", p_oSrcTiles, false );
    
          // Create the destination Texture object.
          auto Texture a_oDstTexture =
          LibRender3D.CreateImage2DArrayTexture( "dst", p_oDstTiles, false );
    
          // Bind Textures.
          int a_nTexUnit = 0;
          bool a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oSrcTexture, a_nTexUnit, p_oInputs.m_slMessages );
          if( !( a_bBindTexture ) )
          {
             return false;
          }
    
          a_nTexUnit = 1;
          a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oDstTexture, a_nTexUnit, p_oInputs.m_slMessages );
          if( !( a_bBindTexture ) )
          {
             return false;
          }
    
          // Execute Program
          int a_nExecuteStatus = p_oInputs.m_oDevice.ExecuteProgram( true, 8, 8, 1, 32, 32, 1 );
          p_oInputs.m_slMessages.Add( "Compute Shader Execution status: " + a_nExecuteStatus );
    
    
          Image a_oSaveImage = ( (Image)a_oDstTexture.ImageList.GetFirst() );
    
          p_oInputs.m_slMessages.Add( "Reading texture back from GPU..." );
          int a_nReadbackStatus = p_oInputs.m_oDevice.ReadTextureBuffer(
          a_oSaveImage, a_oDstTexture, 0 );
          p_oInputs.m_slMessages.Add( "Readback operation status: " +
          a_nReadbackStatus );
    
          // Clean up.
          p_oInputs.m_oDevice.DestroyTexture( a_oSrcTexture );
          p_oInputs.m_oDevice.DestroyTexture( a_oDstTexture );
    
          return true;
       }
    
       // For more than 500 slices...
    
       // First we need to discuss partition sizes. During testing,
       // we were able to reliably use this shader to process array
       // textures with up to 500 slices on all GPUs. So we'll just
       // use that as our initial batch size, and we will run a smaller
       // batch at the end.
    
       // It seems like many GPUs have a limit around 2048 of the number
       // of slices in an array texture. So in any case, we can't exceed 2048.
       // GLSL 450 guarantees 2048, but this shader is running under 430
       // and maybe the number is lower. Since the shader requires GLSL 430
       // we can probably get away with using 500 for the batch size.
    
       // Let's look at batch sizes.
    
       // Typical data size = GEOTIFF with 43x43 chunks.
       // 43 x 43 = 1849 slices.
       // In batches of 500 slices:
       // 1849 = 500, 500, 500, 349
    
       auto Int32Array a_aiBatchSizes;
       GetBatchSizes(
          p_oInputs.m_oLens.Z * p_oInputs.m_oLens.W,
          p_oInputs.m_iMaxBatchSize, a_aiBatchSizes );
       //LibInt32Array.Out( a_aiBatchSizes );
    
       int a_nSliceWidth = p_oSrcTiles.Width;
       int a_nSliceCount = p_oSrcTiles.Height / a_nSliceWidth;
       int a_nSliceHeight = p_oSrcTiles.Height / a_nSliceCount;
    
       //Console.Out( a_nSliceWidth );
       //Console.Out( a_nSliceHeight );
    
       auto Uint8ArrayAlgorithms a_auAlgorithms;
       auto Uint8ArrayView src_view = p_oSrcTiles.GetUint8View();
       auto Uint8ArrayView dst_view = p_oDstTiles.GetUint8View();
    
       uint a_nSrcOffset = 0;
    
       auto Float32ArrayAlgorithms a_afAlgorithms;
    
       for( int i = 0; i < a_aiBatchSizes.GetCount(); ++i )
       //for( int i = 0; i < 1; ++i )
       {
          // Compute the size of the sub-texture for each batch.
          uint a_nBatchSize = a_aiBatchSizes.Get( i );
          uint a_nSubTextureHeight = a_nBatchSize * a_nSliceHeight;
          uint a_nSubTextureWidth = a_nSliceWidth;
    
          //Console.Out( "Sub-texture size: " + a_nSubTextureWidth + " x " + a_nSubTextureHeight );
    
          // Allocate an Image object.
          auto Image a_oSubSrcImage = new Image(
             a_nSubTextureWidth, a_nSubTextureHeight, p_oSrcTiles.PixelFormat );
          auto Uint8ArrayView sub_src_view = a_oSubSrcImage.GetUint8View();
    
          src_view.First.Position = a_nSrcOffset;
          dst_view.First.Position = a_nSrcOffset;
    
          auto Image a_oSubDstImage = new Image(
             a_nSubTextureWidth, a_nSubTextureHeight, p_oDstTiles.PixelFormat );
          auto Uint8ArrayView sub_dst_view = a_oSubDstImage.GetUint8View();
    
          // Copy from source to sub-image destination.
          //Console.Out( a_nSrcOffset );
          a_auAlgorithms.Copy(
             src_view.First, src_view.Last, sub_src_view.First );
    
          // DEBUG
          /*
          auto Float32ArrayView sub_src_view_print = a_oSubSrcImage.GetFloat32View();
          sub_src_view_print.Last.Position = 1024;
    
          auto StrList a_slInfo;
          a_afAlgorithms.Print2D(
             sub_src_view_print.First,
             sub_src_view_print.Last,
             1,
             1024,
             a_slInfo );
    
          auto FilePath a_oDumpPath =
             new FilePath( Model.Filename );
          a_oDumpPath.RemoveFileName();
          a_oDumpPath.RemoveEndSeparator();
          string a_sFile = "array_" + i + ".txt";
          a_oDumpPath.AppendPath( a_sFile );
    
          auto TextFile a_oDoc;
          LibStrList.WriteToDisk(
             a_oDoc, a_oDumpPath, a_slInfo );
          */
    
          // Set up uniforms.
          p_oInputs.m_slMessages.Add( "Setting shader uniforms..." );
    
          bool a_bSetConstant = false;
    
          a_bSetConstant = LibRender3D.SetInt32Uniform(
             p_oInputs.m_oDevice, p_oProgram, "radius", 64, p_oInputs.m_slMessages );
          if( !( a_bSetConstant ) )
          {
             return false;
          }
    
          a_bSetConstant = LibRender3D.SetFloat32Uniform(
             p_oInputs.m_oDevice, p_oProgram, "min_angle", 90.0, p_oInputs.m_slMessages );
          if( !( a_bSetConstant ) )
          {
             return false;
          }
    
          a_bSetConstant = LibRender3D.SetFloat32Uniform(
             p_oInputs.m_oDevice, p_oProgram, "max_angle", 0.0, p_oInputs.m_slMessages );
          if( !( a_bSetConstant ) )
          {
             return false;
          }
    
          a_bSetConstant = LibRender3D.SetInt32Uniform(
             p_oInputs.m_oDevice, p_oProgram, "array_slices", a_nBatchSize, p_oInputs.m_slMessages );
          if( !( a_bSetConstant ) )
          {
             return false;
          }
    
          // Set up Textures.
          // Create the source Texture object.
          auto Texture a_oSrcTexture =
             LibRender3D.CreateImage2DArrayTexture( "src", a_oSubSrcImage, false );
    
          // Create the destination Texture object.
          auto Texture a_oDstTexture =
             LibRender3D.CreateImage2DArrayTexture( "dst", a_oSubDstImage, false );
    
          // Bind Textures.
          int a_nTexUnit = 0;
          bool a_bBindTexture = LibRender3D.BindTexture(
             p_oInputs.m_oDevice, p_oProgram, a_oSrcTexture, a_nTexUnit, p_oInputs.m_slMessages );
          if( !( a_bBindTexture ) )
          {
             return false;
          }
    
          a_nTexUnit = 1;
          a_bBindTexture = LibRender3D.BindTexture(
             p_oInputs.m_oDevice, p_oProgram, a_oDstTexture, a_nTexUnit, p_oInputs.m_slMessages );
          if( !( a_bBindTexture ) )
          {
             return false;
          }
    
          // Execute Program
          int a_nExecuteStatus = p_oInputs.m_oDevice.ExecuteProgram( true, 8, 8, 1, 32, 32, 1 );
          p_oInputs.m_slMessages.Add( "Compute Shader Execution status: " + a_nExecuteStatus );
    
          Image a_oSaveImage = ( (Image)a_oDstTexture.ImageList.GetFirst() );
    
          p_oInputs.m_slMessages.Add( "Reading texture back from GPU..." );
          int a_nReadbackStatus = p_oInputs.m_oDevice.ReadTextureBuffer(
             a_oSaveImage, a_oDstTexture, 0 );
          p_oInputs.m_slMessages.Add( "Readback operation status: " +
             a_nReadbackStatus );
    
          auto Uint8ArrayView read_back_view = a_oSaveImage.GetUint8View();
          a_auAlgorithms.Copy(
             read_back_view.First, read_back_view.Last, dst_view.First );
    
          // Copy back into the destination.
          //Console.Out( a_nSrcOffset );
    
          //a_oSaveImage.SaveFile( a_sTestPath );
    
          // Clean up.
          p_oInputs.m_oDevice.DestroyTexture( a_oSrcTexture );
          p_oInputs.m_oDevice.DestroyTexture( a_oDstTexture );
    
          p_oInputs.m_slMessages.AddBlank();
    
          // Compute and apply the source offsets.
          uint a_nOffset = a_nSubTextureHeight * a_nSubTextureWidth *
             SizeLimits.sizeof_uint8() * 4;
          a_nSrcOffset += a_nOffset;
       }
    
       return true;
    }
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function bool CreateSurfaceTiles(
    
       BuildInputs p_oInputs,
       Program p_oProgram,
       Image p_oSrcTiles,
       Image p_oDstTiles
    
       )
    {
       // Declare a <FilePath> so we
       // can load and execute our shader source.
       string a_sComputeSrc = "avalanche_surface_area_compute_shader.glsl";
    
       auto FilePath a_oShaderPath;
       a_oShaderPath.SetPath( LibAppServiceMain.GetShadersPath() );
       a_oShaderPath.AppendPath( "Avalanche Surface Area" );
       a_oShaderPath.AppendPath( "430" );
       a_oShaderPath.AppendPathWithFile( a_sComputeSrc );
    
       bool a_bBindProgram = LibRender3D.BindComputeProgram(
          p_oInputs.m_oRenderInfo, p_oProgram, a_oShaderPath, p_oInputs.m_slMessages );
       if( !( a_bBindProgram ) )
       {
          return false;
       }
    
       // Set up uniforms.
       p_oInputs.m_slMessages.Add( "Setting shader uniforms..." );
    
       bool a_bSetConstant = false;
    
       int array_slices = p_oInputs.m_iArraySlices;
       a_bSetConstant = LibRender3D.SetInt32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "array_slices", array_slices, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       a_bSetConstant = LibRender3D.SetFloat32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "region_size_x", 10.0, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       a_bSetConstant = LibRender3D.SetFloat32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "region_size_y", 10.0, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       // Set up Textures.
       // Create the source Texture object.
       auto Texture a_oSrcTexture =
          LibRender3D.CreateImage2DArrayTexture( "src_image", p_oSrcTiles, false );
    
       // Create the destination Texture object.
       auto Texture a_oDstTexture =
          LibRender3D.CreateImage2DArrayTexture( "dst_image", p_oDstTiles, false );
    
       // Bind Textures.
       int a_nTexUnit = 0;
       bool a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oSrcTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       a_nTexUnit = 1;
       a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oDstTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       // Execute Program
       int a_nExecuteStatus = p_oInputs.m_oDevice.ExecuteProgram( true, 8, 8, 1, 32, 32, 1 );
       p_oInputs.m_slMessages.Add( "Compute Shader Execution status: " + a_nExecuteStatus );
    
       Image a_oSaveImage = ( (Image)a_oDstTexture.ImageList.GetFirst() );
    
       p_oInputs.m_slMessages.Add( "Reading texture back from GPU..." );
       int a_nReadbackStatus = p_oInputs.m_oDevice.ReadTextureBuffer(
          a_oSaveImage, a_oDstTexture, 0 );
       p_oInputs.m_slMessages.Add( "Readback operation status: " +
          a_nReadbackStatus );
    
       // Dump min/max if needed.
       /*
       auto Float32Array a_afMinMax;
       LibAppOctopusWorkloadTerrainCpuImage.GetMinMaxFromSlices(
          a_oSaveImage, a_afMinMax );
       LibFloat32Array.Out( a_afMinMax );
       */
    
       // Clean up.
       p_oInputs.m_oDevice.DestroyTexture( a_oSrcTexture );
       p_oInputs.m_oDevice.DestroyTexture( a_oDstTexture );
    
       return true;
    }
    
    
    ///////////////////////////////////////////////////////////////////////////////
    // function
    ///////////////////////////////////////////////////////////////////////////////
    
    function bool CreateSumTiles(
    
       BuildInputs p_oInputs,
       Program p_oProgram,
       Image p_oSrcTiles,
       Image p_oDstTiles
    
       )
    {
       // Declare a <FilePath> so we
       // can load and execute our shader source.
       string a_sComputeSrc = "avalanche_prefix_sum_compute_shader.glsl";
    
       auto FilePath a_oShaderPath;
       a_oShaderPath.SetPath( LibAppServiceMain.GetShadersPath() );
       a_oShaderPath.AppendPath( "Avalanche Prefix Sum" );
       a_oShaderPath.AppendPath( "430" );
    
       // We need to rewrite the #include ""
       // It's in the same directory as the
       // compute shader:
       string a_sIncFile = "avalanche_prefix_sum_local_group_decl.glsl";
    
       // Here's what it might contain right now:
       // layout( local_size_x = 256, local_size_y = 1, local_size_z = 1 ) in;
       // We need '256', or whatever value it currently is, to
       // match the actual width of the image.
       string a_sGroupDecl = "layout( local_size_x = " + p_oSrcTiles.Width +
          ", local_size_y = 1, local_size_z = 1 ) in;";
       auto FilePath a_oIncPath = new FilePath( a_oShaderPath.GetPath() );
       a_oIncPath.AppendPathWithFile( a_sIncFile );
       auto StrList a_slGroupDecl;
       a_slGroupDecl.Add( a_sGroupDecl );
       auto TextFile a_oText;
       LibStrList.WriteToDisk(
          a_oText, a_oIncPath, a_slGroupDecl );
    
       // Bind the Program to the rendering device.
       a_oShaderPath.AppendPathWithFile( a_sComputeSrc );
       bool a_bBindProgram = LibRender3D.BindComputeProgram(
          p_oInputs.m_oRenderInfo, p_oProgram, a_oShaderPath, p_oInputs.m_slMessages );
       if( !( a_bBindProgram ) )
       {
          return false;
       }
    
       p_oInputs.m_slMessages.Add( "Wrote layout declaration '" +
          a_sGroupDecl + "'" + " to: " + a_oIncPath.GetPath() );
    
       // Set up uniforms.
       p_oInputs.m_slMessages.Add( "Setting shader uniforms..." );
    
       bool a_bSetConstant = false;
    
       int array_slices = p_oInputs.m_iArraySlices;
       a_bSetConstant = LibRender3D.SetInt32Uniform(
          p_oInputs.m_oDevice, p_oProgram, "array_slices", array_slices, p_oInputs.m_slMessages );
       if( !( a_bSetConstant ) )
       {
          return false;
       }
    
       // Set up Textures.
       // Create the source Texture object.
       auto Texture a_oSrcTexture =
          LibRender3D.CreateImage2DArrayTexture( "input_image", p_oSrcTiles, false );
    
       // Create the first pass results Texture object.
       auto Texture a_oDstTexture =
          LibRender3D.CreateImage2DArrayTexture( "output_image", p_oDstTiles, false );
    
       // Create the final results Texture object.
       auto Texture a_oResTexture =
          LibRender3D.CreateImage2DArrayTexture( "output_image", p_oDstTiles, false );
    
       // Bind Textures.
       int a_nTexUnit = 0;
       bool a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oSrcTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       a_nTexUnit = 1;
       a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oDstTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       // Execute Program
       int a_nPass1ExecuteStatus = p_oInputs.m_oDevice.ExecuteProgram(
          false, a_oSrcTexture.RenderWidth, 1, 1, 32, 32, 1 );
       if( !a_nPass1ExecuteStatus )
       {
          p_oInputs.m_oDevice.DestroyTexture( a_oSrcTexture );
          p_oInputs.m_oDevice.DestroyTexture( a_oDstTexture );
          p_oInputs.m_slMessages.Add( "First Pass Execution failed: " +
             a_nPass1ExecuteStatus );
          return false;
       }
       else
       {
          p_oInputs.m_slMessages.Add( "First Pass Execution status: " +
             a_nPass1ExecuteStatus );
       }
    
       auto GpuMemoryBarrier m;
       m.GlShaderImageAccessBarrierBit = true;
       p_oInputs.m_oDevice.InsertMemoryBarrier( m );
    
       // We need to do this so we can swap the
       // source with the results of the first pass.
       p_oInputs.m_oDevice.DestroyTexture( a_oSrcTexture );
    
       // Bind Textures.
       a_oDstTexture.Name = "input_image";
       a_nTexUnit = 0;
       a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oDstTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       a_nTexUnit = 1;
       a_bBindTexture = LibRender3D.BindTexture(
          p_oInputs.m_oDevice, p_oProgram, a_oResTexture, a_nTexUnit, p_oInputs.m_slMessages );
       if( !( a_bBindTexture ) )
       {
          return false;
       }
    
       // Perform second compute pass.
       int a_nPass2ExecuteStatus = p_oInputs.m_oDevice.ExecuteProgram(
          false, a_oSrcTexture.RenderWidth, 1, 1, 32, 32, 1 );
       if( !a_nPass2ExecuteStatus )
       {
          p_oInputs.m_oDevice.DestroyTexture( a_oDstTexture );
          p_oInputs.m_oDevice.DestroyTexture( a_oResTexture );
          p_oInputs.m_slMessages.Add( "Second Pass Execution failed: " +
             a_nPass1ExecuteStatus );
          return false;
       }
       else
       {
          p_oInputs.m_slMessages.Add( "Second Pass Execution status: " +
             a_nPass1ExecuteStatus );
       }
    
       Image a_oSaveImage = ( (Image)a_oResTexture.ImageList.GetFirst() );
    
       p_oInputs.m_slMessages.Add( "Reading texture back from GPU..." );
       int a_nReadbackStatus = p_oInputs.m_oDevice.ReadTextureBuffer(
          a_oSaveImage, a_oResTexture, 0 );
       p_oInputs.m_slMessages.Add( "Readback operation status: " +
          a_nReadbackStatus );
    
       // Clean up.
       p_oInputs.m_oDevice.DestroyTexture( a_oDstTexture );
       p_oInputs.m_oDevice.DestroyTexture( a_oResTexture );
    
       return true;
    }
    
  3. Save changes to the script.

Review Functions

Let's review the functions in this library.

Table 1.1. Review Script Contents

Function Description
GetBatchSizes This function populates an <Int32Array> with the number of items in a batch of subdivided work. This refers to a batch of array texture slices. For example, if an array texture has 1505 slices, and your batch size is 500, this function populates an <Int32Array> with 500, 500, 500, and 5.
CreateElevationTiles This function starts a compute shader and executes the compute shader on the source <Image> object. The compute shader converts the elevation values from their natural range to 0-255 so the elevation values can be viewed as a texture. When the compute shader completes, the function reads the results back into the destination <Image> object.
CreateAspectTiles This function starts a compute shader and executes the compute shader on the source <Image> object. The compute shader converts the elevation values to a 'normal map' that allows you to view the slope's orientation against the cardinal map directions: North, South, East, West. When the compute shader completes, the function reads the results back into the destination <Image> object.
CreateSlopeTiles This function starts a compute shader and executes the compute shader on the source <Image> object. The compute shader converts the aspect maps' Z value to a slope angle value that allows you to see which slopes are over 30 degrees. When the compute shader completes, the function reads the results back into the destination <Image> object.
CreateOpennessTiles This function starts a compute shader and executes the compute shader on the source <Image> object. The compute shader performs ray casting on the elevation data to allow you to see how open any point on the map is relative to other points in the set of points encountered during ray casting. This lets you visualize the 'openness to the sky' of the terrain. When the compute shader completes, the function reads the results back into the destination <Image> object.
CreateSurfaceTiles This function starts a compute shader and executes the compute shader on the source <Image> object. The compute shader computes the surface area of each point in the terrain. When the compute shader completes, the function reads the results back into the destination <Image> object.
CreateSumTiles This function starts a compute shader and executes the compute shader on the source <Image> object. The compute shader uses a parallel prefix sum algorithm to sum the surface area values of the terrain. When the compute shader completes, the function reads the results back into the destination <Image> object.

Import GPU Image Processing Library Into Service Script

  1. Return to the running text editor.
  2. Find the script named app_service_workload_analyze_terrain_util.ssl.
  3. Find the import statements.

    import library "app_service_assert_util.ssl";
    import library "app_service_build_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_service_workload_analyze_terrain_classes.ssl";
    import library "app_service_analyze_terrain_cpu_image_processing_util.ssl";
    
  4. Copy the following import statement and paste it at the end of the existing import statements.

    Copy Text To Clipboard

    import library "app_service_analyze_terrain_gpu_image_processing_util.ssl";

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. Double click the entry named Spa.Service.AnalyzeTerrain.
  4. The service runs and then displays the build log in the output window.

    --- <Executing Service 'Spa.Service.AnalyzeTerrain'> ---
    
    Executed service: Spa.Service.AnalyzeTerrain
    

    The service runs correctly.