shadows: fix shadow buffer creation deadlock in multithreaded pipeline

Fixes #162
This commit is contained in:
rdb 2017-12-30 17:44:52 +01:00
parent 39abc66025
commit 7ee9467f8d
14 changed files with 335 additions and 230 deletions

View File

@ -84,22 +84,30 @@ DisplayRegion::
*/ */
void DisplayRegion:: void DisplayRegion::
cleanup() { cleanup() {
set_camera(NodePath()); CDStageWriter cdata(_cycler, 0);
if (cdata->_camera_node != nullptr) {
// We need to tell the old camera we're not using it anymore.
cdata->_camera_node->remove_display_region(this);
}
cdata->_camera_node = nullptr;
cdata->_camera = NodePath();
CDCullWriter cdata(_cycler_cull, true); CDCullWriter cdata_cull(_cycler_cull, true);
cdata->_cull_result = NULL; cdata_cull->_cull_result = nullptr;
} }
/** /**
* Sets the lens index, allows for multiple lenses to be attached to a camera. * Sets the lens index, allows for multiple lenses to be attached to a camera.
* This is useful for a variety of setups, such as fish eye rendering. The * This is useful for a variety of setups, such as fish eye rendering. The
* default is 0. * default is 0.
*
* 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 DisplayRegion:: void DisplayRegion::
set_lens_index(int index) { set_lens_index(int index) {
int pipeline_stage = Thread::get_current_pipeline_stage(); Thread *current_thread = Thread::get_current_thread();
nassertv(pipeline_stage == 0); CDWriter cdata(_cycler, true, current_thread);
CDWriter cdata(_cycler);
cdata->_lens_index = index; cdata->_lens_index = index;
} }
@ -107,12 +115,14 @@ set_lens_index(int index) {
* Changes the portion of the framebuffer this DisplayRegion corresponds to. * Changes the portion of the framebuffer this DisplayRegion corresponds to.
* The parameters range from 0 to 1, where 0,0 is the lower left corner and * The parameters range from 0 to 1, where 0,0 is the lower left corner and
* 1,1 is the upper right; (0, 1, 0, 1) represents the whole screen. * 1,1 is the upper right; (0, 1, 0, 1) represents the whole screen.
*
* 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 DisplayRegion:: void DisplayRegion::
set_dimensions(int i, const LVecBase4 &dimensions) { set_dimensions(int i, const LVecBase4 &dimensions) {
int pipeline_stage = Thread::get_current_pipeline_stage(); Thread *current_thread = Thread::get_current_thread();
nassertv(pipeline_stage == 0); CDWriter cdata(_cycler, true, current_thread);
CDWriter cdata(_cycler);
cdata->_regions[i]._dimensions = dimensions; cdata->_regions[i]._dimensions = dimensions;
@ -145,15 +155,13 @@ is_stereo() const {
* *
* The camera is actually set via a NodePath, which clarifies which instance * The camera is actually set via a NodePath, which clarifies which instance
* of the camera (if there happen to be multiple instances) we should use. * of the camera (if there happen to be multiple instances) we should use.
*
* 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 DisplayRegion:: void DisplayRegion::
set_camera(const NodePath &camera) { set_camera(const NodePath &camera) {
int pipeline_stage = Thread::get_current_pipeline_stage(); CDWriter cdata(_cycler, true);
// We allow set_camera(NodePath()) to happen in cleanup(), which can be
// called from any pipeline stage.
nassertv(pipeline_stage == 0 || camera.is_empty());
CDStageWriter cdata(_cycler, 0);
Camera *camera_node = (Camera *)NULL; Camera *camera_node = (Camera *)NULL;
if (!camera.is_empty()) { if (!camera.is_empty()) {
@ -181,16 +189,17 @@ set_camera(const NodePath &camera) {
/** /**
* Sets the active flag associated with the DisplayRegion. If the * Sets the active flag associated with the DisplayRegion. If the
* DisplayRegion is marked inactive, nothing is rendered. * DisplayRegion is marked inactive, nothing is rendered.
*
* 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 DisplayRegion:: void DisplayRegion::
set_active(bool active) { set_active(bool active) {
int pipeline_stage = Thread::get_current_pipeline_stage(); Thread *current_thread = Thread::get_current_thread();
nassertv(pipeline_stage == 0); CDWriter cdata(_cycler, true, current_thread);
CDLockedReader cdata(_cycler);
if (active != cdata->_active) { if (active != cdata->_active) {
CDWriter cdataw(_cycler, cdata); cdata->_active = active;
cdataw->_active = active;
win_display_regions_changed(); win_display_regions_changed();
} }
} }
@ -199,15 +208,17 @@ set_active(bool active) {
* Sets the sort value associated with the DisplayRegion. Within a window, * Sets the sort value associated with the DisplayRegion. Within a window,
* DisplayRegions will be rendered in order from the lowest sort value to the * DisplayRegions will be rendered in order from the lowest sort value to the
* highest. * highest.
*
* 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 DisplayRegion:: void DisplayRegion::
set_sort(int sort) { set_sort(int sort) {
nassertv(Thread::get_current_pipeline_stage() == 0); Thread *current_thread = Thread::get_current_thread();
CDLockedReader cdata(_cycler); CDWriter cdata(_cycler, true, current_thread);
if (sort != cdata->_sort) { if (sort != cdata->_sort) {
CDWriter cdataw(_cycler, cdata); cdata->_sort = sort;
cdataw->_sort = sort;
win_display_regions_changed(); win_display_regions_changed();
} }
} }
@ -332,12 +343,14 @@ get_cull_traverser() {
* *
* This is particularly useful when rendering cube maps and/or stereo * This is particularly useful when rendering cube maps and/or stereo
* textures. * textures.
*
* 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 DisplayRegion:: void DisplayRegion::
set_target_tex_page(int page) { set_target_tex_page(int page) {
int pipeline_stage = Thread::get_current_pipeline_stage(); Thread *current_thread = Thread::get_current_thread();
nassertv(pipeline_stage == 0); CDWriter cdata(_cycler, true, current_thread);
CDWriter cdata(_cycler);
cdata->_target_tex_page = page; cdata->_target_tex_page = page;
} }
@ -555,9 +568,6 @@ compute_pixels() {
*/ */
void DisplayRegion:: void DisplayRegion::
compute_pixels_all_stages() { compute_pixels_all_stages() {
int pipeline_stage = Thread::get_current_pipeline_stage();
nassertv(pipeline_stage == 0);
if (_window != (GraphicsOutput *)NULL) { if (_window != (GraphicsOutput *)NULL) {
OPEN_ITERATE_ALL_STAGES(_cycler) { OPEN_ITERATE_ALL_STAGES(_cycler) {
CDStageWriter cdata(_cycler, pipeline_stage); CDStageWriter cdata(_cycler, pipeline_stage);

View File

@ -153,7 +153,6 @@ GraphicsEngine(Pipeline *pipeline) :
_windows_sorted = true; _windows_sorted = true;
_window_sort_index = 0; _window_sort_index = 0;
_needs_open_windows = false;
set_threading_model(GraphicsThreadingModel(threading_model)); set_threading_model(GraphicsThreadingModel(threading_model));
if (!_threading_model.is_default()) { if (!_threading_model.is_default()) {
@ -326,13 +325,10 @@ make_output(GraphicsPipe *pipe,
// Sanity check everything. // Sanity check everything.
GraphicsThreadingModel threading_model = get_threading_model();
nassertr(pipe != (GraphicsPipe *)NULL, NULL); nassertr(pipe != (GraphicsPipe *)NULL, NULL);
if (gsg != (GraphicsStateGuardian *)NULL) { if (gsg != (GraphicsStateGuardian *)NULL) {
nassertr(pipe == gsg->get_pipe(), NULL); nassertr(pipe == gsg->get_pipe(), NULL);
nassertr(this == gsg->get_engine(), NULL); nassertr(this == gsg->get_engine(), NULL);
nassertr(threading_model.get_draw_name() ==
gsg->get_threading_model().get_draw_name(), NULL);
} }
// Are we really asking for a callback window? // Are we really asking for a callback window?
@ -346,8 +342,8 @@ make_output(GraphicsPipe *pipe,
if (this_gsg != (GraphicsStateGuardian *)NULL) { if (this_gsg != (GraphicsStateGuardian *)NULL) {
CallbackGraphicsWindow *window = new CallbackGraphicsWindow(this, pipe, name, fb_prop, win_prop, flags, this_gsg); CallbackGraphicsWindow *window = new CallbackGraphicsWindow(this, pipe, name, fb_prop, win_prop, flags, this_gsg);
window->_sort = sort; window->_sort = sort;
do_add_window(window, threading_model); do_add_window(window);
do_add_gsg(window->get_gsg(), pipe, threading_model); do_add_gsg(window->get_gsg(), pipe);
display_cat.info() << "Created output of type CallbackGraphicsWindow\n"; display_cat.info() << "Created output of type CallbackGraphicsWindow\n";
return window; return window;
} }
@ -386,8 +382,8 @@ make_output(GraphicsPipe *pipe,
(host->get_fb_properties().subsumes(fb_prop))) { (host->get_fb_properties().subsumes(fb_prop))) {
ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags); ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags);
buffer->_sort = sort; buffer->_sort = sort;
do_add_window(buffer, threading_model); do_add_window(buffer);
do_add_gsg(host->get_gsg(), pipe, threading_model); do_add_gsg(host->get_gsg(), pipe);
display_cat.info() << "Created output of type ParasiteBuffer\n"; display_cat.info() << "Created output of type ParasiteBuffer\n";
return buffer; return buffer;
} }
@ -398,8 +394,8 @@ make_output(GraphicsPipe *pipe,
if (force_parasite_buffer && can_use_parasite) { if (force_parasite_buffer && can_use_parasite) {
ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags); ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags);
buffer->_sort = sort; buffer->_sort = sort;
do_add_window(buffer, threading_model); do_add_window(buffer);
do_add_gsg(host->get_gsg(), pipe, threading_model); do_add_gsg(host->get_gsg(), pipe);
display_cat.info() << "Created output of type ParasiteBuffer\n"; display_cat.info() << "Created output of type ParasiteBuffer\n";
return buffer; return buffer;
} }
@ -412,17 +408,15 @@ make_output(GraphicsPipe *pipe,
pipe->make_output(name, fb_prop, win_prop, flags, this, gsg, host, retry, precertify); pipe->make_output(name, fb_prop, win_prop, flags, this, gsg, host, retry, precertify);
if (window != (GraphicsOutput *)NULL) { if (window != (GraphicsOutput *)NULL) {
window->_sort = sort; window->_sort = sort;
if ((precertify) && (gsg != 0) && (window->get_gsg()==gsg)) { if (precertify && gsg != nullptr && window->get_gsg() == gsg) {
do_add_window(window, threading_model); do_add_window(window);
do_add_gsg(window->get_gsg(), pipe, threading_model);
display_cat.info() display_cat.info()
<< "Created output of type " << window->get_type() << "\n"; << "Created output of type " << window->get_type() << "\n";
return window; return window;
} }
do_add_window(window, threading_model); do_add_window(window);
open_windows(); open_windows();
if (window->is_valid()) { if (window->is_valid()) {
do_add_gsg(window->get_gsg(), pipe, threading_model);
display_cat.info() display_cat.info()
<< "Created output of type " << window->get_type() << "\n"; << "Created output of type " << window->get_type() << "\n";
@ -462,8 +456,8 @@ make_output(GraphicsPipe *pipe,
if (can_use_parasite) { if (can_use_parasite) {
ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags); ParasiteBuffer *buffer = new ParasiteBuffer(host, name, x_size, y_size, flags);
buffer->_sort = sort; buffer->_sort = sort;
do_add_window(buffer, threading_model); do_add_window(buffer);
do_add_gsg(host->get_gsg(), pipe, threading_model); do_add_gsg(host->get_gsg(), pipe);
display_cat.info() << "Created output of type ParasiteBuffer\n"; display_cat.info() << "Created output of type ParasiteBuffer\n";
return buffer; return buffer;
} }
@ -479,30 +473,24 @@ make_output(GraphicsPipe *pipe,
* shouldn't be called by user code as make_output normally does this under * shouldn't be called by user code as make_output normally does this under
* the hood; it may be useful in esoteric cases in which a custom window * the hood; it may be useful in esoteric cases in which a custom window
* object is used. * object is used.
*
* This can be called during the rendering loop, unlike make_output(); the
* window will be opened before the next frame begins rendering. Because it
* doesn't call open_windows(), however, it's not guaranteed that the window
* will succeed opening even if it returns true.
*/ */
bool GraphicsEngine:: bool GraphicsEngine::
add_window(GraphicsOutput *window, int sort) { add_window(GraphicsOutput *window, int sort) {
nassertr(window != NULL, false); nassertr(window != nullptr, false);
GraphicsThreadingModel threading_model = get_threading_model();
nassertr(this == window->get_engine(), false); nassertr(this == window->get_engine(), false);
window->_sort = sort; window->_sort = sort;
do_add_window(window, threading_model); do_add_window(window);
open_windows(); display_cat.info()
if (window->is_valid()) { << "Added output of type " << window->get_type() << "\n";
do_add_gsg(window->get_gsg(), window->get_pipe(), threading_model);
display_cat.info() return true;
<< "Added output of type " << window->get_type() << "\n";
return true;
} else {
remove_window(window);
return false;
}
} }
/** /**
@ -537,6 +525,13 @@ remove_window(GraphicsOutput *window) {
} }
count = _windows.erase(ptwin); count = _windows.erase(ptwin);
} }
// Also check whether it is in _new_windows.
{
MutexHolder new_windows_holder(_new_windows_lock, current_thread);
_new_windows.erase(std::remove(_new_windows.begin(), _new_windows.end(), ptwin));
}
if (count == 0) { if (count == 0) {
// Never heard of this window. Do nothing. // Never heard of this window. Do nothing.
return false; return false;
@ -601,6 +596,11 @@ remove_all_windows() {
} }
} }
{
MutexHolder new_windows_holder(_new_windows_lock, current_thread);
_new_windows.clear();
}
_app.do_close(this, current_thread); _app.do_close(this, current_thread);
_app.do_pending(this, current_thread); _app.do_pending(this, current_thread);
terminate_threads(current_thread); terminate_threads(current_thread);
@ -694,14 +694,12 @@ render_frame() {
} }
#endif #endif
if (_needs_open_windows) { // Make sure our buffers and windows are fully realized before we render a
// Make sure our buffers and windows are fully realized before we render a // frame. We do this particularly to realize our offscreen buffers, so
// frame. We do this particularly to realize our offscreen buffers, so // that we don't render a frame before the offscreen buffers are ready
// that we don't render a frame before the offscreen buffers are ready // (which might result in a frame going by without some textures having
// (which might result in a frame going by without some textures having // been rendered).
// been rendered). open_windows();
open_windows();
}
ClockObject *global_clock = ClockObject::get_global_clock(); ClockObject *global_clock = ClockObject::get_global_clock();
@ -945,10 +943,58 @@ open_windows() {
ReMutexHolder holder(_lock, current_thread); ReMutexHolder holder(_lock, current_thread);
if (!_windows_sorted) { pvector<PT(GraphicsOutput)> new_windows;
do_resort_windows(); {
MutexHolder new_windows_holder(_new_windows_lock, current_thread);
if (_new_windows.empty()) {
return;
}
for (auto it = _new_windows.begin(); it != _new_windows.end(); ++it) {
GraphicsOutput *window = *it;
WindowRenderer *cull =
get_window_renderer(_threading_model.get_cull_name(),
_threading_model.get_cull_stage());
WindowRenderer *draw =
get_window_renderer(_threading_model.get_draw_name(),
_threading_model.get_draw_stage());
if (_threading_model.get_cull_sorting()) {
cull->add_window(cull->_cull, window);
draw->add_window(draw->_draw, window);
} else {
cull->add_window(cull->_cdraw, window);
}
// Ask the pipe which thread it prefers to run its windowing commands in
// (the "window thread"). This is the thread that handles the commands
// to open, resize, etc. the window. X requires this to be done in the
// app thread (along with all the other windows, since X is strictly
// single-threaded), but Windows requires this to be done in draw
// (because once an OpenGL context has been bound in a given thread, it
// cannot subsequently be bound in any other thread, and we have to bind
// a context in open_window()).
switch (window->get_pipe()->get_preferred_window_thread()) {
case GraphicsPipe::PWT_app:
_app.add_window(_app._window, window);
break;
case GraphicsPipe::PWT_draw:
draw->add_window(draw->_window, window);
break;
}
_windows.push_back(window);
}
// Steal the list, since remove_window() may remove from _new_windows.
new_windows.swap(_new_windows);
} }
do_resort_windows();
// We do it twice, to allow both cull and draw to process the window. // We do it twice, to allow both cull and draw to process the window.
for (int i = 0; i < 2; ++i) { for (int i = 0; i < 2; ++i) {
_app.do_windows(this, current_thread); _app.do_windows(this, current_thread);
@ -970,7 +1016,15 @@ open_windows() {
} }
} }
_needs_open_windows = false; // Now go through the list again to check whether they opened successfully.
for (auto it = new_windows.begin(); it != new_windows.end(); ++it) {
GraphicsOutput *window = *it;
if (window->is_valid()) {
do_add_gsg(window->get_gsg(), window->get_pipe());
} else {
remove_window(window);
}
}
} }
/** /**
@ -1927,10 +1981,10 @@ do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg, DisplayRegion *dr, Thre
* list of windows, and to request that the window be opened. * list of windows, and to request that the window be opened.
*/ */
void GraphicsEngine:: void GraphicsEngine::
do_add_window(GraphicsOutput *window, do_add_window(GraphicsOutput *window) {
const GraphicsThreadingModel &threading_model) { nassertv(window != nullptr);
nassertv(window != NULL);
ReMutexHolder holder(_lock); MutexHolder holder(_new_windows_lock);
nassertv(window->get_engine() == this); nassertv(window->get_engine() == this);
// We have a special counter that is unique per window that allows us to // We have a special counter that is unique per window that allows us to
@ -1938,50 +1992,13 @@ do_add_window(GraphicsOutput *window,
window->_internal_sort_index = _window_sort_index; window->_internal_sort_index = _window_sort_index;
++_window_sort_index; ++_window_sort_index;
_windows_sorted = false;
_windows.push_back(window);
WindowRenderer *cull =
get_window_renderer(threading_model.get_cull_name(),
threading_model.get_cull_stage());
WindowRenderer *draw =
get_window_renderer(threading_model.get_draw_name(),
threading_model.get_draw_stage());
if (threading_model.get_cull_sorting()) {
cull->add_window(cull->_cull, window);
draw->add_window(draw->_draw, window);
} else {
cull->add_window(cull->_cdraw, window);
}
/*
* Ask the pipe which thread it prefers to run its windowing commands in (the
* "window thread"). This is the thread that handles the commands to open,
* resize, etc. the window. X requires this to be done in the app thread
* (along with all the other windows, since X is strictly single-threaded),
* but Windows requires this to be done in draw (because once an OpenGL
* context has been bound in a given thread, it cannot subsequently be bound
* in any other thread, and we have to bind a context in open_window()).
*/
switch (window->get_pipe()->get_preferred_window_thread()) {
case GraphicsPipe::PWT_app:
_app.add_window(_app._window, window);
break;
case GraphicsPipe::PWT_draw:
draw->add_window(draw->_window, window);
break;
}
if (display_cat.is_debug()) { if (display_cat.is_debug()) {
display_cat.debug() display_cat.debug()
<< "Created " << window->get_type() << " " << (void *)window << "\n"; << "Created " << window->get_type() << " " << (void *)window << "\n";
} }
window->request_open(); window->request_open();
_needs_open_windows = true; _new_windows.push_back(window);
} }
/** /**
@ -1990,13 +2007,12 @@ do_add_window(GraphicsOutput *window,
* variables based on the gsg's capabilities. * variables based on the gsg's capabilities.
*/ */
void GraphicsEngine:: void GraphicsEngine::
do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe, do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe) {
const GraphicsThreadingModel &threading_model) {
nassertv(gsg != NULL); nassertv(gsg != NULL);
ReMutexHolder holder(_lock); ReMutexHolder holder(_lock);
nassertv(gsg->get_pipe() == pipe && gsg->get_engine() == this); nassertv(gsg->get_pipe() == pipe && gsg->get_engine() == this);
gsg->_threading_model = threading_model; gsg->_threading_model = _threading_model;
if (!_default_loader.is_null()) { if (!_default_loader.is_null()) {
gsg->set_loader(_default_loader); gsg->set_loader(_default_loader);
} }
@ -2004,8 +2020,8 @@ do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe,
auto_adjust_capabilities(gsg); auto_adjust_capabilities(gsg);
WindowRenderer *draw = WindowRenderer *draw =
get_window_renderer(threading_model.get_draw_name(), get_window_renderer(_threading_model.get_draw_name(),
threading_model.get_draw_stage()); _threading_model.get_draw_stage());
draw->add_gsg(gsg); draw->add_gsg(gsg);
} }

View File

@ -166,10 +166,8 @@ private:
void do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg, void do_draw(GraphicsOutput *win, GraphicsStateGuardian *gsg,
DisplayRegion *dr, Thread *current_thread); DisplayRegion *dr, Thread *current_thread);
void do_add_window(GraphicsOutput *window, void do_add_window(GraphicsOutput *window);
const GraphicsThreadingModel &threading_model); void do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe);
void do_add_gsg(GraphicsStateGuardian *gsg, GraphicsPipe *pipe,
const GraphicsThreadingModel &threading_model);
void do_remove_window(GraphicsOutput *window, Thread *current_thread); void do_remove_window(GraphicsOutput *window, Thread *current_thread);
void do_resort_windows(); void do_resort_windows();
void terminate_threads(Thread *current_thread); void terminate_threads(Thread *current_thread);
@ -308,8 +306,11 @@ private:
Pipeline *_pipeline; Pipeline *_pipeline;
Windows _windows; Windows _windows;
bool _windows_sorted; bool _windows_sorted;
// This lock protects the next two fields.
Mutex _new_windows_lock;
unsigned int _window_sort_index; unsigned int _window_sort_index;
bool _needs_open_windows; pvector<PT(GraphicsOutput)> _new_windows;
WindowRenderer _app; WindowRenderer _app;
typedef pmap<string, PT(RenderThread) > Threads; typedef pmap<string, PT(RenderThread) > Threads;

View File

@ -3231,9 +3231,10 @@ async_reload_texture(TextureContext *tc) {
PT(Texture) GraphicsStateGuardian:: PT(Texture) GraphicsStateGuardian::
get_shadow_map(const NodePath &light_np, GraphicsOutputBase *host) { get_shadow_map(const NodePath &light_np, GraphicsOutputBase *host) {
PandaNode *node = light_np.node(); PandaNode *node = light_np.node();
bool is_point = node->is_of_type(PointLight::get_class_type());
nassertr(node->is_of_type(DirectionalLight::get_class_type()) || nassertr(node->is_of_type(DirectionalLight::get_class_type()) ||
node->is_of_type(PointLight::get_class_type()) || node->is_of_type(Spotlight::get_class_type()) ||
node->is_of_type(Spotlight::get_class_type()), NULL); is_point, nullptr);
LightLensNode *light = (LightLensNode *)node; LightLensNode *light = (LightLensNode *)node;
if (light == nullptr || !light->_shadow_caster) { if (light == nullptr || !light->_shadow_caster) {
@ -3246,20 +3247,49 @@ get_shadow_map(const NodePath &light_np, GraphicsOutputBase *host) {
} }
} }
// The light's shadow map should have been created by set_shadow_caster().
nassertr(light->_shadow_map != nullptr, nullptr);
// See if we already have a buffer. If not, create one. // See if we already have a buffer. If not, create one.
if (light->_sbuffers.count(this) == 0) { if (light->_sbuffers.count(this) != 0) {
if (host == (GraphicsOutputBase *)NULL) {
host = _current_display_region->get_window();
}
nassertr(host != NULL, NULL);
// Nope, the light doesn't have a buffer for our GSG. Make one.
return make_shadow_buffer(light_np, host);
} else {
// There's already a buffer - use that. // There's already a buffer - use that.
return light->_sbuffers[this]->get_texture(); return light->_shadow_map;
} }
if (display_cat.is_debug()) {
display_cat.debug()
<< "Constructing shadow buffer for light '" << light->get_name()
<< "', size=" << light->_sb_size[0] << "x" << light->_sb_size[1]
<< ", sort=" << light->_sb_sort << "\n";
}
if (host == nullptr) {
nassertr(_current_display_region != nullptr, nullptr);
host = _current_display_region->get_window();
}
nassertr(host != nullptr, nullptr);
// Nope, the light doesn't have a buffer for our GSG. Make one.
GraphicsOutput *sbuffer = make_shadow_buffer(light, light->_shadow_map,
DCAST(GraphicsOutput, host));
// Assign display region(s) to the buffer and camera
if (is_point) {
for (int i = 0; i < 6; ++i) {
PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
dr->set_lens_index(i);
dr->set_target_tex_page(i);
dr->set_camera(light_np);
dr->set_clear_depth_active(true);
}
} else {
PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
dr->set_camera(light_np);
dr->set_clear_depth_active(true);
}
light->_sbuffers[this] = sbuffer;
return light->_shadow_map;
} }
/** /**
@ -3299,101 +3329,33 @@ get_dummy_shadow_map(Texture::TextureType texture_type) const {
} }
/** /**
* Creates a depth buffer for shadow mapping. This is a convenience function * Creates a depth buffer for shadow mapping. A derived GSG can override this
* for the ShaderGenerator; putting this directly in the ShaderGenerator would * if it knows that a particular buffer type works best for shadow rendering.
* cause circular dependency issues. Returns the depth texture.
*/ */
PT(Texture) GraphicsStateGuardian:: GraphicsOutput *GraphicsStateGuardian::
make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host) { make_shadow_buffer(LightLensNode *light, Texture *tex, GraphicsOutput *host) {
// Make sure everything is valid.
PandaNode *node = light_np.node();
nassertr(node->is_of_type(DirectionalLight::get_class_type()) ||
node->is_of_type(PointLight::get_class_type()) ||
node->is_of_type(Spotlight::get_class_type()), NULL);
LightLensNode *light = (LightLensNode *)node;
if (light == NULL || !light->_shadow_caster) {
return NULL;
}
bool is_point = light->is_of_type(PointLight::get_class_type()); bool is_point = light->is_of_type(PointLight::get_class_type());
nassertr(light->_sbuffers.count(this) == 0, NULL);
if (display_cat.is_debug()) {
display_cat.debug()
<< "Constructing shadow buffer for light '" << light->get_name()
<< "', size=" << light->_sb_size[0] << "x" << light->_sb_size[1]
<< ", sort=" << light->_sb_sort << "\n";
}
// Determine the properties for creating the depth buffer. // Determine the properties for creating the depth buffer.
FrameBufferProperties fbp; FrameBufferProperties fbp;
fbp.set_depth_bits(shadow_depth_bits); fbp.set_depth_bits(shadow_depth_bits);
WindowProperties props = WindowProperties::size(light->_sb_size[0], light->_sb_size[1]); WindowProperties props = WindowProperties::size(light->_sb_size);
int flags = GraphicsPipe::BF_refuse_window; int flags = GraphicsPipe::BF_refuse_window;
if (is_point) { if (is_point) {
flags |= GraphicsPipe::BF_size_square; flags |= GraphicsPipe::BF_size_square;
} }
// Create the buffer // Create the buffer. This is a bit tricky because make_output() can only
PT(GraphicsOutput) sbuffer = get_engine()->make_output(get_pipe(), light->get_name(), // be called from the app thread, but it won't cause issues as long as the
light->_sb_sort, fbp, props, flags, this, DCAST(GraphicsOutput, host)); // pipe can precertify the buffer, which it can in most cases.
nassertr(sbuffer != NULL, NULL); GraphicsOutput *sbuffer = get_engine()->make_output(get_pipe(),
light->get_name(), light->_sb_sort, fbp, props, flags, this, host);
// Create a texture and fill it in with some data to workaround an OpenGL if (sbuffer != nullptr) {
// error sbuffer->add_render_texture(tex, GraphicsOutput::RTM_bind_or_copy, GraphicsOutput::RTP_depth);
PT(Texture) tex = new Texture(light->get_name());
if (is_point) {
if (light->_sb_size[0] != light->_sb_size[1]) {
display_cat.error()
<< "PointLight shadow buffers must have an equal width and height!\n";
}
tex->setup_cube_map(light->_sb_size[0], Texture::T_unsigned_byte, Texture::F_depth_component);
} else {
tex->setup_2d_texture(light->_sb_size[0], light->_sb_size[1], Texture::T_unsigned_byte, Texture::F_depth_component);
} }
tex->make_ram_image(); return sbuffer;
sbuffer->add_render_texture(tex, GraphicsOutput::RTM_bind_or_copy, GraphicsOutput::RTP_depth);
// Set the wrap mode
if (is_point) {
tex->set_wrap_u(SamplerState::WM_clamp);
tex->set_wrap_v(SamplerState::WM_clamp);
} else {
tex->set_wrap_u(SamplerState::WM_border_color);
tex->set_wrap_v(SamplerState::WM_border_color);
tex->set_border_color(LVecBase4(1, 1, 1, 1));
}
// Note: cube map shadow filtering doesn't seem to work in Cg.
if (get_supports_shadow_filter() && !is_point) {
// If we have the ARB_shadow extension, enable shadow filtering.
tex->set_minfilter(SamplerState::FT_shadow);
tex->set_magfilter(SamplerState::FT_shadow);
} else {
tex->set_minfilter(SamplerState::FT_linear);
tex->set_magfilter(SamplerState::FT_linear);
}
// Assign display region(s) to the buffer and camera
if (is_point) {
for (int i = 0; i < 6; ++i) {
PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
dr->set_lens_index(i);
dr->set_target_tex_page(i);
dr->set_camera(light_np);
dr->set_clear_depth_active(true);
}
} else {
PT(DisplayRegion) dr = sbuffer->make_mono_display_region(0, 1, 0, 1);
dr->set_camera(light_np);
dr->set_clear_depth_active(true);
}
light->_sbuffers[this] = sbuffer;
return tex;
} }
/** /**

View File

@ -424,7 +424,7 @@ public:
PT(Texture) get_shadow_map(const NodePath &light_np, GraphicsOutputBase *host=NULL); PT(Texture) get_shadow_map(const NodePath &light_np, GraphicsOutputBase *host=NULL);
PT(Texture) get_dummy_shadow_map(Texture::TextureType texture_type) const; PT(Texture) get_dummy_shadow_map(Texture::TextureType texture_type) const;
PT(Texture) make_shadow_buffer(const NodePath &light_np, GraphicsOutputBase *host); virtual GraphicsOutput *make_shadow_buffer(LightLensNode *light, Texture *tex, GraphicsOutput *host);
virtual void ensure_generated_shader(const RenderState *state); virtual void ensure_generated_shader(const RenderState *state);

View File

@ -133,6 +133,12 @@ clear_default() {
* size is the only property that matters to buffers. * size is the only property that matters to buffers.
*/ */
WindowProperties WindowProperties:: WindowProperties WindowProperties::
size(const LVecBase2i &size) {
WindowProperties props;
props.set_size(size);
return props;
}
WindowProperties WindowProperties::
size(int x_size, int y_size) { size(int x_size, int y_size) {
WindowProperties props; WindowProperties props;
props.set_size(x_size, y_size); props.set_size(x_size, y_size);

View File

@ -52,6 +52,7 @@ PUBLISHED:
MAKE_PROPERTY(config_properties, get_config_properties); MAKE_PROPERTY(config_properties, get_config_properties);
MAKE_PROPERTY(default, get_default, set_default); MAKE_PROPERTY(default, get_default, set_default);
static WindowProperties size(const LVecBase2i &size);
static WindowProperties size(int x_size, int y_size); static WindowProperties size(int x_size, int y_size);
bool operator == (const WindowProperties &other) const; bool operator == (const WindowProperties &other) const;

View File

@ -7561,6 +7561,36 @@ bind_light(Spotlight *light_obj, const NodePath &light, int light_id) {
} }
#endif // SUPPORT_FIXED_FUNCTION #endif // SUPPORT_FIXED_FUNCTION
/**
* Creates a depth buffer for shadow mapping. A derived GSG can override this
* if it knows that a particular buffer type works best for shadow rendering.
*/
GraphicsOutput *CLP(GraphicsStateGuardian)::
make_shadow_buffer(LightLensNode *light, Texture *tex, GraphicsOutput *host) {
// We override this to circumvent the fact that GraphicsEngine::make_output
// can only be called from the app thread.
if (!_supports_framebuffer_object) {
return GraphicsStateGuardian::make_shadow_buffer(light, tex, host);
}
bool is_point = light->is_of_type(PointLight::get_class_type());
// Determine the properties for creating the depth buffer.
FrameBufferProperties fbp;
fbp.set_depth_bits(shadow_depth_bits);
WindowProperties props = WindowProperties::size(light->get_shadow_buffer_size());
int flags = GraphicsPipe::BF_refuse_window;
if (is_point) {
flags |= GraphicsPipe::BF_size_square;
}
CLP(GraphicsBuffer) *sbuffer = new GLGraphicsBuffer(get_engine(), get_pipe(), light->get_name(), fbp, props, flags, this, host);
sbuffer->add_render_texture(tex, GraphicsOutput::RTM_bind_or_copy, GraphicsOutput::RTP_depth);
get_engine()->add_window(sbuffer, light->get_shadow_buffer_sort());
return sbuffer;
}
#ifdef SUPPORT_IMMEDIATE_MODE #ifdef SUPPORT_IMMEDIATE_MODE
/** /**
* Uses the ImmediateModeSender to draw a series of primitives of the * Uses the ImmediateModeSender to draw a series of primitives of the

View File

@ -381,6 +381,8 @@ public:
int light_id); int light_id);
#endif #endif
virtual GraphicsOutput *make_shadow_buffer(LightLensNode *light, Texture *tex, GraphicsOutput *host);
LVecBase4 get_light_color(Light *light) const; LVecBase4 get_light_color(Light *light) const;
#ifdef SUPPORT_IMMEDIATE_MODE #ifdef SUPPORT_IMMEDIATE_MODE

View File

@ -41,6 +41,9 @@ set_shadow_caster(bool caster) {
} }
_shadow_caster = caster; _shadow_caster = caster;
set_active(caster); set_active(caster);
if (caster) {
setup_shadow_map();
}
} }
/** /**
@ -65,6 +68,17 @@ set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int buffer_so
_sb_sort = buffer_sort; _sb_sort = buffer_sort;
} }
set_active(caster); set_active(caster);
if (caster) {
setup_shadow_map();
}
}
/**
* Returns the sort of the shadow buffer to be created for this light source.
*/
INLINE int LightLensNode::
get_shadow_buffer_sort() const {
return _sb_sort;
} }
/** /**
@ -82,8 +96,9 @@ INLINE void LightLensNode::
set_shadow_buffer_size(const LVecBase2i &size) { set_shadow_buffer_size(const LVecBase2i &size) {
if (size != _sb_size) { if (size != _sb_size) {
clear_shadow_buffers(); clear_shadow_buffers();
_sb_size = size;
setup_shadow_map();
} }
_sb_size = size;
} }
/** /**

View File

@ -67,6 +67,9 @@ LightLensNode(const LightLensNode &copy) :
_has_specular_color(copy._has_specular_color), _has_specular_color(copy._has_specular_color),
_attrib_count(0) _attrib_count(0)
{ {
if (_shadow_caster) {
setup_shadow_map();
}
} }
/** /**
@ -75,20 +78,43 @@ LightLensNode(const LightLensNode &copy) :
*/ */
void LightLensNode:: void LightLensNode::
clear_shadow_buffers() { clear_shadow_buffers() {
if (_shadow_map) {
// Clear it to all ones, so that any shaders that might still be using
// it will see the shadows being disabled.
_shadow_map->clear_image();
}
ShadowBuffers::iterator it; ShadowBuffers::iterator it;
for(it = _sbuffers.begin(); it != _sbuffers.end(); ++it) { for(it = _sbuffers.begin(); it != _sbuffers.end(); ++it) {
PT(Texture) tex = (*it).second->get_texture();
if (tex) {
// Clear it to all ones, so that any shaders that might still be using
// it will see the shadows being disabled.
tex->set_clear_color(LColor(1));
tex->clear_image();
}
(*it).first->remove_window((*it).second); (*it).first->remove_window((*it).second);
} }
_sbuffers.clear(); _sbuffers.clear();
} }
/**
* Creates the shadow map texture. Can be overridden.
*/
void LightLensNode::
setup_shadow_map() {
if (_shadow_map != nullptr &&
_shadow_map->get_x_size() == _sb_size[0] &&
_shadow_map->get_y_size() == _sb_size[1]) {
// Nothing to do.
return;
}
if (_shadow_map == nullptr) {
_shadow_map = new Texture(get_name());
}
_shadow_map->setup_2d_texture(_sb_size[0], _sb_size[1], Texture::T_unsigned_byte, Texture::F_depth_component);
_shadow_map->set_clear_color(LColor(1));
_shadow_map->set_wrap_u(SamplerState::WM_border_color);
_shadow_map->set_wrap_v(SamplerState::WM_border_color);
_shadow_map->set_border_color(LColor(1));
_shadow_map->set_minfilter(SamplerState::FT_shadow);
_shadow_map->set_magfilter(SamplerState::FT_shadow);
}
/** /**
* This is called when the light is added to a LightAttrib. * This is called when the light is added to a LightAttrib.

View File

@ -41,6 +41,8 @@ PUBLISHED:
INLINE void set_shadow_caster(bool caster); INLINE void set_shadow_caster(bool caster);
INLINE void set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int sort = -10); INLINE void set_shadow_caster(bool caster, int buffer_xsize, int buffer_ysize, int sort = -10);
INLINE int get_shadow_buffer_sort() const;
INLINE LVecBase2i get_shadow_buffer_size() const; INLINE LVecBase2i get_shadow_buffer_size() const;
INLINE void set_shadow_buffer_size(const LVecBase2i &size); INLINE void set_shadow_buffer_size(const LVecBase2i &size);
@ -53,12 +55,15 @@ PUBLISHED:
protected: protected:
LightLensNode(const LightLensNode &copy); LightLensNode(const LightLensNode &copy);
void clear_shadow_buffers(); void clear_shadow_buffers();
virtual void setup_shadow_map();
LVecBase2i _sb_size; LVecBase2i _sb_size;
bool _shadow_caster; bool _shadow_caster;
bool _has_specular_color; bool _has_specular_color;
int _sb_sort; int _sb_sort;
PT(Texture) _shadow_map;
// This is really a map of GSG -> GraphicsOutput. // This is really a map of GSG -> GraphicsOutput.
typedef pmap<PT(GraphicsStateGuardianBase), PT(GraphicsOutputBase) > ShadowBuffers; typedef pmap<PT(GraphicsStateGuardianBase), PT(GraphicsOutputBase) > ShadowBuffers;
ShadowBuffers _sbuffers; ShadowBuffers _sbuffers;
@ -106,7 +111,6 @@ private:
static TypeHandle _type_handle; static TypeHandle _type_handle;
friend class GraphicsStateGuardian; friend class GraphicsStateGuardian;
friend class ShaderGenerator;
}; };
INLINE ostream &operator << (ostream &out, const LightLensNode &light) { INLINE ostream &operator << (ostream &out, const LightLensNode &light) {

View File

@ -17,6 +17,7 @@
#include "bamReader.h" #include "bamReader.h"
#include "datagram.h" #include "datagram.h"
#include "datagramIterator.h" #include "datagramIterator.h"
#include "config_pgraphnodes.h"
TypeHandle PointLight::_type_handle; TypeHandle PointLight::_type_handle;
@ -184,6 +185,35 @@ bind(GraphicsStateGuardianBase *gsg, const NodePath &light, int light_id) {
gsg->bind_light(this, light, light_id); gsg->bind_light(this, light, light_id);
} }
/**
* Creates the shadow map texture. Can be overridden.
*/
void PointLight::
setup_shadow_map() {
if (_shadow_map != nullptr && _shadow_map->get_x_size() == _sb_size[0]) {
// Nothing to do.
return;
}
if (_sb_size[0] != _sb_size[1]) {
pgraphnodes_cat.error()
<< "PointLight shadow buffers must have an equal width and height!\n";
}
if (_shadow_map == nullptr) {
_shadow_map = new Texture(get_name());
}
_shadow_map->setup_cube_map(_sb_size[0], Texture::T_unsigned_byte, Texture::F_depth_component);
_shadow_map->set_clear_color(LColor(1));
_shadow_map->set_wrap_u(SamplerState::WM_clamp);
_shadow_map->set_wrap_v(SamplerState::WM_clamp);
// Note: cube map shadow filtering doesn't seem to work in Cg.
_shadow_map->set_minfilter(SamplerState::FT_linear);
_shadow_map->set_magfilter(SamplerState::FT_linear);
}
/** /**
* Tells the BamReader how to create objects of type PointLight. * Tells the BamReader how to create objects of type PointLight.
*/ */

View File

@ -63,6 +63,8 @@ public:
int light_id); int light_id);
private: private:
virtual void setup_shadow_map();
// This is the data that must be cycled between pipeline stages. // This is the data that must be cycled between pipeline stages.
class EXPCL_PANDA_PGRAPHNODES CData : public CycleData { class EXPCL_PANDA_PGRAPHNODES CData : public CycleData {
public: public: