Fix Bug #23908 - Crash on resize with SDL2.

SDL2 uses shared memory to communicate with the graphics system when
using a software renderer. A resize event will cause SDL2 to
invalidate the SDL_surface that relies on shared memory and any
subsequent calls to will be SDL_GetWindowSurface will cause the shared
memory do be unmapped as the old surface is released.  This is
problematic when it occurs during a rendering cycle, because the
software rendered may also invoke SDL_GetWindowSurface, making any
references to that surface that the game contains become stale. This
will in turn cause a segmentation fault during blitting.
This commit is contained in:
Andreas Löf 2015-10-13 01:24:51 +13:00
parent 0c27eb9490
commit 449aed0a64
7 changed files with 65 additions and 3 deletions

View file

@ -21,6 +21,7 @@
#include "sound.hpp"
#include "quit_confirmation.hpp"
#include "video.hpp"
#include "display.hpp"
#if defined _WIN32
#include "desktop/windows_tray_notification.hpp"
#endif
@ -278,7 +279,9 @@ bool has_focus(const sdl_handler* hand, const SDL_Event* event)
void pump()
{
SDL_PumpEvents();
#if SDL_VERSION_ATLEAST(2,0,0)
peek_for_resize();
#endif
pump_info info;
//used to keep track of double click events
@ -526,7 +529,7 @@ int pump_info::ticks(unsigned *refresh_counter, unsigned refresh_rate) {
#if SDL_VERSION_ATLEAST(2,0,0)
/* The constants for the minimum and maximum are picked from the headers. */
#define INPUT_MIN 0x200
#define INPUT_MIN 0x300
#define INPUT_MAX 0x8FF
bool is_input(const SDL_Event& event)
@ -539,6 +542,19 @@ void discard_input()
SDL_FlushEvents(INPUT_MIN, INPUT_MAX);
}
void peek_for_resize()
{
SDL_Event events[100];
int num = SDL_PeepEvents(events, 100, SDL_PEEKEVENT, SDL_WINDOWEVENT, SDL_WINDOWEVENT);
for (int i = 0; i < num; i++) {
if (events[i].type == SDL_WINDOWEVENT &&
events[i].window.event == SDL_WINDOWEVENT_RESIZED) {
display::get_singleton()->video().update_framebuffer();
}
}
}
#else
#define INPUT_MASK (SDL_EVENTMASK(SDL_KEYDOWN)|\

View file

@ -93,6 +93,11 @@ struct event_context
//causes events to be dispatched to all handler objects.
void pump();
#if SDL_VERSION_ATLEAST(2,0,0)
//look for resize events and update references to the screen area
void peek_for_resize();
#endif
struct pump_info {
pump_info() : resize_dimensions(), ticks_(0) {}
std::pair<int,int> resize_dimensions;

View file

@ -21,6 +21,9 @@
#include "gui/auxiliary/layout_exception.hpp"
#include "gui/widgets/control.hpp"
#include "utils/foreach.tpp"
#if SDL_VERSION_ATLEAST(2,0,0)
#include "events.hpp"
#endif
#include <numeric>
@ -918,6 +921,10 @@ void tgrid::impl_draw_children(surface& frame_buffer)
* the area not used for resizing the screen, this call `fixes' that.
*/
SDL_PumpEvents();
#if SDL_VERSION_ATLEAST(2,0,0)
events::peek_for_resize();
#endif
assert(get_visible() == twidget::tvisible::visible);
set_is_dirty(false);
@ -956,6 +963,9 @@ tgrid::impl_draw_children(surface& frame_buffer, int x_offset, int y_offset)
* the area not used for resizing the screen, this call `fixes' that.
*/
SDL_PumpEvents();
#if SDL_VERSION_ATLEAST(2,0,0)
events::peek_for_resize();
#endif
assert(get_visible() == twidget::tvisible::visible);
set_is_dirty(false);

View file

@ -598,6 +598,9 @@ void hotkey_preferences_dialog::show_binding_dialog(
&& (event.type != SDL_MOUSEBUTTONDOWN )
)
SDL_PollEvent(&event);
#if SDL_VERSION_ATLEAST(2,0,0)
events::peek_for_resize();
#endif
do {
switch (event.type) {
@ -610,6 +613,9 @@ void hotkey_preferences_dialog::show_binding_dialog(
SDL_PollEvent(&event);
#if SDL_VERSION_ATLEAST(2,0,0)
events::peek_for_resize();
#endif
disp_.flip();
disp_.delay(10);
} while (event.type != SDL_KEYUP && event.type != SDL_JOYBUTTONUP

View file

@ -27,6 +27,9 @@
#include "video.hpp"
#include "image.hpp"
#include "text.hpp"
#if SDL_VERSION_ATLEAST(2,0,0)
#include "display.hpp"
#endif
#include <SDL_events.h>
#include <SDL_image.h>
@ -206,6 +209,10 @@ void loadscreen::draw_screen(const std::string &text)
SDL_Event ev;
while(SDL_PollEvent(&ev)) {
#if SDL_VERSION_ATLEAST(2,0,0)
if (ev.type == SDL_WINDOWEVENT &&
ev.window.type == SDL_WINDOWEVENT_RESIZED) {
display::get_singleton()->video().update_framebuffer();
}
if (ev.type == SDL_WINDOWEVENT &&
(ev.window.type == SDL_WINDOWEVENT_RESIZED ||
ev.window.type == SDL_WINDOWEVENT_EXPOSED))

View file

@ -532,6 +532,19 @@ int CVideo::modePossible( int x, int y, int bits_per_pixel, int flags, bool curr
}
#if SDL_VERSION_ATLEAST(2, 0, 0)
void CVideo::update_framebuffer()
{
if (!window)
return;
surface fb = SDL_GetWindowSurface(*window);
if (!frameBuffer)
frameBuffer = fb;
else
frameBuffer.assign(fb);
}
int CVideo::setMode( int x, int y, int bits_per_pixel, int flags )
{
update_rects.clear();
@ -554,7 +567,7 @@ int CVideo::setMode( int x, int y, int bits_per_pixel, int flags )
}
}
frameBuffer = SDL_GetWindowSurface(*window);
update_framebuffer();
if(frameBuffer != NULL) {
image::set_pixel_format(frameBuffer->format);

View file

@ -75,6 +75,11 @@ class CVideo : private boost::noncopyable {
int modePossible( int x, int y, int bits_per_pixel, int flags, bool current_screen_optimal=false);
int setMode( int x, int y, int bits_per_pixel, int flags );
#if SDL_VERSION_ATLEAST(2, 0, 0)
//this needs to be invoked immediately after a resize event or the game will crash.
void update_framebuffer();
#endif
//did the mode change, since the last call to the modeChanged() method?
bool modeChanged();