mirror of
https://github.com/panda3d/panda3d.git
synced 2025-09-30 16:58:40 -04:00
Experimental InstancedNode class for hardware instancing
This commit is contained in:
parent
ac6a1a7874
commit
92e2c24958
@ -85,24 +85,8 @@ void CullBinBackToFront::
|
||||
draw(bool force, Thread *current_thread) {
|
||||
PStatTimer timer(_draw_this_pcollector, current_thread);
|
||||
|
||||
Objects::const_iterator oi;
|
||||
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
||||
CullableObject *object = (*oi)._object;
|
||||
|
||||
if (object->_draw_callback == nullptr) {
|
||||
nassertd(object->_geom != nullptr) continue;
|
||||
|
||||
_gsg->set_state_and_transform(object->_state, object->_internal_transform);
|
||||
|
||||
GeomPipelineReader geom_reader(object->_geom, current_thread);
|
||||
GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
|
||||
data_reader.check_array_readers();
|
||||
geom_reader.draw(_gsg, &data_reader, force);
|
||||
} else {
|
||||
// It has a callback associated.
|
||||
object->draw_callback(_gsg, force, current_thread);
|
||||
// Now the callback has taken care of drawing.
|
||||
}
|
||||
for (const ObjectData &data : _objects) {
|
||||
data._object->draw(_gsg, force, current_thread);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,24 +71,8 @@ void CullBinFixed::
|
||||
draw(bool force, Thread *current_thread) {
|
||||
PStatTimer timer(_draw_this_pcollector, current_thread);
|
||||
|
||||
Objects::const_iterator oi;
|
||||
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
||||
CullableObject *object = (*oi)._object;
|
||||
|
||||
if (object->_draw_callback == nullptr) {
|
||||
nassertd(object->_geom != nullptr) continue;
|
||||
|
||||
_gsg->set_state_and_transform(object->_state, object->_internal_transform);
|
||||
|
||||
GeomPipelineReader geom_reader(object->_geom, current_thread);
|
||||
GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
|
||||
data_reader.check_array_readers();
|
||||
geom_reader.draw(_gsg, &data_reader, force);
|
||||
} else {
|
||||
// It has a callback associated.
|
||||
object->draw_callback(_gsg, force, current_thread);
|
||||
// Now the callback has taken care of drawing.
|
||||
}
|
||||
for (const ObjectData &data : _objects) {
|
||||
data._object->draw(_gsg, force, current_thread);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -85,24 +85,8 @@ void CullBinFrontToBack::
|
||||
draw(bool force, Thread *current_thread) {
|
||||
PStatTimer timer(_draw_this_pcollector, current_thread);
|
||||
|
||||
Objects::const_iterator oi;
|
||||
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
||||
CullableObject *object = (*oi)._object;
|
||||
|
||||
if (object->_draw_callback == nullptr) {
|
||||
nassertd(object->_geom != nullptr) continue;
|
||||
|
||||
_gsg->set_state_and_transform(object->_state, object->_internal_transform);
|
||||
|
||||
GeomPipelineReader geom_reader(object->_geom, current_thread);
|
||||
GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
|
||||
data_reader.check_array_readers();
|
||||
geom_reader.draw(_gsg, &data_reader, force);
|
||||
} else {
|
||||
// It has a callback associated.
|
||||
object->draw_callback(_gsg, force, current_thread);
|
||||
// Now the callback has taken care of drawing.
|
||||
}
|
||||
for (const ObjectData &data : _objects) {
|
||||
data._object->draw(_gsg, force, current_thread);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,24 +70,8 @@ void CullBinStateSorted::
|
||||
draw(bool force, Thread *current_thread) {
|
||||
PStatTimer timer(_draw_this_pcollector, current_thread);
|
||||
|
||||
Objects::const_iterator oi;
|
||||
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
||||
CullableObject *object = (*oi)._object;
|
||||
|
||||
if (object->_draw_callback == nullptr) {
|
||||
nassertd(object->_geom != nullptr) continue;
|
||||
|
||||
_gsg->set_state_and_transform(object->_state, object->_internal_transform);
|
||||
|
||||
GeomPipelineReader geom_reader(object->_geom, current_thread);
|
||||
GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
|
||||
data_reader.check_array_readers();
|
||||
geom_reader.draw(_gsg, &data_reader, force);
|
||||
} else {
|
||||
// It has a callback associated.
|
||||
object->draw_callback(_gsg, force, current_thread);
|
||||
// Now the callback has taken care of drawing.
|
||||
}
|
||||
for (const ObjectData &data : _objects) {
|
||||
data._object->draw(_gsg, force, current_thread);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,24 +55,8 @@ void CullBinUnsorted::
|
||||
draw(bool force, Thread *current_thread) {
|
||||
PStatTimer timer(_draw_this_pcollector, current_thread);
|
||||
|
||||
Objects::iterator oi;
|
||||
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
|
||||
CullableObject *object = (*oi);
|
||||
|
||||
if (object->_draw_callback == nullptr) {
|
||||
nassertd(object->_geom != nullptr) continue;
|
||||
|
||||
_gsg->set_state_and_transform(object->_state, object->_internal_transform);
|
||||
|
||||
GeomPipelineReader geom_reader(object->_geom, current_thread);
|
||||
GeomVertexDataPipelineReader data_reader(object->_munged_data, current_thread);
|
||||
data_reader.check_array_readers();
|
||||
geom_reader.draw(_gsg, &data_reader, force);
|
||||
} else {
|
||||
// It has a callback associated.
|
||||
object->draw_callback(_gsg, force, current_thread);
|
||||
// Now the callback has taken care of drawing.
|
||||
}
|
||||
for (CullableObject *object : _objects) {
|
||||
object->draw(_gsg, force, current_thread);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2498,9 +2498,13 @@ finish_decal() {
|
||||
bool GraphicsStateGuardian::
|
||||
begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force) {
|
||||
size_t num_instances, bool force) {
|
||||
_data_reader = data_reader;
|
||||
|
||||
if (num_instances == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always draw if we have a shader, since the shader might use a different
|
||||
// mechanism for fetching vertex data.
|
||||
return _data_reader->has_vertex() || (_target_shader && _target_shader->has_shader());
|
||||
|
@ -375,7 +375,7 @@ public:
|
||||
|
||||
virtual bool begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force);
|
||||
size_t num_instances, bool force);
|
||||
virtual bool draw_triangles(const GeomPrimitivePipelineReader *reader,
|
||||
bool force);
|
||||
virtual bool draw_triangles_adj(const GeomPrimitivePipelineReader *reader,
|
||||
|
@ -1180,8 +1180,8 @@ end_frame(Thread *current_thread) {
|
||||
bool DXGraphicsStateGuardian9::
|
||||
begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force) {
|
||||
if (!GraphicsStateGuardian::begin_draw_primitives(geom_reader, data_reader, force)) {
|
||||
size_t num_instances, bool force) {
|
||||
if (!GraphicsStateGuardian::begin_draw_primitives(geom_reader, data_reader, num_instances, force)) {
|
||||
return false;
|
||||
}
|
||||
nassertr(_data_reader != nullptr, false);
|
||||
|
@ -107,7 +107,7 @@ public:
|
||||
|
||||
virtual bool begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force);
|
||||
size_t num_instances, bool force);
|
||||
virtual bool draw_triangles(const GeomPrimitivePipelineReader *reader,
|
||||
bool force);
|
||||
virtual bool draw_tristrips(const GeomPrimitivePipelineReader *reader,
|
||||
|
@ -4292,7 +4292,7 @@ end_frame(Thread *current_thread) {
|
||||
bool CLP(GraphicsStateGuardian)::
|
||||
begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force) {
|
||||
size_t num_instances, bool force) {
|
||||
#ifndef NDEBUG
|
||||
if (GLCAT.is_spam()) {
|
||||
GLCAT.spam() << "begin_draw_primitives: " << *(data_reader->get_object()) << "\n";
|
||||
@ -4309,11 +4309,13 @@ begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!GraphicsStateGuardian::begin_draw_primitives(geom_reader, data_reader, force)) {
|
||||
if (!GraphicsStateGuardian::begin_draw_primitives(geom_reader, data_reader, num_instances, force)) {
|
||||
return false;
|
||||
}
|
||||
nassertr(_data_reader != nullptr, false);
|
||||
|
||||
_instance_count = _supports_geometry_instancing ? num_instances : 1;
|
||||
|
||||
_geom_display_list = 0;
|
||||
|
||||
if (_auto_antialias_mode) {
|
||||
@ -4861,7 +4863,7 @@ draw_triangles(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
}
|
||||
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_TRIANGLES, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -4877,7 +4879,7 @@ draw_triangles(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
}
|
||||
} else {
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_TRIANGLES,
|
||||
reader->get_first_vertex(),
|
||||
num_vertices, _instance_count);
|
||||
@ -4927,7 +4929,7 @@ draw_triangles_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_TRIANGLES_ADJACENCY, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -4940,7 +4942,7 @@ draw_triangles_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
client_pointer);
|
||||
}
|
||||
} else {
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_TRIANGLES_ADJACENCY,
|
||||
reader->get_first_vertex(),
|
||||
num_vertices, _instance_count);
|
||||
@ -4992,7 +4994,7 @@ draw_tristrips(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
return false;
|
||||
}
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_TRIANGLE_STRIP, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -5008,7 +5010,7 @@ draw_tristrips(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
}
|
||||
} else {
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_TRIANGLE_STRIP,
|
||||
reader->get_first_vertex(),
|
||||
num_vertices, _instance_count);
|
||||
@ -5042,7 +5044,7 @@ draw_tristrips(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_tristrip_pcollector.add_level(ends[i] - start);
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_TRIANGLE_STRIP, ends[i] - start,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer + start * index_stride,
|
||||
@ -5064,7 +5066,7 @@ draw_tristrips(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_tristrip_pcollector.add_level(ends[i] - start);
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_TRIANGLE_STRIP, first_vertex + start,
|
||||
ends[i] - start, _instance_count);
|
||||
} else
|
||||
@ -5122,7 +5124,7 @@ draw_tristrips_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
if (!setup_primitive(client_pointer, reader, force)) {
|
||||
return false;
|
||||
}
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_TRIANGLE_STRIP_ADJACENCY, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -5135,7 +5137,7 @@ draw_tristrips_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
client_pointer);
|
||||
}
|
||||
} else {
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_TRIANGLE_STRIP_ADJACENCY,
|
||||
reader->get_first_vertex(),
|
||||
num_vertices, _instance_count);
|
||||
@ -5168,7 +5170,7 @@ draw_tristrips_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
unsigned int start = 0;
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_tristrip_pcollector.add_level(ends[i] - start);
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_TRIANGLE_STRIP_ADJACENCY, ends[i] - start,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer + start * index_stride,
|
||||
@ -5187,7 +5189,7 @@ draw_tristrips_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
int first_vertex = reader->get_first_vertex();
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_tristrip_pcollector.add_level(ends[i] - start);
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_TRIANGLE_STRIP_ADJACENCY, first_vertex + start,
|
||||
ends[i] - start, _instance_count);
|
||||
} else {
|
||||
@ -5245,7 +5247,7 @@ draw_trifans(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_trifan_pcollector.add_level(ends[i] - start);
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_TRIANGLE_FAN, ends[i] - start,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer + start * index_stride,
|
||||
@ -5266,7 +5268,7 @@ draw_trifans(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_trifan_pcollector.add_level(ends[i] - start);
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_TRIANGLE_FAN, first_vertex + start,
|
||||
ends[i] - start, _instance_count);
|
||||
} else
|
||||
@ -5324,7 +5326,7 @@ draw_patches(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
}
|
||||
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_PATCHES, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -5340,7 +5342,7 @@ draw_patches(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
}
|
||||
} else {
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_PATCHES,
|
||||
reader->get_first_vertex(),
|
||||
num_vertices, _instance_count);
|
||||
@ -5390,7 +5392,7 @@ draw_lines(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
return false;
|
||||
}
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_LINES, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -5406,7 +5408,7 @@ draw_lines(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
}
|
||||
} else {
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_LINES,
|
||||
reader->get_first_vertex(),
|
||||
num_vertices, _instance_count);
|
||||
@ -5454,7 +5456,7 @@ draw_lines_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
if (!setup_primitive(client_pointer, reader, force)) {
|
||||
return false;
|
||||
}
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_LINES_ADJACENCY, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -5467,7 +5469,7 @@ draw_lines_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
client_pointer);
|
||||
}
|
||||
} else {
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_LINES_ADJACENCY,
|
||||
reader->get_first_vertex(),
|
||||
num_vertices, _instance_count);
|
||||
@ -5526,7 +5528,7 @@ draw_linestrips(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
return false;
|
||||
}
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_LINE_STRIP, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -5566,7 +5568,7 @@ draw_linestrips(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_other_pcollector.add_level(ends[i] - start);
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_LINE_STRIP, ends[i] - start,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer + start * index_stride,
|
||||
@ -5588,7 +5590,7 @@ draw_linestrips(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_other_pcollector.add_level(ends[i] - start);
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_LINE_STRIP, first_vertex + start,
|
||||
ends[i] - start, _instance_count);
|
||||
} else
|
||||
@ -5646,7 +5648,7 @@ draw_linestrips_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
if (!setup_primitive(client_pointer, reader, force)) {
|
||||
return false;
|
||||
}
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_LINE_STRIP_ADJACENCY, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -5681,7 +5683,7 @@ draw_linestrips_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
unsigned int start = 0;
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_other_pcollector.add_level(ends[i] - start);
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_LINE_STRIP_ADJACENCY, ends[i] - start,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer + start * index_stride,
|
||||
@ -5700,7 +5702,7 @@ draw_linestrips_adj(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
int first_vertex = reader->get_first_vertex();
|
||||
for (size_t i = 0; i < ends.size(); i++) {
|
||||
_vertices_other_pcollector.add_level(ends[i] - start);
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_LINE_STRIP_ADJACENCY, first_vertex + start,
|
||||
ends[i] - start, _instance_count);
|
||||
} else {
|
||||
@ -5747,7 +5749,7 @@ draw_points(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
return false;
|
||||
}
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawElementsInstanced(GL_POINTS, num_vertices,
|
||||
get_numeric_type(reader->get_index_type()),
|
||||
client_pointer, _instance_count);
|
||||
@ -5763,7 +5765,7 @@ draw_points(const GeomPrimitivePipelineReader *reader, bool force) {
|
||||
}
|
||||
} else {
|
||||
#ifndef OPENGLES_1
|
||||
if (_supports_geometry_instancing && _instance_count > 0) {
|
||||
if (_instance_count != 1) {
|
||||
_glDrawArraysInstanced(GL_POINTS,
|
||||
reader->get_first_vertex(),
|
||||
num_vertices, _instance_count);
|
||||
@ -11444,7 +11446,7 @@ set_state_and_transform(const RenderState *target,
|
||||
|
||||
#ifndef OPENGLES_1
|
||||
determine_target_shader();
|
||||
_instance_count = _target_shader->get_instance_count();
|
||||
_sattr_instance_count = _target_shader->get_instance_count();
|
||||
|
||||
if (_target_shader != _state_shader) {
|
||||
do_issue_shader();
|
||||
|
@ -295,7 +295,7 @@ public:
|
||||
|
||||
virtual bool begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force);
|
||||
size_t num_instances, bool force);
|
||||
virtual bool draw_triangles(const GeomPrimitivePipelineReader *reader,
|
||||
bool force);
|
||||
#ifndef OPENGLES
|
||||
@ -1079,6 +1079,7 @@ public:
|
||||
bool _supports_texture_max_level;
|
||||
|
||||
#ifndef OPENGLES_1
|
||||
GLsizei _sattr_instance_count;
|
||||
GLsizei _instance_count;
|
||||
#endif
|
||||
|
||||
|
@ -487,6 +487,13 @@ reflect_attribute(int i, char *name_buffer, GLsizei name_buflen) {
|
||||
bind._name = InternalName::get_texcoord();
|
||||
bind._append_uv = atoi(noprefix.substr(13).c_str());
|
||||
|
||||
} else if (noprefix == "InstanceMatrix") {
|
||||
bind._name = InternalName::get_instance_matrix();
|
||||
|
||||
if (param_type != GL_FLOAT_MAT4x3) {
|
||||
GLCAT.error() << "p3d_InstanceMatrix should be mat4x3!\n";
|
||||
}
|
||||
|
||||
} else {
|
||||
GLCAT.error() << "Unrecognized vertex attrib '" << name_buffer << "'!\n";
|
||||
return;
|
||||
@ -498,15 +505,23 @@ reflect_attribute(int i, char *name_buffer, GLsizei name_buflen) {
|
||||
|
||||
// Get the number of bind points for arrays and matrices.
|
||||
switch (param_type) {
|
||||
case GL_FLOAT_MAT3x2:
|
||||
case GL_FLOAT_MAT3:
|
||||
case GL_FLOAT_MAT3x4:
|
||||
#ifndef OPENGLES
|
||||
case GL_DOUBLE_MAT3x2:
|
||||
case GL_DOUBLE_MAT3:
|
||||
case GL_DOUBLE_MAT3x4:
|
||||
#endif
|
||||
bind._elements = 3 * param_size;
|
||||
break;
|
||||
|
||||
case GL_FLOAT_MAT4x2:
|
||||
case GL_FLOAT_MAT4x3:
|
||||
case GL_FLOAT_MAT4:
|
||||
#ifndef OPENGLES
|
||||
case GL_DOUBLE_MAT4x2:
|
||||
case GL_DOUBLE_MAT4x3:
|
||||
case GL_DOUBLE_MAT4:
|
||||
#endif
|
||||
bind._elements = 4 * param_size;
|
||||
@ -2465,6 +2480,18 @@ update_shader_vertex_arrays(ShaderContext *prev, bool force) {
|
||||
_glgsg->_glVertexAttribI4ui != nullptr) {
|
||||
_glgsg->_glVertexAttribI4ui(p, 0, 1, 2, 3);
|
||||
}
|
||||
else if (name == InternalName::get_instance_matrix()) {
|
||||
const LMatrix4 &ident_mat = LMatrix4::ident_mat();
|
||||
|
||||
for (int i = 0; i < bind._elements; ++i) {
|
||||
#ifdef STDFLOAT_DOUBLE
|
||||
_glgsg->_glVertexAttrib4dv(p, ident_mat.get_data() + i * 4);
|
||||
#else
|
||||
_glgsg->_glVertexAttrib4fv(p, ident_mat.get_data() + i * 4);
|
||||
#endif
|
||||
++p;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1298,18 +1298,20 @@ prepare_now(PreparedGraphicsObjects *prepared_objects,
|
||||
* Actually draws the Geom with the indicated GSG, using the indicated vertex
|
||||
* data (which might have been pre-munged to support the GSG's needs).
|
||||
*
|
||||
* num_instances specifies the number of times to render the geometry.
|
||||
*
|
||||
* Returns true if all of the primitives were drawn normally, false if there
|
||||
* was a problem (for instance, some of the data was nonresident). If force
|
||||
* is passed true, it will wait for the data to become resident if necessary.
|
||||
*/
|
||||
bool Geom::
|
||||
draw(GraphicsStateGuardianBase *gsg, const GeomVertexData *vertex_data,
|
||||
bool force, Thread *current_thread) const {
|
||||
size_t num_instances, bool force, Thread *current_thread) const {
|
||||
GeomPipelineReader geom_reader(this, current_thread);
|
||||
GeomVertexDataPipelineReader data_reader(vertex_data, current_thread);
|
||||
data_reader.check_array_readers();
|
||||
|
||||
return geom_reader.draw(gsg, &data_reader, force);
|
||||
return geom_reader.draw(gsg, &data_reader, num_instances, force);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1847,11 +1849,12 @@ check_valid(const GeomVertexDataPipelineReader *data_reader) const {
|
||||
*/
|
||||
bool GeomPipelineReader::
|
||||
draw(GraphicsStateGuardianBase *gsg,
|
||||
const GeomVertexDataPipelineReader *data_reader, bool force) const {
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
size_t num_instances, bool force) const {
|
||||
bool all_ok;
|
||||
{
|
||||
PStatTimer timer(Geom::_draw_primitive_setup_pcollector);
|
||||
all_ok = gsg->begin_draw_primitives(this, data_reader, force);
|
||||
all_ok = gsg->begin_draw_primitives(this, data_reader, num_instances, force);
|
||||
}
|
||||
if (all_ok) {
|
||||
Geom::Primitives::const_iterator pi;
|
||||
|
@ -158,7 +158,7 @@ PUBLISHED:
|
||||
|
||||
public:
|
||||
bool draw(GraphicsStateGuardianBase *gsg,
|
||||
const GeomVertexData *vertex_data,
|
||||
const GeomVertexData *vertex_data, size_t num_instances,
|
||||
bool force, Thread *current_thread) const;
|
||||
|
||||
INLINE void calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
|
||||
@ -433,7 +433,7 @@ public:
|
||||
|
||||
bool draw(GraphicsStateGuardianBase *gsg,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force) const;
|
||||
size_t num_instances, bool force) const;
|
||||
|
||||
private:
|
||||
const Geom *_object;
|
||||
|
@ -655,6 +655,27 @@ compare_to(const GeomVertexArrayFormat &other) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a suitable format for sending an array of instances to the graphics
|
||||
* backend.
|
||||
*
|
||||
* This may only be called after the format has been registered. The return
|
||||
* value will have been already registered.
|
||||
*/
|
||||
const GeomVertexArrayFormat *GeomVertexArrayFormat::
|
||||
get_instance_array_format() {
|
||||
static CPT(GeomVertexArrayFormat) inst_array_format;
|
||||
|
||||
if (inst_array_format == nullptr) {
|
||||
GeomVertexArrayFormat *new_array_format = new GeomVertexArrayFormat("instance_matrix", 4, NT_stdfloat, C_matrix);
|
||||
new_array_format->set_divisor(1);
|
||||
inst_array_format = GeomVertexArrayFormat::register_format(new_array_format);
|
||||
}
|
||||
|
||||
nassertr(inst_array_format != nullptr, nullptr);
|
||||
return inst_array_format.p();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resorts the _columns vector so that the columns are listed in the same
|
||||
* order they appear in the record.
|
||||
|
@ -123,6 +123,8 @@ PUBLISHED:
|
||||
public:
|
||||
int compare_to(const GeomVertexArrayFormat &other) const;
|
||||
|
||||
static const GeomVertexArrayFormat *get_instance_array_format();
|
||||
|
||||
private:
|
||||
class Registry;
|
||||
INLINE static Registry *get_registry();
|
||||
|
@ -134,6 +134,32 @@ get_post_animated_format() const {
|
||||
return _post_animated_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a suitable vertex format for sending the animated vertices to the
|
||||
* graphics backend. This is the same format as the source format, with the
|
||||
* instancing columns added.
|
||||
*
|
||||
* This may only be called after the format has been registered. The return
|
||||
* value will have been already registered.
|
||||
*/
|
||||
CPT(GeomVertexFormat) GeomVertexFormat::
|
||||
get_post_instanced_format() const {
|
||||
nassertr(is_registered(), nullptr);
|
||||
|
||||
if (_post_instanced_format == nullptr) {
|
||||
PT(GeomVertexFormat) new_format = new GeomVertexFormat(*this);
|
||||
new_format->add_array(GeomVertexArrayFormat::register_format(GeomVertexArrayFormat::get_instance_array_format()));
|
||||
|
||||
CPT(GeomVertexFormat) registered =
|
||||
GeomVertexFormat::register_format(new_format);
|
||||
((GeomVertexFormat *)this)->_post_instanced_format = registered;
|
||||
}
|
||||
|
||||
_post_instanced_format->test_ref_count_integrity();
|
||||
|
||||
return _post_instanced_format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new GeomVertexFormat that includes all of the columns defined in
|
||||
* either this GeomVertexFormat or the other one. If any column is defined in
|
||||
@ -818,6 +844,11 @@ do_unregister() {
|
||||
unref_delete(_post_animated_format);
|
||||
}
|
||||
_post_animated_format = nullptr;
|
||||
|
||||
if (_post_instanced_format != nullptr) {
|
||||
unref_delete(_post_instanced_format);
|
||||
_post_instanced_format = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,6 +72,7 @@ PUBLISHED:
|
||||
MAKE_PROPERTY(animation, get_animation, set_animation);
|
||||
|
||||
CPT(GeomVertexFormat) get_post_animated_format() const;
|
||||
CPT(GeomVertexFormat) get_post_instanced_format() const;
|
||||
CPT(GeomVertexFormat) get_union_format(const GeomVertexFormat *other) const;
|
||||
|
||||
INLINE size_t get_num_arrays() const;
|
||||
@ -222,7 +223,8 @@ private:
|
||||
typedef pvector<MorphRecord> Morphs;
|
||||
Morphs _morphs;
|
||||
|
||||
const GeomVertexFormat *_post_animated_format;
|
||||
const GeomVertexFormat *_post_animated_format = nullptr;
|
||||
const GeomVertexFormat *_post_instanced_format = nullptr;
|
||||
|
||||
// This is the global registry of all currently-in-use formats.
|
||||
typedef pset<GeomVertexFormat *, IndirectCompareTo<GeomVertexFormat> > Formats;
|
||||
|
@ -366,6 +366,17 @@ get_view() {
|
||||
return _view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard InternalName "instance_matrix".
|
||||
*/
|
||||
INLINE PT(InternalName) InternalName::
|
||||
get_instance_matrix() {
|
||||
if (_instance_matrix == nullptr) {
|
||||
_instance_matrix = InternalName::make("instance_matrix");
|
||||
}
|
||||
return _instance_matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -40,6 +40,7 @@ PT(InternalName) InternalName::_world;
|
||||
PT(InternalName) InternalName::_camera;
|
||||
PT(InternalName) InternalName::_model;
|
||||
PT(InternalName) InternalName::_view;
|
||||
PT(InternalName) InternalName::_instance_matrix;
|
||||
|
||||
TypeHandle InternalName::_type_handle;
|
||||
TypeHandle InternalName::_texcoord_type_handle;
|
||||
|
@ -92,6 +92,7 @@ PUBLISHED:
|
||||
INLINE static PT(InternalName) get_camera();
|
||||
INLINE static PT(InternalName) get_model();
|
||||
INLINE static PT(InternalName) get_view();
|
||||
INLINE static PT(InternalName) get_instance_matrix();
|
||||
|
||||
#ifdef HAVE_PYTHON
|
||||
// These versions are exposed to Python, which have additional logic to map
|
||||
@ -141,6 +142,7 @@ private:
|
||||
static PT(InternalName) _camera;
|
||||
static PT(InternalName) _model;
|
||||
static PT(InternalName) _view;
|
||||
static PT(InternalName) _instance_matrix;
|
||||
|
||||
public:
|
||||
// Datagram stuff
|
||||
|
@ -203,7 +203,7 @@ public:
|
||||
|
||||
virtual bool begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force)=0;
|
||||
size_t num_instances, bool force)=0;
|
||||
virtual bool draw_triangles(const GeomPrimitivePipelineReader *reader, bool force)=0;
|
||||
virtual bool draw_triangles_adj(const GeomPrimitivePipelineReader *reader, bool force)=0;
|
||||
virtual bool draw_tristrips(const GeomPrimitivePipelineReader *reader, bool force)=0;
|
||||
|
@ -39,6 +39,8 @@ set(P3PGRAPH_HEADERS
|
||||
geomDrawCallbackData.I geomDrawCallbackData.h
|
||||
geomNode.I geomNode.h
|
||||
geomTransformer.I geomTransformer.h
|
||||
instanceList.I instanceList.h
|
||||
instancedNode.I instancedNode.h
|
||||
internalNameCollection.I internalNameCollection.h
|
||||
lensNode.I lensNode.h
|
||||
light.I light.h
|
||||
@ -139,6 +141,8 @@ set(P3PGRAPH_SOURCES
|
||||
geomDrawCallbackData.cxx
|
||||
geomNode.cxx
|
||||
geomTransformer.cxx
|
||||
instanceList.cxx
|
||||
instancedNode.cxx
|
||||
internalNameCollection.cxx
|
||||
lensNode.cxx
|
||||
light.cxx
|
||||
|
@ -42,6 +42,8 @@
|
||||
#include "geomDrawCallbackData.h"
|
||||
#include "geomNode.h"
|
||||
#include "geomTransformer.h"
|
||||
#include "instanceList.h"
|
||||
#include "instancedNode.h"
|
||||
#include "lensNode.h"
|
||||
#include "light.h"
|
||||
#include "lightAttrib.h"
|
||||
@ -416,6 +418,8 @@ init_libpgraph() {
|
||||
GeomDrawCallbackData::init_type();
|
||||
GeomNode::init_type();
|
||||
GeomTransformer::init_type();
|
||||
InstanceList::init_type();
|
||||
InstancedNode::init_type();
|
||||
LensNode::init_type();
|
||||
Light::init_type();
|
||||
LightAttrib::init_type();
|
||||
@ -484,6 +488,8 @@ init_libpgraph() {
|
||||
Fog::register_with_read_factory();
|
||||
FogAttrib::register_with_read_factory();
|
||||
GeomNode::register_with_read_factory();
|
||||
InstanceList::register_with_read_factory();
|
||||
InstancedNode::register_with_read_factory();
|
||||
LensNode::register_with_read_factory();
|
||||
LightAttrib::register_with_read_factory();
|
||||
LightRampAttrib::register_with_read_factory();
|
||||
|
@ -271,10 +271,10 @@ show_bounds(CullTraverserData &data, bool tight) {
|
||||
CullableObject *outer_viz =
|
||||
new CullableObject(std::move(bounds_viz), get_bounds_outer_viz_state(),
|
||||
internal_transform);
|
||||
outer_viz->_instances = data._instances;
|
||||
_cull_handler->record_object(outer_viz, this);
|
||||
}
|
||||
|
||||
} else {
|
||||
} else if (data._instances == nullptr) {
|
||||
draw_bounding_volume(node->get_bounds(), internal_transform);
|
||||
|
||||
if (node->is_geom_node()) {
|
||||
@ -287,6 +287,22 @@ show_bounds(CullTraverserData &data, bool tight) {
|
||||
internal_transform);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Draw bounds for every instance.
|
||||
for (const InstanceList::Instance &instance : *data._instances) {
|
||||
CPT(TransformState) transform = internal_transform->compose(instance.get_transform());
|
||||
draw_bounding_volume(node->get_bounds(), transform);
|
||||
|
||||
if (node->is_geom_node()) {
|
||||
// Also show the bounding volumes of included Geoms.
|
||||
transform = transform->compose(node->get_transform());
|
||||
GeomNode *gnode = (GeomNode *)node;
|
||||
int num_geoms = gnode->get_num_geoms();
|
||||
for (int i = 0; i < num_geoms; ++i) {
|
||||
draw_bounding_volume(gnode->get_geom(i)->get_bounds(), transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,7 @@ CullTraverserData(const CullTraverserData &parent, PandaNode *child) :
|
||||
_state(parent._state),
|
||||
_view_frustum(parent._view_frustum),
|
||||
_cull_planes(parent._cull_planes),
|
||||
_instances(parent._instances),
|
||||
_draw_mask(parent._draw_mask),
|
||||
_portal_depth(parent._portal_depth)
|
||||
{
|
||||
@ -110,7 +111,8 @@ get_modelview_transform(const CullTraverser *trav) const {
|
||||
*/
|
||||
INLINE CPT(TransformState) CullTraverserData::
|
||||
get_internal_transform(const CullTraverser *trav) const {
|
||||
return trav->get_scene()->get_cs_world_transform()->compose(_net_transform);
|
||||
const TransformState *cs_world_transform = trav->get_scene()->get_cs_world_transform();
|
||||
return cs_world_transform->compose(_net_transform);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,6 +77,15 @@ apply_transform_and_state(CullTraverser *trav) {
|
||||
void CullTraverserData::
|
||||
apply_transform(const TransformState *node_transform) {
|
||||
if (!node_transform->is_identity()) {
|
||||
if (_instances != nullptr) {
|
||||
InstanceList *instances = new InstanceList(*_instances);
|
||||
for (InstanceList::Instance &instance : *instances) {
|
||||
instance.set_transform(instance.get_transform()->compose(node_transform));
|
||||
}
|
||||
_instances = std::move(instances);
|
||||
return;
|
||||
}
|
||||
|
||||
_net_transform = _net_transform->compose(node_transform);
|
||||
|
||||
if ((_view_frustum != nullptr) ||
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "pointerTo.h"
|
||||
#include "drawMask.h"
|
||||
#include "pvector.h"
|
||||
#include "instanceList.h"
|
||||
|
||||
class PandaNode;
|
||||
class CullTraverser;
|
||||
@ -81,6 +82,7 @@ public:
|
||||
CPT(RenderState) _state;
|
||||
PT(GeometricBoundingVolume) _view_frustum;
|
||||
CPT(CullPlanes) _cull_planes;
|
||||
CPT(InstanceList) _instances;
|
||||
DrawMask _draw_mask;
|
||||
int _portal_depth;
|
||||
|
||||
|
@ -70,7 +70,7 @@ operator = (const CullableObject ©) {
|
||||
*/
|
||||
INLINE void CullableObject::
|
||||
draw(GraphicsStateGuardianBase *gsg, bool force, Thread *current_thread) {
|
||||
if (_draw_callback != nullptr) {
|
||||
if (UNLIKELY(_draw_callback != nullptr)) {
|
||||
// It has a callback associated.
|
||||
gsg->clear_before_callback();
|
||||
gsg->set_state_and_transform(_state, _internal_transform);
|
||||
@ -81,11 +81,26 @@ draw(GraphicsStateGuardianBase *gsg, bool force, Thread *current_thread) {
|
||||
gsg->clear_state_and_transform();
|
||||
}
|
||||
// Now the callback has taken care of drawing.
|
||||
} else {
|
||||
}
|
||||
else if (LIKELY(_instances == nullptr)) {
|
||||
nassertv(_geom != nullptr);
|
||||
gsg->set_state_and_transform(_state, _internal_transform);
|
||||
draw_inline(gsg, force, current_thread);
|
||||
}
|
||||
else {
|
||||
// It has an instance list left over (not munged into vertex data), which
|
||||
// means the shader doesn't implement instancing. Just render the object
|
||||
// more than once.
|
||||
nassertv(_geom != nullptr);
|
||||
GeomPipelineReader geom_reader(_geom, current_thread);
|
||||
GeomVertexDataPipelineReader data_reader(_munged_data, current_thread);
|
||||
data_reader.check_array_readers();
|
||||
|
||||
for (const InstanceList::Instance &instance : *_instances) {
|
||||
gsg->set_state_and_transform(_state, _internal_transform->compose(instance.get_transform()));
|
||||
geom_reader.draw(gsg, &data_reader, _num_instances, force);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,7 +145,7 @@ flush_level() {
|
||||
*/
|
||||
INLINE void CullableObject::
|
||||
draw_inline(GraphicsStateGuardianBase *gsg, bool force, Thread *current_thread) {
|
||||
_geom->draw(gsg, _munged_data, force, current_thread);
|
||||
_geom->draw(gsg, _munged_data, _num_instances, force, current_thread);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,6 +39,7 @@ PStatCollector CullableObject::_munge_geom_pcollector("*:Munge:Geom");
|
||||
PStatCollector CullableObject::_munge_sprites_pcollector("*:Munge:Sprites");
|
||||
PStatCollector CullableObject::_munge_sprites_verts_pcollector("*:Munge:Sprites:Verts");
|
||||
PStatCollector CullableObject::_munge_sprites_prims_pcollector("*:Munge:Sprites:Prims");
|
||||
PStatCollector CullableObject::_munge_instances_pcollector("*:Munge:Instances");
|
||||
PStatCollector CullableObject::_sw_sprites_pcollector("SW Sprites");
|
||||
|
||||
TypeHandle CullableObject::_type_handle;
|
||||
@ -173,6 +174,23 @@ munge_geom(GraphicsStateGuardianBase *gsg, GeomMunger *munger,
|
||||
std::swap(_munged_data, animated_vertices);
|
||||
}
|
||||
|
||||
if (sattr != nullptr) {
|
||||
if (_instances != nullptr &&
|
||||
sattr->get_flag(ShaderAttrib::F_hardware_instancing)) {
|
||||
// We are under an InstancedNode, and the shader implements hardware.
|
||||
// Munge the instance list into the vertex data.
|
||||
munge_instances(current_thread);
|
||||
_num_instances = _instances->size();
|
||||
_instances = nullptr;
|
||||
} else {
|
||||
// No, use the instance count from the ShaderAttrib.
|
||||
int count = sattr->get_instance_count();
|
||||
_num_instances = (count > 0) ? (size_t)count : 1;
|
||||
}
|
||||
} else {
|
||||
_num_instances = 1;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (show_vertex_animation) {
|
||||
GeomVertexDataPipelineReader data_reader(_munged_data, current_thread);
|
||||
@ -204,6 +222,22 @@ output(std::ostream &out) const {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a GeomVertexData that represents the results of computing the
|
||||
* instance arrays for this data.
|
||||
*/
|
||||
void CullableObject::
|
||||
munge_instances(Thread *current_thread) {
|
||||
PStatTimer timer(_munge_instances_pcollector, current_thread);
|
||||
|
||||
PT(GeomVertexData) instanced_data = new GeomVertexData(*_munged_data);
|
||||
const GeomVertexArrayFormat *array_format = GeomVertexArrayFormat::get_instance_array_format();
|
||||
|
||||
CPT(GeomVertexArrayData) new_array = _instances->get_array_data(array_format);
|
||||
instanced_data->insert_array((size_t)-1, new_array);
|
||||
_munged_data = instanced_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a table of points to quads for rendering on systems that don't
|
||||
* support fancy points.
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "lightMutex.h"
|
||||
#include "callbackObject.h"
|
||||
#include "geomDrawCallbackData.h"
|
||||
#include "instanceList.h"
|
||||
|
||||
class CullTraverser;
|
||||
class GeomMunger;
|
||||
@ -73,8 +74,11 @@ public:
|
||||
CPT(RenderState) _state;
|
||||
CPT(TransformState) _internal_transform;
|
||||
PT(CallbackObject) _draw_callback;
|
||||
CPT(InstanceList) _instances;
|
||||
int _num_instances = 1;
|
||||
|
||||
private:
|
||||
void munge_instances(Thread *current_thread);
|
||||
bool munge_points_to_quads(const CullTraverser *traverser, bool force);
|
||||
|
||||
static CPT(RenderState) get_flash_cpu_state();
|
||||
@ -113,6 +117,7 @@ private:
|
||||
static PStatCollector _munge_sprites_pcollector;
|
||||
static PStatCollector _munge_sprites_verts_pcollector;
|
||||
static PStatCollector _munge_sprites_prims_pcollector;
|
||||
static PStatCollector _munge_instances_pcollector;
|
||||
static PStatCollector _sw_sprites_pcollector;
|
||||
|
||||
public:
|
||||
|
@ -45,7 +45,6 @@ upcall() {
|
||||
_gsg->clear_state_and_transform();
|
||||
}
|
||||
|
||||
_obj->_geom->draw(_gsg, _obj->_munged_data, _force,
|
||||
Thread::get_current_thread());
|
||||
_obj->draw_inline(_gsg, _force, Thread::get_current_thread());
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "boundingSphere.h"
|
||||
#include "config_mathutil.h"
|
||||
#include "preparedGraphicsObjects.h"
|
||||
#include "instanceList.h"
|
||||
|
||||
|
||||
bool allow_flatten_color = ConfigVariableBool
|
||||
@ -527,6 +528,16 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data._instances != nullptr) {
|
||||
// Draw each individual instance. We don't bother culling each
|
||||
// individual Geom for each instance; that is probably way too slow.
|
||||
CullableObject *object =
|
||||
new CullableObject(std::move(geom), std::move(state), internal_transform);
|
||||
object->_instances = data._instances;
|
||||
trav->get_cull_handler()->record_object(object, trav);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cull the Geom bounding volume against the view frustum andor the cull
|
||||
// planes. Don't bother unless we've got more than one Geom, since
|
||||
// otherwise the bounding volume of the GeomNode is (probably) the same as
|
||||
|
280
panda/src/pgraph/instanceList.I
Normal file
280
panda/src/pgraph/instanceList.I
Normal file
@ -0,0 +1,280 @@
|
||||
/**
|
||||
* 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 instanceList.I
|
||||
* @author rdb
|
||||
* @date 2019-03-10
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initializes an instance with the identity transform.
|
||||
*/
|
||||
INLINE InstanceList::Instance::
|
||||
Instance() : _transform(TransformState::make_identity()) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an instance with the given transformation.
|
||||
*/
|
||||
INLINE InstanceList::Instance::
|
||||
Instance(CPT(TransformState) transform) : _transform(std::move(transform)) {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE LPoint3 InstanceList::Instance::
|
||||
get_pos() const {
|
||||
return get_transform()->get_pos();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void InstanceList::Instance::
|
||||
set_pos(const LPoint3 &pos) {
|
||||
set_transform(get_transform()->set_pos(pos));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void InstanceList::Instance::
|
||||
set_pos(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z) {
|
||||
set_pos(LPoint3(x, y, z));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE LVecBase3 InstanceList::Instance::
|
||||
get_hpr() const {
|
||||
return get_transform()->get_hpr();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void InstanceList::Instance::
|
||||
set_hpr(const LVecBase3 &hpr) {
|
||||
set_transform(get_transform()->set_hpr(hpr));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void InstanceList::Instance::
|
||||
set_hpr(PN_stdfloat h, PN_stdfloat p, PN_stdfloat r) {
|
||||
set_hpr(LVecBase3(h, p, r));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE LQuaternion InstanceList::Instance::
|
||||
get_quat() const {
|
||||
return get_transform()->get_quat();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void InstanceList::Instance::
|
||||
set_quat(const LQuaternion &quat) {
|
||||
set_transform(get_transform()->set_quat(quat));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE LVecBase3 InstanceList::Instance::
|
||||
get_scale() const {
|
||||
return get_transform()->get_scale();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void InstanceList::Instance::
|
||||
set_scale(const LVecBase3 &scale) {
|
||||
set_transform(get_transform()->set_scale(scale));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void InstanceList::Instance::
|
||||
set_scale(PN_stdfloat sx, PN_stdfloat sy, PN_stdfloat sz) {
|
||||
set_scale(LVecBase3(sx, sy, sz));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE const LMatrix4 &InstanceList::Instance::
|
||||
get_mat() const {
|
||||
return get_transform()->get_mat();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void InstanceList::Instance::
|
||||
set_mat(const LMatrix4 &mat) {
|
||||
set_transform(TransformState::make_mat(mat));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE const TransformState *InstanceList::Instance::
|
||||
get_transform() const {
|
||||
return _transform.p();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE void InstanceList::Instance::
|
||||
set_transform(CPT(TransformState) transform) {
|
||||
_transform = std::move(transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new instance with the indicated transformation to the list.
|
||||
*/
|
||||
INLINE void InstanceList::
|
||||
append(InstanceList::Instance instance) {
|
||||
_instances.push_back(std::move(instance));
|
||||
_cached_array.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new instance with the indicated transformation to the list.
|
||||
*/
|
||||
INLINE void InstanceList::
|
||||
append(const TransformState *transform) {
|
||||
_instances.push_back(Instance(transform));
|
||||
_cached_array.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new instance with the indicated transformation to the list.
|
||||
*/
|
||||
INLINE void InstanceList::
|
||||
append(const LPoint3 &pos, const LVecBase3 &hpr, const LVecBase3 &scale) {
|
||||
|
||||
append(TransformState::make_pos_hpr_scale(pos, hpr, scale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new instance with the indicated transformation to the list.
|
||||
*/
|
||||
INLINE void InstanceList::
|
||||
append(const LPoint3 &pos, const LQuaternion &quat, const LVecBase3 &scale) {
|
||||
|
||||
append(TransformState::make_pos_quat_scale(pos, quat, scale));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of instances in the list.
|
||||
*/
|
||||
INLINE size_t InstanceList::
|
||||
size() const {
|
||||
return _instances.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nth instance in the list.
|
||||
*/
|
||||
INLINE const InstanceList::Instance &InstanceList::
|
||||
operator [] (size_t n) const {
|
||||
return _instances[n];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nth instance in the list.
|
||||
*/
|
||||
INLINE InstanceList::Instance &InstanceList::
|
||||
operator [] (size_t n) {
|
||||
_cached_array.clear();
|
||||
return _instances[n];
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the instance list.
|
||||
*/
|
||||
INLINE void InstanceList::
|
||||
clear() {
|
||||
_instances.clear();
|
||||
_cached_array.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserves space for the given number of instances.
|
||||
*/
|
||||
INLINE void InstanceList::
|
||||
reserve(size_t n) {
|
||||
_instances.reserve(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the InstanceList is empty.
|
||||
*/
|
||||
INLINE bool InstanceList::
|
||||
empty() const {
|
||||
return _instances.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator to the beginning of the list.
|
||||
*/
|
||||
INLINE InstanceList::iterator InstanceList::
|
||||
begin() {
|
||||
return _instances.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a const_iterator to the beginning of the list.
|
||||
*/
|
||||
INLINE InstanceList::const_iterator InstanceList::
|
||||
begin() const {
|
||||
return _instances.begin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a const_iterator to the beginning of the list.
|
||||
*/
|
||||
INLINE InstanceList::const_iterator InstanceList::
|
||||
cbegin() const {
|
||||
return _instances.cbegin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator to the end of the list.
|
||||
*/
|
||||
INLINE InstanceList::iterator InstanceList::
|
||||
end() {
|
||||
return _instances.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a const_iterator to the end of the list.
|
||||
*/
|
||||
INLINE InstanceList::const_iterator InstanceList::
|
||||
end() const {
|
||||
return _instances.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a const_iterator to the end of the list.
|
||||
*/
|
||||
INLINE InstanceList::const_iterator InstanceList::
|
||||
cend() const {
|
||||
return _instances.cend();
|
||||
}
|
213
panda/src/pgraph/instanceList.cxx
Normal file
213
panda/src/pgraph/instanceList.cxx
Normal file
@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 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 instanceList.cxx
|
||||
* @author rdb
|
||||
* @date 2019-03-10
|
||||
*/
|
||||
|
||||
#include "instanceList.h"
|
||||
#include "indent.h"
|
||||
#include "bamReader.h"
|
||||
#include "bamWriter.h"
|
||||
#include "bitArray.h"
|
||||
#include "geomVertexWriter.h"
|
||||
|
||||
TypeHandle InstanceList::_type_handle;
|
||||
|
||||
/**
|
||||
* Required to implement CopyOnWriteObject.
|
||||
*/
|
||||
PT(CopyOnWriteObject) InstanceList::
|
||||
make_cow_copy() {
|
||||
return new InstanceList(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
InstanceList::
|
||||
InstanceList() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
InstanceList::
|
||||
InstanceList(const InstanceList ©) :
|
||||
_instances(copy._instances)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
InstanceList::
|
||||
~InstanceList() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms all of the instances in the list by the indicated matrix.
|
||||
*/
|
||||
void InstanceList::
|
||||
xform(const LMatrix4 &mat) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an immutable copy without the bits turned on in the indicated mask.
|
||||
*/
|
||||
CPT(InstanceList) InstanceList::
|
||||
without(const BitArray &mask) const {
|
||||
size_t num_instances = size();
|
||||
size_t num_culled = (size_t)mask.get_num_on_bits();
|
||||
if (num_culled == 0) {
|
||||
return this;
|
||||
}
|
||||
else if (num_culled >= num_instances) {
|
||||
static CPT(InstanceList) empty_list;
|
||||
if (empty_list == nullptr) {
|
||||
empty_list = new InstanceList;
|
||||
}
|
||||
|
||||
nassertr(num_culled <= num_instances, empty_list);
|
||||
return empty_list;
|
||||
}
|
||||
|
||||
InstanceList *new_list = new InstanceList;
|
||||
new_list->_instances.reserve(num_instances - num_culled);
|
||||
|
||||
for (size_t i = (size_t)mask.get_lowest_off_bit(); i < num_instances; ++i) {
|
||||
if (!mask.get_bit(i)) {
|
||||
new_list->_instances.push_back(_instances[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a GeomVertexArrayData containing the matrices.
|
||||
*/
|
||||
CPT(GeomVertexArrayData) InstanceList::
|
||||
get_array_data(const GeomVertexArrayFormat *format) const {
|
||||
CPT(GeomVertexArrayData) array_data = _cached_array;
|
||||
if (array_data != nullptr) {
|
||||
if (array_data->get_array_format() == format) {
|
||||
return array_data;
|
||||
}
|
||||
}
|
||||
|
||||
nassertr(format != nullptr, nullptr);
|
||||
|
||||
size_t num_instances = size();
|
||||
PT(GeomVertexArrayData) new_array = new GeomVertexArrayData(format, GeomEnums::UH_stream);
|
||||
new_array->unclean_set_num_rows(num_instances);
|
||||
|
||||
{
|
||||
GeomVertexWriter writer(new_array, Thread::get_current_thread());
|
||||
writer.set_column(InternalName::get_instance_matrix());
|
||||
for (size_t i = 0; i < num_instances; ++i) {
|
||||
writer.set_matrix4(_instances[i].get_mat());
|
||||
}
|
||||
}
|
||||
|
||||
_cached_array = new_array;
|
||||
return new_array;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void InstanceList::
|
||||
output(std::ostream &out) const {
|
||||
out << "InstanceList[" << size() << "]";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void InstanceList::
|
||||
write(std::ostream &out, int indent_level) const {
|
||||
indent(out, indent_level) << "InstanceList[" << size() << "]:\n";
|
||||
for (const Instance &instance : *this) {
|
||||
indent(out, indent_level + 2) << *instance.get_transform() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the BamReader how to create objects of type InstanceList.
|
||||
*/
|
||||
void InstanceList::
|
||||
register_with_read_factory() {
|
||||
BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the contents of this object to the datagram for shipping out to a
|
||||
* Bam file.
|
||||
*/
|
||||
void InstanceList::
|
||||
write_datagram(BamWriter *manager, Datagram &dg) {
|
||||
CopyOnWriteObject::write_datagram(manager, dg);
|
||||
|
||||
for (const Instance &instance : *(const InstanceList *)this) {
|
||||
manager->write_pointer(dg, instance.get_transform());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives an array of pointers, one for each time manager->read_pointer()
|
||||
* was called in fillin(). Returns the number of pointers processed.
|
||||
*/
|
||||
int InstanceList::
|
||||
complete_pointers(TypedWritable **p_list, BamReader *manager) {
|
||||
int pi = CopyOnWriteObject::complete_pointers(p_list, manager);
|
||||
|
||||
for (Instance &instance : *this) {
|
||||
instance = Instance(DCAST(TransformState, p_list[pi++]));
|
||||
}
|
||||
|
||||
return pi;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called by the BamReader's factory when a new object of
|
||||
* type InstanceList is encountered in the Bam file. It should create
|
||||
* the InstanceList and extract its information from the file.
|
||||
*/
|
||||
TypedWritable *InstanceList::
|
||||
make_from_bam(const FactoryParams ¶ms) {
|
||||
InstanceList *object = new InstanceList;
|
||||
DatagramIterator scan;
|
||||
BamReader *manager;
|
||||
|
||||
parse_params(params, scan, manager);
|
||||
object->fillin(scan, manager);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* This internal function is called by make_from_bam to read in all of the
|
||||
* relevant data from the BamFile for the new InstanceList.
|
||||
*/
|
||||
void InstanceList::
|
||||
fillin(DatagramIterator &scan, BamReader *manager) {
|
||||
CopyOnWriteObject::fillin(scan, manager);
|
||||
|
||||
size_t num_instances = scan.get_uint16();
|
||||
_instances.clear();
|
||||
_instances.resize(num_instances);
|
||||
|
||||
for (size_t i = 0; i < num_instances; ++i) {
|
||||
manager->read_pointer(scan);
|
||||
}
|
||||
|
||||
_cached_array.clear();
|
||||
}
|
159
panda/src/pgraph/instanceList.h
Normal file
159
panda/src/pgraph/instanceList.h
Normal file
@ -0,0 +1,159 @@
|
||||
/**
|
||||
* 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 instanceList.h
|
||||
* @author rdb
|
||||
* @date 2019-03-10
|
||||
*/
|
||||
|
||||
#ifndef INSTANCELIST_H
|
||||
#define INSTANCELIST_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "copyOnWriteObject.h"
|
||||
#include "transformState.h"
|
||||
#include "pvector.h"
|
||||
#include "geomVertexArrayData.h"
|
||||
|
||||
class BitArray;
|
||||
class FactoryParams;
|
||||
|
||||
/**
|
||||
* This structure stores a list of per-instance data, used by InstancedNode.
|
||||
*
|
||||
* @since 1.11.0
|
||||
*/
|
||||
class EXPCL_PANDA_PGRAPH InstanceList : public CopyOnWriteObject {
|
||||
protected:
|
||||
virtual PT(CopyOnWriteObject) make_cow_copy() override;
|
||||
|
||||
PUBLISHED:
|
||||
InstanceList();
|
||||
InstanceList(const InstanceList ©);
|
||||
virtual ~InstanceList();
|
||||
ALLOC_DELETED_CHAIN(InstanceList);
|
||||
|
||||
/**
|
||||
* An individual instance in an InstanceList.
|
||||
*
|
||||
* @since 1.11.0
|
||||
*/
|
||||
class EXPCL_PANDA_PGRAPH Instance {
|
||||
public:
|
||||
INLINE explicit Instance();
|
||||
INLINE explicit Instance(CPT(TransformState) transform);
|
||||
|
||||
PUBLISHED:
|
||||
INLINE LPoint3 get_pos() const;
|
||||
INLINE void set_pos(const LPoint3 &);
|
||||
INLINE void set_pos(PN_stdfloat x, PN_stdfloat y, PN_stdfloat z);
|
||||
|
||||
INLINE LVecBase3 get_hpr() const;
|
||||
INLINE void set_hpr(const LVecBase3 &);
|
||||
INLINE void set_hpr(PN_stdfloat h, PN_stdfloat p, PN_stdfloat r);
|
||||
|
||||
INLINE LQuaternion get_quat() const;
|
||||
INLINE void set_quat(const LQuaternion &);
|
||||
|
||||
INLINE LVecBase3 get_scale() const;
|
||||
INLINE void set_scale(const LVecBase3 &);
|
||||
INLINE void set_scale(PN_stdfloat sx, PN_stdfloat sy, PN_stdfloat sz);
|
||||
|
||||
INLINE const LMatrix4 &get_mat() const;
|
||||
INLINE void set_mat(const LMatrix4 &mat);
|
||||
|
||||
INLINE const TransformState *get_transform() const;
|
||||
INLINE void set_transform(CPT(TransformState));
|
||||
MAKE_PROPERTY(transform, get_transform);
|
||||
|
||||
private:
|
||||
CPT(TransformState) _transform;
|
||||
};
|
||||
|
||||
void append(Instance instance);
|
||||
void append(const TransformState *transform);
|
||||
void append(const LPoint3 &pos,
|
||||
const LVecBase3 &hpr = LVecBase3(0),
|
||||
const LVecBase3 &scale = LVecBase3(1));
|
||||
void append(const LPoint3 &pos,
|
||||
const LQuaternion &quat,
|
||||
const LVecBase3 &scale = LVecBase3(1));
|
||||
|
||||
INLINE size_t size() const;
|
||||
INLINE const Instance &operator [] (size_t n) const;
|
||||
INLINE Instance &operator [] (size_t n);
|
||||
INLINE void clear();
|
||||
INLINE void reserve(size_t);
|
||||
|
||||
void xform(const LMatrix4 &mat);
|
||||
|
||||
public:
|
||||
typedef pvector<Instance> Instances;
|
||||
typedef Instances::iterator iterator;
|
||||
typedef Instances::const_iterator const_iterator;
|
||||
|
||||
INLINE bool empty() const;
|
||||
|
||||
INLINE iterator begin();
|
||||
INLINE const_iterator begin() const;
|
||||
INLINE const_iterator cbegin() const;
|
||||
|
||||
INLINE iterator end();
|
||||
INLINE const_iterator end() const;
|
||||
INLINE const_iterator cend() const;
|
||||
|
||||
CPT(InstanceList) without(const BitArray &mask) const;
|
||||
|
||||
CPT(GeomVertexArrayData) get_array_data(const GeomVertexArrayFormat *format) const;
|
||||
|
||||
virtual void output(std::ostream &out) const;
|
||||
virtual void write(std::ostream &out, int indent_level) const;
|
||||
|
||||
private:
|
||||
Instances _instances;
|
||||
|
||||
mutable CPT(GeomVertexArrayData) _cached_array;
|
||||
|
||||
public:
|
||||
static void register_with_read_factory();
|
||||
virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
|
||||
virtual int complete_pointers(TypedWritable **plist, BamReader *manager) override;
|
||||
|
||||
protected:
|
||||
static TypedWritable *make_from_bam(const FactoryParams ¶ms);
|
||||
void fillin(DatagramIterator &scan, BamReader *manager) override;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
CopyOnWriteObject::init_type();
|
||||
register_type(_type_handle, "InstanceList",
|
||||
CopyOnWriteObject::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const override {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() override {
|
||||
init_type();
|
||||
return get_class_type();
|
||||
}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
inline std::ostream &operator <<(std::ostream &out, const InstanceList &list) {
|
||||
list.output(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
#include "instanceList.I"
|
||||
|
||||
#endif
|
39
panda/src/pgraph/instancedNode.I
Normal file
39
panda/src/pgraph/instancedNode.I
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 instancedNode.I
|
||||
* @author rdb
|
||||
* @date 2019-03-10
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the number of instances.
|
||||
*/
|
||||
INLINE size_t InstancedNode::
|
||||
get_num_instances() const {
|
||||
Thread *current_thread = Thread::get_current_thread();
|
||||
CDReader cdata(_cycler, current_thread);
|
||||
nassertr_always(cdata->_instances != nullptr, 0);
|
||||
return cdata->_instances.get_read_pointer(current_thread)->size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of instances.
|
||||
*/
|
||||
INLINE CPT(InstanceList) InstancedNode::
|
||||
get_instances(Thread *current_thread) const {
|
||||
CDReader cdata(_cycler, current_thread);
|
||||
return cdata->_instances.get_read_pointer(current_thread);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
INLINE InstancedNode::CData::
|
||||
CData() : _instances(new InstanceList) {
|
||||
}
|
492
panda/src/pgraph/instancedNode.cxx
Normal file
492
panda/src/pgraph/instancedNode.cxx
Normal file
@ -0,0 +1,492 @@
|
||||
/**
|
||||
* 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 instancedNode.cxx
|
||||
* @author rdb
|
||||
* @date 2019-03-10
|
||||
*/
|
||||
|
||||
#include "instancedNode.h"
|
||||
#include "boundingBox.h"
|
||||
#include "boundingSphere.h"
|
||||
#include "cullTraverserData.h"
|
||||
#include "cullPlanes.h"
|
||||
|
||||
TypeHandle InstancedNode::_type_handle;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
InstancedNode::
|
||||
InstancedNode(const std::string &name) :
|
||||
PandaNode(name)
|
||||
{
|
||||
set_cull_callback();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
InstancedNode::
|
||||
InstancedNode(const InstancedNode ©) :
|
||||
PandaNode(copy),
|
||||
_cycler(copy._cycler)
|
||||
{
|
||||
set_cull_callback();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
InstancedNode::
|
||||
~InstancedNode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a newly-allocated PandaNode that is a shallow copy of this one. It
|
||||
* will be a different pointer, but its internal data may or may not be shared
|
||||
* with that of the original PandaNode. No children will be copied.
|
||||
*/
|
||||
PandaNode *InstancedNode::
|
||||
make_copy() const {
|
||||
return new InstancedNode(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of instances.
|
||||
*
|
||||
* Don't call this in a downstream thread unless you don't mind it blowing
|
||||
* away other changes you might have recently made in an upstream thread.
|
||||
*/
|
||||
PT(InstanceList) InstancedNode::
|
||||
modify_instances() {
|
||||
Thread *current_thread = Thread::get_current_thread();
|
||||
CDWriter cdata(_cycler, true, current_thread);
|
||||
PT(InstanceList) instances = cdata->_instances.get_write_pointer();
|
||||
mark_bounds_stale(current_thread->get_pipeline_stage(), current_thread);
|
||||
mark_bam_modified();
|
||||
return instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entirely replaces the list of instances with the given list.
|
||||
*
|
||||
* Don't call this in a downstream thread unless you don't mind it blowing
|
||||
* away other changes you might have recently made in an upstream thread.
|
||||
*/
|
||||
void InstancedNode::
|
||||
set_instances(PT(InstanceList) instances) {
|
||||
Thread *current_thread = Thread::get_current_thread();
|
||||
CDWriter cdata(_cycler, true);
|
||||
cdata->_instances = std::move(instances);
|
||||
mark_bounds_stale(current_thread->get_pipeline_stage(), current_thread);
|
||||
mark_bam_modified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it is generally safe to flatten out this particular kind of
|
||||
* PandaNode by duplicating instances (by calling dupe_for_flatten()), false
|
||||
* otherwise (for instance, a Camera cannot be safely flattened, because the
|
||||
* Camera pointer itself is meaningful).
|
||||
*/
|
||||
bool InstancedNode::
|
||||
safe_to_flatten() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it is generally safe to combine this particular kind of
|
||||
* PandaNode with other kinds of PandaNodes of compatible type, adding
|
||||
* children or whatever. For instance, an LODNode should not be combined with
|
||||
* any other PandaNode, because its set of children is meaningful.
|
||||
*/
|
||||
bool InstancedNode::
|
||||
safe_to_combine() const {
|
||||
// This can happen iff the instance list is identical; see combine_with().
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the contents of this node by the indicated matrix, if it means
|
||||
* anything to do so. For most kinds of nodes, this does nothing.
|
||||
*/
|
||||
void InstancedNode::
|
||||
xform(const LMatrix4 &mat) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses this node with the other node, if possible, and returns a pointer
|
||||
* to the combined node, or NULL if the two nodes cannot safely be combined.
|
||||
*
|
||||
* The return value may be this, other, or a new node altogether.
|
||||
*
|
||||
* This function is called from GraphReducer::flatten(), and need not deal
|
||||
* with children; its job is just to decide whether to collapse the two nodes
|
||||
* and what the collapsed node should look like.
|
||||
*/
|
||||
PandaNode *InstancedNode::
|
||||
combine_with(PandaNode *other) {
|
||||
if (is_exact_type(get_class_type()) && other->is_exact_type(get_class_type())) {
|
||||
InstancedNode *iother = DCAST(InstancedNode, other);
|
||||
|
||||
// Only combine them if the instance lists for both are identical.
|
||||
Thread *current_thread = Thread::get_current_thread();
|
||||
CDReader this_cdata(_cycler, current_thread);
|
||||
CDReader other_cdata(iother->_cycler, current_thread);
|
||||
CPT(InstanceList) this_instances = this_cdata->_instances.get_read_pointer(current_thread);
|
||||
CPT(InstanceList) other_instances = other_cdata->_instances.get_read_pointer(current_thread);
|
||||
if (this_instances == other_instances) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used to support NodePath::calc_tight_bounds(). It is not intended
|
||||
* to be called directly, and it has nothing to do with the normal Panda
|
||||
* bounding-volume computation.
|
||||
*
|
||||
* If the node contains any geometry, this updates min_point and max_point to
|
||||
* enclose its bounding box. found_any is to be set true if the node has any
|
||||
* geometry at all, or left alone if it has none. This method may be called
|
||||
* over several nodes, so it may enter with min_point, max_point, and
|
||||
* found_any already set.
|
||||
*/
|
||||
CPT(TransformState) InstancedNode::
|
||||
calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point, bool &found_any,
|
||||
const TransformState *transform, Thread *current_thread) const {
|
||||
|
||||
CPT(InstanceList) instances = get_instances(current_thread);
|
||||
CPT(TransformState) next_transform = transform->compose(get_transform(current_thread));
|
||||
|
||||
for (size_t ii = 0; ii < instances->size(); ++ii) {
|
||||
CPT(TransformState) instance_transform = next_transform->compose((*instances)[ii].get_transform());
|
||||
|
||||
Children cr = get_children(current_thread);
|
||||
size_t num_children = cr.get_num_children();
|
||||
for (size_t ci = 0; ci < num_children; ++ci) {
|
||||
cr.get_child(ci)->calc_tight_bounds(min_point, max_point,
|
||||
found_any, instance_transform,
|
||||
current_thread);
|
||||
}
|
||||
}
|
||||
|
||||
return next_transform;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will be called during the cull traversal to perform any
|
||||
* additional operations that should be performed at cull time. This may
|
||||
* include additional manipulation of render state or additional
|
||||
* visible/invisible decisions, or any other arbitrary operation.
|
||||
*
|
||||
* Note that this function will *not* be called unless set_cull_callback() is
|
||||
* called in the constructor of the derived class. It is necessary to call
|
||||
* set_cull_callback() to indicated that we require cull_callback() to be
|
||||
* called.
|
||||
*
|
||||
* By the time this function is called, the node has already passed the
|
||||
* bounding-volume test for the viewing frustum, and the node's transform and
|
||||
* state have already been applied to the indicated CullTraverserData object.
|
||||
*
|
||||
* The return value is true if this node should be visible, or false if it
|
||||
* should be culled.
|
||||
*/
|
||||
bool InstancedNode::
|
||||
cull_callback(CullTraverser *trav, CullTraverserData &data) {
|
||||
Thread *current_thread = trav->get_current_thread();
|
||||
|
||||
CPT(InstanceList) instances = get_instances(current_thread);
|
||||
|
||||
if (data._instances != nullptr) {
|
||||
// We are already under an instanced node. Create a new combined list.
|
||||
InstanceList *new_list = new InstanceList();
|
||||
new_list->reserve(data._instances->size() * instances->size());
|
||||
for (const InstanceList::Instance &parent_instance : *data._instances) {
|
||||
for (const InstanceList::Instance &this_instance : *instances) {
|
||||
new_list->append(parent_instance.get_transform()->compose(this_instance.get_transform()));
|
||||
}
|
||||
}
|
||||
instances = new_list;
|
||||
}
|
||||
|
||||
if (data._view_frustum != nullptr || !data._cull_planes->is_empty()) {
|
||||
// Culling is on, so we need to figure out which instances are visible.
|
||||
Children children = data.node_reader()->get_children();
|
||||
data.node_reader()->release();
|
||||
|
||||
// Keep track of which instances should be culled away.
|
||||
BitArray culled_instances;
|
||||
culled_instances.set_range(0, instances->size());
|
||||
|
||||
for (size_t ii = 0; ii < instances->size(); ++ii) {
|
||||
CullTraverserData instance_data(data);
|
||||
instance_data.apply_transform((*instances)[ii].get_transform());
|
||||
|
||||
for (size_t ci = 0; ci < children.size(); ++ci) {
|
||||
CullTraverserData child_data(instance_data, children.get_child(ci));
|
||||
if (child_data.is_in_view(trav->get_camera_mask())) {
|
||||
// Yep, the instance is in view.
|
||||
culled_instances.clear_bit(ii);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instances = instances->without(culled_instances);
|
||||
} else {
|
||||
data.node_reader()->release();
|
||||
}
|
||||
|
||||
if (instances->empty()) {
|
||||
// There are no instances, or they are all culled away.
|
||||
return false;
|
||||
}
|
||||
|
||||
data._instances = std::move(instances);
|
||||
|
||||
// Disable culling from this point on, for now. It's probably not worth it
|
||||
// to keep lists of transformed bounding volumes for each instance.
|
||||
data._view_frustum = nullptr;
|
||||
data._cull_planes = CullPlanes::make_empty();
|
||||
|
||||
return true;
|
||||
|
||||
/*
|
||||
for (const InstanceList::Instance &instance : *instances) {
|
||||
CullTraverserData instance_data(data);
|
||||
instance_data.apply_transform(instance.get_transform());
|
||||
trav->traverse_below(instance_data);
|
||||
}
|
||||
return false;
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void InstancedNode::
|
||||
output(std::ostream &out) const {
|
||||
PandaNode::output(out);
|
||||
out << " (" << get_num_instances() << " instances)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BoundingVolume that represents the external contents of the node.
|
||||
* This should encompass the internal bounds, but also the bounding volumes of
|
||||
* of all this node's children, which are passed in.
|
||||
*/
|
||||
void InstancedNode::
|
||||
compute_external_bounds(CPT(BoundingVolume) &external_bounds,
|
||||
BoundingVolume::BoundsType btype,
|
||||
const BoundingVolume **volumes, size_t num_volumes,
|
||||
int pipeline_stage, Thread *current_thread) const {
|
||||
|
||||
CPT(InstanceList) instances = get_instances(current_thread);
|
||||
|
||||
PT(GeometricBoundingVolume) gbv;
|
||||
if (btype == BoundingVolume::BT_sphere) {
|
||||
gbv = new BoundingSphere;
|
||||
} else {
|
||||
gbv = new BoundingBox;
|
||||
}
|
||||
|
||||
if (num_volumes == 0 || instances->empty()) {
|
||||
external_bounds = gbv;
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute a sphere at the origin, encompassing the children. This may not
|
||||
// be the most optimal shape, but it allows us to easily estimate a bounding
|
||||
// volume without having to take each instance transform into account.
|
||||
PN_stdfloat max_radius = 0;
|
||||
LVector3 max_abs_box(0);
|
||||
|
||||
for (size_t i = 0; i < num_volumes; ++i) {
|
||||
const BoundingVolume *child_volume = volumes[i];
|
||||
if (child_volume->is_empty()) {
|
||||
continue;
|
||||
}
|
||||
if (child_volume->is_infinite()) {
|
||||
gbv->set_infinite();
|
||||
break;
|
||||
}
|
||||
if (const BoundingSphere *child_sphere = child_volume->as_bounding_sphere()) {
|
||||
max_radius = child_sphere->get_center().length() + child_sphere->get_radius();
|
||||
}
|
||||
else if (const FiniteBoundingVolume *child_finite = child_volume->as_finite_bounding_volume()) {
|
||||
LPoint3 min1 = child_finite->get_min();
|
||||
LPoint3 max1 = child_finite->get_max();
|
||||
max_abs_box.set(
|
||||
std::max(max_abs_box[0], std::max(std::fabs(min1[0]), std::fabs(max1[0]))),
|
||||
std::max(max_abs_box[1], std::max(std::fabs(min1[1]), std::fabs(max1[1]))),
|
||||
std::max(max_abs_box[2], std::max(std::fabs(min1[2]), std::fabs(max1[2]))));
|
||||
}
|
||||
else {
|
||||
gbv->set_infinite();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
max_radius = std::max(max_radius, max_abs_box.length());
|
||||
if (max_radius == 0 || gbv->is_infinite()) {
|
||||
external_bounds = gbv;
|
||||
return;
|
||||
}
|
||||
|
||||
// Now that we have a sphere encompassing the children, we will make a box
|
||||
// surrounding all the instances, extended by the computed radius.
|
||||
LPoint3 min_point = (*instances)[0].get_pos();
|
||||
LPoint3 max_point(min_point);
|
||||
|
||||
for (const InstanceList::Instance &instance : *instances) {
|
||||
// To make the math easier and not have to take rotations into account, we
|
||||
// take the highest scale component and multiply it by the radius of the
|
||||
// bounding sphere on the origin we just calculated.
|
||||
LVecBase3 scale = instance.get_scale();
|
||||
PN_stdfloat max_scale = std::max(std::fabs(scale[0]), std::max(std::fabs(scale[1]), std::fabs(scale[2])));
|
||||
PN_stdfloat inst_radius = max_scale * max_radius;
|
||||
LVector3 extends_by(inst_radius);
|
||||
LPoint3 pos = instance.get_pos();
|
||||
min_point = min_point.fmin(pos - extends_by);
|
||||
max_point = max_point.fmax(pos + extends_by);
|
||||
}
|
||||
|
||||
if (min_point == max_point) {
|
||||
external_bounds = gbv;
|
||||
return;
|
||||
}
|
||||
|
||||
// If we really need to make a sphere, we use the center of the bounding box
|
||||
// as our sphere center, and iterate again to find the furthest instance.
|
||||
if (btype == BoundingVolume::BT_sphere) {
|
||||
LPoint3 center = (min_point + max_point) * 0.5;
|
||||
|
||||
PN_stdfloat max_distance = 0;
|
||||
for (const InstanceList::Instance &instance : *instances) {
|
||||
LVecBase3 scale = instance.get_scale();
|
||||
PN_stdfloat max_scale = std::max(std::fabs(scale[0]), std::max(std::fabs(scale[1]), std::fabs(scale[2])));
|
||||
PN_stdfloat inst_radius = max_scale * max_radius;
|
||||
PN_stdfloat distance = (instance.get_pos() - center).length() + inst_radius;
|
||||
max_distance = std::max(max_distance, distance);
|
||||
}
|
||||
|
||||
if (max_distance == 0) {
|
||||
external_bounds = gbv;
|
||||
return;
|
||||
}
|
||||
((BoundingSphere *)gbv.p())->set_center(center);
|
||||
((BoundingSphere *)gbv.p())->set_radius(max_distance);
|
||||
} else {
|
||||
((BoundingBox *)gbv.p())->set_min_max(min_point, max_point);
|
||||
}
|
||||
|
||||
// If we have a transform, apply it to the bounding volume we just
|
||||
// computed.
|
||||
CPT(TransformState) transform = get_transform(current_thread);
|
||||
if (!transform->is_identity()) {
|
||||
gbv->xform(transform->get_mat());
|
||||
}
|
||||
|
||||
external_bounds = gbv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the BamReader how to create objects of type GeomNode.
|
||||
*/
|
||||
void InstancedNode::
|
||||
register_with_read_factory() {
|
||||
BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the contents of this object to the datagram for shipping out to a
|
||||
* Bam file.
|
||||
*/
|
||||
void InstancedNode::
|
||||
write_datagram(BamWriter *manager, Datagram &dg) {
|
||||
PandaNode::write_datagram(manager, dg);
|
||||
manager->write_cdata(dg, _cycler);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called by the BamReader's factory when a new object of
|
||||
* type InstancedNode is encountered in the Bam file. It should create the
|
||||
* InstancedNode and extract its information from the file.
|
||||
*/
|
||||
TypedWritable *InstancedNode::
|
||||
make_from_bam(const FactoryParams ¶ms) {
|
||||
InstancedNode *node = new InstancedNode("");
|
||||
DatagramIterator scan;
|
||||
BamReader *manager;
|
||||
|
||||
parse_params(params, scan, manager);
|
||||
node->fillin(scan, manager);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* This internal function is called by make_from_bam to read in all of the
|
||||
* relevant data from the BamFile for the new InstancedNode.
|
||||
*/
|
||||
void InstancedNode::
|
||||
fillin(DatagramIterator &scan, BamReader *manager) {
|
||||
PandaNode::fillin(scan, manager);
|
||||
manager->read_cdata(scan, _cycler);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
InstancedNode::CData::
|
||||
CData(const InstancedNode::CData ©) :
|
||||
_instances(copy._instances)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
CycleData *InstancedNode::CData::
|
||||
make_copy() const {
|
||||
return new CData(*this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the contents of this object to the datagram for shipping out to a
|
||||
* Bam file.
|
||||
*/
|
||||
void InstancedNode::CData::
|
||||
write_datagram(BamWriter *manager, Datagram &dg) const {
|
||||
CPT(InstanceList) instances = _instances.get_read_pointer();
|
||||
manager->write_pointer(dg, instances.p());
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives an array of pointers, one for each time manager->read_pointer()
|
||||
* was called in fillin(). Returns the number of pointers processed.
|
||||
*/
|
||||
int InstancedNode::CData::
|
||||
complete_pointers(TypedWritable **p_list, BamReader *manager) {
|
||||
int pi = CycleData::complete_pointers(p_list, manager);
|
||||
|
||||
_instances = DCAST(InstanceList, p_list[pi++]);
|
||||
return pi;
|
||||
}
|
||||
|
||||
/**
|
||||
* This internal function is called by make_from_bam to read in all of the
|
||||
* relevant data from the BamFile for the new GeomNode.
|
||||
*/
|
||||
void InstancedNode::CData::
|
||||
fillin(DatagramIterator &scan, BamReader *manager) {
|
||||
manager->read_pointer(scan);
|
||||
}
|
136
panda/src/pgraph/instancedNode.h
Normal file
136
panda/src/pgraph/instancedNode.h
Normal file
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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 instancedNode.h
|
||||
* @author rdb
|
||||
* @date 2019-03-09
|
||||
*/
|
||||
|
||||
#ifndef INSTANCEDNODE_H
|
||||
#define INSTANCEDNODE_H
|
||||
|
||||
#include "pandabase.h"
|
||||
#include "pandaNode.h"
|
||||
#include "copyOnWritePointer.h"
|
||||
#include "instanceList.h"
|
||||
|
||||
/**
|
||||
* This is a special node that instances its contents using a list of
|
||||
* transforms that get applied on top of the node's own transform. This is a
|
||||
* bit more limited than the regular instance_to mechanism, but it is a better
|
||||
* choice for hardware instancing.
|
||||
*
|
||||
* For best performance, it is highly recommended to flatten the nodes under
|
||||
* this (by calling flatten_strong()), since culling will not be performed for
|
||||
* individual sub-nodes under each instance.
|
||||
*
|
||||
* @since 1.11.0
|
||||
*/
|
||||
class EXPCL_PANDA_PGRAPH InstancedNode : public PandaNode {
|
||||
PUBLISHED:
|
||||
explicit InstancedNode(const std::string &name);
|
||||
|
||||
protected:
|
||||
InstancedNode(const InstancedNode ©);
|
||||
|
||||
public:
|
||||
virtual ~InstancedNode();
|
||||
virtual PandaNode *make_copy() const override;
|
||||
|
||||
INLINE size_t get_num_instances() const;
|
||||
INLINE CPT(InstanceList) get_instances(Thread *current_thread = Thread::get_current_thread()) const;
|
||||
PT(InstanceList) modify_instances();
|
||||
void set_instances(PT(InstanceList) instances);
|
||||
|
||||
PUBLISHED:
|
||||
MAKE_PROPERTY(instances, modify_instances, set_instances);
|
||||
|
||||
public:
|
||||
virtual bool safe_to_flatten() const override;
|
||||
virtual bool safe_to_combine() const override;
|
||||
virtual void xform(const LMatrix4 &mat) override;
|
||||
virtual PandaNode *combine_with(PandaNode *other) override;
|
||||
|
||||
virtual CPT(TransformState)
|
||||
calc_tight_bounds(LPoint3 &min_point, LPoint3 &max_point,
|
||||
bool &found_any,
|
||||
const TransformState *transform,
|
||||
Thread *current_thread) const override;
|
||||
|
||||
virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data) override;
|
||||
|
||||
virtual void output(std::ostream &out) const override;
|
||||
|
||||
protected:
|
||||
virtual void compute_external_bounds(CPT(BoundingVolume) &external_bounds,
|
||||
BoundingVolume::BoundsType btype,
|
||||
const BoundingVolume **volumes,
|
||||
size_t num_volumes,
|
||||
int pipeline_stage,
|
||||
Thread *current_thread) const override;
|
||||
|
||||
private:
|
||||
// This is the data that must be cycled between pipeline stages.
|
||||
class EXPCL_PANDA_PGRAPH CData final : public CycleData {
|
||||
public:
|
||||
INLINE CData();
|
||||
CData(const CData ©);
|
||||
virtual CycleData *make_copy() const override;
|
||||
virtual void write_datagram(BamWriter *manager, Datagram &dg) const override;
|
||||
virtual int complete_pointers(TypedWritable **plist, BamReader *manager) override;
|
||||
virtual void fillin(DatagramIterator &scan, BamReader *manager) override;
|
||||
virtual TypeHandle get_parent_type() const override {
|
||||
return InstancedNode::get_class_type();
|
||||
}
|
||||
|
||||
private:
|
||||
COWPT(InstanceList) _instances;
|
||||
|
||||
friend class InstancedNode;
|
||||
};
|
||||
|
||||
PipelineCycler<CData> _cycler;
|
||||
typedef CycleDataReader<CData> CDReader;
|
||||
typedef CycleDataWriter<CData> CDWriter;
|
||||
typedef CycleDataStageReader<CData> CDStageReader;
|
||||
typedef CycleDataLockedStageReader<CData> CDLockedStageReader;
|
||||
typedef CycleDataStageWriter<CData> CDStageWriter;
|
||||
|
||||
public:
|
||||
static void register_with_read_factory();
|
||||
virtual void write_datagram(BamWriter *manager, Datagram &dg) override;
|
||||
|
||||
protected:
|
||||
static TypedWritable *make_from_bam(const FactoryParams ¶ms);
|
||||
void fillin(DatagramIterator &scan, BamReader *manager) override;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
PandaNode::init_type();
|
||||
register_type(_type_handle, "InstancedNode",
|
||||
PandaNode::get_class_type());
|
||||
CData::init_type();
|
||||
}
|
||||
virtual TypeHandle get_type() const override {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() override {
|
||||
init_type();
|
||||
return get_class_type();
|
||||
}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
#include "instancedNode.I"
|
||||
|
||||
#endif
|
@ -20,3 +20,5 @@
|
||||
#include "geomDrawCallbackData.cxx"
|
||||
#include "geomNode.cxx"
|
||||
#include "geomTransformer.cxx"
|
||||
#include "instanceList.cxx"
|
||||
#include "instancedNode.cxx"
|
||||
|
@ -2315,6 +2315,59 @@ compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
|
||||
internal_vertices = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a BoundingVolume that represents the external contents of the node.
|
||||
* This should encompass the internal bounds, but also the bounding volumes of
|
||||
* of all this node's children, which are passed in.
|
||||
*/
|
||||
void PandaNode::
|
||||
compute_external_bounds(CPT(BoundingVolume) &external_bounds,
|
||||
BoundingVolume::BoundsType btype,
|
||||
const BoundingVolume **volumes, size_t num_volumes,
|
||||
int pipeline_stage, Thread *current_thread) const {
|
||||
|
||||
CPT(TransformState) transform = get_transform(current_thread);
|
||||
PT(GeometricBoundingVolume) gbv;
|
||||
|
||||
if (btype == BoundingVolume::BT_box) {
|
||||
gbv = new BoundingBox;
|
||||
}
|
||||
else if (btype == BoundingVolume::BT_sphere || !transform->is_identity()) {
|
||||
gbv = new BoundingSphere;
|
||||
}
|
||||
else {
|
||||
// If all of the child volumes are a BoundingBox, and we have no
|
||||
// transform, then our volume is also a BoundingBox.
|
||||
bool all_box = true;
|
||||
|
||||
for (size_t i = 0; i < num_volumes; ++i) {
|
||||
if (volumes[i]->as_bounding_box() == nullptr) {
|
||||
all_box = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_box) {
|
||||
gbv = new BoundingBox;
|
||||
} else {
|
||||
gbv = new BoundingSphere;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_volumes > 0) {
|
||||
const BoundingVolume **child_begin = &volumes[0];
|
||||
const BoundingVolume **child_end = child_begin + num_volumes;
|
||||
((BoundingVolume *)gbv)->around(child_begin, child_end);
|
||||
|
||||
// If we have a transform, apply it to the bounding volume we just
|
||||
// computed.
|
||||
if (!transform->is_identity()) {
|
||||
gbv->xform(transform->get_mat());
|
||||
}
|
||||
}
|
||||
|
||||
external_bounds = gbv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a scene graph update that either adds or remove parents from
|
||||
* this node, this just provides a hook for derived PandaNode objects that
|
||||
@ -3263,7 +3316,6 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
|
||||
#endif
|
||||
int child_volumes_i = 0;
|
||||
|
||||
bool all_box = true;
|
||||
CPT(BoundingVolume) internal_bounds = nullptr;
|
||||
|
||||
if (update_bounds) {
|
||||
@ -3276,9 +3328,6 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
|
||||
#endif
|
||||
nassertr(child_volumes_i < num_children + 1, CDStageWriter(_cycler, pipeline_stage, cdata));
|
||||
child_volumes[child_volumes_i++] = internal_bounds;
|
||||
if (internal_bounds->as_bounding_box() == nullptr) {
|
||||
all_box = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3374,9 +3423,6 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
|
||||
#endif
|
||||
nassertr(child_volumes_i < num_children + 1, CDStageWriter(_cycler, pipeline_stage, cdata));
|
||||
child_volumes[child_volumes_i++] = child_cdataw->_external_bounds;
|
||||
if (child_cdataw->_external_bounds->as_bounding_box() == nullptr) {
|
||||
all_box = false;
|
||||
}
|
||||
}
|
||||
num_vertices += child_cdataw->_nested_vertices;
|
||||
}
|
||||
@ -3429,9 +3475,6 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
|
||||
#endif
|
||||
nassertr(child_volumes_i < num_children + 1, CDStageWriter(_cycler, pipeline_stage, cdata));
|
||||
child_volumes[child_volumes_i++] = child_cdata->_external_bounds;
|
||||
if (child_cdata->_external_bounds->as_bounding_box() == nullptr) {
|
||||
all_box = false;
|
||||
}
|
||||
}
|
||||
num_vertices += child_cdata->_nested_vertices;
|
||||
}
|
||||
@ -3485,38 +3528,17 @@ update_cached(bool update_bounds, int pipeline_stage, PandaNode::CDLockedStageRe
|
||||
if (update_bounds) {
|
||||
cdataw->_nested_vertices = num_vertices;
|
||||
|
||||
CPT(TransformState) transform = get_transform(current_thread);
|
||||
PT(GeometricBoundingVolume) gbv;
|
||||
|
||||
BoundingVolume::BoundsType btype = cdataw->_bounds_type;
|
||||
if (btype == BoundingVolume::BT_default) {
|
||||
btype = bounds_type;
|
||||
}
|
||||
|
||||
if (btype == BoundingVolume::BT_box ||
|
||||
(btype != BoundingVolume::BT_sphere && all_box && transform->is_identity())) {
|
||||
// If all of the child volumes are a BoundingBox, and we have no
|
||||
// transform, then our volume is also a BoundingBox.
|
||||
compute_external_bounds(cdataw->_external_bounds, btype,
|
||||
child_volumes, child_volumes_i,
|
||||
pipeline_stage, current_thread);
|
||||
|
||||
gbv = new BoundingBox;
|
||||
} else {
|
||||
// Otherwise, it's a sphere.
|
||||
gbv = new BoundingSphere;
|
||||
}
|
||||
nassertr(cdataw->_external_bounds != nullptr, cdataw);
|
||||
|
||||
if (child_volumes_i > 0) {
|
||||
const BoundingVolume **child_begin = &child_volumes[0];
|
||||
const BoundingVolume **child_end = child_begin + child_volumes_i;
|
||||
((BoundingVolume *)gbv)->around(child_begin, child_end);
|
||||
|
||||
// If we have a transform, apply it to the bounding volume we just
|
||||
// computed.
|
||||
if (!transform->is_identity()) {
|
||||
gbv->xform(transform->get_mat());
|
||||
}
|
||||
}
|
||||
|
||||
cdataw->_external_bounds = gbv;
|
||||
cdataw->_last_bounds_update = next_update;
|
||||
}
|
||||
|
||||
|
@ -351,6 +351,12 @@ protected:
|
||||
int &internal_vertices,
|
||||
int pipeline_stage,
|
||||
Thread *current_thread) const;
|
||||
virtual void compute_external_bounds(CPT(BoundingVolume) &external_bounds,
|
||||
BoundingVolume::BoundsType btype,
|
||||
const BoundingVolume **volumes,
|
||||
size_t num_volumes,
|
||||
int pipeline_stage,
|
||||
Thread *current_thread) const;
|
||||
virtual void parents_changed();
|
||||
virtual void children_changed();
|
||||
virtual void transform_changed();
|
||||
|
@ -82,7 +82,7 @@ get_shader_priority() const {
|
||||
|
||||
/**
|
||||
* Returns the number of geometry instances. A value of 0 means not to use
|
||||
* instancing at all.
|
||||
* instancing at all. This value is ignored if F_hardware_instancing is set.
|
||||
*/
|
||||
INLINE int ShaderAttrib::
|
||||
get_instance_count() const {
|
||||
|
@ -249,6 +249,8 @@ set_shader_inputs(const pvector<ShaderInput> &inputs) const {
|
||||
* Sets the geometry instance count. Do not confuse this with instanceTo,
|
||||
* which is used for animation instancing, and has nothing to do with this. A
|
||||
* value of 0 means not to use instancing at all.
|
||||
*
|
||||
* This value should not be set if F_hardware_instancing is also set.
|
||||
*/
|
||||
CPT(RenderAttrib) ShaderAttrib::
|
||||
set_instance_count(int instance_count) const {
|
||||
|
@ -51,6 +51,7 @@ PUBLISHED:
|
||||
F_subsume_alpha_test = 1 << 1, // Shader promises to subsume the alpha test using TEXKILL
|
||||
F_hardware_skinning = 1 << 2, // Shader needs pre-animated vertices
|
||||
F_shader_point_size = 1 << 3, // Shader provides point size, not RenderModeAttrib
|
||||
F_hardware_instancing = 1 << 4, // Shader needs instance list
|
||||
};
|
||||
|
||||
INLINE bool has_shader() const;
|
||||
|
@ -472,14 +472,14 @@ end_frame(Thread *current_thread) {
|
||||
bool TinyGraphicsStateGuardian::
|
||||
begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force) {
|
||||
size_t num_instances, bool force) {
|
||||
#ifndef NDEBUG
|
||||
if (tinydisplay_cat.is_spam()) {
|
||||
tinydisplay_cat.spam() << "begin_draw_primitives: " << *(data_reader->get_object()) << "\n";
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
if (!GraphicsStateGuardian::begin_draw_primitives(geom_reader, data_reader, force)) {
|
||||
if (!GraphicsStateGuardian::begin_draw_primitives(geom_reader, data_reader, num_instances, force)) {
|
||||
return false;
|
||||
}
|
||||
nassertr(_data_reader != nullptr, false);
|
||||
|
@ -64,7 +64,7 @@ public:
|
||||
|
||||
virtual bool begin_draw_primitives(const GeomPipelineReader *geom_reader,
|
||||
const GeomVertexDataPipelineReader *data_reader,
|
||||
bool force);
|
||||
size_t num_instances, bool force);
|
||||
virtual bool draw_triangles(const GeomPrimitivePipelineReader *reader,
|
||||
bool force);
|
||||
virtual bool draw_tristrips(const GeomPrimitivePipelineReader *reader,
|
||||
|
Loading…
x
Reference in New Issue
Block a user