Skip to content
19 changes: 14 additions & 5 deletions CGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "s25util/file_handle.h"
#include <libsiedler2/ArchivItem_Ini.h>
#include <libsiedler2/libsiedler2.h>
#include <glad/glad.h>
#include <boost/filesystem.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/program_options.hpp>
Expand Down Expand Up @@ -43,6 +44,11 @@ CGame::CGame(Extent GameResolution_, bool fullscreen_)

CGame::~CGame()
{
if(glContext_)
{
SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr;
}
global::s2 = nullptr;
}

Expand All @@ -69,12 +75,15 @@ int CGame::Execute()
return 0;
}

void CGame::RenderPresent() const
void CGame::RenderPresent()
{
SDL_UpdateTexture(displayTexture_.get(), nullptr, Surf_Display->pixels, Surf_Display->w * sizeof(Uint32));
SDL_RenderClear(renderer_.get());
SDL_RenderCopy(renderer_.get(), displayTexture_.get(), nullptr, nullptr);
SDL_RenderPresent(renderer_.get());
displayTexture_.upload(Surf_Display->pixels);
displayTexture_.Draw(Rect(0, 0, GameResolution.x, GameResolution.y));

const auto& cursorImg = Cursor.clicked ? (Cursor.button.right ? cross_ : cursorClicked_) : cursor_;
cursorImg.Draw(Cursor.pos);

SDL_GL_SwapWindow(window_.get());
}

CMenu* CGame::RegisterMenu(std::unique_ptr<CMenu> Menu)
Expand Down
23 changes: 17 additions & 6 deletions CGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "CIO/CFont.h"
#include "SdlSurface.h"
#include "Texture.h"
#include <boost/filesystem/path.hpp>
#include <Point.h>
#include <memory>
Expand All @@ -27,8 +28,8 @@ class CGame
bool Running;
bool showLoadScreen;
SdlSurface Surf_Display;
SdlTexture displayTexture_;
SdlRenderer renderer_;
Texture displayTexture_;
SDL_GLContext glContext_ = nullptr;
SdlWindow window_;

private:
Expand All @@ -43,7 +44,14 @@ class CGame
CFont lastFps;

Uint32 lastFrameTime = 0;
unsigned suppressResizeEvents_ = 0;
Extent appliedResolution_ = Extent{0, 0}; ///< Last resolution we applied to the window/display
bool appliedFullscreen_ = false; ///< Last fullscreen state we applied

// Textures for splash screen and cursor
Texture splashBg_;
Texture cursor_;
Texture cursorClicked_;
Texture cross_;

// structure for mouse cursor
struct
Expand All @@ -67,9 +75,13 @@ class CGame
std::unique_ptr<CMap> MapObj;

void SetAppIcon();
void RecreateDisplayResources();
void setGLViewport();
bool CreateWindow();

public:
// Apply current GameResolution and fullscreen settings to the window/display.
void ApplyWindowChanges();

void LoadSettings();
void SaveSettings() const;

Expand All @@ -79,7 +91,6 @@ class CGame
int Execute();

bool Init();
bool ReCreateWindow();
void UpdateDisplaySize(const Extent& newSize);

void EventHandling(SDL_Event* Event);
Expand All @@ -88,7 +99,7 @@ class CGame

void Render();

void RenderPresent() const;
void RenderPresent();

CMenu* RegisterMenu(std::unique_ptr<CMenu> Menu);
bool UnregisterMenu(CMenu* Menu);
Expand Down
17 changes: 11 additions & 6 deletions CGame_Event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ void CGame::EventHandling(SDL_Event* Event)
if(Event->key.keysym.mod & KMOD_ALT)
{
fullscreen = !fullscreen;
ApplyWindowChanges();
SaveSettings();
}
break;
Expand Down Expand Up @@ -388,12 +389,16 @@ void CGame::EventHandling(SDL_Event* Event)
{
if(Event->window.event == SDL_WINDOWEVENT_RESIZED)
{
if(suppressResizeEvents_ > 0)
{
suppressResizeEvents_--;
break; // Skip stale event from our own window recreation
}
UpdateDisplaySize(Extent(Event->window.data1, Event->window.data2));
// In fullscreen the compositor (e.g. Wayland) may report a size
// different from the one we requested. We already applied the
// resolution ourselves, so don't let the event override it.
if(fullscreen)
break;
const Extent newSize(Event->window.data1, Event->window.data2);
// Ignore events matching the resolution we already applied.
if(newSize == appliedResolution_)
break;
UpdateDisplaySize(newSize);
}
break;
}
Expand Down
133 changes: 112 additions & 21 deletions CGame_Init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,49 +8,131 @@
#include "CIO/CMenu.h"
#include "CIO/CWindow.h"
#include "CMap.h"
#include "CSurface.h"
#include "callbacks.h"
#include "globals.h"
#include "lua/GameDataLoader.h"
#include <glad/glad.h>
#include <iostream>
#include <vector>

bool CGame::ReCreateWindow()
bool CGame::CreateWindow()
{
suppressResizeEvents_ = 3;
displayTexture_.reset();
renderer_.reset();
window_.reset();
if(window_)
return false;

window_.reset(SDL_CreateWindow("Return to the Roots Map editor [BETA]", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, GameResolution.x, GameResolution.y,
fullscreen ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_RESIZABLE));
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE));
if(!window_)
return false;
renderer_.reset(SDL_CreateRenderer(window_.get(), -1, 0));
if(!renderer_)

glContext_ = SDL_GL_CreateContext(window_.get());
if(!glContext_ || !gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
return false;
RecreateDisplayResources();
if(!displayTexture_ || !Surf_Display)

glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glClearColor(0, 0, 0, 1);

SDL_ShowWindow(window_.get());

ApplyWindowChanges();
Comment thread
Flamefire marked this conversation as resolved.
if(!displayTexture_.isValid() || !Surf_Display)
return false;

SetAppIcon();

return true;
}

void CGame::RecreateDisplayResources()
void CGame::ApplyWindowChanges()
Comment thread
Flamefire marked this conversation as resolved.
{
displayTexture_.reset();
displayTexture_ = makeSdlTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, GameResolution.x,
GameResolution.y);
Surf_Display = makeRGBSurface(GameResolution.x, GameResolution.y, true);
if(!window_)
return;

if(fullscreen)
{
SDL_DisplayMode dm;
SDL_zero(dm);
dm.w = static_cast<int>(GameResolution.x);
dm.h = static_cast<int>(GameResolution.y);
dm.format = 0; // let SDL pick a supported format
dm.refresh_rate = 0;
if(SDL_SetWindowDisplayMode(window_.get(), &dm) != 0)
Comment thread
Flamefire marked this conversation as resolved.
{
std::cerr << "SDL_SetWindowDisplayMode failed: " << SDL_GetError() << std::endl;
return;
}

const Uint32 flags = SDL_GetWindowFlags(window_.get());
if(!(flags & SDL_WINDOW_FULLSCREEN))
{
if(SDL_SetWindowFullscreen(window_.get(), SDL_WINDOW_FULLSCREEN) != 0)
{
std::cerr << "SDL_SetWindowFullscreen failed: " << SDL_GetError() << std::endl;
return;
}
} else if(GameResolution != appliedResolution_)
{
// Already fullscreen and the resolution changed. Toggle fullscreen off and
// back on so SDL/Wayland actually applies the new display mode.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that the part that fixed your issue with half-covered screen/rendering?

@morganchristiansson morganchristiansson Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the async events which can cause reverting of resolution switch.
Instead of the earlier suppressResizeEvents_ which was ugly but had no issues from it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And as for what the fix was. Honestly not completely sure.
If I worked on it by hand I would've had a eureka moment when it started working correctly.

My process is I prompt AI about issue, it reads code and reasons about it, makes changes, then I build, run and test, repeat.
AI doesn't get direct feedback except follow up prompts and toolcall results. It's a visual game it's not ideal to feed back to AI.
I suppose if AI could see and validate results directly then it could make more precise changes. Or a separate test program that proves correct implementation as resolution switching has proven quirky in both s25edit and s25client.
Anyway I kinda like it minimal and limited access and it's working mostly fine like this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that if we don't know what exactly caused and/or fixed the issue we cannot apply it to e.g. s25client or document it in code such that not in 2yrs someone sees strange code and decides to remove it.
I guess that if you try to change parts of this commit or ask your AI about what actually caused the fix and change that (with AI or manually) you'd get your eureka moment and we the fix for s25client ;-)

If it's too much work then we'll just need to keep it as-is

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can do it again in s25client with AI or we can make a small test program that reproduces and validates best practice.

Wayland doesn't even allow apps to change native monitor resolution, it instead rescales them, presumably in hardware. Which is also what modern LCD monitors do.

If you want to keep improving res switching I can make a standalone test program for this purpose.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can let your AI reduce the editor to a simple reproducer and minimal fix based on that commit that would great, yes. Like: Create fullscreen window with 2 textures, could be just red and blue where one rightclick you draw the other one and on leftclick you change the resolution. I expect this to be ~100 lines with most of it copied from s25edit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it can automatically proceed resolution switch, get window events, log what actually happened/didn't happen.

For us humans it might be extra work. For the AI it will save a lot of tokens making a tiny program it can work out without all the surrounding complexity in context.

if(SDL_SetWindowFullscreen(window_.get(), 0) != 0)
{
std::cerr << "SDL_SetWindowFullscreen(0) failed: " << SDL_GetError() << std::endl;
return;
}
SDL_SetWindowSize(window_.get(), GameResolution.x, GameResolution.y);
if(SDL_SetWindowFullscreen(window_.get(), SDL_WINDOW_FULLSCREEN) != 0)
{
std::cerr << "SDL_SetWindowFullscreen failed: " << SDL_GetError() << std::endl;
return;
}
}
} else
{
if(SDL_SetWindowFullscreen(window_.get(), 0) != 0)
{
std::cerr << "SDL_SetWindowFullscreen failed: " << SDL_GetError() << std::endl;
return;
}
SDL_SetWindowSize(window_.get(), GameResolution.x, GameResolution.y);
SDL_SetWindowPosition(window_.get(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}

UpdateDisplaySize(GameResolution);
}

void CGame::setGLViewport()
{
if(!window_)
return;
int w = 0, h = 0;
SDL_GL_GetDrawableSize(window_.get(), &w, &h);
if(w == 0 || h == 0)
return;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, GameResolution.x, GameResolution.y, 0, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void CGame::UpdateDisplaySize(const Extent& newSize)
{
GameResolution = newSize;
RecreateDisplayResources();
appliedResolution_ = GameResolution;
appliedFullscreen_ = fullscreen;

Surf_Display = makeRGBSurface(GameResolution.x, GameResolution.y, true);
displayTexture_.createEmpty(GameResolution);

setGLViewport();
for(auto& menu : Menus)
{
menu->resetSurface();
}
for(auto& wnd : Windows)
wnd->resetSurface();
}
Expand All @@ -62,7 +144,7 @@ bool CGame::Init()
SDL_ShowCursor(SDL_DISABLE);

std::cout << "Create Window...";
if(!ReCreateWindow())
if(!CreateWindow())
{
std::cout << "failure";
return false;
Expand Down Expand Up @@ -94,10 +176,14 @@ bool CGame::Init()
}
}

// Create texture for splash background
splashBg_.load(global::bmpArray[SPLASHSCREEN_LOADING_S2SCREEN].surface.get(), true);

// std::cout << "\nShow loading screen...";
showLoadScreen = true;
CSurface::DrawStretched(Surf_Display, global::bmpArray[SPLASHSCREEN_LOADING_S2SCREEN].surface);
RenderPresent();
glClear(GL_COLOR_BUFFER_BIT);
splashBg_.Draw(Rect(0, 0, GameResolution.x, GameResolution.y));
SDL_GL_SwapWindow(window_.get());

GameDataLoader gdLoader(global::worldDesc);
if(!gdLoader.Load())
Expand Down Expand Up @@ -252,5 +338,10 @@ bool CGame::Init()
// create the mainmenu
callback::mainmenu(INITIALIZING_CALL);

// Create textures for cursor
cursor_.load(global::bmpArray[CURSOR].surface.get());
cursorClicked_.load(global::bmpArray[CURSOR_CLICKED].surface.get());
cross_.load(global::bmpArray[CROSS].surface.get());

return true;
}
23 changes: 5 additions & 18 deletions CGame_Render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "CMap.h"
#include "CSurface.h"
#include "globals.h"
#include <glad/glad.h>
#ifdef _WIN32
# include "s25editResource.h"
# ifndef WIN32_LEAN_AND_MEAN
Expand Down Expand Up @@ -38,18 +39,14 @@ void CGame::SetAppIcon()

void CGame::Render()
{
suppressResizeEvents_ = 0;
if(Extent(Surf_Display->w, Surf_Display->h) != GameResolution
|| fullscreen != ((SDL_GetWindowFlags(window_.get()) & SDL_WINDOW_FULLSCREEN) != 0))
{
ReCreateWindow();
}
glClear(GL_COLOR_BUFFER_BIT);
SDL_FillRect(Surf_Display.get(), nullptr, SDL_MapRGBA(Surf_Display->format, 0, 0, 0, 0));

// if the S2 loading screen is shown, render only this until user clicks a mouse button
if(showLoadScreen)
{
CSurface::DrawStretched(Surf_Display, global::bmpArray[SPLASHSCREEN_LOADING_S2SCREEN].surface);
RenderPresent();
splashBg_.Draw(Rect(0, 0, GameResolution.x, GameResolution.y));
SDL_GL_SwapWindow(window_.get());
return;
}

Expand Down Expand Up @@ -103,16 +100,6 @@ void CGame::Render()
}
}

// render mouse cursor
if(Cursor.clicked)
{
if(Cursor.button.right)
CSurface::Draw(Surf_Display, global::bmpArray[CROSS].surface, Cursor.pos);
else
CSurface::Draw(Surf_Display, global::bmpArray[CURSOR_CLICKED].surface, Cursor.pos);
} else
CSurface::Draw(Surf_Display, global::bmpArray[CURSOR].surface, Cursor.pos);

#ifdef _ADMINMODE
FrameCounter++;
#endif
Expand Down
2 changes: 2 additions & 0 deletions CIO/CFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ bool CFile::read_bbm(FILE* fp)
CHECK_READ(libendian::read(&(color.r), 1, fp));
CHECK_READ(libendian::read(&(color.g), 1, fp));
CHECK_READ(libendian::read(&(color.b), 1, fp));
color.a = 255;
}

palArray++;
Expand Down Expand Up @@ -1250,6 +1251,7 @@ bool CFile::read_bob05(FILE* fp)
CHECK_READ(libendian::read(&(color.r), 1, fp));
CHECK_READ(libendian::read(&(color.g), 1, fp));
CHECK_READ(libendian::read(&(color.b), 1, fp));
color.a = 255;
}

palArray++;
Expand Down
Loading