Attempt to fix continuous creation of dialog surfaces without cleanup, causing crashes on R77.

Remove cache of surfaces from FrameBuffer, into each dialog that owns it.
Make surfaces be unique_ptr instead of shared_ptr, so we can be sure cleanup occurs.
This commit is contained in:
Stephen Anthony 2021-01-13 16:24:09 -03:30
parent 0fa0a339e6
commit 6187d3b542
17 changed files with 113 additions and 86 deletions

View File

@ -71,6 +71,8 @@ FBBackendSDL2::~FBBackendSDL2()
myWindow = nullptr; myWindow = nullptr;
} }
SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER); SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER);
cerr << "~FBBackendSDL2()" << endl;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -40,6 +40,8 @@ namespace {
} }
} }
static int REF_COUNT = 0;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FBSurfaceSDL2::FBSurfaceSDL2(FBBackendSDL2& backend, FBSurfaceSDL2::FBSurfaceSDL2(FBBackendSDL2& backend,
uInt32 width, uInt32 height, uInt32 width, uInt32 height,
@ -48,6 +50,7 @@ FBSurfaceSDL2::FBSurfaceSDL2(FBBackendSDL2& backend,
: myBackend{backend}, : myBackend{backend},
myInterpolationMode{inter} myInterpolationMode{inter}
{ {
REF_COUNT++;
createSurface(width, height, staticData); createSurface(width, height, staticData);
} }
@ -58,6 +61,8 @@ FBSurfaceSDL2::~FBSurfaceSDL2()
if(mySurface) if(mySurface)
{ {
REF_COUNT--;
cerr << " ~FBSurfaceSDL2(): " << this << " " << REF_COUNT << endl;
SDL_FreeSurface(mySurface); SDL_FreeSurface(mySurface);
mySurface = nullptr; mySurface = nullptr;
} }
@ -200,17 +205,10 @@ void FBSurfaceSDL2::invalidateRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h)
SDL_FillRect(mySurface, &tmp, 0); SDL_FillRect(mySurface, &tmp, 0);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::free()
{
myBlitter.reset();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::reload() void FBSurfaceSDL2::reload()
{ {
free(); reinitializeBlitter(true);
reinitializeBlitter();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -221,8 +219,6 @@ void FBSurfaceSDL2::resize(uInt32 width, uInt32 height)
if(mySurface) if(mySurface)
SDL_FreeSurface(mySurface); SDL_FreeSurface(mySurface);
free();
// NOTE: Currently, a resize changes a 'static' surface to 'streaming' // NOTE: Currently, a resize changes a 'static' surface to 'streaming'
// No code currently does this, but we should at least check for it // No code currently does this, but we should at least check for it
if(myIsStatic) if(myIsStatic)
@ -260,12 +256,15 @@ void FBSurfaceSDL2::createSurface(uInt32 width, uInt32 height,
if(myIsStatic) if(myIsStatic)
SDL_memcpy(mySurface->pixels, data, mySurface->w * mySurface->h * 4); SDL_memcpy(mySurface->pixels, data, mySurface->w * mySurface->h * 4);
reinitializeBlitter(); reload();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FBSurfaceSDL2::reinitializeBlitter() void FBSurfaceSDL2::reinitializeBlitter(bool force)
{ {
if (force)
myBlitter.reset();
if (!myBlitter && myBackend.isInitialized()) if (!myBlitter && myBackend.isInitialized())
myBlitter = BlitterFactory::createBlitter( myBlitter = BlitterFactory::createBlitter(
myBackend, scalingAlgorithm(myInterpolationMode)); myBackend, scalingAlgorithm(myInterpolationMode));

View File

@ -60,7 +60,6 @@ class FBSurfaceSDL2 : public FBSurface
void invalidate() override; void invalidate() override;
void invalidateRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h) override; void invalidateRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h) override;
void free() override;
void reload() override; void reload() override;
void resize(uInt32 width, uInt32 height) override; void resize(uInt32 width, uInt32 height) override;
@ -109,7 +108,7 @@ class FBSurfaceSDL2 : public FBSurface
void createSurface(uInt32 width, uInt32 height, const uInt32* data); void createSurface(uInt32 width, uInt32 height, const uInt32* data);
void reinitializeBlitter(); void reinitializeBlitter(bool force = false);
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
FBSurfaceSDL2() = delete; FBSurfaceSDL2() = delete;

View File

@ -350,16 +350,8 @@ class FBSurface
*/ */
virtual void invalidateRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h) = 0; virtual void invalidateRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h) = 0;
/**
This method should be called to free any resources being used by
the surface.
*/
virtual void free() = 0;
/** /**
This method should be called to reload the surface data/state. This method should be called to reload the surface data/state.
It will normally be called after free().
*/ */
virtual void reload() = 0; virtual void reload() = 0;

View File

@ -71,7 +71,7 @@ FrameBuffer::~FrameBuffer()
// Most platforms are fine with doing this in either order, but it seems // Most platforms are fine with doing this in either order, but it seems
// that OpenBSD in particular crashes when attempting to destroy textures // that OpenBSD in particular crashes when attempting to destroy textures
// *after* the renderer is already destroyed // *after* the renderer is already destroyed
freeSurfaces(); cerr << "~FrameBuffer()" << endl << endl;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -551,6 +551,7 @@ void FrameBuffer::updateInEmulationMode(float framesPerSecond)
} }
#ifdef GUI_SUPPORT #ifdef GUI_SUPPORT
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::createMessage(const string& message, MessagePosition position, bool force) void FrameBuffer::createMessage(const string& message, MessagePosition position, bool force)
{ {
// Only show messages if they've been enabled // Only show messages if they've been enabled
@ -864,42 +865,61 @@ void FrameBuffer::setPauseDelay()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
shared_ptr<FBSurface> FrameBuffer::allocateSurface( unique_ptr<FBSurface> FrameBuffer::allocateSurface(
int w, int h, ScalingInterpolation inter, const uInt32* data int w, int h, ScalingInterpolation inter, const uInt32* data)
)
{ {
// Add new surface to the list return myBackend->createSurface(w, h, inter, data);
mySurfaceList.push_back(myBackend->createSurface(w, h, inter, data));
// And return a pointer to it (pointer should be treated read-only)
return mySurfaceList.at(mySurfaceList.size() - 1);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::freeSurfaces()
{
for(auto& s: mySurfaceList)
s->free();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::reloadSurfaces()
{
for(auto& s: mySurfaceList)
s->reload();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::resetSurfaces() void FrameBuffer::resetSurfaces()
{ {
// Free all resources for each surface, then reload them switch(myOSystem.eventHandler().state())
// Due to possible timing and/or synchronization issues, all free()'s {
// are done first, then all reload()'s case EventHandlerState::NONE:
// Any derived FrameBuffer classes that call this method should be case EventHandlerState::EMULATION:
// aware of these restrictions, and act accordingly case EventHandlerState::PAUSE:
case EventHandlerState::PLAYBACK:
myMsg.surface->reload();
myStatsMsg.surface->reload();
myTIASurface->resetSurfaces();
break;
freeSurfaces(); #ifdef GUI_SUPPORT
reloadSurfaces(); case EventHandlerState::OPTIONSMENU:
myOSystem.menu().resetSurfaces();
break;
case EventHandlerState::CMDMENU:
myOSystem.commandMenu().resetSurfaces();
break;
case EventHandlerState::HIGHSCORESMENU:
myOSystem.highscoresMenu().resetSurfaces();
break;
case EventHandlerState::MESSAGEMENU:
myOSystem.messageMenu().resetSurfaces();
break;
case EventHandlerState::TIMEMACHINE:
myOSystem.timeMachine().resetSurfaces();
break;
case EventHandlerState::LAUNCHER:
myOSystem.launcher().resetSurfaces();
break;
#endif
#ifdef DEBUGGER_SUPPORT
case EventHandlerState::DEBUGGER:
myOSystem.debugger().resetSurfaces();
break;
#endif
default:
break;
}
update(UpdateMode::REDRAW); // force full update update(UpdateMode::REDRAW); // force full update
} }

View File

@ -158,7 +158,7 @@ class FrameBuffer
@return A pointer to a valid surface object, or nullptr @return A pointer to a valid surface object, or nullptr
*/ */
shared_ptr<FBSurface> allocateSurface( unique_ptr<FBSurface> allocateSurface(
int w, int w,
int h, int h,
ScalingInterpolation inter = ScalingInterpolation::none, ScalingInterpolation inter = ScalingInterpolation::none,
@ -384,16 +384,6 @@ class FrameBuffer
string getDisplayKey() const; string getDisplayKey() const;
void saveCurrentWindowPosition() const; void saveCurrentWindowPosition() const;
/**
Calls 'free()' on all surfaces that the framebuffer knows about.
*/
void freeSurfaces();
/**
Calls 'reload()' on all surfaces that the framebuffer knows about.
*/
void reloadSurfaces();
/** /**
Frees and reloads all surfaces that the framebuffer knows about. Frees and reloads all surfaces that the framebuffer knows about.
*/ */
@ -520,7 +510,7 @@ class FrameBuffer
int x{0}, y{0}, w{0}, h{0}; int x{0}, y{0}, w{0}, h{0};
MessagePosition position{MessagePosition::BottomCenter}; MessagePosition position{MessagePosition::BottomCenter};
ColorId color{kNone}; ColorId color{kNone};
shared_ptr<FBSurface> surface; unique_ptr<FBSurface> surface;
bool enabled{false}; bool enabled{false};
bool dirty{false}; bool dirty{false};
bool showGauge{false}; bool showGauge{false};
@ -541,9 +531,6 @@ class FrameBuffer
// Maximum TIA zoom level that can be used for this framebuffer // Maximum TIA zoom level that can be used for this framebuffer
float myTIAMaxZoom{1.F}; float myTIAMaxZoom{1.F};
// Holds a reference to all the surfaces that have been created
vector<shared_ptr<FBSurface>> mySurfaceList;
// Maximum message width [chars] // Maximum message width [chars]
static constexpr int MESSAGE_WIDTH = 56; static constexpr int MESSAGE_WIDTH = 56;
// Maximum gauge bar width [chars] // Maximum gauge bar width [chars]

View File

@ -541,3 +541,12 @@ bool TIASurface::correctAspect() const
{ {
return myOSystem.settings().getBool("tia.correct_aspect"); return myOSystem.settings().getBool("tia.correct_aspect");
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TIASurface::resetSurfaces()
{
myTiaSurface->reload();
mySLineSurface->reload();
myBaseTiaSurface->reload();
myShadeSurface->reload();
}

View File

@ -183,6 +183,11 @@ class TIASurface
*/ */
void updateSurfaceSettings(); void updateSurfaceSettings();
/**
Issue a 'reload' to each surface.
*/
void resetSurfaces();
private: private:
/** /**
Average current calculated buffer's pixel with previous calculated buffer's pixel (50:50). Average current calculated buffer's pixel with previous calculated buffer's pixel (50:50).
@ -208,7 +213,7 @@ class TIASurface
FrameBuffer& myFB; FrameBuffer& myFB;
TIA* myTIA{nullptr}; TIA* myTIA{nullptr};
shared_ptr<FBSurface> myTiaSurface, mySLineSurface, myBaseTiaSurface, myShadeSurface; unique_ptr<FBSurface> myTiaSurface, mySLineSurface, myBaseTiaSurface, myShadeSurface;
// NTSC object to use in TIA rendering mode // NTSC object to use in TIA rendering mode
NTSCFilter myNTSCFilter; NTSCFilter myNTSCFilter;

View File

@ -583,7 +583,6 @@ void ContextMenu::setArrows()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void ContextMenu::drawDialog() void ContextMenu::drawDialog()
{ {
// Normally we add widgets and let Dialog::draw() take care of this // Normally we add widgets and let Dialog::draw() take care of this
// logic. But for some reason, this Dialog was written differently // logic. But for some reason, this Dialog was written differently
// by the ScummVM guys, so I'm not going to mess with it. // by the ScummVM guys, so I'm not going to mess with it.

View File

@ -87,6 +87,8 @@ Dialog::~Dialog()
_firstWidget = nullptr; _firstWidget = nullptr;
_buttonGroup.clear(); _buttonGroup.clear();
cerr << "\n~Dialog(): " << this << endl;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -252,7 +254,7 @@ void Dialog::render()
// Extra surfaces must be rendered afterwards, so they are drawn on top // Extra surfaces must be rendered afterwards, so they are drawn on top
if(_surface->render()) if(_surface->render())
{ {
mySurfaceStack.applyAll([](shared_ptr<FBSurface>& surface) { mySurfaceStack.applyAll([](unique_ptr<FBSurface>& surface) {
surface->render(); surface->render();
}); });
} }
@ -418,9 +420,10 @@ void Dialog::buildCurrentFocusList(int tabID)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Dialog::addSurface(const shared_ptr<FBSurface>& surface) void Dialog::addSurface(const unique_ptr<FBSurface>& surface)
{ {
mySurfaceStack.push(surface); // FIXME : add this to the stack somehow
// mySurfaceStack.push(surface);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -90,7 +90,7 @@ class Dialog : public GuiObject
the surface render() call will always occur in such a case, the the surface render() call will always occur in such a case, the
surface should call setVisible() to enable/disable its output. surface should call setVisible() to enable/disable its output.
*/ */
void addSurface(const shared_ptr<FBSurface>& surface); void addSurface(const unique_ptr<FBSurface>& surface);
void setTitle(const string& title); void setTitle(const string& title);
bool hasTitle() { return !_title.empty(); } bool hasTitle() { return !_title.empty(); }
@ -220,8 +220,6 @@ class Dialog : public GuiObject
int _layer{0}; int _layer{0};
unique_ptr<ToolTip> _toolTip; unique_ptr<ToolTip> _toolTip;
Common::FixedStack<shared_ptr<FBSurface>> mySurfaceStack;
private: private:
struct Focus { struct Focus {
Widget* widget{nullptr}; Widget* widget{nullptr};
@ -248,13 +246,15 @@ class Dialog : public GuiObject
TabFocusList _myTabList; // focus for each tab (if any) TabFocusList _myTabList; // focus for each tab (if any)
WidgetArray _buttonGroup; WidgetArray _buttonGroup;
shared_ptr<FBSurface> _surface; unique_ptr<FBSurface> _surface;
shared_ptr<FBSurface> _shadeSurface; unique_ptr<FBSurface> _shadeSurface;
int _tabID{0}; int _tabID{0};
uInt32 _max_w{0}; // maximum wanted width uInt32 _max_w{0}; // maximum wanted width
uInt32 _max_h{0}; // maximum wanted height uInt32 _max_h{0}; // maximum wanted height
Common::FixedStack<unique_ptr<FBSurface>> mySurfaceStack;
private: private:
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
Dialog() = delete; Dialog() = delete;

View File

@ -199,6 +199,14 @@ void DialogContainer::reStack()
reset(); reset();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DialogContainer::resetSurfaces()
{
myDialogStack.applyAll([&](Dialog*& d) {
d->surface().reload();
});
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DialogContainer::handleTextEvent(char text) void DialogContainer::handleTextEvent(char text)
{ {

View File

@ -150,6 +150,13 @@ class DialogContainer
*/ */
void reStack(); void reStack();
/**
Issue a 'reload' event to each dialog surface in the stack. This
is typically used when interpolation or attributes for a dialog
have changed.
*/
void resetSurfaces();
/** /**
Inform the container that it should resize according to the current Inform the container that it should resize according to the current
screen dimensions. We make this virtual, since the container may or screen dimensions. We make this virtual, since the container may or

View File

@ -50,7 +50,7 @@ class RomInfoWidget : public Widget
private: private:
// Surface pointer holding the PNG image // Surface pointer holding the PNG image
shared_ptr<FBSurface> mySurface; unique_ptr<FBSurface> mySurface;
// Whether the surface should be redrawn by drawWidget() // Whether the surface should be redrawn by drawWidget()
bool mySurfaceIsValid{false}; bool mySurfaceIsValid{false};

View File

@ -47,15 +47,13 @@ void ToolTip::setFont(const GUI::Font& font)
myWidth = fontWidth * MAX_COLUMNS + myTextXOfs * 2; myWidth = fontWidth * MAX_COLUMNS + myTextXOfs * 2;
myHeight = fontHeight * MAX_ROWS + myTextYOfs * 2; myHeight = fontHeight * MAX_ROWS + myTextYOfs * 2;
// unallocate
if(mySurface != nullptr) if(mySurface != nullptr)
{ mySurface.reset();
// TODO: unallocate
mySurface = nullptr;
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
shared_ptr<FBSurface> ToolTip::surface() const unique_ptr<FBSurface>& ToolTip::surface()
{ {
if(mySurface == nullptr) if(mySurface == nullptr)
mySurface = myDialog.instance().frameBuffer().allocateSurface(myWidth, myHeight); mySurface = myDialog.instance().frameBuffer().allocateSurface(myWidth, myHeight);

View File

@ -76,7 +76,7 @@ class ToolTip
/** /**
Allocate surface if required and return it Allocate surface if required and return it
*/ */
shared_ptr<FBSurface> surface(); const unique_ptr<FBSurface>& surface();
void show(const string& tip); void show(const string& tip);
@ -100,7 +100,7 @@ class ToolTip
uInt32 myTextYOfs{0}; uInt32 myTextYOfs{0};
bool myTipShown{false}; bool myTipShown{false};
uInt32 myScale{1}; uInt32 myScale{1};
shared_ptr<FBSurface> mySurface; unique_ptr<FBSurface> mySurface;
}; };
#endif #endif

View File

@ -55,7 +55,6 @@ class FBSurfaceLIBRETRO : public FBSurface
bool render() override { return true; } bool render() override { return true; }
void invalidate() override { } void invalidate() override { }
void invalidateRect(uInt32, uInt32, uInt32, uInt32) override { } void invalidateRect(uInt32, uInt32, uInt32, uInt32) override { }
void free() override { }
void reload() override { } void reload() override { }
void resize(uInt32 width, uInt32 height) override { } void resize(uInt32 width, uInt32 height) override { }
void setScalingInterpolation(ScalingInterpolation) override { } void setScalingInterpolation(ScalingInterpolation) override { }