To get better performance (though whether it’s actually measurably faster will depend on whether this is at all a bottleneck), I think the trick is to use a single buffer, not two.
For layered rendering that requires something like a single pass to render all 6 faces of a cube map, where the geometry shader decides which layer the triangle should go to (possibly several layers at a time for ones that are in a corner). Geometry shaders have their own caveats, though, so ultimately I don’t know how much this will help.
With the set_target_tex_page approach the most efficient way is to use a single buffer as well with different display regions rendering into different pages. Then you can have the buffer clear all pages in one go without a problem too.
The reason set_target_tex_page isn’t working is because Panda notices there’s already a texture bound with RTM_bind_or_copy to the RTP_depth slot; the default shadow map that Panda creates. That causes Panda to switch that attachment to copy-texture mode rather than bind mode, which apparently doesn’t really handle target-tex-page setting well. I need to think about how we should handle this. In the meantime, binding is more efficient anyway, so call sBuffer.clear_render_textures()
to clear that first.
So, something like:
sBuffer.clear_render_textures()
sBuffer.add_render_texture(self._tex_array, GraphicsOutput.RTM_bind_or_copy, GraphicsOutput.RTP_depth)
for dr in sBuffer.get_display_regions():
dr.set_target_tex_page(0)
dr = sBuffer.make_mono_display_region((0, 1, 0, 1))
dr.set_target_tex_page(1)
dr.set_camera(self._light2_np)
dr.set_clear_depth_active(True)
dr.set_active(True)
sBuffer2.set_active(False)
At this point, not a whole lot of Panda’s own shadow system is left. 
I still intend to add support for atlases and arrays to Panda’s shadow system properly.
As for retrieving the shadowViewMatrix, I loosely converted the code in GraphicsStateGuardian to Python, but I’m not 100% sure I made no mistakes:
shadow_bias_mat = Mat4(0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0)
inv_cs_transform = Mat4.convert_mat(gsg.get_internal_coordinate_system(), gsg.coordinate_system)
t = inv_cs_transform *
base.cam.get_transform(render).get_mat() *
light.get_net_transform().get_inverse().get_mat() *
Mat4.convert_mat(coordinate_system, lens.get_coordinate_system())
if not isinstance(light.node(), PointLight):
t *= light.node().get_lens().get_projection_mat() * shadow_bias_mat