Browse Source

shaderpipeline: Add some files I forgot in my previous commit

rdb 1 year ago
parent
commit
9150d59c4a

+ 103 - 0
panda/src/display/shaderInputBinding_impls.I

@@ -0,0 +1,103 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file shaderInputBinding_impls.I
+ * @author rdb
+ * @date 2024-09-22
+ */
+
+/**
+ *
+ */
+INLINE ShaderMatrixBinding::
+ShaderMatrixBinding(ShaderEnums::StateMatrix input, CPT_InternalName arg,
+                    bool transpose, size_t offset, size_t num_rows, size_t num_cols) :
+  _input(input),
+  _arg(std::move(arg)),
+  _transpose(transpose),
+  _offset(offset),
+  _num_rows(num_rows),
+  _num_cols(num_cols) {
+}
+
+/**
+ *
+ */
+INLINE ShaderMatrixComposeBinding::
+ShaderMatrixComposeBinding(ShaderEnums::StateMatrix input0, CPT_InternalName arg0,
+                           ShaderEnums::StateMatrix input1, CPT_InternalName arg1,
+                           bool transpose, size_t offset, size_t num_rows, size_t num_cols) :
+  _input0(input0),
+  _input1(input1),
+  _arg0(std::move(arg0)),
+  _arg1(std::move(arg1)),
+  _transpose(transpose),
+  _offset(offset),
+  _num_rows(num_rows),
+  _num_cols(num_cols) {
+}
+
+/**
+ *
+ */
+INLINE ShaderPackedLightBinding::
+ShaderPackedLightBinding(int index) :
+  _index(index) {
+}
+
+/**
+ *
+ */
+INLINE ShaderLegacyLightBinding::
+ShaderLegacyLightBinding(CPT_InternalName input, ShaderEnums::StateMatrix matrix,
+                         CPT_InternalName arg) :
+  _input(std::move(input)),
+  _matrix(matrix),
+  _arg(std::move(arg)) {
+}
+
+/**
+ *
+ */
+INLINE ShaderTextureStagesBinding::
+ShaderTextureStagesBinding(Texture::TextureType desired_type, size_t count,
+                           Texture *default_texture, unsigned int mode_mask) :
+  _count(count),
+  _default_texture(default_texture),
+  _desired_type(desired_type),
+  _mode_mask(mode_mask) {
+}
+
+/**
+ *
+ */
+INLINE ShaderTextureBinding::
+ShaderTextureBinding(CPT(InternalName) input, Texture::TextureType desired_type) :
+  _input(std::move(input)),
+  _desired_type(desired_type) {
+}
+
+/**
+ *
+ */
+INLINE ShaderDataBinding::
+ShaderDataBinding(CPT_InternalName input,
+                  size_t num_elements, size_t num_rows, size_t num_cols) :
+  _input(std::move(input)),
+  _num_elements(num_elements),
+  _num_rows(num_rows),
+  _num_cols(num_cols) {
+}
+
+/**
+ *
+ */
+INLINE ShaderAggregateBinding::
+ShaderAggregateBinding(CPT_InternalName name, const ShaderType *type) {
+  r_collect_members(name, type);
+}

+ 3425 - 0
panda/src/display/shaderInputBinding_impls.cxx

@@ -0,0 +1,3425 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file shaderInputBinding_impls.cxx
+ * @author rdb
+ * @date 2024-09-22
+ */
+
+#include "shaderInputBinding_impls.h"
+
+#include "clipPlaneAttrib.h"
+#include "colorAttrib.h"
+#include "colorScaleAttrib.h"
+#include "fogAttrib.h"
+#include "lightAttrib.h"
+#include "materialAttrib.h"
+
+#include "directionalLight.h"
+#include "spotlight.h"
+#include "pointLight.h"
+#include "sphereLight.h"
+
+using State = ShaderInputBinding::State;
+
+static const LMatrix4 shadow_bias_mat(0.5f, 0.0f, 0.0f, 0.0f,
+                                      0.0f, 0.5f, 0.0f, 0.0f,
+                                      0.0f, 0.0f, 0.5f, 0.0f,
+                                      0.5f, 0.5f, 0.5f, 1.0f);
+
+static PT(Texture) white_texture;
+
+/**
+ * Returns a texture that is purely white.
+ */
+static Texture *
+get_white_texture() {
+  Texture *tex = white_texture.p();
+  if (tex == nullptr) {
+    tex = new Texture;
+    tex->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_rgba);
+    tex->set_clear_color(LVecBase4(1, 1, 1, 1));
+    tex->set_minfilter(SamplerState::FT_nearest);
+    tex->set_magfilter(SamplerState::FT_nearest);
+    white_texture = tex;
+  }
+  return tex;
+}
+
+/**
+ * Generate an error message including a description of the specified
+ * parameter.  Always returns nullptr.
+ */
+static nullptr_t
+report_parameter_error(const InternalName *name, const ShaderType *type, const char *msg) {
+  shader_cat.error()
+    << *type << ' ' << *name << ": " << msg << "\n";
+  return nullptr;
+}
+
+/**
+ * Make sure the provided parameter contains the specified number of words.
+ * If not, print error message and return false.
+ */
+static bool
+expect_num_words(const InternalName *name, const ShaderType *type, size_t len) {
+  const std::string &name_str = name->get_basename();
+  size_t n = std::count(name_str.begin(), name_str.end(), '_');
+  if (n + 1 != len) {
+    std::string msg = "parameter name has wrong number of words, expected ";
+    msg += format_string(len);
+    report_parameter_error(name, type, msg.c_str());
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Make sure the provided parameter has a floating point vector type.  If not,
+ * print error message and return false.
+ */
+static bool
+expect_float_vector(const InternalName *name, const ShaderType *type,
+                    int lo, int hi, bool allow_double = false) {
+  int nfloat;
+  ShaderType::ScalarType scalar_type = ShaderType::ST_unknown;
+  if (const ShaderType::Scalar *scalar = type->as_scalar()) {
+    nfloat = 1;
+    scalar_type = scalar->get_scalar_type();
+  }
+  else if (const ShaderType::Vector *vector = type->as_vector()) {
+    nfloat = vector->get_num_components();
+    scalar_type = vector->get_scalar_type();
+  }
+  else {
+    nfloat = 0;
+  }
+  if ((scalar_type != ShaderType::ST_float && (!allow_double || scalar_type != ShaderType::ST_double)) || nfloat < lo || nfloat > hi) {
+    report_parameter_error(name, type, allow_double ? "expected float or double vector" : "expected float vector");
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Make sure the provided parameter has a square floating-point matrix type.
+ * Otherwise, prints an error message and returns false.
+ */
+static bool
+expect_float_matrix(const InternalName *name, const ShaderType *type, int lo, int hi, bool allow_double = false) {
+  ShaderType::ScalarType scalar_type = ShaderType::ST_unknown;
+  uint32_t num_rows;
+  uint32_t num_columns;
+  if (const ShaderType::Matrix *matrix = type->as_matrix()) {
+    num_rows = matrix->get_num_rows();
+    num_columns = matrix->get_num_columns();
+    scalar_type = matrix->get_scalar_type();
+  }
+  if ((scalar_type != ShaderType::ST_float && (!allow_double || scalar_type != ShaderType::ST_double)) ||
+      (int)num_rows < lo || (int)num_rows > hi ||
+      (int)num_columns < lo || (int)num_columns > hi) {
+
+    std::string msg = allow_double ? "expected float or double matrix of " : "expected float matrix of ";
+    if (lo < hi) {
+      msg += "at least ";
+    }
+    msg += "size ";
+    msg += format_string(lo);
+    report_parameter_error(name, type, msg.c_str());
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Convert a single-word coordinate system name into a PART/ARG of a
+ * ShaderMatSpec.
+ */
+static bool
+expect_coordinate_system(const InternalName *name, const ShaderType *type,
+                         vector_string &pieces, int &next, bool fromflag,
+                         Shader::StateMatrix *part, CPT(InternalName) *arg) {
+
+  if (pieces[next] == "" || pieces[next] == "to" || pieces[next] == "rel") {
+    report_parameter_error(name, type, "invalid coordinate system name");
+    return false;
+  }
+
+  string word1 = pieces[next++];
+  if (pieces[next] == "of") {
+    next++;
+  }
+  string word2;
+  if (pieces[next] != "" && pieces[next] != "to" && pieces[next] != "rel") {
+    word2 = pieces[next++];
+  }
+
+  Shader::StateMatrix from_single;
+  Shader::StateMatrix from_double;
+  Shader::StateMatrix to_single;
+  Shader::StateMatrix to_double;
+
+  if (word1 == "world") {
+    from_single = Shader::SM_world_to_view;
+    from_double = Shader::SM_INVALID;
+    to_single   = Shader::SM_view_to_world;
+    to_double   = Shader::SM_INVALID;
+  }
+  else if (word1 == "model") {
+    from_single = Shader::SM_model_to_view;
+    from_double = Shader::SM_view_x_to_view;
+    to_single   = Shader::SM_view_to_model;
+    to_double   = Shader::SM_view_to_view_x;
+  }
+  else if (word1 == "clip") {
+    from_single = Shader::SM_clip_to_view;
+    from_double = Shader::SM_clip_x_to_view;
+    to_single   = Shader::SM_view_to_clip;
+    to_double   = Shader::SM_view_to_clip_x;
+  }
+  else if (word1 == "view") {
+    from_single = Shader::SM_identity;
+    from_double = Shader::SM_view_x_to_view;
+    to_single   = Shader::SM_identity;
+    to_double   = Shader::SM_view_to_view_x;
+  }
+  else if (word1 == "apiview") {
+    from_single = Shader::SM_apiview_to_view;
+    from_double = Shader::SM_apiview_x_to_view;
+    to_single   = Shader::SM_view_to_apiview;
+    to_double   = Shader::SM_view_to_apiview_x;
+  }
+  else if (word1 == "apiclip") {
+    from_single = Shader::SM_apiclip_to_view;
+    from_double = Shader::SM_apiclip_x_to_view;
+    to_single   = Shader::SM_view_to_apiclip;
+    to_double   = Shader::SM_view_to_apiclip_x;
+  }
+  else {
+    from_single = Shader::SM_view_x_to_view;
+    from_double = Shader::SM_view_x_to_view;
+    to_single   = Shader::SM_view_to_view_x;
+    to_double   = Shader::SM_view_to_view_x;
+    word2 = word1;
+  }
+
+  if (fromflag) {
+    if (word2 == "") {
+      part[0] = from_single;
+      arg[0] = nullptr;
+    } else {
+      if (from_double == Shader::SM_INVALID) {
+        report_parameter_error(name, type, "invalid coordinate system name");
+        return false;
+      }
+      part[0] = from_double;
+      arg[0] = InternalName::make(word2);
+    }
+  } else {
+    if (word2 == "") {
+      part[1] = to_single;
+      arg[1] = nullptr;
+    } else {
+      if (to_double == Shader::SM_INVALID) {
+        report_parameter_error(name, type, "invalid coordinate system name");
+        return false;
+      }
+      part[1] = to_double;
+      arg[1] = InternalName::make(word2);
+    }
+  }
+  return true;
+}
+
+/**
+ * Returns true if this could be a member of a light structure.
+ */
+static bool
+check_light_struct_member(const string &name, const ShaderType *type) {
+  uint32_t num_rows = 1;
+  uint32_t min_cols = 3;
+  uint32_t max_cols = 4;
+  if (name == "color") {
+  } else if (name == "specular") {
+  } else if (name == "ambient") {
+  } else if (name == "diffuse") {
+  } else if (name == "position") {
+  } else if (name == "halfVector") {
+  } else if (name == "spotDirection") {
+  } else if (name == "spotCosCutoff") {
+    min_cols = max_cols = 1;
+  } else if (name == "spotCutoff") {
+    min_cols = max_cols = 1;
+  } else if (name == "spotExponent") {
+    min_cols = max_cols = 1;
+  } else if (name == "attenuation") {
+  } else if (name == "constantAttenuation") {
+    min_cols = max_cols = 1;
+  } else if (name == "linearAttenuation") {
+    min_cols = max_cols = 1;
+  } else if (name == "quadraticAttenuation") {
+    min_cols = max_cols = 1;
+  } else if (name == "radius") {
+    min_cols = max_cols = 1;
+  } else if (name == "shadowViewMatrix") {
+    num_rows = 4;
+    min_cols = max_cols = 4;
+  } else if (name == "shadowMap") {
+    return type->as_sampled_image() != nullptr;
+  } else {
+    return false;
+  }
+
+  const ::ShaderType::Matrix *matrix = type->as_matrix();
+  if (matrix != nullptr) {
+    if (matrix->get_num_rows() != num_rows ||
+        matrix->get_num_columns() < min_cols ||
+        matrix->get_num_columns() > max_cols) {
+      return false;
+    }
+  }
+  else if (num_rows != 1) {
+    return false;
+  }
+  else {
+    uint32_t num_components = 1;
+    if (const ::ShaderType::Vector *vector = type->as_vector()) {
+      num_components = vector->get_num_components();
+    }
+    else if (type->as_scalar() == nullptr) {
+      return false;
+    }
+    if (num_components < min_cols || num_components > max_cols) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+/**
+ * Binds to a generic named shader input.
+ */
+static ShaderInputBinding *
+make_shader_input(const ShaderType *type, CPT_InternalName name) {
+  if (const ShaderType::SampledImage *sampler = type->as_sampled_image()) {
+    Texture::TextureType desired_type = sampler->get_texture_type();
+    return new ShaderTextureBinding(std::move(name), desired_type);
+  }
+  else if (const ShaderType::Image *image = type->as_image()) {
+    Texture::TextureType desired_type = image->get_texture_type();
+    return new ShaderTextureBinding(std::move(name), desired_type);
+  }
+  else if (const ShaderType::Matrix *matrix = type->as_matrix()) {
+    // For historical reasons, we handle non-arrayed matrices differently,
+    // which has the additional feature that they can be passed in as a
+    // NodePath instead of a matrix.
+    int dep = Shader::D_shader_inputs | Shader::D_frame;
+    if (matrix->get_num_columns() == 4) {
+      uint32_t num_rows = matrix->get_num_rows();
+      if (num_rows == 4) {
+        if (matrix->get_scalar_type() == ShaderType::ST_double) {
+          return ShaderInputBinding::make_data(dep, [=](const State &state, void *into, bool pad_rows) {
+            state.gsg->get_target_shader_attrib()->get_shader_input_matrix(name, *(LMatrix4d *)into);
+          });
+        } else {
+          return ShaderInputBinding::make_data(dep, [=](const State &state, void *into, bool pad_rows) {
+            state.gsg->get_target_shader_attrib()->get_shader_input_matrix(name, *(LMatrix4f *)into);
+          });
+        }
+      } else {
+        if (matrix->get_scalar_type() == ShaderType::ST_double) {
+          return ShaderInputBinding::make_data(dep, [=](const State &state, void *into, bool pad_rows) {
+            LMatrix4d tmp;
+            state.gsg->get_target_shader_attrib()->get_shader_input_matrix(name, tmp);
+            memcpy(into, tmp.get_data(), num_rows * sizeof(double) * 4);
+          });
+        } else {
+          return ShaderInputBinding::make_data(dep, [=](const State &state, void *into, bool pad_rows) {
+            LMatrix4f tmp;
+            state.gsg->get_target_shader_attrib()->get_shader_input_matrix(name, tmp);
+            memcpy(into, tmp.get_data(), num_rows * sizeof(float) * 4);
+          });
+        }
+      }
+    }
+    else if (matrix->get_num_columns() == 3) {
+      uint32_t num_rows = matrix->get_num_rows();
+      if (num_rows == 3) {
+        // Short-cut for most common case
+        if (matrix->get_scalar_type() == ShaderType::ST_double) {
+          return ShaderInputBinding::make_data(dep, [=](const State &state, void *into, bool pad_rows) {
+            LMatrix4d tmp;
+            state.gsg->get_target_shader_attrib()->get_shader_input_matrix(name, tmp);
+            if (pad_rows) {
+              memcpy(into, tmp.get_data(), sizeof(double) * 4 * 3);
+            } else {
+              *((LMatrix3d *)into) = tmp.get_upper_3();
+            }
+          });
+        } else {
+          return ShaderInputBinding::make_data(dep, [=](const State &state, void *into, bool pad_rows) {
+            LMatrix4f tmp;
+            state.gsg->get_target_shader_attrib()->get_shader_input_matrix(name, tmp);
+            if (pad_rows) {
+              memcpy(into, tmp.get_data(), sizeof(float) * 4 * 3);
+            } else {
+              *((LMatrix3f *)into) = tmp.get_upper_3();
+            }
+          });
+        }
+      } else {
+        if (matrix->get_scalar_type() == ShaderType::ST_double) {
+          return ShaderInputBinding::make_data(dep, [=](const State &state, void *into, bool pad_rows) {
+            LMatrix4d tmp;
+            state.gsg->get_target_shader_attrib()->get_shader_input_matrix(name, tmp);
+            if (pad_rows) {
+              memcpy(into, tmp.get_data(), num_rows * sizeof(double) * 4);
+            } else {
+              for (uint32_t i = 0; i < num_rows; ++i) {
+                ((LVecBase3d *)into)[i] = tmp.get_row3(i);
+              }
+            }
+          });
+        } else {
+          return ShaderInputBinding::make_data(dep, [=](const State &state, void *into, bool pad_rows) {
+            LMatrix4f tmp;
+            state.gsg->get_target_shader_attrib()->get_shader_input_matrix(name, tmp);
+            if (pad_rows) {
+              memcpy(into, tmp.get_data(), num_rows * sizeof(float) * 4);
+            } else {
+              for (uint32_t i = 0; i < num_rows; ++i) {
+                ((LVecBase3f *)into)[i] = tmp.get_row3(i);
+              }
+            }
+          });
+        }
+      }
+    }
+  }
+
+  ShaderType::ScalarType scalar_type;
+  uint32_t arg_dim[3];
+  if (type->as_scalar_type(scalar_type, arg_dim[0], arg_dim[1], arg_dim[2])) {
+    if (arg_dim[0] > 1 && arg_dim[1] > 1 && arg_dim[1] != arg_dim[2]) {
+      shader_cat.error()
+        << "Non-square matrix arrays are not supported in custom shader inputs\n";
+      return nullptr;
+    }
+
+    switch (scalar_type) {
+    case ShaderType::ST_float:
+      return new ShaderFloatBinding(name, arg_dim[0], arg_dim[1], arg_dim[2]);
+
+    case ShaderType::ST_double:
+      return new ShaderDoubleBinding(name, arg_dim[0], arg_dim[1], arg_dim[2]);
+
+    case ShaderType::ST_bool:
+      return new ShaderBoolBinding(name, arg_dim[0], arg_dim[1], arg_dim[2]);
+
+    default:
+      return new ShaderIntBinding(name, arg_dim[0], arg_dim[1], arg_dim[2]);
+    }
+  }
+
+  const ShaderType::Struct *struct_type = type->as_struct();
+
+  // This could be a custom user struct or a light struct.  Does it look like
+  // a light struct?
+  if (struct_type != nullptr && struct_type->get_num_members() > 0) {
+    bool may_be_light_struct = true;
+    for (size_t i = 0; i < struct_type->get_num_members(); ++i) {
+      const ShaderType::Struct::Member &member = struct_type->get_member(i);
+
+      if (!check_light_struct_member(member.name, member.type)) {
+        may_be_light_struct = false;
+      }
+    }
+    if (may_be_light_struct) {
+      // Yeah, might be.  There's special code in the light struct binding
+      // to fall back to a regular struct otherwise.
+      return new ShaderLightStructBinding(type, name);
+    }
+  }
+
+  if (struct_type != nullptr || type->as_array() != nullptr) {
+    return new ShaderAggregateBinding(name, type);
+  }
+
+  return nullptr;
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_matrix(const ShaderType *type, Shader::StateMatrix input,
+            CPT_InternalName arg, bool transpose = false, size_t offset = 0) {
+  ShaderType::ScalarType scalar_type;
+  uint32_t num_elements;
+  uint32_t num_rows;
+  uint32_t num_cols;
+  if (!type->as_scalar_type(scalar_type, num_elements, num_rows, num_cols) ||
+      scalar_type != ShaderType::ST_float ||
+      num_rows > 4 || num_cols > 4 || num_elements != 1) {
+    return nullptr;
+  }
+
+  return new ShaderMatrixBinding(input, arg, transpose, offset, num_rows, num_cols);
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_matrix_compose(const ShaderType *type, Shader::StateMatrix input0,
+                    CPT_InternalName arg0, Shader::StateMatrix input1,
+                    CPT_InternalName arg1, bool transpose = false, size_t offset = 0) {
+
+  // clip == apiclip in OpenGL, and the apiclip matrices are cached.
+  if (input0 == Shader::SM_view_to_clip) {
+    input0 = Shader::SM_view_to_apiclip;
+  }
+  else if (input0 == Shader::SM_clip_to_view) {
+    input0 = Shader::SM_apiclip_to_view;
+  }
+
+  if (input1 == Shader::SM_view_to_clip) {
+    input1 = Shader::SM_view_to_apiclip;
+  }
+  else if (input1 == Shader::SM_clip_to_view) {
+    input1 = Shader::SM_apiclip_to_view;
+  }
+
+  if (input0 == Shader::SM_world_to_view && input1 == Shader::SM_view_to_apiview) {
+    input0 = Shader::SM_world_to_apiview;
+    input1 = Shader::SM_identity;
+  }
+
+  if (input1 == Shader::SM_identity) {
+    return make_matrix(type, input0, arg0, transpose, offset);
+  }
+  if (input0 == Shader::SM_identity) {
+    return make_matrix(type, input1, arg1, transpose, offset);
+  }
+
+  ShaderType::ScalarType scalar_type;
+  uint32_t num_elements;
+  uint32_t num_rows;
+  uint32_t num_cols;
+  if (!type->as_scalar_type(scalar_type, num_elements, num_rows, num_cols) ||
+      scalar_type != ShaderType::ST_float || num_elements != 1 ||
+      num_rows > 4 || num_cols < 3 || num_cols > 4) {
+    return nullptr;
+  }
+
+  return new ShaderMatrixComposeBinding(input0, arg0, input1, arg1, transpose, offset, num_rows, num_cols);
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_transform_table(const ShaderType *type, bool transpose) {
+  ShaderType::ScalarType scalar_type;
+  uint32_t num_elements;
+  uint32_t num_rows;
+  uint32_t num_cols;
+  if (!type->as_scalar_type(scalar_type, num_elements, num_rows, num_cols) ||
+      num_cols != 4) {
+    return nullptr;
+  }
+
+  if (num_rows == 4) {
+    return ShaderInputBinding::make_data(Shader::D_vertex_data,
+                                         [=](const State &state, void *into, bool pad_rows) {
+
+      const TransformTable *table = state.gsg->get_data_reader()->get_transform_table();
+      LMatrix4f *matrices = (LMatrix4f *)into;
+      size_t i = 0;
+      if (table != nullptr) {
+        size_t num_transforms = std::min((size_t)num_elements, table->get_num_transforms());
+        for (; i < num_transforms; ++i) {
+#ifdef STDFLOAT_DOUBLE
+          LMatrix4 matrix;
+          table->get_transform(i)->get_matrix(matrix);
+          matrices[i] = LCAST(float, matrix);
+#else
+          table->get_transform(i)->get_matrix(matrices[i]);
+#endif
+        }
+      }
+      for (; i < num_elements; ++i) {
+        matrices[i] = LMatrix4f::ident_mat();
+      }
+    });
+  }
+  else if (num_rows == 3) {
+    // Reduced size, used by shader generator
+    nassertr(transpose, nullptr);
+
+    return ShaderInputBinding::make_data(Shader::D_vertex_data,
+                                         [=](const State &state, void *into, bool pad_rows) {
+
+      const TransformTable *table = state.gsg->get_data_reader()->get_transform_table();
+      LVecBase4f *vectors = (LVecBase4f *)into;
+      size_t i = 0;
+      if (table != nullptr) {
+        size_t num_transforms = std::min((size_t)num_elements, table->get_num_transforms());
+        for (; i < num_transforms; ++i) {
+          LMatrix4f matrix;
+#ifdef STDFLOAT_DOUBLE
+          LMatrix4d matrixd;
+          table->get_transform(i)->get_matrix(matrixd);
+          matrix = LCAST(float, matrixd);
+#else
+          table->get_transform(i)->get_matrix(matrix);
+#endif
+          vectors[i * 3 + 0] = matrix.get_col(0);
+          vectors[i * 3 + 1] = matrix.get_col(1);
+          vectors[i * 3 + 2] = matrix.get_col(2);
+        }
+      }
+      for (; i < num_elements; ++i) {
+        vectors[i * 3 + 0].set(1, 0, 0, 0);
+        vectors[i * 3 + 1].set(0, 1, 0, 0);
+        vectors[i * 3 + 2].set(0, 0, 1, 0);
+      }
+    });
+  }
+  else {
+    return nullptr;
+  }
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_slider_table(const ShaderType *type) {
+  const ShaderType *element_type;
+  uint32_t num_elements;
+  type->unwrap_array(element_type, num_elements);
+  nassertr(element_type == ShaderType::float_type, nullptr);
+
+  return ShaderInputBinding::make_data(Shader::D_vertex_data,
+                                       [=](const State &state, void *into, bool pad_rows) {
+
+    const SliderTable *table = state.gsg->get_data_reader()->get_slider_table();
+    float *sliders = (float *)into;
+    memset(sliders, 0, num_elements * sizeof(float));
+    size_t i = 0;
+    if (table != nullptr) {
+      size_t num_transforms = std::min((size_t)num_elements, table->get_num_sliders());
+      for (size_t i = 0; i < num_elements; ++i) {
+        sliders[i] = table->get_slider(i)->get_slider();
+      }
+    }
+  });
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_frame_time(const ShaderType *type) {
+  if (type == ShaderType::float_type) {
+    return ShaderInputBinding::make_data(Shader::D_frame,
+                                         [](const State &state, void *into, bool pad_rows) {
+      *(float *)into = ClockObject::get_global_clock()->get_frame_time();
+    });
+  }
+  if (type == ShaderType::double_type) {
+    return ShaderInputBinding::make_data(Shader::D_frame,
+                                         [](const State &state, void *into, bool pad_rows) {
+      *(double *)into = ClockObject::get_global_clock()->get_frame_time();
+    });
+  }
+  return nullptr;
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_color(const ShaderType *type) {
+  return ShaderInputBinding::make_data(Shader::D_color,
+                                       [](const State &state, void *into, bool pad_rows) {
+
+    const ColorAttrib *target_color = (const ColorAttrib *)
+      state.gsg->get_target_state()->get_attrib_def(ColorAttrib::get_class_slot());
+    if (target_color->get_color_type() == ColorAttrib::T_flat) {
+      *(LVecBase4f *)into = LCAST(float, target_color->get_color());
+    } else {
+      *(LVecBase4f *)into = LVecBase4f(1, 1, 1, 1);
+    }
+  });
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_color_scale(const ShaderType *type) {
+  return ShaderInputBinding::make_data(Shader::D_colorscale,
+                                       [](const State &state, void *into, bool pad_rows) {
+
+    const ColorScaleAttrib *target_color_scale = (const ColorScaleAttrib *)
+      state.gsg->get_target_state()->get_attrib_def(ColorScaleAttrib::get_class_slot());
+    if (!target_color_scale->is_identity()) {
+      *(LVecBase4f *)into = LCAST(float, target_color_scale->get_scale());
+    } else {
+      *(LVecBase4f *)into = LVecBase4f(1, 1, 1, 1);
+    }
+  });
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_texture_stage(const ShaderType *type, size_t index) {
+  const ShaderType::SampledImage *sampled_image_type = type->as_sampled_image();
+  if (sampled_image_type == nullptr) {
+    return nullptr;
+  }
+  Texture::TextureType desired_type = sampled_image_type->get_texture_type();
+
+  return ShaderInputBinding::make_texture(Shader::D_texture,
+                                          [=](const State &state, SamplerState &sampler, int &view) {
+
+    // We get the TextureAttrib directly from the _target_rs, not the
+    // filtered TextureAttrib in _target_texture.
+    const TextureAttrib *texattrib;
+    state.gsg->get_target_state()->get_attrib_def(texattrib);
+
+    PT(Texture) tex;
+    if (index < (size_t)texattrib->get_num_on_stages()) {
+      TextureStage *stage = texattrib->get_on_stage(index);
+      sampler = texattrib->get_on_sampler(stage);
+      view += stage->get_tex_view_offset();
+
+      tex = texattrib->get_on_texture(stage);
+      if (tex->get_texture_type() != desired_type) {
+        shader_cat.error()
+          << "Texture " << *tex << " at stage " << stage
+          << " does not match type desired by shader\n";
+      }
+    } else {
+      tex = get_white_texture();
+    }
+
+    return tex;
+  });
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_texture_matrix(const ShaderType *type, size_t index, bool inverse, bool transpose) {
+  ShaderType::ScalarType scalar_type;
+  uint32_t num_elements;
+  uint32_t num_rows;
+  uint32_t num_cols;
+  if (!type->as_scalar_type(scalar_type, num_elements, num_rows, num_cols) ||
+      num_rows != 4 || num_cols != 4) {
+    return nullptr;
+  }
+
+  return ShaderInputBinding::make_data(Shader::D_tex_matrix,
+                                       [=](const State &state, void *into, bool pad_rows) {
+
+    const TexMatrixAttrib *tma;
+    const TextureAttrib *ta;
+
+    uint32_t num_stages = 0;
+    if (state.gsg->get_target_state()->get_attrib(ta) && state.gsg->get_target_state()->get_attrib(tma)) {
+      num_stages = std::min(num_elements, (uint32_t)ta->get_num_on_stages());
+    }
+
+    uint32_t i = index;
+    for (; i < num_stages; ++i) {
+      ((LMatrix4f *)into)[i] = LCAST(float, tma->get_mat(ta->get_on_stage(i)));
+      if (inverse) {
+        ((LMatrix4f *)into)[i].invert_in_place();
+      }
+      if (transpose) {
+        ((LMatrix4f *)into)[i].transpose_in_place();
+      }
+    }
+    for (; i < index + num_elements; ++i) {
+      ((LMatrix4f *)into)[i] = LCAST(float, LMatrix4::ident_mat());
+    }
+  });
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_fog(const ShaderType *type) {
+  const ShaderType::Struct *struct_type = type->as_struct();
+  nassertr_always(struct_type != nullptr, nullptr);
+
+  // Unfortunately the user could have jumbled up the members of this
+  // struct.  Should we try and bless a certain ordering, and have a fast
+  // path for that?
+  int color_offset = -1;
+  int density_offset = -1;
+  int start_offset = -1;
+  int end_offset = -1;
+  int scale_offset = -1;
+
+  bool success = true;
+  for (size_t i = 0; i < struct_type->get_num_members(); ++i) {
+    const ShaderType::Struct::Member &member = struct_type->get_member(i);
+
+    CPT(InternalName) fqname = InternalName::make(member.name);
+    if (member.name == "color") {
+      success = success && expect_float_vector(fqname, member.type, 3, 4);
+      color_offset = member.offset;
+    }
+    else if (member.name == "density") {
+      success = success && expect_float_vector(fqname, member.type, 1, 1);
+      density_offset = member.offset;
+    }
+    else if (member.name == "start") {
+      success = success && expect_float_vector(fqname, member.type, 1, 1);
+      start_offset = member.offset;
+    }
+    else if (member.name == "end") {
+      success = success && expect_float_vector(fqname, member.type, 1, 1);
+      end_offset = member.offset;
+    }
+    else if (member.name == "scale") {
+      success = success && expect_float_vector(fqname, member.type, 1, 1);
+      scale_offset = member.offset;
+    }
+    else {
+      report_parameter_error(fqname, member.type, "unrecognized fog attribute");
+      success = false;
+    }
+  }
+
+  if (!success) {
+    return nullptr;
+  }
+
+  return ShaderInputBinding::make_data(Shader::D_fog | Shader::D_frame,
+                                       [=](const State &state, void *into, bool pad_rows) {
+
+    LVecBase4f color(1, 1, 1, 1);
+    PN_stdfloat density = 0, start = 1, end = 1, scale = 1;
+
+    const FogAttrib *target_fog;
+    if (state.gsg->get_target_state()->get_attrib(target_fog) && target_fog->get_fog() != nullptr) {
+      Fog *fog = target_fog->get_fog();
+      color = LCAST(float, fog->get_color());
+      density = fog->get_exp_density();
+      fog->get_linear_range(start, end);
+      scale = 1.0f / (end - start);
+    }
+
+    unsigned char *p = (unsigned char *)into;
+    if (color_offset >= 0) *(LVecBase4f *)(p + color_offset) = color;
+    if (density_offset >= 0) *(float *)(p + density_offset) = density;
+    if (start_offset >= 0) *(float *)(p + start_offset) = start;
+    if (end_offset >= 0) *(float *)(p + end_offset) = end;
+    if (scale_offset >= 0) *(float *)(p + scale_offset) = scale;
+  });
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_material(const ShaderType *type) {
+  const ShaderType::Struct *struct_type = type->as_struct();
+  nassertr_always(struct_type != nullptr, nullptr);
+
+  // Unfortunately the user could have jumbled up the members of this
+  // struct.  Should we try and bless a certain ordering, and have a fast
+  // path for that?
+  int base_color_offset = -1;
+  int ambient_offset = -1;
+  int diffuse_offset = -1;
+  int emission_offset = -1;
+  int specular_offset = -1;
+  int shininess_offset = -1;
+  int roughness_offset = -1;
+  int metallic_offset = -1;
+  int ior_offset = -1;
+
+  bool success = true;
+  for (size_t i = 0; i < struct_type->get_num_members(); ++i) {
+    const ShaderType::Struct::Member &member = struct_type->get_member(i);
+
+    CPT(InternalName) fqname = InternalName::make(member.name);
+    if (member.name == "baseColor") {
+      success = success && expect_float_vector(fqname, member.type, 4, 4);
+      base_color_offset = member.offset;
+    }
+    else if (member.name == "ambient") {
+      success = success && expect_float_vector(fqname, member.type, 4, 4);
+      ambient_offset = member.offset;
+    }
+    else if (member.name == "diffuse") {
+      success = success && expect_float_vector(fqname, member.type, 4, 4);
+      diffuse_offset = member.offset;
+    }
+    else if (member.name == "emission") {
+      success = success && expect_float_vector(fqname, member.type, 4, 4);
+      emission_offset = member.offset;
+    }
+    else if (member.name == "specular") {
+      success = success && expect_float_vector(fqname, member.type, 3, 3);
+      specular_offset = member.offset;
+    }
+    else if (member.name == "shininess") {
+      success = success && expect_float_vector(fqname, member.type, 1, 1);
+      shininess_offset = member.offset;
+    }
+    else if (member.name == "roughness") {
+      success = success && expect_float_vector(fqname, member.type, 1, 1);
+      roughness_offset = member.offset;
+    }
+    else if (member.name == "metallic") {
+      if (member.type != ShaderType::bool_type &&
+          member.type != ShaderType::float_type) {
+        report_parameter_error(fqname, member.type, "expected bool or float");
+        success = false;
+      }
+      metallic_offset = member.offset;
+    }
+    else if (member.name == "refractiveIndex") {
+      success = success && expect_float_vector(fqname, member.type, 1, 1);
+      ior_offset = member.offset;
+    }
+    else {
+      report_parameter_error(fqname, member.type, "unrecognized material attribute");
+      success = false;
+    }
+  }
+
+  if (!success) {
+    return nullptr;
+  }
+
+  return ShaderInputBinding::make_data(Shader::D_material | Shader::D_frame,
+                                       [=](const State &state, void *into, bool pad_rows) {
+
+    LVecBase4f base_color(0, 0, 0, 0);
+    LVecBase4f ambient(1, 1, 1, 1);
+    LVecBase4f diffuse(1, 1, 1, 1);
+    LVecBase4f emission(0, 0, 0, 0);
+    LVecBase4f specular(0, 0, 0, 0);
+    float shininess = 0;
+    float metallic = 0;
+    float ior = 0;
+    float roughness = 1;
+
+    const MaterialAttrib *target_material;
+    if (state.gsg->get_target_state()->get_attrib(target_material) && !target_material->is_off()) {
+      Material *m = target_material->get_material();
+      base_color = LCAST(float, m->get_base_color());
+      ambient = LCAST(float, m->get_ambient());
+      diffuse = LCAST(float, m->get_diffuse());
+      emission = LCAST(float, m->get_emission());
+      specular = LCAST(float, m->get_specular());
+      shininess = m->get_shininess();
+      metallic = m->get_metallic();
+      ior = m->get_refractive_index();
+      roughness = m->get_roughness();
+    }
+
+    unsigned char *p = (unsigned char *)into;
+    if (base_color_offset >= 0) *(LVecBase4f *)(p + base_color_offset) = base_color;
+    if (ambient_offset >= 0) *(LVecBase4f *)(p + ambient_offset) = ambient;
+    if (diffuse_offset >= 0) *(LVecBase4f *)(p + diffuse_offset) = diffuse;
+    if (emission_offset >= 0) *(LVecBase4f *)(p + emission_offset) = emission;
+    if (specular_offset >= 0) *(LVecBase4f *)(p + specular_offset) = specular;
+    if (shininess_offset >= 0) *(float *)(p + shininess_offset) = shininess;
+    if (metallic_offset >= 0) *(float *)(p + metallic_offset) = metallic;
+    if (ior_offset >= 0) *(float *)(p + ior_offset) = ior;
+    if (roughness_offset >= 0) *(float *)(p + roughness_offset) = roughness;
+  });
+}
+
+/**
+ *
+ */
+static ShaderInputBinding *
+make_light_ambient(const ShaderType *type) {
+  return ShaderInputBinding::make_data(Shader::D_frame | Shader::D_light,
+                                       [](const State &state, void *into, bool pad_rows) {
+    const LightAttrib *target_light;
+    if (state.gsg->get_target_state()->get_attrib(target_light) && target_light->has_any_on_light()) {
+      *(LVecBase4f *)into = LCAST(float, target_light->get_ambient_contribution());
+    } else {
+      // There are no lights at all.  This means, to follow the fixed-
+      // function model, we pretend there is an all-white ambient light.
+      *(LVecBase4f *)into = LVecBase4f(1, 1, 1, 1);
+    }
+  });
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderInputBinding::
+get_state_dep() const {
+  // Normally we respecify everything per frame.
+  return Shader::D_frame;
+}
+
+/**
+ * Sets up anything necessary to prepare this binding to be used with the given
+ * shader.
+ */
+void ShaderInputBinding::
+setup(Shader *shader) {
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderInputBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+}
+
+/**
+ * Returns an opaque resource identifier that can later be used to fetch the
+ * nth resource, which is of the given type.
+ */
+ShaderInputBinding::ResourceId ShaderInputBinding::
+get_resource_id(int index, const ShaderType *type) const {
+  return 0;
+}
+
+/**
+ * Fetches the texture associated with this shader input.
+ */
+PT(Texture) ShaderInputBinding::
+fetch_texture(const State &state, ResourceId resource_id, SamplerState &sampler, int &view) const {
+  return nullptr;
+}
+
+/**
+ * Fetches the texture that should be bound as a storage image.
+ */
+PT(Texture) ShaderInputBinding::
+fetch_texture_image(const State &state, ResourceId resource_id, ShaderType::Access &access, int &z, int &n) const {
+  return nullptr;
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderMatrixBinding::
+get_state_dep() const {
+  return Shader::get_matrix_deps(_input);
+}
+
+/**
+ * Sets up anything necessary to prepare this binding to be used with the given
+ * shader.
+ */
+void ShaderMatrixBinding::
+setup(Shader *shader) {
+  _cache_index = shader->add_matrix_cache_item(_input, _arg, Shader::get_matrix_deps(_input));
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderMatrixBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  LMatrix4f m = LCAST(float, state.matrix_cache[_cache_index]);
+  if (_transpose) {
+    m.transpose_in_place();
+  }
+  if (pad_rows || _num_cols == 4) {
+    memcpy(into, m.get_data(), _num_cols * 4 * sizeof(float));
+  } else {
+    for (size_t i = 0; i < _num_rows; ++i) {
+      memcpy((float *)into + i * _num_cols, m.get_data() + i * 4, _num_cols * sizeof(float));
+    }
+  }
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderMatrixComposeBinding::
+get_state_dep() const {
+  return (Shader::get_matrix_deps(_input0)
+        | Shader::get_matrix_deps(_input1));
+}
+
+/**
+ * Sets up anything necessary to prepare this binding to be used with the given
+ * shader.
+ */
+void ShaderMatrixComposeBinding::
+setup(Shader *shader) {
+  _cache_index0 = shader->add_matrix_cache_item(_input0, _arg0, Shader::get_matrix_deps(_input0));
+  _cache_index1 = shader->add_matrix_cache_item(_input1, _arg1, Shader::get_matrix_deps(_input1));
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderMatrixComposeBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  LMatrix4f m;
+  m.multiply(LCAST(float, state.matrix_cache[_cache_index0]),
+             LCAST(float, state.matrix_cache[_cache_index1]));
+  if (_transpose) {
+    m.transpose_in_place();
+  }
+  if (pad_rows || _num_cols == 4) {
+    memcpy(into, m.get_data(), _num_rows * 4 * sizeof(float));
+  } else {
+    for (size_t i = 0; i < _num_rows; ++i) {
+      memcpy((float *)into + i * _num_cols, m.get_data() + i * 4, _num_cols * sizeof(float));
+    }
+  }
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderPointParamsBinding::
+get_state_dep() const {
+  return (Shader::D_scene
+        | Shader::D_render_mode
+        | Shader::D_transform
+        | Shader::get_matrix_deps(Shader::SM_point_attenuation));
+}
+
+/**
+ * Sets up anything necessary to prepare this binding to be used with the given
+ * shader.
+ */
+void ShaderPointParamsBinding::
+setup(Shader *shader) {
+  _cache_index = shader->add_matrix_cache_item(Shader::SM_point_attenuation, nullptr,
+    Shader::get_matrix_deps(Shader::SM_point_attenuation));
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderPointParamsBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  const RenderModeAttrib *target_render_mode;
+  state.gsg->get_target_state()->get_attrib_def(target_render_mode);
+
+  PN_stdfloat thickness = target_render_mode->get_thickness();
+  if (target_render_mode->get_perspective()) {
+    LMatrix4 m = state.matrix_cache[_cache_index];
+    PN_stdfloat scale = state.gsg->get_internal_transform()->get_scale()[1];
+
+    LVector3 height = LVector3(0, thickness, 1) * m;
+    height[1] *= scale;
+    height[2] *= scale;
+    *(LVecBase4f *)into = LCAST(float, height);
+  } else {
+    ((LVecBase4f *)into)->set(thickness, thickness, 0, 0);
+  }
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderPackedLightBinding::
+get_state_dep() const {
+  return Shader::D_light | Shader::D_frame |
+         Shader::get_matrix_deps(Shader::SM_world_to_view);
+}
+
+/**
+ * Sets up anything necessary to prepare this binding to be used with the given
+ * shader.
+ */
+void ShaderPackedLightBinding::
+setup(Shader *shader) {
+  _world_mat_cache_index = shader->add_matrix_cache_item(Shader::SM_world_to_view, nullptr, Shader::get_matrix_deps(Shader::SM_world_to_view));
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderPackedLightBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  // The light matrix contains COLOR, ATTENUATION, VIEWVECTOR, POSITION
+  LVecBase4f *data = (LVecBase4f *)into;
+
+  // We don't count ambient lights, which would be pretty silly to handle
+  // via this mechanism.
+  const LightAttrib *target_light;
+  if (state.gsg->get_target_state()->get_attrib(target_light) &&
+      _index < target_light->get_num_non_ambient_lights()) {
+    NodePath np = target_light->get_on_light(_index);
+    nassertv(!np.is_empty());
+    PandaNode *node = np.node();
+    Light *light = node->as_light();
+    nassertv(light != nullptr);
+    data[0] = LCAST(float, light->get_color());
+    data[1] = LVecBase4f(LCAST(float, light->get_attenuation()), 0);
+
+    LMatrix4 mat = np.get_net_transform()->get_mat() *
+      state.matrix_cache[_world_mat_cache_index];
+
+    if (node->is_of_type(DirectionalLight::get_class_type())) {
+      LVecBase3 d = mat.xform_vec(((const DirectionalLight *)node)->get_direction());
+      d.normalize();
+      data[2] = LVecBase4f(LCAST(float, d), 0);
+      data[3] = LVecBase4f(LCAST(float, -d), 0);
+    }
+    else if (node->is_of_type(LightLensNode::get_class_type())) {
+      const Lens *lens = ((const LightLensNode *)node)->get_lens();
+
+      LPoint3 p = mat.xform_point(lens->get_nodal_point());
+      data[3] = LVecBase4f(p[0], p[1], p[2], 1.0f);
+
+      // For shadowed point light we need to store near/far.
+      // For spotlight we need to store cutoff angle.
+      if (node->is_of_type(Spotlight::get_class_type())) {
+        PN_stdfloat cutoff = ccos(deg_2_rad(lens->get_hfov() * 0.5f));
+        LVecBase3 d = -(mat.xform_vec(lens->get_view_vector()));
+        data[1][3] = ((const Spotlight *)node)->get_exponent();
+        data[2] = LVecBase4f(LCAST(float, d), cutoff);
+      }
+      else if (node->is_of_type(PointLight::get_class_type())) {
+        data[1][3] = lens->get_far();
+        data[3][3] = lens->get_near();
+
+        if (node->is_of_type(SphereLight::get_class_type())) {
+          data[2][3] = ((const SphereLight *)node)->get_radius();
+        }
+      }
+    }
+  } else {
+    // Apply the default OpenGL lights otherwise.
+    // Special exception for light 0, which defaults to white.
+    if (_index == 0) {
+      data[0].set(1, 1, 1, 1);
+    } else {
+      data[0].set(0, 0, 0, 0);
+    }
+    data[1].set(1, 0, 0, 0);
+    data[2].set(0, 0, 0, 0);
+    data[3].set(0, 0, 0, 0);
+  }
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderLegacyLightBinding::
+get_state_dep() const {
+  return Shader::D_shader_inputs |
+         Shader::D_view_transform |
+         Shader::D_frame |
+         Shader::get_matrix_deps(_matrix);
+}
+
+/**
+ * Sets up anything necessary to prepare this binding to be used with the given
+ * shader.
+ */
+void ShaderLegacyLightBinding::
+setup(Shader *shader) {
+  _mat_cache_index = shader->add_matrix_cache_item(_matrix, _arg, Shader::get_matrix_deps(_matrix));
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderLegacyDirectionalLightBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  // The dlight matrix contains COLOR, SPECULAR, DIRECTION, PSEUDOHALFANGLE
+  const NodePath &np = state.gsg->get_target_shader_attrib()->get_shader_input_nodepath(_input);
+  nassertv(!np.is_empty());
+  DirectionalLight *lt;
+  DCAST_INTO_V(lt, np.node());
+  LColor const &c = lt->get_color();
+  LColor const &s = lt->get_specular_color();
+  const LMatrix4 &cached_mat = state.matrix_cache[_mat_cache_index];
+  LMatrix4 t = np.get_net_transform()->get_mat() *
+               state.gsg->get_scene()->get_world_transform()->get_mat();
+  LVecBase3 d = -(t.xform_vec(lt->get_direction()));
+  d.normalize();
+  d = cached_mat.xform_vec(d);
+  d.normalize();
+  LVecBase3 h = d + LVecBase3(0,-1,0);
+  h.normalize();
+  h = cached_mat.xform_vec(d);
+  h.normalize();
+  LVecBase4f *v = (LVecBase4f *)into;
+  v[0].set(c[0], c[1], c[2], c[3]);
+  v[1].set(s[0], s[1], s[2], c[3]);
+  v[2].set(d[0], d[1], d[2], 0);
+  v[3].set(h[0], h[1], h[2], 0);
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderLegacyPointLightBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  // The plight matrix contains COLOR, SPECULAR, POINT, ATTENUATION
+  const NodePath &np = state.gsg->get_target_shader_attrib()->get_shader_input_nodepath(_input);
+  nassertv(!np.is_empty());
+  PointLight *lt;
+  DCAST_INTO_V(lt, np.node());
+  LColor const &c = lt->get_color();
+  LColor const &s = lt->get_specular_color();
+  LMatrix4 t = np.get_net_transform()->get_mat() *
+               state.gsg->get_scene()->get_world_transform()->get_mat();
+  LVecBase3 p = (t.xform_point(lt->get_point()));
+  p = state.matrix_cache[_mat_cache_index].xform_point(p);
+  LVecBase3 a = lt->get_attenuation();
+  Lens *lens = lt->get_lens(0);
+  PN_stdfloat lnear = lens->get_near();
+  PN_stdfloat lfar = lens->get_far();
+  LVecBase4f *v = (LVecBase4f *)into;
+  v[0].set(c[0], c[1], c[2], c[3]);
+  v[1].set(s[0], s[1], s[2], s[3]);
+  v[2].set(p[0], p[1], p[2], lnear);
+  v[3].set(a[0], a[1], a[2], lfar);
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderLegacySpotlightBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  // The slight matrix contains COLOR, SPECULAR, POINT, DIRECTION
+  const NodePath &np = state.gsg->get_target_shader_attrib()->get_shader_input_nodepath(_input);
+  nassertv(!np.is_empty());
+  Spotlight *lt;
+  DCAST_INTO_V(lt, np.node());
+  Lens *lens = lt->get_lens();
+  nassertv(lens != nullptr);
+  LColor const &c = lt->get_color();
+  LColor const &s = lt->get_specular_color();
+  PN_stdfloat cutoff = ccos(deg_2_rad(lens->get_hfov() * 0.5f));
+  const LMatrix4 &cached_mat = state.matrix_cache[_mat_cache_index];
+  LMatrix4 t = np.get_net_transform()->get_mat() *
+               state.gsg->get_scene()->get_world_transform()->get_mat();
+  LVecBase3 p = t.xform_point(lens->get_nodal_point());
+  p = cached_mat.xform_point(p);
+  LVecBase3 d = -(t.xform_vec(lens->get_view_vector()));
+  d = cached_mat.xform_vec(d);
+  d.normalize();
+  LVecBase4f *v = (LVecBase4f *)into;
+  v[0].set(c[0], c[1], c[2], c[3]);
+  v[1].set(s[0], s[1], s[2], s[3]);
+  v[2].set(p[0], p[1], p[2], 0);
+  v[3].set(d[0], d[1], d[2], cutoff);
+}
+
+/**
+ *
+ */
+ShaderLightStructBinding::
+ShaderLightStructBinding(const ShaderType *type, const InternalName *input) {
+  const ShaderType *element_type;
+  if (input != nullptr) {
+    element_type = type;
+    _count = 1;
+    _input = input;
+    _stride = 0;
+  } else {
+    uint32_t num_elements;
+    type->unwrap_array(element_type, num_elements);
+    _count = num_elements;
+    if (num_elements > 1) {
+      _stride = ((const ShaderType::Array *)type)->get_stride_bytes();
+    } else {
+      _stride = 0;
+    }
+  }
+
+  const ShaderType::Struct *struct_type = element_type->as_struct();
+  nassertv_always(struct_type != nullptr);
+
+  for (size_t i = 0; i < struct_type->get_num_members(); ++i) {
+    const ShaderType::Struct::Member &member = struct_type->get_member(i);
+
+    PT(InternalName) fqname = InternalName::make("light")->append(member.name);
+    if (member.name == "color") {
+      _color_offset = member.offset;
+    }
+    else if (member.name == "specular") {
+      _specular_offset = member.offset;
+    }
+    else if (member.name == "ambient") {
+      _ambient_offset = member.offset;
+    }
+    else if (member.name == "diffuse") {
+      _diffuse_offset = member.offset;
+    }
+    else if (member.name == "position") {
+      _position_offset = member.offset;
+    }
+    else if (member.name == "halfVector") {
+      _half_vector_offset = member.offset;
+    }
+    else if (member.name == "spotDirection") {
+      _spot_direction_offset = member.offset;
+    }
+    else if (member.name == "spotCosCutoff") {
+      _spot_cos_cutoff_offset = member.offset;
+    }
+    else if (member.name == "spotCutoff") {
+      _spot_cutoff_offset = member.offset;
+    }
+    else if (member.name == "spotExponent") {
+      _spot_exponent_offset = member.offset;
+    }
+    else if (member.name == "attenuation") {
+      _attenuation_offset = member.offset;
+    }
+    else if (member.name == "constantAttenuation") {
+      _constant_attenuation_offset = member.offset;
+    }
+    else if (member.name == "linearAttenuation") {
+      _linear_attenuation_offset = member.offset;
+    }
+    else if (member.name == "quadraticAttenuation") {
+      _quadratic_attenuation_offset = member.offset;
+    }
+    else if (member.name == "radius") {
+      _radius_offset = member.offset;
+    }
+    else if (member.name == "shadowViewMatrix") {
+      _shadow_view_matrix_offset = member.offset;
+    }
+    else if (member.name == "shadowMap") {
+      const ShaderType::SampledImage *sampler = member.type->as_sampled_image();
+      nassertd(sampler != nullptr) continue;
+      _cube_shadow_map = sampler->get_texture_type() == Texture::TT_cube_map;
+    }
+  }
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderLightStructBinding::
+get_state_dep() const {
+  int dep = Shader::D_frame |
+            Shader::get_matrix_deps(Shader::SM_world_to_view) |
+            Shader::get_matrix_deps(Shader::SM_apiview_to_world);
+  if (_input != nullptr) {
+    return Shader::D_shader_inputs | dep;
+  } else {
+    return Shader::D_light | dep;
+  }
+}
+
+/**
+ * Sets up anything necessary to prepare this binding to be used with the given
+ * shader.
+ */
+void ShaderLightStructBinding::
+setup(Shader *shader) {
+  _world_to_view_mat_cache_index = shader->add_matrix_cache_item(Shader::SM_world_to_view, nullptr, Shader::get_matrix_deps(Shader::SM_world_to_view));
+  _apiview_to_world_mat_cache_index = shader->add_matrix_cache_item(Shader::SM_apiview_to_world, nullptr, Shader::get_matrix_deps(Shader::SM_apiview_to_world));
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderLightStructBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  if (_input != nullptr) {
+    // Fetch shader input.
+    if (state.gsg->get_target_shader_attrib()->has_shader_input(_input)) {
+      const NodePath &np = state.gsg->get_target_shader_attrib()->get_shader_input_nodepath(_input);
+      fetch_light(state, np, into);
+    } else {
+      // Maybe it's a data struct after all, even though it looked like a light
+      // struct initially.
+      fetch_from_input(state.gsg->get_target_shader_attrib(), into);
+    }
+  } else {
+    // Fetch array from state.
+    size_t num_lights = 0;
+    const LightAttrib *target_light;
+    if (state.gsg->get_target_state()->get_attrib(target_light)) {
+      num_lights = std::min(_count, target_light->get_num_non_ambient_lights());
+    }
+
+    size_t i = 0;
+    for (i = 0; i < num_lights; ++i) {
+      NodePath light = target_light->get_on_light(i);
+      fetch_light(state, light, into);
+      into = (unsigned char *)into + _stride;
+    }
+    // Apply the default OpenGL lights otherwise.
+    // Special exception for light 0, which defaults to white.
+    for (; i < _count; ++i) {
+      fetch_light(state, NodePath(), into);
+      if (i == 0 && _color_offset >= 0) {
+        *(LVecBase4f *)((unsigned char *)into + _color_offset) = LVecBase4f(1, 1, 1, 1);
+      }
+      into = (unsigned char *)into + _stride;
+    }
+  }
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderLightStructBinding::
+fetch_light(const State &state, const NodePath &np, void *into) const {
+  PandaNode *node = nullptr;
+  if (!np.is_empty()) {
+    node = np.node();
+  }
+
+  LVecBase4f color(0, 0, 0, 1);
+  LVecBase4f specular(0, 0, 0, 1);
+  LVecBase4f ambient(0, 0, 0, 1);
+  LVecBase4f diffuse(0, 0, 0, 1);
+  LVecBase4f position(0, 0, 1, 0);
+  LVecBase4f half_vector(0, 0, 1, 0);
+  LVecBase4f spot_direction(0, 0, -1, 0);
+  float spot_cos_cutoff = -1;
+  float spot_cutoff = 180;
+  float spot_exponent = 0;
+  LVecBase3f attenuation(1, 0, 0);
+  float radius = 0;
+  LMatrix4f shadow_view_matrix = LCAST(float, shadow_bias_mat);
+
+  if (node != nullptr) {
+    Light *light = node->as_light();
+    nassertv(light != nullptr);
+
+    color = LCAST(float, light->get_color());
+    specular = LCAST(float, light->get_specular_color());
+
+    if (node->is_ambient_light()) {
+      ambient = color;
+      diffuse.set(0, 0, 0, 1);
+      position.set(0, 0, 0, 0);
+      half_vector.set(0, 0, 0, 0);
+      spot_direction.set(0, 0, 0, 0);
+    } else {
+      ambient.set(0, 0, 0, 1);
+      diffuse = color;
+
+      CPT(TransformState) net_transform = np.get_net_transform();
+      LMatrix4 mat = net_transform->get_mat() *
+        state.matrix_cache[_world_to_view_mat_cache_index];
+
+      LightLensNode *light;
+      DCAST_INTO_V(light, node);
+      Lens *lens = light->get_lens();
+      nassertv(lens != nullptr);
+
+      if (node->is_of_type(DirectionalLight::get_class_type())) {
+        DirectionalLight *light;
+        DCAST_INTO_V(light, node);
+
+        LVector3 dir = -(light->get_direction() * mat);
+        position.set(dir[0], dir[1], dir[2], 0);
+
+        dir.normalize();
+        dir += LVector3(0, 0, 1);
+        dir.normalize();
+        half_vector.set(dir[0], dir[1], dir[2], 1);
+      }
+      else {
+        LPoint3 pos = lens->get_nodal_point() * mat;
+        position.set(pos[0], pos[1], pos[2], 1);
+
+        pos.normalize();
+        pos += LVector3(0, 0, 1);
+        pos.normalize();
+        half_vector.set(pos[0], pos[1], pos[2], 1);
+      }
+
+      if (node->is_of_type(Spotlight::get_class_type())) {
+        float cutoff = lens->get_hfov() * 0.5f;
+        spot_cos_cutoff = ccos(deg_2_rad(cutoff));
+        spot_cutoff = cutoff;
+      }
+      spot_exponent = light->get_exponent();
+
+      LVector3 dir = lens->get_view_vector() * mat;
+      spot_direction.set(dir[0], dir[1], dir[2], 0);
+
+      if (_shadow_view_matrix_offset >= 0) {
+        CPT(TransformState) inv_net_transform = net_transform->get_inverse();
+        LMatrix4 t = state.matrix_cache[_apiview_to_world_mat_cache_index] *
+          inv_net_transform->get_mat() *
+          LMatrix4::convert_mat(state.gsg->get_coordinate_system(), lens->get_coordinate_system());
+
+        if (!node->is_of_type(PointLight::get_class_type())) {
+          t *= lens->get_projection_mat() * shadow_bias_mat;
+        }
+        shadow_view_matrix = LCAST(float, t);
+      }
+    }
+
+    attenuation = LCAST(float, light->get_attenuation());
+    if (_radius_offset >= 0 && node->is_of_type(SphereLight::get_class_type())) {
+      radius = ((const SphereLight *)node)->get_radius();
+    }
+  }
+
+  unsigned char *p = (unsigned char *)into;
+
+  if (_color_offset >= 0) *(LVecBase4f *)(p + _color_offset) = color;
+  if (_specular_offset >= 0) *(LVecBase4f *)(p + _specular_offset) = specular;
+  if (_ambient_offset >= 0) *(LVecBase4f *)(p + _ambient_offset) = ambient;
+  if (_diffuse_offset >= 0) *(LVecBase4f *)(p + _diffuse_offset) = diffuse;
+  if (_position_offset >= 0) *(LVecBase4f *)(p + _position_offset) = position;
+  if (_half_vector_offset >= 0) *(LVecBase4f *)(p + _half_vector_offset) = half_vector;
+  if (_spot_direction_offset >= 0) *(LVecBase4f *)(p + _spot_direction_offset) = spot_direction;
+  if (_spot_cos_cutoff_offset >= 0) *(float *)(p + _spot_cos_cutoff_offset) = spot_cos_cutoff;
+  if (_spot_cutoff_offset >= 0) *(float *)(p + _spot_cutoff_offset) = spot_cutoff;
+  if (_spot_exponent_offset >= 0) *(float *)(p + _spot_exponent_offset) = spot_exponent;
+  if (_attenuation_offset >= 0) *(LVecBase3f *)(p + _attenuation_offset) = attenuation;
+  if (_constant_attenuation_offset >= 0) *(float *)(p + _constant_attenuation_offset) = attenuation[0];
+  if (_linear_attenuation_offset >= 0) *(float *)(p + _linear_attenuation_offset) = attenuation[1];
+  if (_quadratic_attenuation_offset >= 0) *(float *)(p + _quadratic_attenuation_offset) = attenuation[2];
+  if (_radius_offset >= 0) *(float *)(p + _radius_offset) = radius;
+  if (_shadow_view_matrix_offset >= 0) *(LMatrix4f *)(p + _shadow_view_matrix_offset) = shadow_view_matrix;
+}
+
+/**
+ * Fetches the light information from a shader input, if it doesn't turn out
+ * to be a light structure after all.
+ */
+void ShaderLightStructBinding::
+fetch_from_input(const ShaderAttrib *target_shader, void *into) const {
+  InternalName *input = (InternalName *)_input.p();
+  unsigned char *p = (unsigned char *)into;
+  if (_color_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("color"));
+    *(LVecBase4f *)(p + _color_offset) = LCAST(float, v);
+  }
+  if (_specular_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("specular"));
+    *(LVecBase4f *)(p + _specular_offset) = LCAST(float, v);
+  }
+  if (_ambient_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("ambient"));
+    *(LVecBase4f *)(p + _ambient_offset) = LCAST(float, v);
+  }
+  if (_diffuse_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("diffuse"));
+    *(LVecBase4f *)(p + _diffuse_offset) = LCAST(float, v);
+  }
+  if (_position_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("position"));
+    *(LVecBase4f *)(p + _position_offset) = LCAST(float, v);
+  }
+  if (_half_vector_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("halfVector"));
+    *(LVecBase4f *)(p + _half_vector_offset) = LCAST(float, v);
+  }
+  if (_spot_direction_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("spotDirection"));
+    *(LVecBase4f *)(p + _spot_direction_offset) = LCAST(float, v);
+  }
+  if (_spot_cos_cutoff_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("spotCosCutoff"));
+    *(float *)(p + _spot_cos_cutoff_offset) = v[0];
+  }
+  if (_spot_cutoff_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("spotCutoff"));
+    *(float *)(p + _spot_cutoff_offset) = v[0];
+  }
+  if (_spot_exponent_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("spotExponent"));
+    *(float *)(p + _spot_exponent_offset) = v[0];
+  }
+  if (_attenuation_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("attenuation"));
+    *(LVecBase4f *)(p + _attenuation_offset) = LCAST(float, v);
+  }
+  if (_constant_attenuation_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("constantAttenuation"));
+    *(float *)(p + _constant_attenuation_offset) = v[0];
+  }
+  if (_linear_attenuation_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("linearAttenuation"));
+    *(float *)(p + _linear_attenuation_offset) = v[0];
+  }
+  if (_quadratic_attenuation_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("quadraticAttenuation"));
+    *(float *)(p + _quadratic_attenuation_offset) = v[0];
+  }
+  if (_radius_offset >= 0) {
+    LVecBase4 v = target_shader->get_shader_input_vector(input->append("radius"));
+    *(float *)(p + _radius_offset) = v[0];
+  }
+  if (_shadow_view_matrix_offset >= 0) {
+    target_shader->get_shader_input_matrix(input->append("shadowViewMatrix"), *(LMatrix4f *)(p + _shadow_view_matrix_offset));
+  }
+}
+
+/**
+ * Returns an opaque resource identifier that can later be used to fetch the
+ * nth resource, which is of the given type.
+ */
+ShaderInputBinding::ResourceId ShaderLightStructBinding::
+get_resource_id(int index, const ShaderType *type) const {
+  if (_input != nullptr) {
+    nassertr(index == 0, 0);
+  }
+  return index;
+}
+
+/**
+ * Fetches the texture associated with this shader input.
+ */
+PT(Texture) ShaderLightStructBinding::
+fetch_texture(const State &state, ResourceId resource_id, SamplerState &sampler, int &view) const {
+  NodePath light;
+  if (_input != nullptr) {
+    if (state.gsg->get_target_shader_attrib()->has_shader_input(_input)) {
+      light = state.gsg->get_target_shader_attrib()->get_shader_input_nodepath(_input);
+    } else {
+      // Maybe it's a data struct after all, even though it looked like a light
+      // struct initially.
+      return state.gsg->get_target_shader_attrib()->get_shader_input_texture(((InternalName *)_input.p())->append("shadowMap"), &sampler);
+    }
+  } else {
+    const LightAttrib *target_light;
+    size_t index = (size_t)resource_id;
+    if (state.gsg->get_target_state()->get_attrib(target_light) && index < target_light->get_num_non_ambient_lights()) {
+      light = target_light->get_on_light(index);
+    }
+  }
+
+  PT(Texture) tex;
+  if (!light.is_empty()) {
+    tex = state.gsg->get_shadow_map(light);
+  } else {
+    // There is no such light assigned.  Bind a dummy shadow map.
+    tex = state.gsg->get_dummy_shadow_map(_cube_shadow_map);
+  }
+  if (tex != nullptr) {
+    sampler = tex->get_default_sampler();
+  }
+  return tex;
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderTextureStagesBinding::
+get_state_dep() const {
+  return Shader::D_texture;
+}
+
+/**
+ * Returns an opaque resource identifier that can later be used to fetch the
+ * nth resource, which is of the given type.
+ */
+ShaderInputBinding::ResourceId ShaderTextureStagesBinding::
+get_resource_id(int index, const ShaderType *type) const {
+  return index;
+}
+
+/**
+ * Fetches the texture associated with this shader input.
+ */
+PT(Texture) ShaderTextureStagesBinding::
+fetch_texture(const State &state, ResourceId resource_id, SamplerState &sampler, int &view) const {
+  int index = (int)resource_id;
+
+  // We get the TextureAttrib directly from the _target_rs, not the
+  // filtered TextureAttrib in _target_texture.
+  const TextureAttrib *texattrib;
+  if (state.gsg->get_target_state()->get_attrib(texattrib) && index < texattrib->get_num_on_stages()) {
+    int si = 0;
+    for (int i = 0; i < texattrib->get_num_on_stages(); ++i) {
+      TextureStage *stage = texattrib->get_on_stage(i);
+      TextureStage::Mode mode = stage->get_mode();
+
+      if ((1 << mode) & _mode_mask) {
+        if (si++ == index) {
+          sampler = texattrib->get_on_sampler(stage);
+          view += stage->get_tex_view_offset();
+          PT(Texture) tex = texattrib->get_on_texture(stage);
+          if (tex->get_texture_type() != _desired_type) {
+            shader_cat.error()
+              << "Texture " << *tex
+              << " does not match type desired by shader\n";
+          }
+          return tex;
+        }
+      }
+    }
+  }
+
+  return _default_texture;
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderTextureBinding::
+get_state_dep() const {
+  return Shader::D_frame | Shader::D_shader_inputs;
+}
+
+/**
+ * Returns an opaque resource identifier that can later be used to fetch the
+ * nth resource, which is of the given type.
+ */
+ShaderInputBinding::ResourceId ShaderTextureBinding::
+get_resource_id(int index, const ShaderType *type) const {
+  return (ResourceId)_input.p();
+}
+
+/**
+ * Fetches the texture associated with this shader input.
+ */
+PT(Texture) ShaderTextureBinding::
+fetch_texture(const State &state, ResourceId resource_id, SamplerState &sampler, int &view) const {
+  const InternalName *name = (const InternalName *)resource_id;
+  PT(Texture) tex = state.gsg->get_target_shader_attrib()->get_shader_input_texture(name, &sampler);
+#ifndef NDEBUG
+  if (!_shown_error && tex->get_texture_type() != _desired_type) {
+    _shown_error = true;
+    shader_cat.error()
+      << "Sampler type of shader input '" << *name << "' does not "
+         "match type of texture " << *tex << ".\n";
+    // TODO: also check whether shadow sampler textures have shadow filter
+    // enabled.
+  }
+#endif
+  return tex;
+}
+
+/**
+ * Fetches the texture that should be bound as a storage image.
+ */
+PT(Texture) ShaderTextureBinding::
+fetch_texture_image(const State &state, ResourceId resource_id, ShaderType::Access &access, int &z, int &n) const {
+  const InternalName *name = (const InternalName *)resource_id;
+  PT(Texture) tex = state.gsg->get_target_shader_attrib()->get_shader_input_texture_image(name, access, z, n);
+#ifndef NDEBUG
+  if (!_shown_error && tex->get_texture_type() != _desired_type) {
+    _shown_error = true;
+    shader_cat.error()
+      << "Sampler type of shader input '" << *name << "' does not "
+         "match type of texture " << *tex << ".\n";
+    // TODO: also check whether shadow sampler textures have shadow filter
+    // enabled.
+  }
+#endif
+  return tex;
+}
+
+/**
+ * Returns a mask indicating which state changes should cause the parameter to
+ * be respecified.
+ */
+int ShaderDataBinding::
+get_state_dep() const {
+  // We specify SD_frame because a PTA may be modified by the app from
+  // frame to frame, and we have no way to know.  So, we must respecify a
+  // PTA at least once every frame.
+  return Shader::D_frame | Shader::D_shader_inputs;
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderFloatBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  Shader::ShaderPtrData ptr_data;
+  if (!state.gsg->get_target_shader_attrib()->get_shader_input_ptr(_input, ptr_data)) {
+    return;
+  }
+
+  int total_rows = std::min(_num_elements * _num_rows, (int)ptr_data._size / _num_cols);
+  if (total_rows == 1) {
+    pad_rows = false;
+  }
+
+  float *data = (float *)into;
+
+  switch (ptr_data._type) {
+  case ShaderType::ST_int:
+    // Convert int data to float data.
+    if (!pad_rows || _num_cols == 4) {
+      for (int i = 0; i < total_rows * _num_cols; ++i) {
+        data[i] = (float)(((int *)ptr_data._ptr)[i]);
+      }
+    } else {
+      const int *from_data = (const int *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (float)*from_data++;
+        }
+      }
+    }
+    return;
+
+  case ShaderType::ST_uint:
+    // Convert unsigned int data to float data.
+    if (!pad_rows || _num_cols == 4) {
+      for (int i = 0; i < total_rows * _num_cols; ++i) {
+        data[i] = (float)(((unsigned int *)ptr_data._ptr)[i]);
+      }
+    } else {
+      const unsigned int *from_data = (const unsigned int *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (float)*from_data++;
+        }
+      }
+    }
+    return;
+
+  case ShaderType::ST_double:
+    // Downgrade double data to float data.
+    if (!pad_rows || _num_cols == 4) {
+      for (int i = 0; i < total_rows * _num_cols; ++i) {
+        data[i] = (float)(((double *)ptr_data._ptr)[i]);
+      }
+    } else {
+      const double *from_data = (const double *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (float)*from_data++;
+        }
+      }
+    }
+    return;
+
+  case ShaderType::ST_float:
+    if (!pad_rows || _num_cols == 4) {
+      // No conversion needed.
+      memcpy(data, ptr_data._ptr, total_rows * _num_cols * sizeof(float));
+      return;
+      /*if (always_copy) {
+        memcpy(data, ptr_data._ptr, total_rows * _num_cols * sizeof(float));
+        return;
+      } else {
+        return (float *)ptr_data._ptr;
+      }*/
+    } else {
+      const float *from_data = (const float *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (float)*from_data++;
+        }
+      }
+    }
+    return;
+
+  default:
+#ifndef NDEBUG
+    shader_cat.error()
+      << "Invalid ShaderPtrData type " << (int)ptr_data._type
+      << " for shader input '" << *_input << "'\n";
+#endif
+  }
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderDoubleBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  Shader::ShaderPtrData ptr_data;
+  if (!state.gsg->get_target_shader_attrib()->get_shader_input_ptr(_input, ptr_data)) {
+    return;
+  }
+
+  int total_rows = std::min(_num_elements * _num_rows, (int)ptr_data._size / _num_cols);
+  if (total_rows == 1) {
+    pad_rows = false;
+  }
+
+  double *data = (double *)into;
+
+  switch (ptr_data._type) {
+  case ShaderType::ST_int:
+    // Convert int data to double data.
+    if (!pad_rows || _num_cols == 4) {
+      for (int i = 0; i < total_rows * _num_cols; ++i) {
+        data[i] = (double)(((int *)ptr_data._ptr)[i]);
+      }
+    } else {
+      const int *from_data = (const int *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (double)*from_data++;
+        }
+      }
+    }
+    return;
+
+  case ShaderType::ST_uint:
+    // Convert int data to double data.
+    if (!pad_rows || _num_cols == 4) {
+      for (int i = 0; i < total_rows * _num_cols; ++i) {
+        data[i] = (double)(((unsigned int *)ptr_data._ptr)[i]);
+      }
+    } else {
+      const int *from_data = (const int *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (double)*from_data++;
+        }
+      }
+    }
+    return;
+
+  case ShaderType::ST_double:
+    if (!pad_rows || _num_cols == 4) {
+      // No conversion needed.
+      //if (always_copy) {
+        memcpy(data, ptr_data._ptr, total_rows * _num_cols * sizeof(double));
+        return;
+      //} else {
+      //  return (double *)ptr_data._ptr;
+      //}
+    } else {
+      const double *from_data = (const double *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (double)*from_data++;
+        }
+      }
+    }
+    return;
+
+  case ShaderType::ST_float:
+    // Upgrade float data to double data.
+    if (!pad_rows || _num_cols == 4) {
+      for (int i = 0; i < total_rows * _num_cols; ++i) {
+        data[i] = (double)(((float *)ptr_data._ptr)[i]);
+      }
+    } else {
+      const float *from_data = (const float *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (double)*from_data++;
+        }
+      }
+    }
+    return;
+
+  default:
+#ifndef NDEBUG
+    shader_cat.error()
+      << "Invalid ShaderPtrData type " << (int)ptr_data._type
+      << " for shader input '" << *_input << "'\n";
+#endif
+  }
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderIntBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  Shader::ShaderPtrData ptr_data;
+  if (!state.gsg->get_target_shader_attrib()->get_shader_input_ptr(_input, ptr_data)) {
+    return;
+  }
+
+  if (ptr_data._type != ShaderType::ST_int &&
+      ptr_data._type != ShaderType::ST_uint &&
+      ptr_data._type != ShaderType::ST_bool) {
+    shader_cat.error()
+      << "Cannot pass floating-point data to integer shader input '" << *_input << "'\n";
+    return;
+  }
+
+  int total_rows = std::min(_num_elements * _num_rows, (int)ptr_data._size / _num_cols);
+  if (total_rows == 1) {
+    pad_rows = false;
+  }
+
+  if (!pad_rows || _num_cols == 4) {
+    memcpy(into, ptr_data._ptr, total_rows * _num_cols * sizeof(int));
+  } else {
+    int *data = (int *)into;
+    const int *from_data = (const int *)ptr_data._ptr;
+    for (int i = 0; i < total_rows; ++i) {
+      for (int c = 0; c < _num_cols; ++c) {
+        data[i * 4 + c] = *from_data++;
+      }
+    }
+  }
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderBoolBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  Shader::ShaderPtrData ptr_data;
+  if (!state.gsg->get_target_shader_attrib()->get_shader_input_ptr(_input, ptr_data)) {
+    return;
+  }
+
+  int total_rows = std::min(_num_elements * _num_rows, (int)ptr_data._size / _num_cols);
+  if (total_rows == 1) {
+    pad_rows = false;
+  }
+
+  uint32_t *data = (uint32_t *)into;
+
+  switch (ptr_data._type) {
+  case ShaderType::ST_int:
+  case ShaderType::ST_uint:
+  case ShaderType::ST_bool:
+    // Convert int data to bool data.
+    if (!pad_rows || _num_cols == 4) {
+      for (int i = 0; i < total_rows * _num_cols; ++i) {
+        data[i] = (uint32_t)(((unsigned int *)ptr_data._ptr)[i] != 0);
+      }
+    } else {
+      const int *from_data = (const int *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (uint32_t)(*from_data++ != 0);
+        }
+      }
+    }
+    return;
+
+  case ShaderType::ST_double:
+    // Convert double data to bool data.
+    if (!pad_rows || _num_cols == 4) {
+      for (int i = 0; i < total_rows * _num_cols; ++i) {
+        data[i] = (uint32_t)(((double *)ptr_data._ptr)[i] != 0.0);
+      }
+    } else {
+      const double *from_data = (const double *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (uint32_t)(*from_data++ != 0.0);
+        }
+      }
+    }
+    return;
+
+  case ShaderType::ST_float:
+    // Convert float data to bool data.
+    if (!pad_rows || _num_cols == 4) {
+      for (int i = 0; i < total_rows * _num_cols; ++i) {
+        data[i] = (uint32_t)(((float *)ptr_data._ptr)[i] != 0.0f);
+      }
+    } else {
+      const float *from_data = (const float *)ptr_data._ptr;
+      for (int i = 0; i < total_rows; ++i) {
+        for (int c = 0; c < _num_cols; ++c) {
+          data[i * 4 + c] = (uint32_t)(*from_data++ != 0.0f);
+        }
+      }
+    }
+    return;
+
+  default:
+#ifndef NDEBUG
+    shader_cat.error()
+      << "Invalid ShaderPtrData type " << (int)ptr_data._type
+      << " for shader input '" << *_input << "'\n";
+#endif
+  }
+}
+
+/**
+ * Fetches the part of the shader input that is plain numeric data.
+ */
+void ShaderAggregateBinding::
+fetch_data(const State &state, void *into, bool pad_rows) const {
+  for (const DataMember &member : _data_members) {
+    member._binding->fetch_data(state, (unsigned char *)into + member._offset, pad_rows);
+  }
+}
+
+/**
+ * Returns an opaque resource identifier that can later be used to fetch the
+ * nth resource, which is of the given type.
+ */
+ShaderInputBinding::ResourceId ShaderAggregateBinding::
+get_resource_id(int index, const ShaderType *type) const {
+  nassertr(index >= 0 && (size_t)index < _resources.size(), 0);
+  return (ResourceId)_resources[index].p();
+}
+
+/**
+ * Fetches the texture associated with this shader input.
+ */
+PT(Texture) ShaderAggregateBinding::
+fetch_texture(const State &state, ResourceId resource_id, SamplerState &sampler, int &view) const {
+  const InternalName *name = (const InternalName *)resource_id;
+  return state.gsg->get_target_shader_attrib()->get_shader_input_texture(name, &sampler);
+}
+
+/**
+ * Fetches the texture that should be bound as a storage image.
+ */
+PT(Texture) ShaderAggregateBinding::
+fetch_texture_image(const State &state, ResourceId resource_id, ShaderType::Access &access, int &z, int &n) const {
+  const InternalName *name = (const InternalName *)resource_id;
+  return state.gsg->get_target_shader_attrib()->get_shader_input_texture_image(name, access, z, n);
+}
+
+/**
+ * Unwraps the aggregate type, storing the individual members.
+ */
+void ShaderAggregateBinding::
+r_collect_members(const InternalName *name, const ShaderType *type, size_t offset) {
+  ShaderType::ScalarType scalar_type;
+  uint32_t arg_dim[3];
+  if (type->as_scalar_type(scalar_type, arg_dim[0], arg_dim[1], arg_dim[2])) {
+    ShaderDataBinding *binding;
+    switch (scalar_type) {
+    case ShaderType::ST_float:
+      binding = new ShaderFloatBinding(name, arg_dim[0], arg_dim[1], arg_dim[2]);
+      break;
+
+    case ShaderType::ST_double:
+      binding = new ShaderDoubleBinding(name, arg_dim[0], arg_dim[1], arg_dim[2]);
+      break;
+
+    case ShaderType::ST_bool:
+      binding = new ShaderBoolBinding(name, arg_dim[0], arg_dim[1], arg_dim[2]);
+      break;
+
+    default:
+      binding = new ShaderIntBinding(name, arg_dim[0], arg_dim[1], arg_dim[2]);
+      break;
+    }
+
+    _data_members.push_back({binding, offset});
+  }
+  else if (const ::ShaderType::Struct *struct_type = type->as_struct()) {
+    for (size_t i = 0; i < struct_type->get_num_members(); ++i) {
+      const ::ShaderType::Struct::Member &member = struct_type->get_member(i);
+
+      PT(InternalName) fqname = ((InternalName *)name)->append(member.name);
+      r_collect_members(fqname, member.type, offset + member.offset);
+    }
+  }
+  else if (const ::ShaderType::Array *array_type = type->as_array()) {
+    size_t basename_size = name->get_basename().size();
+    char *buffer = (char *)alloca(basename_size + 14);
+    memcpy(buffer, name->get_basename().c_str(), basename_size);
+
+    const ShaderType *element_type = array_type->get_element_type();
+    size_t stride = (size_t)array_type->get_stride_bytes();
+
+    for (uint32_t ai = 0; ai < array_type->get_num_elements(); ++ai) {
+      sprintf(buffer + basename_size, "[%d]", (int)ai);
+
+      r_collect_members(name->get_parent()->append(buffer), element_type, offset);
+      offset += stride;
+    }
+  }
+  else if (type->as_sampled_image() != nullptr || type->as_image() != nullptr) {
+    _resources.push_back(name);
+  }
+}
+
+/**
+ * Creates the appropriate binding for the input with the given name and type.
+ */
+ShaderInputBinding *
+make_binding_glsl(const InternalName *name, const ShaderType *type) {
+  std::string name_str = name->get_name();
+
+  // Split it at the underscores.
+  vector_string pieces;
+  tokenize(name_str, pieces, "_");
+  nassertr(!pieces.empty(), nullptr);
+
+  // Check if it has a p3d_ prefix - if so, assign special meaning.
+  if (pieces[0] == "p3d") {
+    // Check for matrix inputs.
+    bool transpose = false;
+    bool inverse = false;
+    string matrix_name = pieces[1];
+
+    // Check for and chop off any "Transpose" or "Inverse" suffix.
+    if (matrix_name.size() > 6 + 9 &&
+        matrix_name.compare(matrix_name.size() - 9, 9, "Transpose") == 0) {
+      transpose = true;
+      matrix_name = matrix_name.substr(0, matrix_name.size() - 9);
+    }
+    if (matrix_name.size() > 6 + 7 &&
+        matrix_name.compare(matrix_name.size() - 7, 7, "Inverse") == 0) {
+      inverse = true;
+      matrix_name = matrix_name.substr(0, matrix_name.size() - 7);
+    }
+
+    // Now if the suffix that is left over is "Matrix", we know that it is
+    // supposed to be a matrix input.
+    if (matrix_name.size() > 6 &&
+        matrix_name.compare(matrix_name.size() - 6, 6, "Matrix") == 0) {
+
+      if (!expect_float_matrix(name, type, 3, 4)) {
+        return nullptr;
+      }
+
+      Shader::StateMatrix part[2] = {
+        Shader::SM_identity,
+        Shader::SM_identity,
+      };
+      if (matrix_name == "ModelViewProjectionMatrix") {
+        if (inverse) {
+          part[0] = Shader::SM_apiclip_to_apiview;
+          part[1] = Shader::SM_apiview_to_model;
+        } else {
+          part[0] = Shader::SM_model_to_apiview;
+          part[1] = Shader::SM_apiview_to_apiclip;
+        }
+      }
+      else if (matrix_name == "ModelViewMatrix") {
+        part[0] = inverse ? Shader::SM_apiview_to_model
+                          : Shader::SM_model_to_apiview;
+      }
+      else if (matrix_name == "ProjectionMatrix") {
+        part[0] = inverse ? Shader::SM_apiclip_to_apiview
+                          : Shader::SM_apiview_to_apiclip;
+      }
+      else if (matrix_name == "NormalMatrix") {
+        // This is really the upper 3x3 of the ModelViewMatrixInverseTranspose.
+        part[0] = inverse ? Shader::SM_model_to_apiview
+                          : Shader::SM_apiview_to_model;
+
+        if (!expect_float_matrix(name, type, 3, 3)) {
+          return nullptr;
+        }
+      }
+      else if (matrix_name == "ModelMatrix") {
+        if (inverse) {
+          part[0] = Shader::SM_world_to_view;
+          part[1] = Shader::SM_view_to_model;
+        } else {
+          part[0] = Shader::SM_model_to_view;
+          part[1] = Shader::SM_view_to_world;
+        }
+      }
+      else if (matrix_name == "ViewMatrix") {
+        if (inverse) {
+          part[0] = Shader::SM_apiview_to_view;
+          part[1] = Shader::SM_view_to_world;
+        } else {
+          part[0] = Shader::SM_world_to_view;
+          part[1] = Shader::SM_view_to_apiview;
+        }
+      }
+      else if (matrix_name == "ViewProjectionMatrix") {
+        if (inverse) {
+          part[0] = Shader::SM_apiclip_to_view;
+          part[1] = Shader::SM_view_to_world;
+        } else {
+          part[0] = Shader::SM_world_to_view;
+          part[1] = Shader::SM_view_to_apiclip;
+        }
+      }
+      else if (matrix_name == "TextureMatrix") {
+        // We may support 2-D texmats later, but let's make sure that people
+        // don't think they can just use a mat3 to get the 2-D version.
+        if (!expect_float_matrix(name, type, 4, 4)) {
+          return nullptr;
+        }
+
+        return make_texture_matrix(type, 0, inverse, transpose);
+      }
+      else {
+        return report_parameter_error(name, type, "unrecognized matrix name");
+      }
+
+      return make_matrix_compose(type, part[0], nullptr, part[1], nullptr, transpose);
+    }
+    if (pieces[1].compare(0, 7, "Texture") == 0) {
+      const ShaderType *element_type;
+      uint32_t num_elements;
+      type->unwrap_array(element_type, num_elements);
+
+      const ShaderType::SampledImage *sampled_image_type = element_type->as_sampled_image();
+      if (sampled_image_type == nullptr) {
+        return report_parameter_error(name, type, "expected sampled image");
+      }
+
+      if (pieces[1].size() > 7 && isdigit(pieces[1][7])) {
+        // p3d_Texture0, p3d_Texture1, etc.
+        std::string tail;
+        int stage = string_to_int(pieces[1].substr(7), tail);
+        if (!tail.empty()) {
+          string msg = "unexpected '" + tail + "'";
+          return report_parameter_error(name, type, msg.c_str());
+        }
+
+        return make_texture_stage(type, stage);
+      }
+      else {
+        // p3d_Texture[] or p3d_TextureModulate[], etc.
+        Texture *default_texture = get_white_texture();
+        unsigned int mode_mask;
+        if (pieces[1].size() == 7) {
+          mode_mask = ~0u;
+        }
+        else if (pieces[1].compare(7, string::npos, "FF") == 0) {
+          mode_mask = (1 << TextureStage::M_normal) - 1;
+        }
+        else if (pieces[1].compare(7, string::npos, "Modulate") == 0) {
+          mode_mask = (1 << TextureStage::M_modulate)
+                    | (1 << TextureStage::M_modulate_glow)
+                    | (1 << TextureStage::M_modulate_gloss);
+        }
+        else if (pieces[1].compare(7, string::npos, "Add") == 0) {
+          static PT(Texture) default_add_tex;
+          if (default_add_tex == nullptr) {
+            PT(Texture) tex = new Texture("default-add");
+            tex->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_luminance);
+            tex->set_clear_color(LColor(0, 0, 0, 1));
+            default_add_tex = std::move(tex);
+          }
+          default_texture = default_add_tex.p();
+          mode_mask = (1 << TextureStage::M_add);
+        }
+        else if (pieces[1].compare(7, string::npos, "Normal") == 0) {
+          mode_mask = (1 << TextureStage::M_normal)
+                    | (1 << TextureStage::M_normal_height);
+        }
+        else if (pieces[1].compare(7, string::npos, "Height") == 0) {
+          mode_mask = (1 << TextureStage::M_height)
+                    | (1 << TextureStage::M_normal_height);
+        }
+        else if (pieces[1].compare(7, string::npos, "Selector") == 0) {
+          mode_mask = (1 << TextureStage::M_selector);
+        }
+        else if (pieces[1].compare(7, string::npos, "Gloss") == 0) {
+          mode_mask = (1 << TextureStage::M_gloss)
+                    | (1 << TextureStage::M_modulate_gloss)
+                    | (1 << TextureStage::M_normal_gloss);
+        }
+        else if (pieces[1].compare(7, string::npos, "Emission") == 0) {
+          mode_mask = (1 << TextureStage::M_emission);
+        }
+        else {
+          return report_parameter_error(name, type, "unrecognized parameter name");
+        }
+
+        if (pieces[1].size() > 7 && (mode_mask & (1 << TextureStage::M_normal_height)) != 0) {
+          static PT(Texture) default_normal_height_tex;
+          if (default_normal_height_tex == nullptr) {
+            PT(Texture) tex = new Texture("default-normal-height");
+            tex->setup_2d_texture(1, 1, Texture::T_unsigned_byte, Texture::F_rgba);
+            tex->set_clear_color(LColor(0.5, 0.5, 1, 0));
+            default_normal_height_tex = std::move(tex);
+          }
+          default_texture = default_normal_height_tex.p();
+        }
+
+        return new ShaderTextureStagesBinding(sampled_image_type->get_texture_type(), num_elements, default_texture, mode_mask);
+      }
+    }
+    if (pieces[1] == "Material") {
+      const ShaderType::Struct *struct_type = type->as_struct();
+      if (struct_type == nullptr) {
+        return report_parameter_error(name, type, "expected struct");
+      }
+      return make_material(type);
+    }
+    if (pieces[1] == "ColorScale") {
+      if (!expect_float_vector(name, type, 3, 4)) {
+        return nullptr;
+      }
+      return make_color_scale(type);
+    }
+    if (pieces[1] == "Color") {
+      if (!expect_float_vector(name, type, 3, 4)) {
+        return nullptr;
+      }
+      return make_color(type);
+    }
+    if (pieces[1] == "ClipPlane") {
+      const ShaderType *element_type;
+      uint32_t num_elements;
+      type->unwrap_array(element_type, num_elements);
+      if (!expect_float_vector(name, element_type, 4, 4)) {
+        return nullptr;
+      }
+
+      return ShaderInputBinding::make_data(Shader::D_clip_planes | Shader::D_view_transform,
+                                           [=](const State &state, void *into, bool pad_rows) {
+
+        LPlanef *planes = (LPlanef *)into;
+
+        size_t i = 0;
+
+        const ClipPlaneAttrib *cpa;
+        if (state.gsg->get_target_state()->get_attrib(cpa)) {
+          SceneSetup *scene_setup = state.gsg->get_scene();
+          size_t num_planes = std::min((size_t)num_elements, (size_t)cpa->get_num_on_planes());
+          for (; i < num_planes; ++i) {
+            const NodePath &plane = cpa->get_on_plane(i);
+            nassertv(!plane.is_empty());
+            const PlaneNode *plane_node;
+            DCAST_INTO_V(plane_node, plane.node());
+
+            CPT(TransformState) transform =
+              scene_setup->get_cs_world_transform()->compose(
+                plane.get_transform(scene_setup->get_scene_root().get_parent()));
+
+            planes[i] = LCAST(float, plane_node->get_plane() * transform->get_mat());
+          }
+        }
+
+        for (; i < num_elements; ++i) {
+          // Fill the remainder with zeroes.
+          planes[i].set(0, 0, 0, 0);
+        }
+      });
+    }
+    if (pieces[1] == "TexAlphaOnly") {
+      // This is a hack so we can support both F_alpha and other formats in the
+      // default shader, to fix font rendering in GLES2
+      const ShaderType *element_type;
+      uint32_t num_elements = 1;
+      type->unwrap_array(element_type, num_elements);
+
+      return ShaderInputBinding::make_data(Shader::D_texture | Shader::D_frame,
+                                           [=](const State &state, void *into, bool pad_rows) {
+
+        const TextureAttrib *ta;
+
+        uint32_t num_stages = 0;
+        if (state.gsg->get_target_state()->get_attrib(ta)) {
+          num_stages = std::min(num_elements, (uint32_t)ta->get_num_on_stages());
+        }
+
+        uint32_t i = 0;
+        for (; i < num_stages; ++i) {
+          TextureStage *ts = ta->get_on_stage(i);
+          PN_stdfloat v = (ta->get_on_texture(ts)->get_format() == Texture::F_alpha);
+          ((LVecBase4f *)into)[i].set(v, v, v, 0);
+        }
+        for (; i < num_elements; ++i) {
+          ((LVecBase4f *)into)[i].set(0, 0, 0, 0);
+        }
+      });
+    }
+    if (pieces[1] == "Fog") {
+      const ShaderType::Struct *struct_type = type->as_struct();
+      if (struct_type == nullptr) {
+        return report_parameter_error(name, type, "expected struct");
+      }
+      return make_fog(struct_type);
+    }
+    if (pieces[1] == "LightModel") {
+      const ShaderType::Struct *struct_type = type->as_struct();
+      if (struct_type == nullptr || struct_type->get_num_members() != 1) {
+        return report_parameter_error(name, type, "expected struct with 1 member");
+      }
+
+      const ShaderType::Struct::Member &member = struct_type->get_member(0);
+      if (member.name != "ambient") {
+        return report_parameter_error(name, type, "expected 'ambient' member");
+      }
+
+      CPT(InternalName) fqname = ((InternalName *)name)->append(member.name);
+      if (!expect_float_vector(fqname, member.type, 3, 4)) {
+        return nullptr;
+      }
+
+      return make_light_ambient(member.type);
+    }
+    if (pieces[1] == "LightSource") {
+      const ShaderType::Array *array = type->as_array();
+      if (array == nullptr) {
+        return report_parameter_error(name, type, "expected array of structs");
+      }
+
+      const ShaderType::Struct *struct_type = array->get_element_type()->as_struct();
+      if (struct_type == nullptr) {
+        return report_parameter_error(name, type, "expected array of structs");
+      }
+
+      bool success = true;
+      for (size_t i = 0; i < struct_type->get_num_members(); ++i) {
+        const ShaderType::Struct::Member &member = struct_type->get_member(i);
+
+        if (!check_light_struct_member(member.name, member.type)) {
+          PT(InternalName) fqname = ((InternalName *)name)->append(member.name);
+          report_parameter_error(fqname, member.type, "not a valid light struct member");
+          success = false;
+        }
+      }
+      if (!success) {
+        return nullptr;
+      }
+
+      return new ShaderLightStructBinding(type);
+    }
+    if (pieces[1] == "TransformTable") {
+      const ShaderType *element_type;
+      uint32_t num_elements;
+      type->unwrap_array(element_type, num_elements);
+
+      const ShaderType::Matrix *matrix = element_type->as_matrix();
+      if (matrix == nullptr ||
+          matrix->get_num_rows() != 4 ||
+          matrix->get_num_columns() != 4 ||
+          matrix->get_scalar_type() != ShaderType::ST_float) {
+        return report_parameter_error(name, type, "expected mat4[]");
+      }
+
+      return make_transform_table(type, false);
+    }
+    if (pieces[1] == "SliderTable") {
+      const ShaderType *element_type;
+      uint32_t num_elements;
+      type->unwrap_array(element_type, num_elements);
+
+      if (element_type != ShaderType::float_type) {
+        return report_parameter_error(name, type, "expected float");
+      }
+
+      return make_slider_table(type);
+    }
+
+    return report_parameter_error(name, type, "unrecognized parameter name");
+  }
+
+  if (pieces[0] == "osg") {
+    if (!expect_num_words(name, type, 2)) {
+      return nullptr;
+    }
+
+    // These inputs are supported by OpenSceneGraph.  We can support them as
+    // well, to increase compatibility.
+    if (pieces[1] == "ViewMatrix") {
+      return make_matrix(type, Shader::SM_world_to_apiview, nullptr);
+    }
+    else if (pieces[1] == "InverseViewMatrix" || pieces[1] == "ViewMatrixInverse") {
+      return make_matrix_compose(type, Shader::SM_apiview_to_view, nullptr,
+                                       Shader::SM_view_to_world, nullptr);
+    }
+    else if (pieces[1] == "FrameTime") {
+      if (!expect_float_vector(name, type, 1, 1, true)) {
+        return nullptr;
+      }
+      return make_frame_time(type);
+    }
+    else if (pieces[1] == "DeltaFrameTime") {
+      if (type == ShaderType::float_type) {
+        return ShaderInputBinding::make_data(Shader::D_frame,
+                                             [](const State &state, void *into, bool pad_rows) {
+          *(float *)into = ClockObject::get_global_clock()->get_dt();
+        });
+      }
+      else if (type == ShaderType::double_type) {
+        return ShaderInputBinding::make_data(Shader::D_frame,
+                                             [](const State &state, void *into, bool pad_rows) {
+          *(double *)into = ClockObject::get_global_clock()->get_dt();
+        });
+      }
+      else {
+        return report_parameter_error(name, type, "expected float");
+      }
+    }
+    else if (pieces[1] == "FrameNumber") {
+      if (type == ShaderType::int_type) {
+        return ShaderInputBinding::make_data(Shader::D_frame,
+                                             [](const State &state, void *into, bool pad_rows) {
+          *(int *)into = ClockObject::get_global_clock()->get_frame_count();
+        });
+      } else {
+        return report_parameter_error(name, type, "expected int");
+      }
+    }
+    else {
+      return report_parameter_error(name, type, "unrecognized parameter name");
+    }
+  }
+
+  // Check for mstrans, wstrans, vstrans, cstrans, mspos, wspos, vspos, cspos
+  if (pieces[0].size() >= 5 &&
+      (pieces[0].compare(1, std::string::npos, "strans") == 0 ||
+       pieces[0].compare(1, std::string::npos, "spos") == 0)) {
+    pieces.push_back("to");
+
+    switch (pieces[0][0]) {
+    case 'm':
+      pieces.push_back("model");
+      break;
+    case 'w':
+      pieces.push_back("world");
+      break;
+    case 'v':
+      pieces.push_back("view");
+      break;
+    case 'c':
+      pieces.push_back("clip");
+      break;
+    default:
+      return nullptr;
+    }
+    if (pieces[0].compare(1, std::string::npos, "strans") == 0) {
+      pieces[0] = "trans";
+    } else {
+      pieces[0] = "row3";
+    }
+  }
+  else if (pieces[0].size() == 3 && // mat_modelproj et al
+           (pieces[0] == "mat" || pieces[0] == "inv" ||
+            pieces[0] == "tps" || pieces[0] == "itp")) {
+    std::string trans = pieces[0];
+    std::string matrix = pieces[1];
+    pieces.clear();
+    if (matrix == "modelview") {
+      tokenize("trans_model_to_apiview", pieces, "_");
+    }
+    else if (matrix == "projection") {
+      tokenize("trans_apiview_to_apiclip", pieces, "_");
+    }
+    else if (matrix == "modelproj") {
+      tokenize("trans_model_to_apiclip", pieces, "_");
+    }
+    else {
+      return report_parameter_error(name, type, "unrecognized matrix name");
+    }
+    if (trans == "mat") {
+      pieces[0] = "trans";
+    } else if (trans == "inv") {
+      string t = pieces[1];
+      pieces[1] = pieces[3];
+      pieces[3] = t;
+    } else if (trans == "tps") {
+      pieces[0] = "tpose";
+    } else if (trans == "itp") {
+      string t = pieces[1];
+      pieces[1] = pieces[3];
+      pieces[3] = t;
+      pieces[0] = "tpose";
+    }
+  }
+
+  // Implement the Cg-style transform-matrix generator.
+  if (pieces[0] == "trans" ||
+      pieces[0] == "tpose" ||
+      pieces[0] == "row0" ||
+      pieces[0] == "row1" ||
+      pieces[0] == "row2" ||
+      pieces[0] == "row3" ||
+      pieces[0] == "col0" ||
+      pieces[0] == "col1" ||
+      pieces[0] == "col2" ||
+      pieces[0] == "col3") {
+
+    bool transpose = false;
+    int offset = 0;
+    if (pieces[0] == "trans") {
+      if (!expect_float_matrix(name, type, 3, 4)) {
+        return nullptr;
+      }
+    }
+    else if (pieces[0] == "tpose") {
+      if (!expect_float_matrix(name, type, 3, 4)) {
+        return nullptr;
+      }
+      transpose = true;
+    }
+    else {
+      if (!expect_float_vector(name, type, 4, 4)) {
+        return nullptr;
+      }
+      if (pieces[0][0] == 'r') {
+        offset = (pieces[0][3] - '0') * 4;
+      }
+      else if (pieces[0][0] == 'c') {
+        offset = pieces[0][3] - '0';
+      }
+      else {
+        nassertr(false, nullptr);
+      }
+    }
+
+    Shader::StateMatrix part[2];
+    CPT(InternalName) arg[2];
+
+    int next = 1;
+    pieces.push_back("");
+    if (!expect_coordinate_system(name, type, pieces, next, true, part, arg)) {
+      return nullptr;
+    }
+    if (pieces[next] != "to" && pieces[next] != "rel") {
+      return report_parameter_error(name, type, "expected 'to' or 'rel'");
+    }
+    ++next;
+    if (!expect_coordinate_system(name, type, pieces, next, false, part, arg)) {
+      return nullptr;
+    }
+    if (pieces.size() > (size_t)(next + 1)) {
+      return report_parameter_error(name, type,
+        "unexpected extra words after parameter name");
+    }
+
+    return make_matrix_compose(type, part[0], arg[0], part[1], arg[1], transpose, offset);
+  }
+
+  // If we get here, it's not a specially recognized input, but just a regular
+  // user-defined input.
+  return make_shader_input(type, name);
+}
+
+/**
+ * Creates the appropriate binding for the input with the given name and type.
+ */
+ShaderInputBinding *
+make_binding_cg(const InternalName *name, const ShaderType *type) {
+  std::string name_str = name->get_name();
+
+  // Split it at the underscores.
+  vector_string pieces;
+  tokenize(name_str, pieces, "_");
+  nassertr(!pieces.empty(), nullptr);
+
+  // Check for mstrans, wstrans, vstrans, cstrans, mspos, wspos, vspos, cspos
+  if (pieces[0].size() >= 5 &&
+      (pieces[0].compare(1, std::string::npos, "strans") == 0 ||
+       pieces[0].compare(1, std::string::npos, "spos") == 0)) {
+    pieces.push_back("to");
+
+    switch (pieces[0][0]) {
+    case 'm':
+      pieces.push_back("model");
+      break;
+    case 'w':
+      pieces.push_back("world");
+      break;
+    case 'v':
+      pieces.push_back("view");
+      break;
+    case 'c':
+      pieces.push_back("clip");
+      break;
+    default:
+      return nullptr;
+    }
+    if (pieces[0].compare(1, std::string::npos, "strans") == 0) {
+      pieces[0] = "trans";
+    } else {
+      pieces[0] = "row3";
+    }
+  }
+  else if (pieces[0].size() == 3 && // mat_modelproj et al
+           (pieces[0] == "mat" || pieces[0] == "inv" ||
+            pieces[0] == "tps" || pieces[0] == "itp")) {
+    std::string trans = pieces[0];
+    std::string matrix = pieces[1];
+    pieces.clear();
+    if (matrix == "modelview") {
+      tokenize("trans_model_to_apiview", pieces, "_");
+    }
+    else if (matrix == "projection") {
+      tokenize("trans_apiview_to_apiclip", pieces, "_");
+    }
+    else if (matrix == "modelproj") {
+      tokenize("trans_model_to_apiclip", pieces, "_");
+    }
+    else if (matrix == "shadow") {
+      if (!expect_num_words(name, type, 3) ||
+          !expect_float_matrix(name, type, 4, 4)) {
+        return nullptr;
+      }
+      return make_matrix_compose(type, Shader::SM_view_to_world, nullptr,
+                                 Shader::SM_world_to_apiclip_light_i,
+                                 InternalName::make(pieces[2]), true);
+    }
+    else {
+      return report_parameter_error(name, type, "unrecognized matrix name");
+    }
+    if (trans == "mat") {
+      pieces[0] = "trans";
+    } else if (trans == "inv") {
+      string t = pieces[1];
+      pieces[1] = pieces[3];
+      pieces[3] = t;
+    } else if (trans == "tps") {
+      pieces[0] = "tpose";
+    } else if (trans == "itp") {
+      string t = pieces[1];
+      pieces[1] = pieces[3];
+      pieces[3] = t;
+      pieces[0] = "tpose";
+    }
+  }
+
+  // Implement the Cg-style transform-matrix generator.
+  if (pieces[0] == "trans" ||
+      pieces[0] == "tpose" ||
+      pieces[0] == "row0" ||
+      pieces[0] == "row1" ||
+      pieces[0] == "row2" ||
+      pieces[0] == "row3" ||
+      pieces[0] == "col0" ||
+      pieces[0] == "col1" ||
+      pieces[0] == "col2" ||
+      pieces[0] == "col3") {
+
+    bool transpose = false;
+    int offset = 0;
+    if (pieces[0] == "trans") {
+      if (!expect_float_matrix(name, type, 3, 4)) {
+        return nullptr;
+      }
+    }
+    else if (pieces[0] == "tpose") {
+      if (!expect_float_matrix(name, type, 3, 4)) {
+        return nullptr;
+      }
+      transpose = true;
+    }
+    else {
+      if (!expect_float_vector(name, type, 4, 4)) {
+        return nullptr;
+      }
+      if (pieces[0][0] == 'r') {
+        offset = (pieces[0][3] - '0') * 4;
+      }
+      else if (pieces[0][0] == 'c') {
+        offset = pieces[0][3] - '0';
+      }
+      else {
+        nassertr(false, nullptr);
+      }
+    }
+
+    Shader::StateMatrix part[2];
+    CPT(InternalName) arg[2];
+
+    int next = 1;
+    pieces.push_back("");
+    if (!expect_coordinate_system(name, type, pieces, next, true, part, arg)) {
+      return nullptr;
+    }
+    if (pieces[next] != "to" && pieces[next] != "rel") {
+      return report_parameter_error(name, type, "expected 'to' or 'rel'");
+    }
+    ++next;
+    if (!expect_coordinate_system(name, type, pieces, next, false, part, arg)) {
+      return nullptr;
+    }
+    if (pieces.size() > (size_t)(next + 1)) {
+      return report_parameter_error(name, type,
+        "unexpected extra words after parameter name");
+    }
+
+    return make_matrix_compose(type, part[0], arg[0], part[1], arg[1], !transpose, offset);
+  }
+
+  // Other Cg-specific inputs.
+  //bool k_prefix = false;
+  //if (name_str.size() >= 2 && name_str.substr(0, 2) == "__") {
+  //  return true;
+  //}
+
+  // Special parameter: attr_material or attr_color
+  if (pieces[0] == "attr") {
+    if (!expect_num_words(name, type,  2)) {
+      return nullptr;
+    }
+
+    if (pieces[1] == "material") {
+      if (!expect_float_matrix(name, type, 4, 4)) {
+        return nullptr;
+      }
+      return ShaderInputBinding::make_data(Shader::D_material | Shader::D_frame,
+                                           [=](const State &state, void *into, bool pad_rows) {
+
+        LVecBase4f &ambient = ((LVecBase4f *)into)[0];
+        LVecBase4f &diffuse = ((LVecBase4f *)into)[1];
+        LVecBase4f &emission = ((LVecBase4f *)into)[2];
+        LVecBase4f &specular = ((LVecBase4f *)into)[3];
+
+        const MaterialAttrib *target_material;
+        if (state.gsg->get_target_state()->get_attrib(target_material) && !target_material->is_off()) {
+          Material *m = target_material->get_material();
+          ambient = LCAST(float, m->get_ambient());
+          diffuse = LCAST(float, m->get_diffuse());
+          emission = LCAST(float, m->get_emission());
+          specular = LCAST(float, m->get_specular());
+          specular[3] = m->get_shininess();
+        } else {
+          ambient.set(1, 1, 1, 1);
+          diffuse.set(1, 1, 1, 1);
+          emission.set(0, 0, 0, 0);
+          specular.set(0, 0, 0, 0);
+        }
+      });
+    }
+    else if (pieces[1] == "color") {
+      if (!expect_float_vector(name, type, 3, 4)) {
+        return nullptr;
+      }
+      return make_color(type);
+    }
+    else if (pieces[1] == "colorscale") {
+      if (!expect_float_vector(name, type, 3, 4)) {
+        return nullptr;
+      }
+      return make_color_scale(type);
+    }
+    else if (pieces[1] == "fog") {
+      if (!expect_float_vector(name, type, 3, 4)) {
+        return nullptr;
+      }
+      return ShaderInputBinding::make_data(Shader::D_fog | Shader::D_frame,
+                                           [](const State &state, void *into, bool pad_rows) {
+
+        LVecBase4f &params = *(LVecBase4f *)into;
+
+        const FogAttrib *target_fog;
+        if (state.gsg->get_target_state()->get_attrib(target_fog) && target_fog->get_fog() != nullptr) {
+          Fog *fog = target_fog->get_fog();
+          PN_stdfloat start, end;
+          fog->get_linear_range(start, end);
+          params.set(fog->get_exp_density(), start, end, 1.0f / (end - start));
+        } else {
+          params.set(0, 1, 1, 1);
+        }
+      });
+    }
+    else if (pieces[1] == "fogcolor") {
+      if (!expect_float_vector(name, type, 3, 4)) {
+        return nullptr;
+      }
+      return ShaderInputBinding::make_data(Shader::D_fog | Shader::D_frame,
+                                           [](const State &state, void *into, bool pad_rows) {
+
+        LVecBase4f &color = *(LVecBase4f *)into;
+
+        const FogAttrib *target_fog;
+        if (state.gsg->get_target_state()->get_attrib(target_fog) && target_fog->get_fog() != nullptr) {
+          Fog *fog = target_fog->get_fog();
+          color = LCAST(float, fog->get_color());
+        } else {
+          color.set(1, 1, 1, 1);
+        }
+      });
+    }
+    else if (pieces[1] == "ambient") {
+      if (!expect_float_vector(name, type, 3, 4)) {
+        return nullptr;
+      }
+      return make_light_ambient(type);
+    }
+    else if (pieces[1].compare(0, 5, "light") == 0) {
+      int index = atoi(pieces[1].c_str() + 5);
+      if (!expect_float_matrix(name, type, 4, 4)) {
+        return nullptr;
+      }
+      return new ShaderPackedLightBinding((size_t)index);
+    }
+    else if (pieces[1].compare(0, 5, "lspec") == 0) {
+      int index = atoi(pieces[1].c_str() + 5);
+      if (!expect_float_vector(name, type, 3, 4)) {
+        return nullptr;
+      }
+      return ShaderInputBinding::make_data(Shader::D_light | Shader::D_frame,
+                                           [=](const State &state, void *into, bool pad_rows) {
+
+        // We don't count ambient lights, which would be pretty silly to handle
+        // via this mechanism.
+        const LightAttrib *target_light;
+        if (state.gsg->get_target_state()->get_attrib(target_light) &&
+            index < (int)target_light->get_num_non_ambient_lights()) {
+          NodePath np = target_light->get_on_light(index);
+          nassertv(!np.is_empty());
+          PandaNode *node = np.node();
+          Light *light = node->as_light();
+          nassertv(light != nullptr);
+
+          *(LVecBase4f *)into = LCAST(float, light->get_specular_color());
+        } else {
+          *(LVecBase4f *)into = LVecBase4f(0, 0, 0, 1);
+        }
+      });
+    }
+    else if (pieces[1] == "pointparams") {
+      if (!expect_float_vector(name, type, 3, 4)) {
+        return nullptr;
+      }
+      return new ShaderPointParamsBinding;
+    }
+    else {
+      return report_parameter_error(name, type, "unrecognized parameter name");
+    }
+  }
+
+  // Keywords to access light properties.
+  if (pieces[0] == "alight") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_vector(name, type, 3, 4)) {
+      return nullptr;
+    }
+
+    CPT(InternalName) input = InternalName::make(pieces[1]);
+    return ShaderInputBinding::make_data(Shader::D_shader_inputs | Shader::D_frame,
+                                         [=](const State &state, void *into, bool pad_rows) {
+      const NodePath &np = state.gsg->get_target_shader_attrib()->get_shader_input_nodepath(input);
+      nassertv(!np.is_empty());
+      Light *light = np.node()->as_light();
+      nassertv(light != nullptr);
+      *(LVecBase4f *)into = LCAST(float, light->get_color());
+    });
+  }
+
+  if (pieces[0] == "satten") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_vector(name, type, 3, 4)) {
+      return nullptr;
+    }
+
+    CPT(InternalName) input = InternalName::make(pieces[1]);
+    return ShaderInputBinding::make_data(Shader::D_shader_inputs | Shader::D_frame,
+                                         [=](const State &state, void *into, bool pad_rows) {
+      const NodePath &np = state.gsg->get_target_shader_attrib()->get_shader_input_nodepath(input);
+      nassertv(!np.is_empty());
+      Light *light = np.node()->as_light();
+      nassertv(light != nullptr);
+      *(LVecBase4f *)into = LVecBase4f(LCAST(float, light->get_attenuation()), light->get_exponent());
+    });
+  }
+
+  if (pieces[0] == "dlight" || pieces[0] == "plight" || pieces[0] == "slight") {
+    if (!expect_float_matrix(name, type, 4, 4)) {
+      return nullptr;
+    }
+    int next = 1;
+    pieces.push_back("");
+    if (pieces[next] == "") {
+      return report_parameter_error(name, type, "expected light input name");
+    }
+    Shader::StateMatrix part[2];
+    CPT(InternalName) arg[2] {InternalName::make(pieces[next]), nullptr};
+    next += 1;
+    if (pieces[next] != "to" && pieces[next] != "rel") {
+      return report_parameter_error(name, type, "expected 'to' or 'rel'");
+    }
+    if (!expect_coordinate_system(name, type, pieces, next, true, part, arg)) {
+      return nullptr;
+    }
+    if ((int)pieces.size() > next) {
+      return report_parameter_error(name, type,
+        "unexpected extra words after parameter name");
+    }
+
+    if (pieces[0] == "dlight") {
+      return new ShaderLegacyDirectionalLightBinding(std::move(arg[0]), part[1], std::move(arg[1]));
+    }
+    else if (pieces[0] == "plight") {
+      return new ShaderLegacyPointLightBinding(std::move(arg[0]), part[1], std::move(arg[1]));
+    }
+    else if (pieces[0] == "slight") {
+      return new ShaderLegacySpotlightBinding(std::move(arg[0]), part[1], std::move(arg[1]));
+    }
+    else {
+      return nullptr;
+    }
+  }
+
+  if (pieces[0] == "texmat") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_matrix(name, type, 4, 4)) {
+      return nullptr;
+    }
+    return make_texture_matrix(type, atoi(pieces[1].c_str()), false, false);
+  }
+
+  if (pieces[0] == "texscale") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_vector(name, type, 3, 4)) {
+      return nullptr;
+    }
+
+    int stage = atoi(pieces[1].c_str());
+    return ShaderInputBinding::make_data(Shader::D_texture | Shader::D_tex_matrix,
+                                         [=](const State &state, void *into, bool pad_rows) {
+
+      const TextureAttrib *ta;
+      const TexMatrixAttrib *tma;
+      if (state.gsg->get_target_state()->get_attrib(ta) && state.gsg->get_target_state()->get_attrib(tma) && stage < ta->get_num_on_stages()) {
+        LVecBase3 scale = tma->get_transform(ta->get_on_stage(stage))->get_scale();
+        ((LVecBase4f *)into)->set(scale[0], scale[1], scale[2], 0);
+      } else {
+        ((LVecBase4f *)into)->set(0, 0, 0, 1);
+      }
+    });
+  }
+
+  if (pieces[0] == "texcolor") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_vector(name, type, 3, 4)) {
+      return nullptr;
+    }
+
+    // D_frame because the user may change a TextureStage's color without a
+    // state change
+    int stage = atoi(pieces[1].c_str());
+    return ShaderInputBinding::make_data(Shader::D_texture | Shader::D_frame,
+                                         [=](const State &state, void *into, bool pad_rows) {
+
+      const TextureAttrib *ta;
+      if (state.gsg->get_target_state()->get_attrib(ta) && stage < ta->get_num_on_stages()) {
+        TextureStage *ts = ta->get_on_stage(stage);
+        *(LVecBase4f *)into = LCAST(float, ts->get_color());
+      } else {
+        ((LVecBase4f *)into)->set(0, 0, 0, 1);
+      }
+    });
+  }
+
+  if (pieces[0] == "texconst") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_vector(name, type, 3, 4)) {
+      return nullptr;
+    }
+
+    // D_frame because the user may change a TextureStage's constant without
+    // a state change
+    int stage = atoi(pieces[1].c_str());
+    return ShaderInputBinding::make_data(Shader::D_texture | Shader::D_tex_gen | Shader::D_frame,
+                                         [=](const State &state, void *into, bool pad_rows) {
+
+      const TextureAttrib *ta;
+      const TexGenAttrib *tga;
+      if (state.gsg->get_target_state()->get_attrib(ta) && state.gsg->get_target_state()->get_attrib(tga) && stage < ta->get_num_on_stages()) {
+        LVecBase3 value = tga->get_constant_value(ta->get_on_stage(stage));
+        ((LVecBase4f *)into)->set(value[0], value[1], value[2], 1);
+      } else {
+        ((LVecBase4f *)into)->set(0, 0, 0, 1);
+      }
+    });
+  }
+
+  if (pieces[0] == "plane") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_vector(name, type, 4, 4)) {
+      return nullptr;
+    }
+
+    // D_frame because the user may change a PlaneNode's plane without a
+    // state change
+    CPT(InternalName) input = InternalName::make(pieces[1]);
+    return ShaderInputBinding::make_data(Shader::D_frame | Shader::D_shader_inputs,
+                                         [=](const State &state, void *into, bool pad_rows) {
+      const NodePath &np = state.gsg->get_target_shader_attrib()->get_shader_input_nodepath(name);
+      nassertv(!np.is_empty());
+      const PlaneNode *plane_node;
+      DCAST_INTO_V(plane_node, np.node());
+      *(LVecBase4f *)into = LCAST(float, plane_node->get_plane());
+    });
+  }
+
+  if (pieces[0] == "clipplane") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_vector(name, type, 4, 4)) {
+      return nullptr;
+    }
+
+    // D_frame because the user may change a PlaneNode's plane without a
+    // state change
+    int index = atoi(pieces[1].c_str());
+    return ShaderInputBinding::make_data(Shader::D_clip_planes | Shader::D_frame,
+                                         [=](const State &state, void *into, bool pad_rows) {
+      const ClipPlaneAttrib *cpa;
+      state.gsg->get_target_state()->get_attrib_def(cpa);
+      if (index >= cpa->get_num_on_planes()) {
+        (*(LVecBase4f *)into).set(0, 0, 0, 0);
+        return;
+      }
+      const NodePath &np = cpa->get_on_plane(index);
+      nassertv(!np.is_empty());
+      const PlaneNode *plane_node;
+      DCAST_INTO_V(plane_node, np.node());
+
+      // Transform plane to world space
+      CPT(TransformState) transform = np.get_net_transform();
+      LPlane plane = plane_node->get_plane();
+      if (!transform->is_identity()) {
+        plane.xform(transform->get_mat());
+      }
+      *(LVecBase4f *)into = LCAST(float, plane);
+    });
+  }
+
+  // Keywords to access unusual parameters.
+  if (pieces[0] == "sys") {
+    if (!expect_num_words(name, type, 2)) {
+      return nullptr;
+    }
+    if (pieces[1] == "pixelsize" || pieces[1] == "windowsize") {
+      if (!expect_float_vector(name, type, 2, 2)) {
+        return nullptr;
+      }
+      return ShaderInputBinding::make_data(Shader::D_scene,
+                                           [=](const State &state, void *into, bool pad_rows) {
+        const DisplayRegion *region = state.gsg->get_current_display_region();
+        *(LVecBase2f *)into = LCAST(float, region->get_pixel_size());
+      });
+    }
+    if (pieces[1] == "time") {
+      if (!expect_float_vector(name, type, 1, 1, true)) {
+        return nullptr;
+      }
+      return make_frame_time(type);
+    }
+    return report_parameter_error(name, type, "unrecognized parameter name");
+  }
+
+  // Keywords to access textures.
+  if (pieces[0] == "tex") {
+    if (pieces.size() == 3) {
+      return report_parameter_error(name, type, "texture suffix feature is no longer supported");
+    }
+    if (!expect_num_words(name, type, 2)) {
+      return nullptr;
+    }
+    if (type->as_sampled_image() == nullptr) {
+      return report_parameter_error(name, type, "expected sampler type");
+    }
+    return make_texture_stage(type, atoi(pieces[1].c_str()));
+  }
+
+  if (pieces[0] == "shadow") {
+    if (!expect_num_words(name, type, 2)) {
+      return nullptr;
+    }
+    const ShaderType::SampledImage *sampler = type->as_sampled_image();
+    if (sampler == nullptr) {
+      return report_parameter_error(name, type, "expected sampler type");
+    }
+    size_t index = (size_t)atoi(pieces[1].c_str());
+    bool is_cube = sampler->get_texture_type() == Texture::TT_cube_map;
+
+    return ShaderInputBinding::make_texture(Shader::D_frame | Shader::D_light,
+                                            [=](const State &state, SamplerState &sampler, int &view) {
+
+      const LightAttrib *target_light;
+      state.gsg->get_target_state()->get_attrib_def(target_light);
+
+      PT(Texture) tex;
+
+      size_t num_lights = target_light->get_num_non_ambient_lights();
+      if (index >= 0 && (size_t)index < num_lights) {
+        tex = state.gsg->get_shadow_map(target_light->get_on_light(index));
+      } else {
+        // There is no such light assigned.  Bind a dummy shadow map.
+        tex = state.gsg->get_dummy_shadow_map(is_cube);
+      }
+      if (tex != nullptr) {
+        sampler = tex->get_default_sampler();
+      }
+      return tex;
+    });
+  }
+
+  // Keywords to fetch texture parameter data.
+  if (pieces[0] == "texpad") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_vector(name, type, 3, 4)) {
+      return nullptr;
+    }
+    CPT(InternalName) input = InternalName::make(pieces[1]);
+    return ShaderInputBinding::make_data(Shader::D_frame | Shader::D_shader_inputs,
+                                         [=](const State &state, void *into, bool pad_rows) {
+      Texture *tex = state.gsg->get_target_shader_attrib()->get_shader_input_texture(input);
+      nassertv(tex != nullptr);
+      int sx = tex->get_x_size() - tex->get_pad_x_size();
+      int sy = tex->get_y_size() - tex->get_pad_y_size();
+      int sz = tex->get_z_size() - tex->get_pad_z_size();
+      double cx = (sx * 0.5) / tex->get_x_size();
+      double cy = (sy * 0.5) / tex->get_y_size();
+      double cz = (sz * 0.5) / tex->get_z_size();
+      ((LVecBase4f *)into)->set(cx, cy, cz, 0);
+    });
+  }
+
+  if (pieces[0] == "texpix") {
+    if (!expect_num_words(name, type, 2) ||
+        !expect_float_vector(name, type, 2, 4)) {
+      return nullptr;
+    }
+    CPT(InternalName) input = InternalName::make(pieces[1]);
+    return ShaderInputBinding::make_data(Shader::D_frame | Shader::D_shader_inputs,
+                                         [=](const State &state, void *into, bool pad_rows) {
+      Texture *tex = state.gsg->get_target_shader_attrib()->get_shader_input_texture(input);
+      nassertv(tex != nullptr);
+      double px = 1.0 / tex->get_x_size();
+      double py = 1.0 / tex->get_y_size();
+      double pz = 1.0 / tex->get_z_size();
+      ((LVecBase4f *)into)->set(px, py, pz, 0);
+    });
+  }
+
+  if (pieces[0] == "tbl") {
+    const ShaderType *element_type;
+    uint32_t num_elements;
+    if (!expect_num_words(name, type, 2) ||
+        !type->unwrap_array(element_type, num_elements)) {
+      return report_parameter_error(name, type, "expected array");
+    }
+
+    if (pieces[1] == "transforms") {
+      const ShaderType::Matrix *matrix = element_type->as_matrix();
+      if (matrix == nullptr ||
+          matrix->get_num_rows() < 3 ||
+          matrix->get_num_columns() != 4 ||
+          matrix->get_scalar_type() != ShaderType::ST_float) {
+        return report_parameter_error(name, type, "expected float3x4[] or float4x4[]");
+      }
+
+      return make_transform_table(type, true);
+    }
+    else if (pieces[1] == "sliders") {
+      return make_slider_table(type);
+    }
+    else {
+      return report_parameter_error(name, type, "unrecognized parameter name");
+    }
+  }
+
+  // Previously, custom shader inputs needed the k_ prefix, so we have to
+  // strip it now.
+  if (pieces[0] == "k") {
+    //k_prefix = true;
+    name_str = name_str.substr(2);
+    name = InternalName::make(name_str);
+  }
+
+  // If we get here, it's not a specially recognized input, but just a regular
+  // user-defined input.
+  return make_shader_input(type, name);
+}

+ 342 - 0
panda/src/display/shaderInputBinding_impls.h

@@ -0,0 +1,342 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file shaderInputBinding_impls.h
+ * @author rdb
+ * @date 2024-09-22
+ */
+
+#ifndef SHADERINPUTBINDING_IMPLS_H
+#define SHADERINPUTBINDING_IMPLS_H
+
+#include "shaderInputBinding.h"
+
+ShaderInputBinding *make_binding_cg(const InternalName *name, const ShaderType *type);
+ShaderInputBinding *make_binding_glsl(const InternalName *name, const ShaderType *type);
+
+/**
+ * This binds a parameter to a specific transformation matrix or part thereof.
+ */
+class EXPCL_PANDA_DISPLAY ShaderMatrixBinding : public ShaderInputBinding {
+public:
+  INLINE ShaderMatrixBinding(ShaderEnums::StateMatrix input, CPT_InternalName arg,
+                             bool transpose = false, size_t offset = 0,
+                             size_t num_rows = 4, size_t num_cols = 4);
+
+  virtual int get_state_dep() const override;
+  virtual void setup(Shader *shader) override;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+
+protected:
+  size_t _cache_index = 0;
+  ShaderEnums::StateMatrix _input;
+  CPT_InternalName _arg;
+  bool _transpose = false;
+  size_t _offset = 0;
+  size_t _num_rows;
+  size_t _num_cols;
+};
+
+/**
+ * This binds a parameter to a composition of two matrices.
+ */
+class EXPCL_PANDA_DISPLAY ShaderMatrixComposeBinding : public ShaderInputBinding {
+public:
+  INLINE ShaderMatrixComposeBinding(
+    ShaderEnums::StateMatrix input0, CPT_InternalName arg0,
+    ShaderEnums::StateMatrix input1, CPT_InternalName arg1,
+    bool transpose = false, size_t offset = 0,
+    size_t num_rows = 4, size_t num_cols = 4);
+
+  virtual int get_state_dep() const override;
+  virtual void setup(Shader *shader) override;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+
+private:
+  size_t _cache_index0 = 0;
+  size_t _cache_index1 = 0;
+  ShaderEnums::StateMatrix _input0;
+  ShaderEnums::StateMatrix _input1;
+  CPT_InternalName _arg0;
+  CPT_InternalName _arg1;
+  bool _transpose = false;
+  size_t _offset = 0;
+  size_t _num_rows;
+  size_t _num_cols;
+};
+
+/**
+ *
+ */
+class EXPCL_PANDA_DISPLAY ShaderPointParamsBinding : public ShaderInputBinding {
+public:
+  virtual int get_state_dep() const override;
+  virtual void setup(Shader *shader) override;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+
+protected:
+  size_t _cache_index = 0;
+};
+
+/**
+ *
+ */
+class EXPCL_PANDA_DISPLAY ShaderPackedLightBinding : public ShaderInputBinding {
+public:
+  INLINE ShaderPackedLightBinding(int index);
+
+  virtual int get_state_dep() const override;
+  virtual void setup(Shader *shader) override;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+
+protected:
+  size_t _index;
+  size_t _world_mat_cache_index;
+};
+
+/**
+ *
+ */
+class EXPCL_PANDA_DISPLAY ShaderLegacyLightBinding : public ShaderInputBinding {
+public:
+  INLINE ShaderLegacyLightBinding(CPT_InternalName input,
+                                  ShaderEnums::StateMatrix matrix,
+                                  CPT_InternalName arg);
+
+  virtual int get_state_dep() const override;
+  virtual void setup(Shader *shader) override;
+
+protected:
+  CPT_InternalName _input;
+  ShaderEnums::StateMatrix _matrix;
+  CPT_InternalName _arg;
+  size_t _mat_cache_index;
+};
+
+/**
+ *
+ */
+class EXPCL_PANDA_DISPLAY ShaderLegacyDirectionalLightBinding : public ShaderLegacyLightBinding {
+public:
+  using ShaderLegacyLightBinding::ShaderLegacyLightBinding;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+};
+
+/**
+ *
+ */
+class EXPCL_PANDA_DISPLAY ShaderLegacyPointLightBinding : public ShaderLegacyLightBinding {
+public:
+  using ShaderLegacyLightBinding::ShaderLegacyLightBinding;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+};
+
+/**
+ *
+ */
+class EXPCL_PANDA_DISPLAY ShaderLegacySpotlightBinding : public ShaderLegacyLightBinding {
+public:
+  using ShaderLegacyLightBinding::ShaderLegacyLightBinding;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+};
+
+/**
+ *
+ */
+class EXPCL_PANDA_DISPLAY ShaderLightStructBinding : public ShaderInputBinding {
+public:
+  ShaderLightStructBinding(const ShaderType *type,
+                           const InternalName *input = nullptr);
+
+  virtual int get_state_dep() const override;
+  virtual void setup(Shader *shader) override;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+
+  virtual ResourceId get_resource_id(int index, const ShaderType *type) const;
+  virtual PT(Texture) fetch_texture(const State &state,
+                                    ResourceId index,
+                                    SamplerState &sampler, int &view) const;
+
+private:
+  void fetch_light(const State &state, const NodePath &np, void *into) const;
+  void fetch_from_input(const ShaderAttrib *target_shader, void *into) const;
+
+protected:
+  CPT(InternalName) _input;
+  size_t _count;
+  size_t _stride;
+  size_t _world_to_view_mat_cache_index;
+  size_t _apiview_to_world_mat_cache_index;
+  bool _cube_shadow_map = false;
+  int _color_offset = -1;
+  int _specular_offset = -1;
+  int _ambient_offset = -1;
+  int _diffuse_offset = -1;
+  int _position_offset = -1;
+  int _half_vector_offset = -1;
+  int _spot_direction_offset = -1;
+  int _spot_cos_cutoff_offset = -1;
+  int _spot_cutoff_offset = -1;
+  int _spot_exponent_offset = -1;
+  int _attenuation_offset = -1;
+  int _constant_attenuation_offset = -1;
+  int _linear_attenuation_offset = -1;
+  int _quadratic_attenuation_offset = -1;
+  int _radius_offset = -1;
+  int _shadow_view_matrix_offset = -1;
+};
+
+/**
+ * Binds a parameter to a texture stage.
+ */
+class EXPCL_PANDA_DISPLAY ShaderTextureStagesBinding : public ShaderInputBinding {
+public:
+  INLINE ShaderTextureStagesBinding(Texture::TextureType desired_type,
+                                    size_t count,
+                                    Texture *default_texture = nullptr,
+                                    unsigned int mode_mask = ~0);
+
+  virtual int get_state_dep() const override;
+
+  virtual ResourceId get_resource_id(int index, const ShaderType *type) const;
+  virtual PT(Texture) fetch_texture(const State &state,
+                                    ResourceId resource_id,
+                                    SamplerState &sampler, int &view) const;
+
+protected:
+  size_t const _count;
+  Texture *const _default_texture;
+  Texture::TextureType const _desired_type;
+  unsigned int _mode_mask;
+  mutable bool _shown_error = false;
+};
+
+/**
+ * Binds a parameter to a generic texture shader input.
+ */
+class EXPCL_PANDA_DISPLAY ShaderTextureBinding : public ShaderInputBinding {
+public:
+  INLINE ShaderTextureBinding(CPT(InternalName) input, Texture::TextureType desired_type);
+
+  virtual int get_state_dep() const override;
+
+  virtual ResourceId get_resource_id(int index, const ShaderType *type) const;
+  virtual PT(Texture) fetch_texture(const State &state,
+                                    ResourceId resource_id,
+                                    SamplerState &sampler, int &view) const;
+  virtual PT(Texture) fetch_texture_image(const State &state,
+                                          ResourceId resource_id,
+                                          ShaderType::Access &access,
+                                          int &z, int &n) const;
+
+protected:
+  CPT(InternalName) const _input;
+  Texture::TextureType const _desired_type;
+  mutable bool _shown_error = false;
+};
+
+/**
+ * This binds a parameter to a generic numeric data shader input.
+ */
+class EXPCL_PANDA_DISPLAY ShaderDataBinding : public ShaderInputBinding {
+public:
+  INLINE ShaderDataBinding(CPT_InternalName input, size_t num_elements,
+                           size_t num_rows, size_t num_cols);
+
+  virtual int get_state_dep() const override;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override=0;
+
+protected:
+  CPT_InternalName _input;
+  size_t _num_elements;
+  size_t _num_rows;
+  size_t _num_cols;
+};
+
+/**
+ * This binds a parameter to a single-precision float shader input.
+ */
+class EXPCL_PANDA_DISPLAY ShaderFloatBinding : public ShaderDataBinding {
+public:
+  using ShaderDataBinding::ShaderDataBinding;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+};
+
+/**
+ * This binds a parameter to a double-precision float shader input.
+ */
+class EXPCL_PANDA_DISPLAY ShaderDoubleBinding : public ShaderDataBinding {
+public:
+  using ShaderDataBinding::ShaderDataBinding;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+};
+
+/**
+ * This binds a parameter to an integer shader input.
+ */
+class EXPCL_PANDA_DISPLAY ShaderIntBinding : public ShaderDataBinding {
+public:
+  using ShaderDataBinding::ShaderDataBinding;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+};
+
+/**
+ * This binds a parameter to an boolean shader input.
+ */
+class EXPCL_PANDA_DISPLAY ShaderBoolBinding : public ShaderDataBinding {
+public:
+  using ShaderDataBinding::ShaderDataBinding;
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+};
+
+/**
+ * This binds an aggregate parameter (such as a struct or an array of structs)
+ * to a set of shader inputs.
+ */
+class EXPCL_PANDA_DISPLAY ShaderAggregateBinding : public ShaderInputBinding {
+public:
+  INLINE ShaderAggregateBinding(CPT_InternalName input, const ShaderType *type);
+
+  virtual void fetch_data(const State &state, void *into, bool pad_rows) const override;
+
+  virtual ResourceId get_resource_id(int index, const ShaderType *type) const;
+  virtual PT(Texture) fetch_texture(const State &state,
+                                    ResourceId index,
+                                    SamplerState &sampler, int &view) const;
+  virtual PT(Texture) fetch_texture_image(const State &state,
+                                          ResourceId index,
+                                          ShaderType::Access &access,
+                                          int &z, int &n) const;
+private:
+  void r_collect_members(const InternalName *name, const ShaderType *type, size_t offset = 0);
+
+private:
+  struct DataMember {
+    PT(ShaderDataBinding) _binding;
+    size_t _offset;
+  };
+  pvector<DataMember> _data_members;
+  pvector<CPT_InternalName> _resources;
+};
+
+#include "shaderInputBinding_impls.I"
+
+#endif

+ 99 - 0
panda/src/gobj/shaderInputBinding.I

@@ -0,0 +1,99 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file shaderInputBinding.I
+ * @author rdb
+ * @date 2024-09-22
+ */
+
+/**
+ * Convenient shorthand for creating an input that fetches a single texture
+ * using an arbitrary callable.
+ */
+template<class Callable>
+INLINE ShaderInputBinding *ShaderInputBinding::
+make_data(int dep, Callable callable) {
+  class InlineShaderInputBinding final : public ShaderInputBinding {
+  public:
+    InlineShaderInputBinding(Callable callable, int dep) :
+      _callable(std::move(callable)),
+      _dep(dep) {
+    }
+
+    virtual int get_state_dep() const override final {
+      return _dep;
+    }
+
+    virtual void fetch_data(const State &state, void *scratch, bool pad_rows) const override final {
+      _callable(state, scratch, pad_rows);
+    }
+
+  private:
+    Callable _callable;
+    int _dep;
+  };
+  return new InlineShaderInputBinding(std::move(callable), dep);
+}
+
+/**
+ * Convenient shorthand for creating an input that fetches a single texture
+ * using an arbitrary callable.
+ */
+template<class Callable>
+INLINE ShaderInputBinding *ShaderInputBinding::
+make_texture(int dep, Callable callable) {
+  class InlineShaderInputBinding final : public ShaderInputBinding {
+  public:
+    InlineShaderInputBinding(Callable callable, int dep) :
+      _callable(std::move(callable)),
+      _dep(dep) {
+    }
+
+    virtual int get_state_dep() const override final {
+      return _dep;
+    }
+
+    virtual PT(Texture) fetch_texture(const State &state, ResourceId resource_id, SamplerState &sampler, int &view) const override final {
+      return _callable(state, sampler, view);
+    }
+
+  private:
+    Callable _callable;
+    int _dep;
+  };
+  return new InlineShaderInputBinding(std::move(callable), dep);
+}
+
+/**
+ * Convenient shorthand for creating an input that fetches a single image using
+ * an arbitrary callable.
+ */
+template<class Callable>
+INLINE ShaderInputBinding *ShaderInputBinding::
+make_texture_image(int dep, Callable callable) {
+  class InlineShaderInputBinding final : public ShaderInputBinding {
+  public:
+    InlineShaderInputBinding(Callable callable, int dep) :
+      _callable(std::move(callable)),
+      _dep(dep) {
+    }
+
+    virtual int get_state_dep() const override final {
+      return _dep;
+    }
+
+    virtual PT(Texture) fetch_texture_image(const State &state, ResourceId resource_id, ShaderType::Access &access, int &z, int &n) const override final {
+      return _callable(state, access, z, n);
+    }
+
+  private:
+    Callable _callable;
+    int _dep;
+  };
+  return new InlineShaderInputBinding(std::move(callable), dep);
+}

+ 44 - 0
panda/src/gobj/shaderInputBinding.cxx

@@ -0,0 +1,44 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file shaderInputBinding.cxx
+ * @author rdb
+ * @date 2024-09-22
+ */
+
+#include "shaderInputBinding.h"
+
+small_vector<ShaderInputBinding::BinderDef, 2> ShaderInputBinding::_binders;
+
+/**
+ * Creates the appropriate binding for the input with the given name and type.
+ */
+ShaderInputBinding *ShaderInputBinding::
+make(Shader::ShaderLanguage language,
+     const InternalName *name, const ShaderType *type) {
+
+  for (const BinderDef &def : _binders) {
+    if (def._lang == language) {
+      ShaderInputBinding *binding = def._func(name, type);
+      if (binding != nullptr) {
+        return binding;
+      }
+    }
+  }
+
+  return nullptr;
+}
+
+/**
+ * Registers a factory function to create a binding.
+ */
+void ShaderInputBinding::
+register_binder(ShaderEnums::ShaderLanguage language, int sort, Binder binder) {
+  _binders.push_back({language, sort, binder});
+  std::sort(_binders.begin(), _binders.end());
+}

+ 87 - 0
panda/src/gobj/shaderInputBinding.h

@@ -0,0 +1,87 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file shaderInputBinding.h
+ * @author rdb
+ * @date 2024-09-22
+ */
+
+#ifndef SHADERINPUTBINDING_H
+#define SHADERINPUTBINDING_H
+
+#include "pandabase.h"
+#include "referenceCount.h"
+#include "internalName.h"
+#include "shaderEnums.h"
+#include "small_vector.h"
+
+class GraphicsStateGuardian;
+class LMatrix4f;
+
+/**
+ * Controls how a shader parameter's value is filled in by Panda at runtime.
+ */
+class EXPCL_PANDA_GOBJ ShaderInputBinding : public ReferenceCount {
+protected:
+  ShaderInputBinding() = default;
+
+public:
+  static ShaderInputBinding *make(ShaderEnums::ShaderLanguage language,
+                                  const InternalName *name, const ShaderType *type);
+
+  template<class Callable>
+  INLINE static ShaderInputBinding *make_data(int dep, Callable callable);
+  template<class Callable>
+  INLINE static ShaderInputBinding *make_texture(int dep, Callable callable);
+  template<class Callable>
+  INLINE static ShaderInputBinding *make_texture_image(int dep, Callable callable);
+
+  virtual int get_state_dep() const;
+  virtual void setup(Shader *shader);
+
+  // Encapsulates parameters to pass to fetch_data et al, so we can add
+  // members without having to modify their signature.
+  struct State {
+    GraphicsStateGuardian *gsg;
+    LMatrix4f *matrix_cache;
+  };
+
+  virtual void fetch_data(const State &state, void *into,
+                          bool pad_rows = false) const;
+
+  typedef uintptr_t ResourceId;
+  virtual ResourceId get_resource_id(int index, const ShaderType *type) const;
+  virtual PT(Texture) fetch_texture(const State &state,
+                                    ResourceId resource_id,
+                                    SamplerState &sampler, int &view) const;
+  virtual PT(Texture) fetch_texture_image(const State &state,
+                                          ResourceId resource_id,
+                                          ShaderType::Access &access,
+                                          int &z, int &n) const;
+
+  // All the binders are defined in display, we provide this mechanism so that
+  // we don't get a dependency on display here.
+  typedef ShaderInputBinding *(*Binder)(const InternalName *name, const ShaderType *type);
+  static void register_binder(ShaderEnums::ShaderLanguage language, int sort, Binder binder);
+
+private:
+  struct BinderDef {
+    ShaderEnums::ShaderLanguage _lang;
+    int _sort;
+    Binder _func;
+
+    bool operator < (const BinderDef &other) const {
+      return _sort < other._sort;
+    }
+  };
+  static small_vector<BinderDef, 2> _binders;
+};
+
+#include "shaderInputBinding.I"
+
+#endif