@Override
  public void onReceiveShadowMap(
      final JCGLTexturesType g_tex,
      final JCGLShadersType g_sh,
      final JCGLTextureUnitContextMutableType tc,
      final R2Texture2DUsableType map) {
    NullCheck.notNull(g_tex);
    NullCheck.notNull(g_sh);
    NullCheck.notNull(tc);
    NullCheck.notNull(map);

    this.unit_shadow = tc.unitContextBindTexture2D(g_tex, map.get());
    g_sh.shaderUniformPutTexture2DUnit(this.u_shadow_map, this.unit_shadow);
  }
  @Override
  public void onReceiveValues(
      final JCGLTexturesType g_tex,
      final JCGLShadersType g_sh,
      final JCGLTextureUnitContextMutableType tc,
      final AreaInclusiveUnsignedLType viewport,
      final R2LightProjectiveWithShadowVarianceType values,
      final R2MatricesObserverValuesType m) {
    NullCheck.notNull(g_tex);
    NullCheck.notNull(g_sh);
    NullCheck.notNull(tc);
    NullCheck.notNull(values);
    NullCheck.notNull(m);

    /** Upload the current view rays. */
    final R2ViewRaysReadableType view_rays = m.getViewRays();
    g_sh.shaderUniformPutVector3f(this.u_view_rays_origin_x0y0, view_rays.getOriginX0Y0());
    g_sh.shaderUniformPutVector3f(this.u_view_rays_origin_x1y0, view_rays.getOriginX1Y0());
    g_sh.shaderUniformPutVector3f(this.u_view_rays_origin_x0y1, view_rays.getOriginX0Y1());
    g_sh.shaderUniformPutVector3f(this.u_view_rays_origin_x1y1, view_rays.getOriginX1Y1());

    g_sh.shaderUniformPutVector3f(this.u_view_rays_ray_x0y0, view_rays.getRayX0Y0());
    g_sh.shaderUniformPutVector3f(this.u_view_rays_ray_x1y0, view_rays.getRayX1Y0());
    g_sh.shaderUniformPutVector3f(this.u_view_rays_ray_x0y1, view_rays.getRayX0Y1());
    g_sh.shaderUniformPutVector3f(this.u_view_rays_ray_x1y1, view_rays.getRayX1Y1());

    /** Upload the viewport. */
    final UnsignedRangeInclusiveL range_x = viewport.getRangeX();
    final UnsignedRangeInclusiveL range_y = viewport.getRangeY();
    g_sh.shaderUniformPutFloat(
        this.u_viewport_inverse_width, (float) (1.0 / (double) range_x.getInterval()));
    g_sh.shaderUniformPutFloat(
        this.u_viewport_inverse_height, (float) (1.0 / (double) range_y.getInterval()));

    /** Upload the scene's depth coefficient. */
    g_sh.shaderUniformPutFloat(
        this.u_depth_coefficient, (float) R2Projections.getDepthCoefficient(m.getProjection()));

    /** Upload the projection for the light volume. */
    g_sh.shaderUniformPutMatrix4x4f(this.u_transform_projection, m.getMatrixProjection());
    g_sh.shaderUniformPutMatrix4x4f(
        this.u_transform_projection_inverse, m.getMatrixProjectionInverse());

    /** Transform the light's position to eye-space and upload it. */
    final PVectorReadable3FType<R2SpaceWorldType> position = values.getPosition();

    this.position_world.copyFrom3F(position);
    this.position_world.setWF(1.0f);

    final R2TransformContextType trc = m.getTransformContext();
    PMatrixM4x4F.multiplyVector4F(
        trc.getContextPM4F(), m.getMatrixView(), this.position_world, this.position_eye);

    this.position_eye3.copyFrom3F(this.position_eye);

    g_sh.shaderUniformPutVector3f(this.u_light_projective_position, this.position_eye3);

    /** Upload the projected image. */
    this.unit_image = tc.unitContextBindTexture2D(g_tex, values.getImage().get());
    g_sh.shaderUniformPutTexture2DUnit(this.u_light_projective_image, this.unit_image);

    /** Upload the light values. */
    g_sh.shaderUniformPutVector3f(this.u_light_projective_color, values.getColor());
    g_sh.shaderUniformPutFloat(this.u_light_projective_intensity, values.getIntensity());
    g_sh.shaderUniformPutFloat(this.u_light_projective_inverse_falloff, 1.0f / values.getFalloff());
    g_sh.shaderUniformPutFloat(this.u_light_projective_inverse_range, 1.0f / values.getRadius());
    g_sh.shaderUniformPutTexture2DUnit(this.u_light_projective_image, this.unit_image);

    /** Upload the shadow values. */
    final R2ShadowDepthVarianceType shadow = values.getShadow();
    g_sh.shaderUniformPutFloat(this.u_shadow_bleed_reduction, shadow.getLightBleedReduction());
    g_sh.shaderUniformPutFloat(
        this.u_shadow_depth_coefficient,
        (float) R2Projections.getDepthCoefficient(values.getProjection()));
    g_sh.shaderUniformPutFloat(this.u_shadow_factor_minimum, shadow.getMinimumFactor());
    g_sh.shaderUniformPutFloat(this.u_shadow_variance_minimum, shadow.getMinimumVariance());
  }