endSurfaceWithComposite() {
    const gl = cc._renderContext;
    const { shaderCache } = cc;
    const needsCache = this.getSurfaceCacheRequested();

    // always end surface pass
    if (this._redirectingToSurfacePass) {
      this._redirectingToSurfacePass = false;

      // rebuild batches
      if (this.batchLights.getDirty()) {
        this.batchLights.rebuild();
      }
      if (this.batchShadowCastingLights.getDirty()) {
        this.batchShadowCastingLights.rebuild();
      }

      // stop redirecting drawing to surface pass
      this._currentSurfacePass.endWithReset(this._postProcessingRenderPassStackId);

      if (needsCache) {
        this.passes.cache.beginWithResetClear(this._postProcessingRenderPassStackId);
      }

      // setup for surface blur
      const needsSurfaceBlur = this.getIsBlurringSurface();
      if (needsSurfaceBlur) {
        // redirect to tone curve pass
        this.passes.blurComposite.beginWithResetClear(this._postProcessingRenderPassStackId);
      }

      // setup for tone curve
      if (this._needsGradientColorMap) {
        // redirect to tone curve pass
        this.passes.gradientColorMap.beginWithResetClear(this._postProcessingRenderPassStackId);
      }

      // bloom compositing
      // high pass
      this.passes.highpass.beginWithResetClear(this._postProcessingRenderPassStackId);
      const highpassProgram = shaderCache.programForKey('Highpass');
      highpassProgram.use();
      this.setRenderPassMatrices(highpassProgram);
      highpassProgram.setUniformLocationWith1f(highpassProgram.loc_threshold, this.bloomThreshold);
      highpassProgram.setUniformLocationWith1f(highpassProgram.loc_intensity, this.bloomIntensity * CONFIG.bloom);
      cc.glBlendFunc(gl.ONE, gl.ZERO);
      cc.glBindTexture2DN(0, this._currentSurfacePass.texture);
      this.passes.highpass.render();
      this.passes.highpass.endWithReset(this._postProcessingRenderPassStackId);

      // blur: horizontal pass
      this.passes.blur.beginWithResetClear(this._postProcessingRenderPassStackId);
      var blurProgram = shaderCache.programForKey('BlurStrong');
      blurProgram.use();
      this.setRenderPassMatrices(blurProgram);
      blurProgram.setUniformLocationWith1f(blurProgram.loc_xStep, 1.0 / this.passes.blur.width);
      blurProgram.setUniformLocationWith1f(blurProgram.loc_yStep, 0.0);
      cc.glBlendFunc(gl.ONE, gl.ZERO);
      cc.glBindTexture2DN(0, this.passes.highpass.texture);
      this.passes.blur.render();
      this.passes.blur.endWithReset(this._postProcessingRenderPassStackId);

      // blur: vertical pass
      this.passes.bloom.beginWithResetClear(this._postProcessingRenderPassStackId);
      this.setRenderPassMatrices(blurProgram);
      blurProgram.setUniformLocationWith1f(blurProgram.loc_xStep, 0.0);
      blurProgram.setUniformLocationWith1f(blurProgram.loc_yStep, 1.0 / this.passes.blur.height);
      cc.glBlendFunc(gl.ONE, gl.ZERO);
      cc.glBindTexture2DN(0, this.passes.blur.texture);
      this.passes.bloom.render();
      this.passes.bloom.endWithReset(this._postProcessingRenderPassStackId);

      // merge new bloom and previous
      this._currentBloomCompositePass.beginWithResetClear(this._postProcessingRenderPassStackId);
      const bloomProgram = shaderCache.programForKey('Bloom');
      bloomProgram.use();
      this.setRenderPassMatrices(bloomProgram);
      const bloomTransition = this.getSurfaceCacheRequested() || this.getScreenCacheRequested() ? 1.0 : this.bloomTransition;
      bloomProgram.setUniformLocationWith1f(bloomProgram.loc_transition, bloomTransition);
      cc.glBlendFunc(gl.ONE, gl.ZERO);
      cc.glBindTexture2DN(0, this.passes.bloom.texture);
      cc.glBindTexture2DN(1, this._previousBloomCompositePass.texture);
      this._currentBloomCompositePass.render();
      this._currentBloomCompositePass.endWithReset(this._postProcessingRenderPassStackId);

      // setup for tone curve
      const toneCurveAmount = this.getToneCurveAmount();
      const needsToneCurve = toneCurveAmount > 0.0;
      if (needsToneCurve) {
        // redirect to tone curve pass
        this.passes.toneCurve.beginWithResetClear(this._postProcessingRenderPassStackId);
      }

      // setup for radial blur
      const needsRadialBlur = this.radialBlurStrength > 0.0 && this.radialBlurSpread > 0.0;
      if (needsRadialBlur) {
        // redirect to radial blur pass
        this.passes.radialBlur.beginWithResetClear(this._postProcessingRenderPassStackId);
      }

      // composite bloom and current surface image
      const surfaceProgram = shaderCache.programForKey('Surface');
      surfaceProgram.use();
      this.setRenderPassMatrices(surfaceProgram);
      cc.glBlendFunc(gl.ONE, gl.ZERO);
      cc.glBindTexture2DN(0, this._currentSurfacePass.texture);
      cc.glBindTexture2DN(1, this._currentBloomCompositePass.texture);
      this._currentSurfacePass.render();

      // debug draw depth
      if (CONFIG.DEBUG_DEPTH) {
        const depthDebugProgram = shaderCache.programForKey('DepthDebug');
        depthDebugProgram.use();
        this.setRenderPassMatrices(depthDebugProgram);
        cc.glBlendFunc(gl.ONE, gl.ZERO);
        cc.glBindTexture2DN(0, this.getDepthMap());
        this._currentSurfacePass.render();
      }

      // draw distortions
      const { distortions } = this;
      if (distortions.length > 0) {
        if (this._distortionsDirty) {
          this._distortionsDirty = false;
          this.distortions.sort(this._compareDistortions);
        }
        for (let i = 0, il = distortions.length; i < il; i++) {
          const distortion = distortions[i];
          const renderCmd = distortion._renderCmd;
          renderCmd.renderingDistortion();
        }
      }

      // composite radial blur
      if (needsRadialBlur) {
        // stop redirecting to radial blur pass
        this.passes.radialBlur.endWithReset(this._postProcessingRenderPassStackId);

        // radial blur
        const radialBlurProgram = shaderCache.programForKey('RadialBlur');
        radialBlurProgram.use();
        this.setRenderPassMatrices(radialBlurProgram);
        radialBlurProgram.setUniformLocationWith2f(radialBlurProgram.loc_origin, this.radialBlurScreenPct.x, this.radialBlurScreenPct.y);
        radialBlurProgram.setUniformLocationWith1f(radialBlurProgram.loc_strength, this.radialBlurStrength);
        radialBlurProgram.setUniformLocationWith1f(radialBlurProgram.loc_deadZone, this.radialBlurDeadZone);
        radialBlurProgram.setUniformLocationWith1f(radialBlurProgram.loc_ramp, this.radialBlurRamp);
        radialBlurProgram.setUniformLocationWith1f(radialBlurProgram.loc_decay, this.radialBlurDecay);
        radialBlurProgram.setUniformLocationWith1f(radialBlurProgram.loc_spread, this.radialBlurSpread);
        cc.glBlendFunc(gl.ONE, gl.ZERO);
        cc.glBindTexture2DN(0, this.passes.radialBlur.texture);
        this._currentSurfacePass.render();
      }

      // composite tone curve
      if (needsToneCurve) {
        // stop redirecting to tone curve pass
        this.passes.toneCurve.endWithReset(this._postProcessingRenderPassStackId);

        // tone pass
        const toneCurveProgram = shaderCache.programForKey('ToneCurve');
        toneCurveProgram.use();
        this.setRenderPassMatrices(toneCurveProgram);
        toneCurveProgram.setUniformLocationWith1f(toneCurveProgram.loc_amount, toneCurveAmount);
        cc.glBlendFunc(gl.ONE, gl.ZERO);
        cc.glBindTexture2DN(0, this.passes.toneCurve.texture);
        cc.glBindTexture2DN(1, cc.textureCache.getTextureForKey(this.toneCurveTextureKey));
        this._currentSurfacePass.render();
      }

      // composite gradient color map
      if (this._needsGradientColorMap) {
        this._gradientMapDirty = false;

        // stop redirecting to color map pass
        this.passes.gradientColorMap.endWithReset(this._postProcessingRenderPassStackId);

        // color map pass
        var gradientColorMapProgram = shaderCache.programForKey('GradientColorMap');
        gradientColorMapProgram.use();
        this.setRenderPassMatrices(gradientColorMapProgram);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_fromColorBlack, this._fromGradientColorMapBlackColor.r / 255.0, this._fromGradientColorMapBlackColor.g / 255.0, this._fromGradientColorMapBlackColor.b / 255.0, this._fromGradientColorMapBlackColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_fromColorMid, this._fromGradientColorMapMidColor.r / 255.0, this._fromGradientColorMapMidColor.g / 255.0, this._fromGradientColorMapMidColor.b / 255.0, this._fromGradientColorMapMidColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_fromColorWhite, this._fromGradientColorMapWhiteColor.r / 255.0, this._fromGradientColorMapWhiteColor.g / 255.0, this._fromGradientColorMapWhiteColor.b / 255.0, this._fromGradientColorMapWhiteColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_toColorBlack, this._toGradientColorMapBlackColor.r / 255.0, this._toGradientColorMapBlackColor.g / 255.0, this._toGradientColorMapBlackColor.b / 255.0, this._toGradientColorMapBlackColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_toColorMid, this._toGradientColorMapMidColor.r / 255.0, this._toGradientColorMapMidColor.g / 255.0, this._toGradientColorMapMidColor.b / 255.0, this._toGradientColorMapMidColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_toColorWhite, this._toGradientColorMapWhiteColor.r / 255.0, this._toGradientColorMapWhiteColor.g / 255.0, this._toGradientColorMapWhiteColor.b / 255.0, this._toGradientColorMapWhiteColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith1f(gradientColorMapProgram.loc_phase, this._gradientMapTransitionPhase);
        cc.glBlendFunc(gl.ONE, gl.ZERO);
        cc.glBindTexture2DN(0, this.passes.gradientColorMap.texture);
        this._currentSurfacePass.render();
      }

      // surface blur
      if (needsSurfaceBlur) {
        // stop redirecting to surface blur pass
        this.passes.blurComposite.endWithReset(this._postProcessingRenderPassStackId);

        // blur: horizontal pass
        this.passes.blur.beginWithResetClear(this._postProcessingRenderPassStackId);
        var blurProgram = shaderCache.programForKey(this.surfaceBlurShaderProgramKey);
        blurProgram.use();
        this.setRenderPassMatrices(blurProgram);
        blurProgram.setUniformLocationWith1f(blurProgram.loc_xStep, 1.0 / this.passes.blur.width);
        blurProgram.setUniformLocationWith1f(blurProgram.loc_yStep, 0.0);
        cc.glBlendFunc(gl.ONE, gl.ZERO);
        cc.glBindTexture2DN(0, this.passes.blurComposite.texture);
        this.passes.blur.render();
        this.passes.blur.endWithReset(this._postProcessingRenderPassStackId);

        // blur: vertical pass
        this.setRenderPassMatrices(blurProgram);
        blurProgram.setUniformLocationWith1f(blurProgram.loc_xStep, 0.0);
        blurProgram.setUniformLocationWith1f(blurProgram.loc_yStep, 1.0 / this.passes.blur.height);
        cc.glBlendFunc(gl.ONE, gl.ZERO);
        cc.glBindTexture2DN(0, this.passes.blur.texture);
        this._currentSurfacePass.render();
      }

      if (needsCache) {
        this.passes.cache.endWithReset(this._postProcessingRenderPassStackId);
        this._startCachingSurface();
      }
    }

    if (this._cachingSurface) {
      // draw cache
      if (this._needsGradientColorMap) {
        this._gradientMapDirty = false;

        // color map pass
        var gradientColorMapProgram = shaderCache.programForKey('GradientColorMap');
        gradientColorMapProgram.use();
        this.setRenderPassMatrices(gradientColorMapProgram);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_fromColorBlack, this._fromGradientColorMapBlackColor.r / 255.0, this._fromGradientColorMapBlackColor.g / 255.0, this._fromGradientColorMapBlackColor.b / 255.0, this._fromGradientColorMapBlackColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_fromColorMid, this._fromGradientColorMapMidColor.r / 255.0, this._fromGradientColorMapMidColor.g / 255.0, this._fromGradientColorMapMidColor.b / 255.0, this._fromGradientColorMapMidColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_fromColorWhite, this._fromGradientColorMapWhiteColor.r / 255.0, this._fromGradientColorMapWhiteColor.g / 255.0, this._fromGradientColorMapWhiteColor.b / 255.0, this._fromGradientColorMapWhiteColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_toColorBlack, this._toGradientColorMapBlackColor.r / 255.0, this._toGradientColorMapBlackColor.g / 255.0, this._toGradientColorMapBlackColor.b / 255.0, this._toGradientColorMapBlackColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_toColorMid, this._toGradientColorMapMidColor.r / 255.0, this._toGradientColorMapMidColor.g / 255.0, this._toGradientColorMapMidColor.b / 255.0, this._toGradientColorMapMidColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith4f(gradientColorMapProgram.loc_toColorWhite, this._toGradientColorMapWhiteColor.r / 255.0, this._toGradientColorMapWhiteColor.g / 255.0, this._toGradientColorMapWhiteColor.b / 255.0, this._toGradientColorMapWhiteColor.a / 255.0);
        gradientColorMapProgram.setUniformLocationWith1f(gradientColorMapProgram.loc_phase, this._gradientMapTransitionPhase);
      } else {
        const posTexProgram = shaderCache.programForKey(cc.SHADER_POSITION_TEXTURE);
        posTexProgram.use();
        this.setRenderPassMatrices(posTexProgram);
      }
      cc.glBlendFunc(gl.ONE, gl.ZERO);
      cc.glBindTexture2DN(0, this.passes.cache.texture);
      this.passes.cache.render();
    }
  }
Composites frame fx by handling post processing framebuffers and appling bloom, distortion, etc.
NOTE: This should be called once per frame by a FXCompositeLayer just after drawing the last thing that is affected by post processing.