Maelstrom: Removed rendering to a rendering target

From 0a80acd8b3cc5c594df961f5c52f11e07834fbf0 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 22 Mar 2026 19:09:47 -0700
Subject: [PATCH] Removed rendering to a rendering target

This reduces memory usage and allows drawing to the entire screen in the future.
---
 game/main.cpp              | 78 ++++++++++++++++++++------------------
 maclib/Mac_FontServ.cpp    |  8 +++-
 screenlib/SDL_FrameBuf.cpp | 73 +++++++++++++++--------------------
 screenlib/SDL_FrameBuf.h   |  5 +--
 screenlib/UIManager.cpp    | 43 ++++++++++++---------
 screenlib/UIManager.h      |  2 +-
 6 files changed, 106 insertions(+), 103 deletions(-)

diff --git a/game/main.cpp b/game/main.cpp
index e990095b..3c4a545c 100644
--- a/game/main.cpp
+++ b/game/main.cpp
@@ -58,7 +58,7 @@ Bool	gNetworkAvailable = false;
 Bool	gUpdateBuffer = false;
 Bool	gDelaySound = false;
 int		gDelayTicks = 0;
-Bool	gRunning = false;
+Bool	gRunning = true;
 
 
 // Main Menu actions:
@@ -210,52 +210,56 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
 
 SDL_AppResult SDL_AppIterate(void *appstate)
 {
-	if (screen->Fading()) {
-		screen->FadeStep();
-		return SDL_APP_CONTINUE;
-	}
+	while (!screen->Fading()) {
 
-	if (gDelaySound) {
-		if (sound->Playing()) {
-			screen->Update();
-			Delay(2);
-			return SDL_APP_CONTINUE;
+		if (gDelaySound) {
+			if (sound->Playing()) {
+				ui->Draw(false);
+				Delay(2);
+				break;
+			}
+			gDelaySound = false;
 		}
-		gDelaySound = false;
-	}
 
-	if (gDelayTicks) {
-		int ticks = SDL_min(gDelayTicks, 2);
-		screen->Update();
-		Delay(ticks);
-		gDelayTicks -= ticks;
-		return SDL_APP_CONTINUE;
-	}
+		if (gDelayTicks) {
+			int ticks = SDL_min(gDelayTicks, 2);
+			ui->Draw(false);
+			Delay(ticks);
+			gDelayTicks -= ticks;
+			break;
+		}
 
-	if (gInitializing) {
-		if (ContinueInitialization()) {
-			ui->Draw();
-			Delay(2);
-			return SDL_APP_CONTINUE;
-		} else {
-			return SDL_APP_FAILURE;
+		if (gInitializing) {
+			if (ContinueInitialization()) {
+				ui->Draw();
+				Delay(2);
+				break;
+			} else {
+				return SDL_APP_FAILURE;
+			}
 		}
-	}
 
-	ui->Draw();
+		ui->Draw();
 
-	if (!gGameOn) {
-		// If we got a replay event, start it up!
-		if (gReplayFile) {
-			RunReplayGame(gReplayFile);
-			SDL_free(gReplayFile);
-			gReplayFile = nullptr;
+		if (!gGameOn) {
+			// If we got a replay event, start it up!
+			if (gReplayFile) {
+				RunReplayGame(gReplayFile);
+				SDL_free(gReplayFile);
+				gReplayFile = nullptr;
+			}
 		}
-	}
 
-	UpdateSteam();
+		UpdateSteam();
+
+		DelayFrame();
 
-	DelayFrame();
+		break;
+	}
+
+	if (screen->Fading()) {
+		screen->FadeStep();
+	}
 
 	if (gRunning) {
 		return SDL_APP_CONTINUE;
diff --git a/maclib/Mac_FontServ.cpp b/maclib/Mac_FontServ.cpp
index 02c7c185..308f0d40 100644
--- a/maclib/Mac_FontServ.cpp
+++ b/maclib/Mac_FontServ.cpp
@@ -378,7 +378,9 @@ FontServ::TextImage(const char *text, MFont *font, Uint8 style, SDL_Color fg)
 
 		image = screen->LoadImage(surface);
 		SDL_FreeSurface(surface);
-		SDL_SetTextureBlendMode(image, SDL_BLENDMODE_BLEND);
+		SDL_SetTextureBlendMode(image, SDL_ComposeCustomBlendMode(
+			SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD,
+			SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD));
 		SDL_SetTextureColorMod(image, fg.r, fg.g, fg.b);
 		return image;
 	}
@@ -491,7 +493,9 @@ FontServ::TextImage(const char *text, MFont *font, Uint8 style, SDL_Color fg)
 	/* Create the image */
 	image = screen->LoadImage(width, height, bitmap);
 	delete[] bitmap;
-	SDL_SetTextureBlendMode(image, SDL_BLENDMODE_BLEND);
+	SDL_SetTextureBlendMode(image, SDL_ComposeCustomBlendMode(
+		SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD,
+		SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD));
 	SDL_SetTextureColorMod(image, fg.r, fg.g, fg.b);
 
 	return(image);
diff --git a/screenlib/SDL_FrameBuf.cpp b/screenlib/SDL_FrameBuf.cpp
index 82b15421..767f69fb 100644
--- a/screenlib/SDL_FrameBuf.cpp
+++ b/screenlib/SDL_FrameBuf.cpp
@@ -70,6 +70,9 @@ FrameBuf::Init(int width, int height, Uint32 window_flags, const char *title, SD
 		SetError("Couldn't create renderer: %s", SDL_GetError());
 		return(-1);
 	}
+	//SDL_SetDefaultTextureScaleMode(m_renderer, SDL_SCALEMODE_PIXELART);
+
+	Clear();
 
 	/* Set the output area */
 	SDL_SetRenderLogicalPresentation(m_renderer, width, height, SDL_LOGICAL_PRESENTATION_LETTERBOX);
@@ -81,16 +84,6 @@ FrameBuf::Init(int width, int height, Uint32 window_flags, const char *title, SD
 	m_clip.w = (float)width;
 	m_clip.h = (float)height;
 
-	/* Create the render target */
-	m_target = SDL_CreateTexture(m_renderer, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_TARGET, width, height);
-	if (!m_target) {
-		SetError("Couldn't create target: %s", SDL_GetError());
-		return(-1);
-	}
-	//SDL_SetTextureScaleMode(m_target, SDL_SCALEMODE_PIXELART);
-
-	SDL_SetRenderTarget(m_renderer, m_target);
-
 	return(0);
 }
 
@@ -99,9 +92,6 @@ FrameBuf::~FrameBuf()
 	for (unsigned int i = 0; i < m_gamepads.length(); ++i) {
 		SDL_CloseGamepad(m_gamepads[i]);
 	}
-	if (m_target) {
-		SDL_DestroyTexture(m_target);
-	}
 	if (m_renderer) {
 		SDL_DestroyRenderer(m_renderer);
 	}
@@ -435,25 +425,7 @@ FrameBuf::Update(void)
 		return;
 	}
 
-	if (m_target) {
-		Update(m_target);
-	} else {
-		SDL_RenderPresent(m_renderer);
-	}
-}
-
-void
-FrameBuf::Update(SDL_Texture *texture)
-{
-	SDL_SetRenderTarget(m_renderer, NULL);
-
-	SDL_SetRenderDrawColor(m_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
-	SDL_RenderClear(m_renderer);
-
-	SDL_RenderTexture(m_renderer, texture, NULL, NULL);
 	SDL_RenderPresent(m_renderer);
-
-	SDL_SetRenderTarget(m_renderer, m_target);
 }
 
 void
@@ -461,12 +433,10 @@ FrameBuf::Fade(void)
 {
 	m_fadeStep = 1;
 
-	if (!m_fadeTexture) {
-		m_fadeTexture = SDL_CreateTexture(m_renderer, SDL_PIXELFORMAT_UNKNOWN, SDL_TEXTUREACCESS_TARGET, m_width, m_height);
-	}
-	SDL_SetRenderTarget(m_renderer, m_fadeTexture);
-	SDL_RenderTexture(m_renderer, m_target, nullptr, nullptr);
-	SDL_SetRenderTarget(m_renderer, m_target);
+	SDL_Surface *content = SDL_RenderReadPixels(m_renderer, NULL);
+	SDL_DestroyTexture(m_fadeTexture);
+	m_fadeTexture = SDL_CreateTextureFromSurface(m_renderer, content);
+	SDL_DestroySurface(content);
 }
 
 void
@@ -475,17 +445,27 @@ FrameBuf::FadeStep(void)
 	const int max = 32;
 	int v = m_faded ? m_fadeStep : max - m_fadeStep;
 	Uint8 value = (Uint8)(255 * v / max);
+	SDL_SetRenderDrawColor(m_renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
+	SDL_RenderClear(m_renderer);
 	SDL_SetTextureColorMod(m_fadeTexture, value, value, value);
-	Update(m_fadeTexture);
+	SDL_RenderTexture(m_renderer, m_fadeTexture, NULL, NULL);
+	SDL_RenderPresent(m_renderer);
 	SDL_Delay(10);
 	++m_fadeStep;
 
 	if (m_fadeStep > max) {
-		SDL_DestroyTexture(m_fadeTexture);
-		m_fadeTexture = nullptr;
-		m_faded = !m_faded;
+		FadeComplete();
 	}
-} 
+}
+
+void
+FrameBuf::FadeComplete(void)
+{
+	SDL_SetRenderTarget(m_renderer, nullptr);
+	SDL_DestroyTexture(m_fadeTexture);
+	m_fadeTexture = nullptr;
+	m_faded = !m_faded;
+}
 
 int
 FrameBuf::ScreenDump(const char *prefix, int x, int y, int w, int h)
@@ -560,7 +540,14 @@ FrameBuf::LoadImage(const char *file)
 SDL_Texture *
 FrameBuf::LoadImage(SDL_Surface *surface)
 {
-	return SDL_CreateTextureFromSurface(m_renderer, surface);
+	SDL_Texture *texture;
+
+	texture = SDL_CreateTextureFromSurface(m_renderer, surface);
+	if (!texture) {
+		SetError("%s", SDL_GetError());
+		return NULL;
+	}
+	return(texture);
 }
 
 SDL_Texture *
diff --git a/screenlib/SDL_FrameBuf.h b/screenlib/SDL_FrameBuf.h
index f138997b..0fd87762 100644
--- a/screenlib/SDL_FrameBuf.h
+++ b/screenlib/SDL_FrameBuf.h
@@ -109,7 +109,6 @@ class FrameBuf : public ErrorBase {
 	void StretchBlit(const SDL_Rect *dstrect, SDL_Texture *src, const SDL_Rect *srcrect);
 
 	void Update(void);
-	void Update(SDL_Texture *texture);
 	void FadeOut(void) {
 		if (!m_faded) {
 			Fade();
@@ -125,6 +124,7 @@ class FrameBuf : public ErrorBase {
 		return m_fadeTexture ? true : false;
 	}
 	void FadeStep(void);
+	void FadeComplete(void);
 
 	/* Drawing routines */
 	void Clear(int x, int y, int w, int h) {
@@ -194,7 +194,6 @@ class FrameBuf : public ErrorBase {
 	/* The current display */
 	SDL_Window *m_window = nullptr;
 	SDL_Renderer *m_renderer = nullptr;
-	SDL_Texture *m_target = nullptr;
 	SDL_Texture *m_fadeTexture = nullptr;
 	int m_fadeStep = 0;
 	bool m_faded = false;
@@ -212,7 +211,7 @@ class FrameBuf : public ErrorBase {
 		r = (color >> 16) & 0xFF;
 		g = (color >>  8) & 0xFF;
 		b = (color >>  0) & 0xFF;
-		SDL_SetRenderDrawColor(m_renderer, r, g, b, 0xFF);
+		SDL_SetRenderDrawColor(m_renderer, r, g, b, SDL_ALPHA_OPAQUE);
 	}
 
 	void OpenGamepad(SDL_JoystickID id);
diff --git a/screenlib/UIManager.cpp b/screenlib/UIManager.cpp
index 6d33c860..cd316477 100644
--- a/screenlib/UIManager.cpp
+++ b/screenlib/UIManager.cpp
@@ -278,19 +278,30 @@ UIManager::ShowPanel(UIPanel *panel)
 void
 UIManager::HidePanel(UIPanel *panel)
 {
-	if (panel && m_visible.remove(panel)) {
-		panel->Hide();
-
+	if (panel && m_visible.find(panel)) {
 		if (m_panelTransition == PANEL_TRANSITION_FADE &&
-		    panel->IsFullscreen()) {
+			panel->IsFullscreen()) {
+			// Draw one last time so we have valid contents to fade
+			m_screen->Clear();
+			for (unsigned int i = 0; i < m_visible.length(); ++i) {
+				UIPanel* panel = m_visible[i];
+
+				for (int drawLevel = 0; drawLevel < NUM_DRAWLEVELS; ++drawLevel) {
+					panel->Draw((DRAWLEVEL)drawLevel);
+				}
+			}
 			m_screen->FadeOut();
 		}
+
+		panel->Hide();
+
 		if (!panel->IsCursorVisible()) {
 			m_screen->ShowCursor();
 		}
 		if (panel->IsCursorVisible()) {
 			m_screen->SetGamepadMouse(false);
 		}
+		m_visible.remove(panel);
 
 #ifdef FAST_ITERATION
 		// This is useful for iteration, panels are reloaded 
@@ -387,21 +398,21 @@ UIManager::Poll()
 }
 
 void
-UIManager::Draw(bool fullUpdate)
+UIManager::Draw(bool tick)
 {
 	unsigned int i;
 
 	// Run the tick before we draw in case it changes drawing state
-	for (i = 0; i < m_visible.length(); ++i) {
-		UIPanel *panel = m_visible[i];
+	if (tick) {
+		for (i = 0; i < m_visible.length(); ++i) {
+			UIPanel* panel = m_visible[i];
 
-		panel->Poll();
-		panel->Tick();
+			panel->Poll();
+			panel->Tick();
+		}
 	}
 
-	if (fullUpdate) {
-		m_screen->Clear();
-	}
+	m_screen->Clear();
 	for (i = 0; i < m_visible.length(); ++i) {
 		UIPanel *panel = m_visible[i];
 
@@ -409,11 +420,9 @@ UIManager::Draw(bool fullUpdate)
 			panel->Draw((DRAWLEVEL)drawLevel);
 		}
 	}
-	if (fullUpdate) {
-		m_screen->Update();
-		if (m_panelTransition == PANEL_TRANSITION_FADE) {
-			m_screen->FadeIn();
-		}
+	m_screen->Update();
+	if (m_panelTransition == PANEL_TRANSITION_FADE) {
+		m_screen->FadeIn();
 	}
 }
 
diff --git a/screenlib/UIManager.h b/screenlib/UIManager.h
index 1f428310..17148181 100644
--- a/screenlib/UIManager.h
+++ b/screenlib/UIManager.h
@@ -121,7 +121,7 @@ class UIManager : public UIArea, public UIFontInterface, public UIImageInterface
 	bool CheckCondition(const char *condition);
 
 	void Poll();
-	void Draw(bool fullUpdate = true);
+	void Draw(bool tick = true);
 	bool HandleEvent(const SDL_Event &event);
 
 	virtual void OnRectChanged() {