diff --git a/include/renderer/font.hpp b/include/renderer/font.hpp index 70f6cc0..ff6277d 100644 --- a/include/renderer/font.hpp +++ b/include/renderer/font.hpp @@ -17,11 +17,16 @@ namespace Game::Renderer { // Build the texture for the font; Call getSDLTexture() afterwards void build(SDL_Color color, std::string text); + // Rebuild GPU-backed texture after a renderer/device reset + bool reload(SDL_Renderer* renderer); SDL_Texture* getSDLTexture(); std::string getId(); private: TTF_Font* mFont; SDL_Renderer* mRenderer; + // Remember last build params so we can rebuild fonts on device reset + std::string mLastText; + SDL_Color mLastColor; }; } \ No newline at end of file diff --git a/include/renderer/texture.hpp b/include/renderer/texture.hpp index 8ae6995..811f97e 100644 --- a/include/renderer/texture.hpp +++ b/include/renderer/texture.hpp @@ -21,10 +21,15 @@ namespace Game::Renderer { float getHeight(); bool isTiled() { return mIsTiled; } void setTiled(bool tiled) { mIsTiled = tiled; } + // Reload GPU-backed texture using a new renderer after device reset + virtual bool reload(SDL_Renderer* renderer); protected: SDL_Texture* mTex; std::string mId; + // For textures created from disk, store the path so we can reload on device reset + std::string mPath; + bool mIsFromFile = false; private: bool mIsTiled = false; // Whether the texture is a tileset that should be rendered as a single tile or not }; diff --git a/src/renderer/font.cpp b/src/renderer/font.cpp index 0f64549..8dbbf7e 100644 --- a/src/renderer/font.cpp +++ b/src/renderer/font.cpp @@ -36,6 +36,10 @@ namespace Game::Renderer { void Font::build(SDL_Color color, std::string text) { if (!mFont) { return; } + // Store last build parameters so we can rebuild after device resets + mLastText = text; + mLastColor = color; + SDL_Surface* surf = TTF_RenderText_Blended(mFont, text.c_str(), text.size(), color); if (!surf) { ERROR("TTF_RenderText_Blended Error: " << SDL_GetError() << " (This object may be unusuable)"); @@ -57,6 +61,14 @@ namespace Game::Renderer { } + bool Font::reload(SDL_Renderer* renderer) { + mRenderer = renderer; + if (mLastText.empty()) return false; + // Rebuild the texture using the stored last text and color + build(mLastColor, mLastText); + return true; + } + SDL_Texture* Font::getSDLTexture() { return mTex; } std::string Font::getId() { return mId; } } \ No newline at end of file diff --git a/src/renderer/renderer.cpp b/src/renderer/renderer.cpp index ccd5a8f..94375a6 100644 --- a/src/renderer/renderer.cpp +++ b/src/renderer/renderer.cpp @@ -24,12 +24,18 @@ namespace Game::Renderer { bool Renderer::init(SDL_Window* window) { // Request VSync before/at renderer setup; some backends honor this hint. SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); - + // Create renderer using the portable API. Some SDL3 backends may not support the old flags, + // so fall back to a software renderer via hint if the first attempt fails. mRenderer = SDL_CreateRenderer(window, nullptr); if (!mRenderer) { - std::string errorMsg = std::string("Neuspešno ustvarjanje rendererja: ") + std::string(SDL_GetError()); - ERROR(errorMsg.c_str()); - return false; + WARN("Renderer creation failed, attempting software renderer: " << SDL_GetError()); + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); + mRenderer = SDL_CreateRenderer(window, nullptr); + if (!mRenderer) { + std::string errorMsg = std::string("Neuspešno ustvarjanje rendererja: ") + std::string(SDL_GetError()); + ERROR(errorMsg.c_str()); + return false; + } } mVSyncEnabled = SDL_SetRenderVSync(mRenderer, 1); diff --git a/src/renderer/texture.cpp b/src/renderer/texture.cpp index 4a8ec5d..e93a55a 100644 --- a/src/renderer/texture.cpp +++ b/src/renderer/texture.cpp @@ -7,6 +7,9 @@ namespace Game::Renderer { Texture::Texture(const std::string& path, SDL_Renderer* renderer, std::string id) : mTex(nullptr), mId(id) { + mPath = path; + mIsFromFile = true; + SDL_Surface* surf = IMG_Load(path.c_str()); if (!surf) { ERROR("Failed to load image at " << path); @@ -44,6 +47,34 @@ namespace Game::Renderer { LOG("Tekstura '" << mId << "' uničena") } + bool Texture::reload(SDL_Renderer* renderer) { + if (!mIsFromFile || mPath.empty()) return false; + if (mTex) { + SDL_DestroyTexture(mTex); + mTex = nullptr; + } + + SDL_Surface* surf = IMG_Load(mPath.c_str()); + if (!surf) { + ERROR("Failed to reload image at " << mPath); + return false; + } + + mTex = SDL_CreateTextureFromSurface(renderer, surf); + SDL_DestroySurface(surf); + if (!mTex) { + ERROR("Failed to create texture from surface when reloading " << mPath << ": " << SDL_GetError()); + return false; + } + + // Restore scale mode + if (!SDL_SetTextureScaleMode(mTex, SDL_SCALEMODE_NEAREST)) { + WARN("Failed to set texture scale mode to NEAREST for '" << mId << "' during reload: " << SDL_GetError()); + } + + return true; + } + SDL_Texture* Texture::getSDLTexture() { return mTex; } diff --git a/src/window/window.cpp b/src/window/window.cpp index e250e93..83d4247 100644 --- a/src/window/window.cpp +++ b/src/window/window.cpp @@ -1,6 +1,7 @@ #include #include +#include namespace Game::Window { std::mutex Window::sMutex; @@ -113,6 +114,45 @@ namespace Game::Window { } }); } + // Renderer device/targets reset (GPU device removed) - attempt to re-create renderer and reload textures + if (event.type == SDL_EVENT_RENDER_DEVICE_RESET || event.type == SDL_EVENT_RENDER_TARGETS_RESET) { + LOG("Renderer device reset event received: attempting to reinitialize renderer and reload textures"); + + // Destroy current renderer and try to re-init + mRenderer.destroy(); + bool reinitOk = mRenderer.init(mWindow); + + // If reinit failed, try forcing the software renderer + if (!reinitOk) { + WARN("Renderer re-init failed, forcing software renderer fallback"); + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); + reinitOk = mRenderer.init(mWindow); + if (!reinitOk) { + ERROR("Software renderer fallback also failed: " << SDL_GetError()); + // Unable to recover; stop running to avoid crashes + mRunning = false; + break; + } + } + + // Rebuild GPU textures for all entities (fonts and file-based textures) + State::GameState::getInstance().withEntitiesLocked([&](auto& entities) { + for (auto& [name, entity] : entities) { + (void)name; + if (entity) { + auto tex = entity->getTexture(); + if (tex) { + try { + tex->reload(mRenderer.getSDLRenderer()); + } catch (...) { + WARN("Exception while reloading texture for entity: " << entity->getName()); + } + } + entity->onWindowResized(mLastWindowWidth, mLastWindowHeight); // ensure layout is correct + } + } + }); + } } mRenderer.renderFrame();