From 9c86241cf890068f831d315fa834c83f2368a36f Mon Sep 17 00:00:00 2001 From: Aloshi Date: Thu, 9 Aug 2012 16:19:07 -0500 Subject: [PATCH] Simple resizing for GuiImages using SDL_gfx. Added multithreaded image loading for the GuiImage class. --- Makefile | 2 +- README.md | 9 +- changelog.txt | 5 + src/InputManager.cpp | 2 +- src/SystemData.cpp | 4 +- src/components/GuiGameList.cpp | 3 +- src/components/GuiImage.cpp | 165 ++++++++++++++++++++++++++++++--- src/components/GuiImage.h | 24 ++++- 8 files changed, 187 insertions(+), 27 deletions(-) diff --git a/Makefile b/Makefile index adff81bd8..1ba4cdde7 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ CC=g++ CFLAGS=-c -Wall -LDFLAGS=-lSDL -lSDL_ttf -lSDL_image -lboost_system -lboost_filesystem +LDFLAGS=-lSDL -lSDL_ttf -lSDL_image -lSDL_gfx -lboost_system -lboost_filesystem SRCSOURCES=main.cpp Renderer.cpp Renderer_draw.cpp GuiComponent.cpp InputManager.cpp SystemData.cpp GameData.cpp FolderData.cpp XMLReader.cpp components/GuiGameList.cpp components/GuiInputConfig.cpp components/GuiImage.cpp components/GuiMenu.cpp pugiXML/pugixml.cpp SOURCES=$(addprefix src/,$(SRCSOURCES)) OBJECTS=$(SOURCES:.cpp=.o) diff --git a/README.md b/README.md index 84f4f5451..b48ea0274 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,9 @@ I'm not associated with RetroArch in any way! Building ======== -EmulationStation has a few dependencies. For building, you'll need SDL 1.2, the SDL TTF library, the SDL image library, and Boost.Filesystem, which can easily be obtained with apt-get: +EmulationStation has quite a few dependencies. For building, you'll need SDL 1.2, the SDL TTF library, the SDL image library, the SDL_gfx library, and Boost.Filesystem, which can easily be obtained with apt-get: ``` -sudo apt-get install libsdl1.2-dev -sudo apt-get install libsdl-ttf2.0-dev -sudo apt-get install libboost-filesystem-dev -sudo apt-get install libsdl-image1.2-dev +sudo apt-get install libsdl1.2-dev libsdl-ttf2.0-dev libboost-filesystem-dev libsdl-image1.2-dev libsdl-gfx1.2-dev ``` You can build EmulationStation by simply running `make`. @@ -24,7 +21,7 @@ Configuring When first run, an example systems configuration file will be created at $HOME/.emulationstation/es_systems.cfg. This example has some comments explaining how to write the configuration file, and an example RetroArch launch command. Keep in mind you can define more than one system! Just use all the variables again. -If an SDL Joystick is detected at startup, and $HOME/.es_input.cfg is nonexistant, an Input Configuration screen will appear instead of the game list. This should be pretty self-explanatory. If you want to reconfigure, just delete $HOME/.emulationstation/es_input.cfg. +If an SDL Joystick is detected at startup, and $HOME/.emulationstation/es_input.cfg is nonexistant, an Input Configuration screen will appear instead of the game list. This should be pretty self-explanatory. If you want to reconfigure, just delete $HOME/.emulationstation/es_input.cfg. Mappings will always be applied to the SDL joystick at index 0. An Xbox 360 controller with the xboxdrv driver was tested. POV hats are automatically mapped to directions (so if you're not using an analog stick, you'll need to skip mapping Up/Down/Left/Right by pressing a keyboard key). diff --git a/changelog.txt b/changelog.txt index d836b4397..6135e6cdb 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +August 8 +-Added automatic resizing of images using SDL_gfx +-Multithreaded image loading for the GuiImage class! +-Removed warning if an unknown variable is found in a systems config file (useful for additional utilities) + August 7 -gamelist.xml files are now read from each system's individual search directory. -The switch --gamelist-only was added. Use it to skip automatic searching and only use files defined in gamelist.xml. diff --git a/src/InputManager.cpp b/src/InputManager.cpp index 233322fb2..242139072 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -74,7 +74,7 @@ void InputManager::processEvent(SDL_Event* event) } //catch emergency quit event - if(event->key.keysym.sym == SDLK_F4) + if(event->key.keysym.sym == SDLK_F4 && keyDown) { //I have no idea if SDL will delete this event, but we're quitting, so I don't think it really matters SDL_Event* quit = new SDL_Event(); diff --git a/src/SystemData.cpp b/src/SystemData.cpp index 90c8524e7..fe901b7c5 100644 --- a/src/SystemData.cpp +++ b/src/SystemData.cpp @@ -168,8 +168,8 @@ void SystemData::loadConfig() sysExtension = varValue; else if(varName == "COMMAND") sysCommand = varValue; - else - std::cerr << "Error reading config file - unknown variable name \"" << varName << "\"!\n"; + //else + // std::cerr << "Error reading config file - unknown variable name \"" << varName << "\"!\n"; //we have all our variables - create the system object if(!sysName.empty() && !sysPath.empty() &&!sysExtension.empty() && !sysCommand.empty()) diff --git a/src/components/GuiGameList.cpp b/src/components/GuiGameList.cpp index 0f7d405a2..d8057c981 100644 --- a/src/components/GuiGameList.cpp +++ b/src/components/GuiGameList.cpp @@ -18,7 +18,7 @@ GuiGameList::GuiGameList(bool useDetail) { mList = new GuiList(Renderer::getScreenWidth() * 0.4, Renderer::getFontHeight(Renderer::LARGE) + 2); - mScreenshot = new GuiImage(Renderer::getScreenWidth() * 0.2 - (SCREENSHOTWIDTH / 2), Renderer::getFontHeight(Renderer::LARGE) + 2); + mScreenshot = new GuiImage(Renderer::getScreenWidth() * 0.2, Renderer::getFontHeight(Renderer::LARGE) + 2, "", Renderer::getScreenWidth() * 0.3); addChild(mScreenshot); }else{ mList = new GuiList(0, Renderer::getFontHeight(Renderer::LARGE) + 2); @@ -87,6 +87,7 @@ void GuiGameList::onRender() { GameData* game = (GameData*)mList->getSelectedObject(); + //todo: cache this std::string desc = game->getDescription(); if(!desc.empty()) Renderer::drawWrappedText(desc, 2, Renderer::getFontHeight(Renderer::LARGE) + SCREENSHOTHEIGHT + 12, Renderer::getScreenWidth() * 0.4, 0xFF0000); diff --git a/src/components/GuiImage.cpp b/src/components/GuiImage.cpp index 1487a6e22..48c6182e0 100644 --- a/src/components/GuiImage.cpp +++ b/src/components/GuiImage.cpp @@ -1,12 +1,36 @@ #include "GuiImage.h" #include +#include #include +#include -GuiImage::GuiImage(int offsetX, int offsetY, std::string path) +int GuiImage::getWidth() { if(mSurface) return mSurface->w; else return 0; } +int GuiImage::getHeight() { if(mSurface) return mSurface->h; else return 0; } + +int startImageLoadThread(void*); + +GuiImage::GuiImage(int offsetX, int offsetY, std::string path, unsigned int maxWidth, unsigned int maxHeight) { + std::cout << "Creating GuiImage\n"; + mSurface = NULL; - SDL_Rect newRect = {offsetX, offsetY, 0, 0}; - mRect = newRect; + + mOffsetX = offsetX; + mOffsetY = offsetY; + + mMaxWidth = maxWidth; + mMaxHeight = maxHeight; + + mPathMutex = SDL_CreateMutex(); + mSurfaceMutex = SDL_CreateMutex(); + mDeleting = false; + + mLoadThread = SDL_CreateThread(&startImageLoadThread, this); + if(!mLoadThread) + { + std::cerr << "Error - could not create image load thread!\n"; + std::cerr << " " << SDL_GetError() << "\n"; + } if(!path.empty()) setImage(path); @@ -14,31 +38,142 @@ GuiImage::GuiImage(int offsetX, int offsetY, std::string path) GuiImage::~GuiImage() { + mDeleting = true; + if(mLoadThread) + SDL_WaitThread(mLoadThread, NULL); + if(mSurface) SDL_FreeSurface(mSurface); } -void GuiImage::setImage(std::string path) + + +std::string GuiImage::getPathThreadSafe() { - //if we already have an image, clear it - if(mSurface) + std::string ret; + + SDL_mutexP(mPathMutex); + ret = mPath; + SDL_mutexV(mPathMutex); + + return ret; +} + +void GuiImage::setPathThreadSafe(std::string path) +{ + SDL_mutexP(mPathMutex); + mPath = path; + + if(mPath.empty()) + mLoadedPath = ""; + SDL_mutexV(mPathMutex); +} + +int GuiImage::runImageLoadThread() +{ + while(!mDeleting) { - SDL_FreeSurface(mSurface); - mSurface = NULL; + std::string path = getPathThreadSafe(); + + if(path != mLoadedPath && path != "" && boost::filesystem::exists(path)) + { + //start loading the image + SDL_Surface* newSurf = IMG_Load(path.c_str()); + + //if we started loading something else or failed to load, don't bother resizing + if(path != getPathThreadSafe() || newSurf == NULL) + { + if(newSurf) + SDL_FreeSurface(newSurf); + + continue; + } + + + //std::cout << "Loading complete, checking for resize\n"; + + //resize it + if(mMaxWidth && newSurf->w > mMaxWidth) + { + double scale = (double)mMaxWidth / (double)newSurf->w; + + SDL_Surface* resSurf = zoomSurface(newSurf, scale, scale, SMOOTHING_OFF); + SDL_FreeSurface(newSurf); + newSurf = resSurf; + } + + if(mMaxHeight && newSurf->h > mMaxHeight) + { + double scale = (double)mMaxHeight / (double)newSurf->h; + + SDL_Surface* resSurf = zoomSurface(newSurf, scale, scale, SMOOTHING_OFF); + SDL_FreeSurface(newSurf); + newSurf = resSurf; + } + + //again, make sure we're still good to go + if(path != getPathThreadSafe() || newSurf == NULL) + { + if(newSurf) + SDL_FreeSurface(newSurf); + + continue; + } + + //finally set the image and delete the old one + SDL_mutexP(mSurfaceMutex); + if(mSurface) + SDL_FreeSurface(mSurface); + + mSurface = newSurf; + + //Also update the rect + mRect.x = mOffsetX - (mSurface->w / 2); + mRect.y = mOffsetY; + mRect.w = 0; + mRect.h = 0; + + mLoadedPath = path; + SDL_mutexV(mSurfaceMutex); + } } - if(!path.empty()) + std::cout << "Finishing image loader thread.\n"; + + return 0; +} + +int startImageLoadThread(void* img) +{ + return ((GuiImage*)img)->runImageLoadThread(); +} + +void GuiImage::setImage(std::string path) +{ + setPathThreadSafe(path); + + if(path.empty()) { - mSurface = IMG_Load(path.c_str()); - - if(mSurface == NULL) - std::cerr << "Error loading image \"" << path.c_str() << "\"\n"; - + if(mSurface) + { + SDL_mutexP(mSurfaceMutex); + SDL_FreeSurface(mSurface); + mSurface = NULL; + SDL_mutexV(mSurfaceMutex); + } } } void GuiImage::onRender() { if(mSurface) - SDL_BlitSurface(mSurface, NULL, Renderer::screen, &mRect); + { + SDL_mutexP(mSurfaceMutex); + SDL_BlitSurface(mSurface, NULL, Renderer::screen, &mRect); + SDL_mutexV(mSurfaceMutex); + }else if(!getPathThreadSafe().empty()) + { + Renderer::drawCenteredText("Loading...", -(Renderer::getScreenWidth() - mOffsetX)/*-mOffsetX * 3*/, mOffsetY, 0x000000); + } } + diff --git a/src/components/GuiImage.h b/src/components/GuiImage.h index 68daedcfa..84bc7727f 100644 --- a/src/components/GuiImage.h +++ b/src/components/GuiImage.h @@ -3,20 +3,42 @@ #include "../GuiComponent.h" #include +#include #include class GuiImage : public GuiComponent { public: - GuiImage(int offsetX = 0, int offsetY = 0, std::string path = ""); + GuiImage(int offsetX = 0, int offsetY = 0, std::string path = "", unsigned int maxWidth = 0, unsigned int maxHeight = 0); ~GuiImage(); void setImage(std::string path); + int getWidth(); + int getHeight(); + void onRender(); + + //this should really never be called by anything except setImage + //but it was either make this function public or make mSurface public + //so just don't use this, okay? + int runImageLoadThread(); + private: + int mMaxWidth, mMaxHeight; + + std::string mPath, mLoadedPath; + SDL_Surface* mSurface; + int mOffsetX, mOffsetY; SDL_Rect mRect; + + SDL_Thread* mLoadThread; + void setPathThreadSafe(std::string path); + std::string getPathThreadSafe(); + SDL_mutex* mPathMutex; + SDL_mutex* mSurfaceMutex; + bool mDeleting; }; #endif