Changeset - r24851:a41e925d9dc7
[Not reviewed]
master
0 11 0
Patric Stout - 3 years ago 2021-02-20 10:08:20
truebrain@openttd.org
Codechange: be consistent in naming the paint function Paint()

Also move this function to be a class member. This to allow
further deduplicating of code in a later commit.
11 files changed with 58 insertions and 23 deletions:
0 comments (0 inline, 0 general)
src/video/allegro_v.cpp
Show inline comments
 
@@ -11,97 +11,97 @@
 
 *       not faster (it's a few percent slower) in contrast to the
 
 *       results gained with threading it for SDL.
 
 */
 

	
 
#ifdef WITH_ALLEGRO
 

	
 
#include "../stdafx.h"
 
#include "../openttd.h"
 
#include "../gfx_func.h"
 
#include "../rev.h"
 
#include "../blitter/factory.hpp"
 
#include "../network/network.h"
 
#include "../core/random_func.hpp"
 
#include "../core/math_func.hpp"
 
#include "../framerate_type.h"
 
#include "../progress.h"
 
#include "../thread.h"
 
#include "../window_func.h"
 
#include "allegro_v.h"
 
#include <allegro.h>
 

	
 
#include "../safeguards.h"
 

	
 
#ifdef _DEBUG
 
/* Allegro replaces SEGV/ABRT signals meaning that the debugger will never
 
 * be triggered, so rereplace the signals and make the debugger useful. */
 
#include <signal.h>
 
#endif
 

	
 
static FVideoDriver_Allegro iFVideoDriver_Allegro;
 

	
 
static BITMAP *_allegro_screen;
 

	
 
#define MAX_DIRTY_RECTS 100
 
static PointDimension _dirty_rects[MAX_DIRTY_RECTS];
 
static int _num_dirty_rects;
 

	
 
void VideoDriver_Allegro::MakeDirty(int left, int top, int width, int height)
 
{
 
	if (_num_dirty_rects < MAX_DIRTY_RECTS) {
 
		_dirty_rects[_num_dirty_rects].x = left;
 
		_dirty_rects[_num_dirty_rects].y = top;
 
		_dirty_rects[_num_dirty_rects].width = width;
 
		_dirty_rects[_num_dirty_rects].height = height;
 
	}
 
	_num_dirty_rects++;
 
}
 

	
 
static void DrawSurfaceToScreen()
 
void VideoDriver_Allegro::Paint()
 
{
 
	PerformanceMeasurer framerate(PFE_VIDEO);
 

	
 
	int n = _num_dirty_rects;
 
	if (n == 0) return;
 

	
 
	_num_dirty_rects = 0;
 
	if (n > MAX_DIRTY_RECTS) {
 
		blit(_allegro_screen, screen, 0, 0, 0, 0, _allegro_screen->w, _allegro_screen->h);
 
		return;
 
	}
 

	
 
	for (int i = 0; i < n; i++) {
 
		blit(_allegro_screen, screen, _dirty_rects[i].x, _dirty_rects[i].y, _dirty_rects[i].x, _dirty_rects[i].y, _dirty_rects[i].width, _dirty_rects[i].height);
 
	}
 
}
 

	
 

	
 
static void UpdatePalette(uint start, uint count)
 
{
 
	static PALETTE pal;
 

	
 
	uint end = start + count;
 
	for (uint i = start; i != end; i++) {
 
		pal[i].r = _cur_palette.palette[i].r / 4;
 
		pal[i].g = _cur_palette.palette[i].g / 4;
 
		pal[i].b = _cur_palette.palette[i].b / 4;
 
		pal[i].filler = 0;
 
	}
 

	
 
	set_palette_range(pal, start, end - 1, 1);
 
}
 

	
 
static void InitPalette()
 
{
 
	UpdatePalette(0, 256);
 
}
 

	
 
static void CheckPaletteAnim()
 
{
 
	if (_cur_palette.count_dirty != 0) {
 
		Blitter *blitter = BlitterFactory::GetCurrentBlitter();
 

	
 
		switch (blitter->UsePaletteAnimation()) {
 
			case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
 
				UpdatePalette(_cur_palette.first_dirty, _cur_palette.count_dirty);
 
				break;
 

	
 
@@ -479,87 +479,87 @@ void VideoDriver_Allegro::InputLoop()
 

	
 
void VideoDriver_Allegro::MainLoop()
 
{
 
	auto cur_ticks = std::chrono::steady_clock::now();
 
	auto last_realtime_tick = cur_ticks;
 
	auto next_game_tick = cur_ticks;
 
	auto next_draw_tick = cur_ticks;
 

	
 
	CheckPaletteAnim();
 

	
 
	for (;;) {
 
		InteractiveRandom(); // randomness
 

	
 
		PollEvent();
 
		if (_exit_game) return;
 

	
 
		cur_ticks = std::chrono::steady_clock::now();
 

	
 
		/* If more than a millisecond has passed, increase the _realtime_tick. */
 
		if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
 
			auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
 
			_realtime_tick += delta.count();
 
			last_realtime_tick += delta;
 
		}
 

	
 
		if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
 
			if (_fast_forward && !_pause_mode) {
 
				next_game_tick = cur_ticks + this->GetGameInterval();
 
			} else {
 
				next_game_tick += this->GetGameInterval();
 
				/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
 
				if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
 
			}
 

	
 
			GameLoop();
 
		}
 

	
 
		/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
 
		if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
 
			next_draw_tick += this->GetDrawInterval();
 
			/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
 
			if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
 

	
 
			this->InputLoop();
 
			::InputLoop();
 
			UpdateWindows();
 
			CheckPaletteAnim();
 

	
 
			DrawSurfaceToScreen();
 
			this->Paint();
 
		}
 

	
 
		/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
 
		if (!_fast_forward || _pause_mode) {
 
			/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
 
			auto next_tick = std::min(next_draw_tick, next_game_tick);
 
			auto now = std::chrono::steady_clock::now();
 

	
 
			if (next_tick > now) {
 
				std::this_thread::sleep_for(next_tick - now);
 
			}
 
		}
 
	}
 
}
 

	
 
bool VideoDriver_Allegro::ChangeResolution(int w, int h)
 
{
 
	return CreateMainSurface(w, h);
 
}
 

	
 
bool VideoDriver_Allegro::ToggleFullscreen(bool fullscreen)
 
{
 
	_fullscreen = fullscreen;
 
	GetVideoModes(); // get the list of available video modes
 
	if (_resolutions.empty() || !this->ChangeResolution(_cur_resolution.width, _cur_resolution.height)) {
 
		/* switching resolution failed, put back full_screen to original status */
 
		_fullscreen ^= true;
 
		return false;
 
	}
 
	return true;
 
}
 

	
 
bool VideoDriver_Allegro::AfterBlitterChange()
 
{
 
	return CreateMainSurface(_screen.width, _screen.height);
 
}
 

	
 
#endif /* WITH_ALLEGRO */
src/video/allegro_v.h
Show inline comments
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file allegro_v.h Base of the Allegro video driver. */
 

	
 
#ifndef VIDEO_ALLEGRO_H
 
#define VIDEO_ALLEGRO_H
 

	
 
#include "video_driver.hpp"
 

	
 
/** The allegro video driver. */
 
class VideoDriver_Allegro : public VideoDriver {
 
public:
 
	const char *Start(const StringList &param) override;
 

	
 
	void Stop() override;
 

	
 
	void MakeDirty(int left, int top, int width, int height) override;
 

	
 
	void MainLoop() override;
 

	
 
	bool ChangeResolution(int w, int h) override;
 

	
 
	bool ToggleFullscreen(bool fullscreen) override;
 

	
 
	bool AfterBlitterChange() override;
 

	
 
	bool ClaimMousePointer() override;
 

	
 
	const char *GetName() const override { return "allegro"; }
 

	
 
protected:
 
	void InputLoop() override;
 
	void Paint() override;
 
};
 

	
 
/** Factory for the allegro video driver. */
 
class FVideoDriver_Allegro : public DriverFactoryBase {
 
public:
 
	FVideoDriver_Allegro() : DriverFactoryBase(Driver::DT_VIDEO, 4, "allegro", "Allegro Video Driver") {}
 
	Driver *CreateInstance() const override { return new VideoDriver_Allegro(); }
 
};
 

	
 
#endif /* VIDEO_ALLEGRO_H */
src/video/cocoa/cocoa_v.h
Show inline comments
 
@@ -30,73 +30,73 @@ private:
 

	
 
	int buffer_depth;     ///< Colour depth of used frame buffer
 
	void *pixel_buffer;   ///< used for direct pixel access
 
	void *window_buffer;  ///< Colour translation from palette to screen
 

	
 
	static const int MAX_DIRTY_RECTS = 100;
 

	
 
	Rect dirty_rects[MAX_DIRTY_RECTS]; ///< dirty rectangles
 
	uint num_dirty_rects;  ///< Number of dirty rectangles
 
	uint32 palette[256];  ///< Colour Palette
 

	
 
public:
 
	bool setup; ///< Window is currently being created.
 

	
 
	OTTD_CocoaWindow *window;    ///< Pointer to window object
 
	OTTD_CocoaView *cocoaview;   ///< Pointer to view object
 
	CGColorSpaceRef color_space; ///< Window color space
 
	CGContextRef cgcontext;      ///< Context reference for Quartz subdriver
 

	
 
	OTTD_CocoaWindowDelegate *delegate; //!< Window delegate object
 

	
 
public:
 
	VideoDriver_Cocoa();
 

	
 
	const char *Start(const StringList &param) override;
 
	void Stop() override;
 
	void MainLoop() override;
 

	
 
	void MakeDirty(int left, int top, int width, int height) override;
 
	bool AfterBlitterChange() override;
 

	
 
	bool ChangeResolution(int w, int h) override;
 
	bool ToggleFullscreen(bool fullscreen) override;
 

	
 
	void EditBoxLostFocus() override;
 

	
 
	const char *GetName() const override { return "cocoa"; }
 

	
 
	/* --- The following methods should be private, but can't be due to Obj-C limitations. --- */
 

	
 
	void GameLoop();
 

	
 
	void AllocateBackingStore();
 

	
 
protected:
 
	Dimension GetScreenSize() const override;
 
	float GetDPIScale() override;
 
	void InputLoop() override;
 
	void Paint() override;
 

	
 
private:
 
	bool PollEvent();
 

	
 
	bool IsFullscreen();
 
	void GameSizeChanged();
 

	
 
	void UpdateVideoModes();
 

	
 
	bool MakeWindow(int width, int height);
 

	
 
	void UpdatePalette(uint first_color, uint num_colors);
 
	void CheckPaletteAnim();
 

	
 
	void Draw(bool force_update = false);
 
	void BlitIndexedToView32(int left, int top, int right, int bottom);
 
};
 

	
 
class FVideoDriver_Cocoa : public DriverFactoryBase {
 
public:
 
	FVideoDriver_Cocoa() : DriverFactoryBase(Driver::DT_VIDEO, 10, "cocoa", "Cocoa Video Driver") {}
 
	Driver *CreateInstance() const override { return new VideoDriver_Cocoa(); }
 
};
 

	
 
#endif /* VIDEO_COCOA_H */
src/video/cocoa/cocoa_v.mm
Show inline comments
 
@@ -420,136 +420,135 @@ bool VideoDriver_Cocoa::MakeWindow(int w
 
		return false;
 
	}
 
	[ draw_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
 

	
 
	/* Create view chain: window -> input wrapper view -> content view. */
 
	[ this->window setContentView:this->cocoaview ];
 
	[ this->cocoaview addSubview:draw_view ];
 
	[ this->window makeFirstResponder:this->cocoaview ];
 
	[ draw_view release ];
 

	
 
	[ this->window setColorSpace:[ NSColorSpace sRGBColorSpace ] ];
 
	CGColorSpaceRelease(this->color_space);
 
	this->color_space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
 
	if (this->color_space == nullptr) this->color_space = CGColorSpaceCreateDeviceRGB();
 
	if (this->color_space == nullptr) error("Could not get a valid colour space for drawing.");
 

	
 
	this->setup = false;
 

	
 
	this->UpdatePalette(0, 256);
 
	this->AllocateBackingStore();
 

	
 
	return true;
 
}
 

	
 
/**
 
 * This function copies 8bpp pixels to the screen buffer in 32bpp windowed mode.
 
 *
 
 * @param left The x coord for the left edge of the box to blit.
 
 * @param top The y coord for the top edge of the box to blit.
 
 * @param right The x coord for the right edge of the box to blit.
 
 * @param bottom The y coord for the bottom edge of the box to blit.
 
 */
 
void VideoDriver_Cocoa::BlitIndexedToView32(int left, int top, int right, int bottom)
 
{
 
	const uint32 *pal   = this->palette;
 
	const uint8  *src   = (uint8*)this->pixel_buffer;
 
	uint32       *dst   = (uint32*)this->window_buffer;
 
	uint          width = this->window_width;
 
	uint          pitch = this->window_pitch;
 

	
 
	for (int y = top; y < bottom; y++) {
 
		for (int x = left; x < right; x++) {
 
			dst[y * pitch + x] = pal[src[y * width + x]];
 
		}
 
	}
 
}
 

	
 
/**
 
 * Draw window.
 
 * Paint window.
 
 * @param force_update Whether to redraw unconditionally
 
 */
 
void VideoDriver_Cocoa::Draw(bool force_update)
 
void VideoDriver_Cocoa::Paint()
 
{
 
	PerformanceMeasurer framerate(PFE_VIDEO);
 

	
 
	/* Check if we need to do anything */
 
	if (this->num_dirty_rects == 0 || [ this->window isMiniaturized ]) return;
 

	
 
	if (this->num_dirty_rects >= lengthof(this->dirty_rects)) {
 
		this->num_dirty_rects = 1;
 
		this->dirty_rects[0].left = 0;
 
		this->dirty_rects[0].top = 0;
 
		this->dirty_rects[0].right = this->window_width;
 
		this->dirty_rects[0].bottom = this->window_height;
 
	}
 

	
 
	/* Build the region of dirty rectangles */
 
	for (uint i = 0; i < this->num_dirty_rects; i++) {
 
		/* We only need to blit in indexed mode since in 32bpp mode the game draws directly to the image. */
 
		if (this->buffer_depth == 8) {
 
			BlitIndexedToView32(
 
				this->dirty_rects[i].left,
 
				this->dirty_rects[i].top,
 
				this->dirty_rects[i].right,
 
				this->dirty_rects[i].bottom
 
			);
 
		}
 

	
 
		NSRect dirtyrect;
 
		dirtyrect.origin.x = this->dirty_rects[i].left;
 
		dirtyrect.origin.y = this->window_height - this->dirty_rects[i].bottom;
 
		dirtyrect.size.width = this->dirty_rects[i].right - this->dirty_rects[i].left;
 
		dirtyrect.size.height = this->dirty_rects[i].bottom - this->dirty_rects[i].top;
 

	
 
		/* Normally drawRect will be automatically called by Mac OS X during next update cycle,
 
		 * and then blitting will occur. If force_update is true, it will be done right now. */
 
		 * and then blitting will occur. */
 
		[ this->cocoaview setNeedsDisplayInRect:[ this->cocoaview getVirtualRect:dirtyrect ] ];
 
		if (force_update) [ this->cocoaview displayIfNeeded ];
 
	}
 

	
 
	this->num_dirty_rects = 0;
 
}
 

	
 
/** Update the palette. */
 
void VideoDriver_Cocoa::UpdatePalette(uint first_color, uint num_colors)
 
{
 
	if (this->buffer_depth != 8) return;
 

	
 
	for (uint i = first_color; i < first_color + num_colors; i++) {
 
		uint32 clr = 0xff000000;
 
		clr |= (uint32)_cur_palette.palette[i].r << 16;
 
		clr |= (uint32)_cur_palette.palette[i].g << 8;
 
		clr |= (uint32)_cur_palette.palette[i].b;
 
		this->palette[i] = clr;
 
	}
 

	
 
	this->num_dirty_rects = lengthof(this->dirty_rects);
 
}
 

	
 
/** Clear buffer to opaque black. */
 
static void ClearWindowBuffer(uint32 *buffer, uint32 pitch, uint32 height)
 
{
 
	uint32 fill = Colour(0, 0, 0).data;
 
	for (uint32 y = 0; y < height; y++) {
 
		for (uint32 x = 0; x < pitch; x++) {
 
			buffer[y * pitch + x] = fill;
 
		}
 
	}
 
}
 

	
 
/** Resize the window. */
 
void VideoDriver_Cocoa::AllocateBackingStore()
 
{
 
	if (this->window == nil || this->cocoaview == nil || this->setup) return;
 

	
 
	NSRect newframe = [ this->cocoaview getRealRect:[ this->cocoaview frame ] ];
 

	
 
	this->window_width = (int)newframe.size.width;
 
	this->window_height = (int)newframe.size.height;
 
	this->window_pitch = Align(this->window_width, 16 / sizeof(uint32)); // Quartz likes lines that are multiple of 16-byte.
 
	this->buffer_depth = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
 

	
 
	/* Create Core Graphics Context */
 
	free(this->window_buffer);
 
	this->window_buffer = malloc(this->window_pitch * this->window_height * sizeof(uint32));
 
	/* Initialize with opaque black. */
 
@@ -663,97 +662,97 @@ void VideoDriver_Cocoa::GameLoop()
 
	auto next_draw_tick = cur_ticks;
 

	
 
	for (;;) {
 
		@autoreleasepool {
 

	
 
			InteractiveRandom(); // randomness
 

	
 
			while (this->PollEvent()) {}
 

	
 
			if (_exit_game) {
 
				/* Restore saved resolution if in fullscreen mode. */
 
				if (this->IsFullscreen()) _cur_resolution = this->orig_res;
 
				break;
 
			}
 

	
 

	
 
			cur_ticks = std::chrono::steady_clock::now();
 

	
 
			/* If more than a millisecond has passed, increase the _realtime_tick. */
 
			if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
 
				auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
 
				_realtime_tick += delta.count();
 
				last_realtime_tick += delta;
 
			}
 

	
 
			if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
 
				if (_fast_forward && !_pause_mode) {
 
					next_game_tick = cur_ticks + this->GetGameInterval();
 
				} else {
 
					next_game_tick += this->GetGameInterval();
 
					/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
 
					if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
 
				}
 

	
 
				::GameLoop();
 
			}
 

	
 
			/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
 
			if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
 
				next_draw_tick += this->GetDrawInterval();
 
				/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
 
				if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
 

	
 
				this->InputLoop();
 
				::InputLoop();
 
				UpdateWindows();
 
				this->CheckPaletteAnim();
 

	
 
				this->Draw();
 
				this->Paint();
 
			}
 

	
 
			/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
 
			if (!_fast_forward || _pause_mode) {
 
				/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
 
				auto next_tick = std::min(next_draw_tick, next_game_tick);
 
				auto now = std::chrono::steady_clock::now();
 

	
 
				if (next_tick > now) {
 
					std::this_thread::sleep_for(next_tick - now);
 
				}
 
			}
 
		}
 
	}
 
}
 

	
 

	
 
@implementation OTTD_QuartzView
 

	
 
- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv
 
{
 
	if (self = [ super initWithFrame:frameRect ]) {
 
		self->driver = drv;
 

	
 
		/* We manage our content updates ourselves. */
 
		self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
 
		self.wantsLayer = YES;
 

	
 
		self.layer.magnificationFilter = kCAFilterNearest;
 
	}
 
	return self;
 
}
 

	
 
- (BOOL)acceptsFirstResponder
 
{
 
	return NO;
 
}
 

	
 
- (BOOL)isOpaque
 
{
 
	return YES;
 
}
 

	
 
- (BOOL)wantsUpdateLayer
 
{
 
	return YES;
 
}
 

	
src/video/sdl2_v.cpp
Show inline comments
 
@@ -79,149 +79,154 @@ static void UpdatePalette()
 
	SDL_SetSurfacePalette(_sdl_surface, _sdl_palette);
 
}
 

	
 
static void MakePalette()
 
{
 
	if (_sdl_palette == nullptr) {
 
		_sdl_palette = SDL_AllocPalette(256);
 
		if (_sdl_palette == nullptr) usererror("SDL2: Couldn't allocate palette: %s", SDL_GetError());
 
	}
 

	
 
	_cur_palette.first_dirty = 0;
 
	_cur_palette.count_dirty = 256;
 
	_local_palette = _cur_palette;
 
	UpdatePalette();
 

	
 
	if (_sdl_surface != _sdl_real_surface) {
 
		/* When using a shadow surface, also set our palette on the real screen. This lets SDL
 
		 * allocate as many colors (or approximations) as
 
		 * possible, instead of using only the default SDL
 
		 * palette. This allows us to get more colors exactly
 
		 * right and might allow using better approximations for
 
		 * other colors.
 
		 *
 
		 * Note that colors allocations are tried in-order, so
 
		 * this favors colors further up into the palette. Also
 
		 * note that if two colors from the same animation
 
		 * sequence are approximated using the same color, that
 
		 * animation will stop working.
 
		 *
 
		 * Since changing the system palette causes the colours
 
		 * to change right away, and allocations might
 
		 * drastically change, we can't use this for animation,
 
		 * since that could cause weird coloring between the
 
		 * palette change and the blitting below, so we only set
 
		 * the real palette during initialisation.
 
		 */
 
		SDL_SetSurfacePalette(_sdl_real_surface, _sdl_palette);
 
	}
 
}
 

	
 
void VideoDriver_SDL::CheckPaletteAnim()
 
{
 
	if (_cur_palette.count_dirty == 0) return;
 

	
 
	_local_palette = _cur_palette;
 
	this->MakeDirty(0, 0, _screen.width, _screen.height);
 
}
 

	
 
static void Paint()
 
void VideoDriver_SDL::Paint()
 
{
 
	PerformanceMeasurer framerate(PFE_VIDEO);
 

	
 
	if (IsEmptyRect(_dirty_rect) && _cur_palette.count_dirty == 0) return;
 

	
 
	if (_cur_palette.count_dirty != 0) {
 
		Blitter *blitter = BlitterFactory::GetCurrentBlitter();
 

	
 
		switch (blitter->UsePaletteAnimation()) {
 
			case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
 
				UpdatePalette();
 
				break;
 

	
 
			case Blitter::PALETTE_ANIMATION_BLITTER:
 
				blitter->PaletteAnimate(_local_palette);
 
				break;
 

	
 
			case Blitter::PALETTE_ANIMATION_NONE:
 
				break;
 

	
 
			default:
 
				NOT_REACHED();
 
		}
 
		_cur_palette.count_dirty = 0;
 
	}
 

	
 
	SDL_Rect r = { _dirty_rect.left, _dirty_rect.top, _dirty_rect.right - _dirty_rect.left, _dirty_rect.bottom - _dirty_rect.top };
 

	
 
	if (_sdl_surface != _sdl_real_surface) {
 
		SDL_BlitSurface(_sdl_surface, &r, _sdl_real_surface, &r);
 
	}
 
	SDL_UpdateWindowSurfaceRects(_sdl_window, &r, 1);
 

	
 
	MemSetT(&_dirty_rect, 0);
 
}
 

	
 
static void PaintThread()
 
void VideoDriver_SDL::PaintThread()
 
{
 
	/* First tell the main thread we're started */
 
	std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
 
	_draw_signal->notify_one();
 

	
 
	/* Now wait for the first thing to draw! */
 
	_draw_signal->wait(*_draw_mutex);
 

	
 
	while (_draw_continue) {
 
		/* Then just draw and wait till we stop */
 
		Paint();
 
		this->Paint();
 
		_draw_signal->wait(lock);
 
	}
 
}
 

	
 
/* static */ void VideoDriver_SDL::PaintThreadThunk(VideoDriver_SDL *drv)
 
{
 
	drv->PaintThread();
 
}
 

	
 
static const Dimension default_resolutions[] = {
 
	{  640,  480 },
 
	{  800,  600 },
 
	{ 1024,  768 },
 
	{ 1152,  864 },
 
	{ 1280,  800 },
 
	{ 1280,  960 },
 
	{ 1280, 1024 },
 
	{ 1400, 1050 },
 
	{ 1600, 1200 },
 
	{ 1680, 1050 },
 
	{ 1920, 1200 }
 
};
 

	
 
static void FindResolutions()
 
{
 
	_resolutions.clear();
 

	
 
	for (int i = 0; i < SDL_GetNumDisplayModes(0); i++) {
 
		SDL_DisplayMode mode;
 
		SDL_GetDisplayMode(0, i, &mode);
 

	
 
		if (mode.w < 640 || mode.h < 480) continue;
 
		if (std::find(_resolutions.begin(), _resolutions.end(), Dimension(mode.w, mode.h)) != _resolutions.end()) continue;
 
		_resolutions.emplace_back(mode.w, mode.h);
 
	}
 

	
 
	/* We have found no resolutions, show the default list */
 
	if (_resolutions.empty()) {
 
		_resolutions.assign(std::begin(default_resolutions), std::end(default_resolutions));
 
	}
 

	
 
	SortResolutions();
 
}
 

	
 
static void GetAvailableVideoMode(uint *w, uint *h)
 
{
 
	/* All modes available? */
 
	if (!_fullscreen || _resolutions.empty()) return;
 

	
 
	/* Is the wanted mode among the available modes? */
 
	if (std::find(_resolutions.begin(), _resolutions.end(), Dimension(*w, *h)) != _resolutions.end()) return;
 

	
 
	/* Use the closest possible resolution */
 
	uint best = 0;
 
	uint delta = Delta(_resolutions[0].width, *w) * Delta(_resolutions[0].height, *h);
 
	for (uint i = 1; i != _resolutions.size(); ++i) {
 
		uint newdelta = Delta(_resolutions[i].width, *w) * Delta(_resolutions[i].height, *h);
 
@@ -775,138 +780,138 @@ void VideoDriver_SDL::LoopOnce()
 
		 * normally done at the end of the main loop for non-Emscripten.
 
		 * After that, Emscripten just halts, and the HTML shows a nice
 
		 * "bye, see you next time" message. */
 
		emscripten_cancel_main_loop();
 
		MainLoopCleanup();
 
#endif
 
		return;
 
	}
 

	
 
	cur_ticks = std::chrono::steady_clock::now();
 

	
 
	/* If more than a millisecond has passed, increase the _realtime_tick. */
 
	if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
 
		auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
 
		_realtime_tick += delta.count();
 
		last_realtime_tick += delta;
 
	}
 

	
 
	if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
 
		if (_fast_forward && !_pause_mode) {
 
			next_game_tick = cur_ticks + this->GetGameInterval();
 
		} else {
 
			next_game_tick += this->GetGameInterval();
 
			/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
 
			if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
 
		}
 

	
 
		/* The gameloop is the part that can run asynchronously. The rest
 
		 * except sleeping can't. */
 
		this->UnlockVideoBuffer();
 
		GameLoop();
 
		this->LockVideoBuffer();
 
	}
 

	
 
	/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
 
	if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
 
		next_draw_tick += this->GetDrawInterval();
 
		/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
 
		if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
 

	
 
		this->InputLoop();
 
		::InputLoop();
 
		UpdateWindows();
 
		this->CheckPaletteAnim();
 

	
 
		if (_draw_mutex != nullptr && !HasModalProgress()) {
 
			_draw_signal->notify_one();
 
		} else {
 
			Paint();
 
			this->Paint();
 
		}
 
	}
 

	
 
/* Emscripten is running an event-based mainloop; there is already some
 
 * downtime between each iteration, so no need to sleep. */
 
#ifndef __EMSCRIPTEN__
 
	/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
 
	if (!_fast_forward || _pause_mode) {
 
		/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
 
		auto next_tick = std::min(next_draw_tick, next_game_tick);
 
		auto now = std::chrono::steady_clock::now();
 

	
 
		if (next_tick > now) {
 
			this->UnlockVideoBuffer();
 
			std::this_thread::sleep_for(next_tick - now);
 
			this->LockVideoBuffer();
 
		}
 
	}
 
#endif
 
}
 

	
 
void VideoDriver_SDL::MainLoop()
 
{
 
	cur_ticks = std::chrono::steady_clock::now();
 
	last_realtime_tick = cur_ticks;
 
	next_game_tick = cur_ticks;
 

	
 
	this->CheckPaletteAnim();
 

	
 
	if (_draw_threaded) {
 
		/* Initialise the mutex first, because that's the thing we *need*
 
		 * directly in the newly created thread. */
 
		_draw_mutex = new std::recursive_mutex();
 
		if (_draw_mutex == nullptr) {
 
			_draw_threaded = false;
 
		} else {
 
			draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
 
			_draw_signal = new std::condition_variable_any();
 
			_draw_continue = true;
 

	
 
			_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &PaintThread);
 
			_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &VideoDriver_SDL::PaintThreadThunk, this);
 

	
 
			/* Free the mutex if we won't be able to use it. */
 
			if (!_draw_threaded) {
 
				draw_lock.unlock();
 
				draw_lock.release();
 
				delete _draw_mutex;
 
				delete _draw_signal;
 
				_draw_mutex = nullptr;
 
				_draw_signal = nullptr;
 
			} else {
 
				/* Wait till the draw mutex has started itself. */
 
				_draw_signal->wait(*_draw_mutex);
 
			}
 
		}
 
	}
 

	
 
	DEBUG(driver, 1, "SDL2: using %sthreads", _draw_threaded ? "" : "no ");
 

	
 
#ifdef __EMSCRIPTEN__
 
	/* Run the main loop event-driven, based on RequestAnimationFrame. */
 
	emscripten_set_main_loop_arg(&this->EmscriptenLoop, this, 0, 1);
 
#else
 
	while (!_exit_game) {
 
		LoopOnce();
 
	}
 

	
 
	MainLoopCleanup();
 
#endif
 
}
 

	
 
void VideoDriver_SDL::MainLoopCleanup()
 
{
 
	if (_draw_mutex != nullptr) {
 
		_draw_continue = false;
 
		/* Sending signal if there is no thread blocked
 
		 * is very valid and results in noop */
 
		_draw_signal->notify_one();
 
		if (draw_lock.owns_lock()) draw_lock.unlock();
 
		draw_lock.release();
 
		draw_thread.join();
 

	
 
		delete _draw_mutex;
 
		delete _draw_signal;
 

	
 
		_draw_mutex = nullptr;
 
		_draw_signal = nullptr;
 
	}
 

	
src/video/sdl2_v.h
Show inline comments
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file sdl2_v.h Base of the SDL2 video driver. */
 

	
 
#ifndef VIDEO_SDL_H
 
#define VIDEO_SDL_H
 

	
 
#include "video_driver.hpp"
 

	
 
/** The SDL video driver. */
 
class VideoDriver_SDL : public VideoDriver {
 
public:
 
	const char *Start(const StringList &param) override;
 

	
 
	void Stop() override;
 

	
 
	void MakeDirty(int left, int top, int width, int height) override;
 

	
 
	void MainLoop() override;
 

	
 
	bool ChangeResolution(int w, int h) override;
 

	
 
	bool ToggleFullscreen(bool fullscreen) override;
 

	
 
	bool AfterBlitterChange() override;
 

	
 
	void AcquireBlitterLock() override;
 

	
 
	void ReleaseBlitterLock() override;
 

	
 
	bool ClaimMousePointer() override;
 

	
 
	void EditBoxGainedFocus() override;
 

	
 
	void EditBoxLostFocus() override;
 

	
 
	const char *GetName() const override { return "sdl"; }
 

	
 
protected:
 
	Dimension GetScreenSize() const override;
 
	void InputLoop() override;
 
	bool LockVideoBuffer() override;
 
	void UnlockVideoBuffer() override;
 
	void Paint() override;
 
	void PaintThread() override;
 

	
 
private:
 
	int PollEvent();
 
	void LoopOnce();
 
	void MainLoopCleanup();
 
	bool CreateMainSurface(uint w, uint h, bool resize);
 
	bool CreateMainWindow(uint w, uint h);
 
	void CheckPaletteAnim();
 

	
 
#ifdef __EMSCRIPTEN__
 
	/* Convert a constant pointer back to a non-constant pointer to a member function. */
 
	static void EmscriptenLoop(void *self) { ((VideoDriver_SDL *)self)->LoopOnce(); }
 
#endif
 

	
 
	/**
 
	 * This is true to indicate that keyboard input is in text input mode, and SDL_TEXTINPUT events are enabled.
 
	 */
 
	bool edit_box_focused;
 

	
 
	std::chrono::steady_clock::time_point cur_ticks;
 
	std::chrono::steady_clock::time_point last_realtime_tick;
 
	std::chrono::steady_clock::time_point next_game_tick;
 
	std::chrono::steady_clock::time_point next_draw_tick;
 

	
 
	int startup_display;
 
	std::thread draw_thread;
 
	std::unique_lock<std::recursive_mutex> draw_lock;
 

	
 
	static void PaintThreadThunk(VideoDriver_SDL *drv);
 
};
 

	
 
/** Factory for the SDL video driver. */
 
class FVideoDriver_SDL : public DriverFactoryBase {
 
public:
 
	FVideoDriver_SDL() : DriverFactoryBase(Driver::DT_VIDEO, 5, "sdl", "SDL Video Driver") {}
 
	Driver *CreateInstance() const override { return new VideoDriver_SDL(); }
 
};
 

	
 
#endif /* VIDEO_SDL_H */
src/video/sdl_v.cpp
Show inline comments
 
@@ -102,139 +102,144 @@ static void UpdatePalette(bool init = fa
 
	if (_sdl_surface != _sdl_realscreen && !init) {
 
		/* We're not using real hardware palette, but are letting SDL
 
		 * approximate the palette during shadow -> screen copy. To
 
		 * change the palette, we need to recopy the entire screen.
 
		 *
 
		 * Note that this operation can slow down the rendering
 
		 * considerably, especially since changing the shadow
 
		 * palette will need the next blit to re-detect the
 
		 * best mapping of shadow palette colors to real palette
 
		 * colors from scratch.
 
		 */
 
		SDL_BlitSurface(_sdl_surface, nullptr, _sdl_realscreen, nullptr);
 
		SDL_UpdateRect(_sdl_realscreen, 0, 0, 0, 0);
 
	}
 
}
 

	
 
static void InitPalette()
 
{
 
	_local_palette = _cur_palette;
 
	_local_palette.first_dirty = 0;
 
	_local_palette.count_dirty = 256;
 
	UpdatePalette(true);
 
}
 

	
 
static void CheckPaletteAnim()
 
{
 
	if (_cur_palette.count_dirty != 0) {
 
		Blitter *blitter = BlitterFactory::GetCurrentBlitter();
 

	
 
		switch (blitter->UsePaletteAnimation()) {
 
			case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
 
				UpdatePalette();
 
				break;
 

	
 
			case Blitter::PALETTE_ANIMATION_BLITTER:
 
				blitter->PaletteAnimate(_local_palette);
 
				break;
 

	
 
			case Blitter::PALETTE_ANIMATION_NONE:
 
				break;
 

	
 
			default:
 
				NOT_REACHED();
 
		}
 
		_cur_palette.count_dirty = 0;
 
	}
 
}
 

	
 
static void DrawSurfaceToScreen()
 
void VideoDriver_SDL::Paint()
 
{
 
	PerformanceMeasurer framerate(PFE_VIDEO);
 

	
 
	int n = _num_dirty_rects;
 
	if (n == 0) return;
 

	
 
	_num_dirty_rects = 0;
 

	
 
	if (n > MAX_DIRTY_RECTS) {
 
		if (_sdl_surface != _sdl_realscreen) {
 
			SDL_BlitSurface(_sdl_surface, nullptr, _sdl_realscreen, nullptr);
 
		}
 

	
 
		SDL_UpdateRect(_sdl_realscreen, 0, 0, 0, 0);
 
	} else {
 
		if (_sdl_surface != _sdl_realscreen) {
 
			for (int i = 0; i < n; i++) {
 
				SDL_BlitSurface(_sdl_surface, &_dirty_rects[i], _sdl_realscreen, &_dirty_rects[i]);
 
			}
 
		}
 

	
 
		SDL_UpdateRects(_sdl_realscreen, n, _dirty_rects);
 
	}
 
}
 

	
 
static void DrawSurfaceToScreenThread()
 
void VideoDriver_SDL::PaintThread()
 
{
 
	/* First tell the main thread we're started */
 
	std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
 
	_draw_signal->notify_one();
 

	
 
	/* Now wait for the first thing to draw! */
 
	_draw_signal->wait(*_draw_mutex);
 

	
 
	while (_draw_continue) {
 
		CheckPaletteAnim();
 
		/* Then just draw and wait till we stop */
 
		DrawSurfaceToScreen();
 
		this->Paint();
 
		_draw_signal->wait(lock);
 
	}
 
}
 

	
 
/* static */ void VideoDriver_SDL::PaintThreadThunk(VideoDriver_SDL *drv)
 
{
 
	drv->PaintThread();
 
}
 

	
 
static const Dimension _default_resolutions[] = {
 
	{ 640,  480},
 
	{ 800,  600},
 
	{1024,  768},
 
	{1152,  864},
 
	{1280,  800},
 
	{1280,  960},
 
	{1280, 1024},
 
	{1400, 1050},
 
	{1600, 1200},
 
	{1680, 1050},
 
	{1920, 1200}
 
};
 

	
 
static void GetVideoModes()
 
{
 
	SDL_Rect **modes = SDL_ListModes(nullptr, SDL_SWSURFACE | SDL_FULLSCREEN);
 
	if (modes == nullptr) usererror("sdl: no modes available");
 

	
 
	_resolutions.clear();
 

	
 
	_all_modes = (SDL_ListModes(nullptr, SDL_SWSURFACE | (_fullscreen ? SDL_FULLSCREEN : 0)) == (void*)-1);
 
	if (modes == (void*)-1) {
 
		for (uint i = 0; i < lengthof(_default_resolutions); i++) {
 
			if (SDL_VideoModeOK(_default_resolutions[i].width, _default_resolutions[i].height, 8, SDL_FULLSCREEN) != 0) {
 
				_resolutions.push_back(_default_resolutions[i]);
 
			}
 
		}
 
	} else {
 
		for (int i = 0; modes[i]; i++) {
 
			uint w = modes[i]->w;
 
			uint h = modes[i]->h;
 
			if (w < 640 || h < 480) continue; // reject too small resolutions
 
			if (std::find(_resolutions.begin(), _resolutions.end(), Dimension(w, h)) != _resolutions.end()) continue;
 
			_resolutions.emplace_back(w, h);
 
		}
 
		if (_resolutions.empty()) usererror("No usable screen resolutions found!\n");
 
		SortResolutions();
 
	}
 
}
 

	
 
static void GetAvailableVideoMode(uint *w, uint *h)
 
{
 
	/* All modes available? */
 
	if (_all_modes || _resolutions.empty()) return;
 

	
 
	/* Is the wanted mode among the available modes? */
 
	if (std::find(_resolutions.begin(), _resolutions.end(), Dimension(*w, *h)) != _resolutions.end()) return;
 
@@ -672,162 +677,162 @@ void VideoDriver_SDL::InputLoop()
 
#else
 
	if (keys[SDLK_TAB] && (mod & KMOD_ALT) == 0)
 
#endif /* SDL_VERSION_ATLEAST(1, 3, 0) */
 
#endif /* defined(_DEBUG) */
 
	{
 
		if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
 
	} else if (_fast_forward & 2) {
 
		_fast_forward = 0;
 
	}
 

	
 
	/* Determine which directional keys are down. */
 
	_dirkeys =
 
#if SDL_VERSION_ATLEAST(1, 3, 0)
 
		(keys[SDL_SCANCODE_LEFT]  ? 1 : 0) |
 
		(keys[SDL_SCANCODE_UP]    ? 2 : 0) |
 
		(keys[SDL_SCANCODE_RIGHT] ? 4 : 0) |
 
		(keys[SDL_SCANCODE_DOWN]  ? 8 : 0);
 
#else
 
		(keys[SDLK_LEFT]  ? 1 : 0) |
 
		(keys[SDLK_UP]    ? 2 : 0) |
 
		(keys[SDLK_RIGHT] ? 4 : 0) |
 
		(keys[SDLK_DOWN]  ? 8 : 0);
 
#endif
 

	
 
	if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
 
}
 

	
 
void VideoDriver_SDL::MainLoop()
 
{
 
	auto cur_ticks = std::chrono::steady_clock::now();
 
	auto last_realtime_tick = cur_ticks;
 
	auto next_game_tick = cur_ticks;
 
	auto next_draw_tick = cur_ticks;
 

	
 
	CheckPaletteAnim();
 

	
 
	std::thread draw_thread;
 
	if (_draw_threaded) {
 
		/* Initialise the mutex first, because that's the thing we *need*
 
		 * directly in the newly created thread. */
 
		_draw_mutex = new std::recursive_mutex();
 
		if (_draw_mutex == nullptr) {
 
			_draw_threaded = false;
 
		} else {
 
			this->draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
 
			_draw_signal = new std::condition_variable_any();
 
			_draw_continue = true;
 

	
 
			_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &DrawSurfaceToScreenThread);
 
			_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &VideoDriver_SDL::PaintThreadThunk, this);
 

	
 
			/* Free the mutex if we won't be able to use it. */
 
			if (!_draw_threaded) {
 
				this->draw_lock.unlock();
 
				this->draw_lock.release();
 
				delete _draw_mutex;
 
				delete _draw_signal;
 
				_draw_mutex = nullptr;
 
				_draw_signal = nullptr;
 
			} else {
 
				/* Wait till the draw mutex has started itself. */
 
				_draw_signal->wait(*_draw_mutex);
 
			}
 
		}
 
	}
 

	
 
	DEBUG(driver, 1, "SDL: using %sthreads", _draw_threaded ? "" : "no ");
 

	
 
	for (;;) {
 
		InteractiveRandom(); // randomness
 

	
 
		while (PollEvent() == -1) {}
 
		if (_exit_game) break;
 

	
 
		cur_ticks = std::chrono::steady_clock::now();
 

	
 
		/* If more than a millisecond has passed, increase the _realtime_tick. */
 
		if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
 
			auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
 
			_realtime_tick += delta.count();
 
			last_realtime_tick += delta;
 
		}
 

	
 
		if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
 
			if (_fast_forward && !_pause_mode) {
 
				next_game_tick = cur_ticks + this->GetGameInterval();
 
			} else {
 
				next_game_tick += this->GetGameInterval();
 
				/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
 
				if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
 
			}
 

	
 
			/* The gameloop is the part that can run asynchronously. The rest
 
			 * except sleeping can't. */
 
			this->UnlockVideoBuffer();
 
			GameLoop();
 
			this->LockVideoBuffer();
 
		}
 

	
 
		/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
 
		if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
 
			next_draw_tick += this->GetDrawInterval();
 
			/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
 
			if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
 

	
 
			this->InputLoop();
 
			::InputLoop();
 
			UpdateWindows();
 
			_local_palette = _cur_palette;
 

	
 
			if (_draw_mutex != nullptr && !HasModalProgress()) {
 
				_draw_signal->notify_one();
 
			} else {
 
				CheckPaletteAnim();
 
				DrawSurfaceToScreen();
 
				this->Paint();
 
			}
 
		}
 

	
 
		/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
 
		if (!_fast_forward || _pause_mode) {
 
			/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
 
			auto next_tick = std::min(next_draw_tick, next_game_tick);
 
			auto now = std::chrono::steady_clock::now();
 

	
 
			if (next_tick > now) {
 
				this->UnlockVideoBuffer();
 
				std::this_thread::sleep_for(next_tick - now);
 
				this->LockVideoBuffer();
 
			}
 
		}
 
	}
 

	
 
	if (_draw_mutex != nullptr) {
 
		_draw_continue = false;
 
		/* Sending signal if there is no thread blocked
 
		 * is very valid and results in noop */
 
		_draw_signal->notify_one();
 
		if (this->draw_lock.owns_lock()) this->draw_lock.unlock();
 
		this->draw_lock.release();
 
		draw_thread.join();
 

	
 
		delete _draw_mutex;
 
		delete _draw_signal;
 

	
 
		_draw_mutex = nullptr;
 
		_draw_signal = nullptr;
 
	}
 
}
 

	
 
bool VideoDriver_SDL::ChangeResolution(int w, int h)
 
{
 
	std::unique_lock<std::recursive_mutex> lock;
 
	if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
 

	
 
	return CreateMainSurface(w, h);
 
}
 

	
 
bool VideoDriver_SDL::ToggleFullscreen(bool fullscreen)
 
{
 
	std::unique_lock<std::recursive_mutex> lock;
 
	if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
 

	
 
	_fullscreen = fullscreen;
src/video/sdl_v.h
Show inline comments
 
/*
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file sdl_v.h Base of the SDL video driver. */
 

	
 
#ifndef VIDEO_SDL_H
 
#define VIDEO_SDL_H
 

	
 
#include "video_driver.hpp"
 

	
 
/** The SDL video driver. */
 
class VideoDriver_SDL : public VideoDriver {
 
public:
 
	const char *Start(const StringList &param) override;
 

	
 
	void Stop() override;
 

	
 
	void MakeDirty(int left, int top, int width, int height) override;
 

	
 
	void MainLoop() override;
 

	
 
	bool ChangeResolution(int w, int h) override;
 

	
 
	bool ToggleFullscreen(bool fullscreen) override;
 

	
 
	bool AfterBlitterChange() override;
 

	
 
	void AcquireBlitterLock() override;
 

	
 
	void ReleaseBlitterLock() override;
 

	
 
	bool ClaimMousePointer() override;
 

	
 
	const char *GetName() const override { return "sdl"; }
 

	
 
protected:
 
	void InputLoop() override;
 
	bool LockVideoBuffer() override;
 
	void UnlockVideoBuffer() override;
 
	void Paint() override;
 
	void PaintThread() override;
 

	
 
private:
 
	std::unique_lock<std::recursive_mutex> draw_lock;
 

	
 
	int PollEvent();
 
	bool CreateMainSurface(uint w, uint h);
 
	void SetupKeyboard();
 

	
 
	static void PaintThreadThunk(VideoDriver_SDL *drv);
 
};
 

	
 
/** Factory for the SDL video driver. */
 
class FVideoDriver_SDL : public DriverFactoryBase {
 
public:
 
	FVideoDriver_SDL() : DriverFactoryBase(Driver::DT_VIDEO, 5, "sdl", "SDL Video Driver") {}
 
	Driver *CreateInstance() const override { return new VideoDriver_SDL(); }
 
};
 

	
 
#endif /* VIDEO_SDL_H */
src/video/video_driver.hpp
Show inline comments
 
@@ -129,60 +129,70 @@ public:
 

	
 
protected:
 
	const uint ALLOWED_DRIFT = 5; ///< How many times videodriver can miss deadlines without it being overly compensated.
 

	
 
	/**
 
	 * Get the resolution of the main screen.
 
	 */
 
	virtual Dimension GetScreenSize() const { return { DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT }; }
 

	
 
	/**
 
	 * Get DPI scaling factor of the screen OTTD is displayed on.
 
	 * @return 1.0 for default platform DPI, > 1.0 for higher DPI values, and < 1.0 for smaller DPI values.
 
	 */
 
	virtual float GetDPIScale() { return 1.0f; }
 

	
 
	/**
 
	 * Apply resolution auto-detection and clamp to sensible defaults.
 
	 */
 
	void UpdateAutoResolution()
 
	{
 
		if (_cur_resolution.width == 0 || _cur_resolution.height == 0) {
 
			/* Auto-detect a good resolution. We aim for 75% of the screen size.
 
			 * Limit width times height times bytes per pixel to fit a 32 bit
 
			 * integer, This way all internal drawing routines work correctly. */
 
			Dimension res = this->GetScreenSize();
 
			_cur_resolution.width  = ClampU(res.width  * 3 / 4, DEFAULT_WINDOW_WIDTH, UINT16_MAX / 2);
 
			_cur_resolution.height = ClampU(res.height * 3 / 4, DEFAULT_WINDOW_HEIGHT, UINT16_MAX / 2);
 
		}
 
	}
 

	
 
	/**
 
	 * Handle input logic, is CTRL pressed, should we fast-forward, etc.
 
	 */
 
	virtual void InputLoop() {}
 

	
 
	/**
 
	 * Make sure the video buffer is ready for drawing.
 
	 * @returns True if the video buffer has to be unlocked.
 
	 */
 
	virtual bool LockVideoBuffer() {
 
		return false;
 
	}
 

	
 
	/**
 
	 * Unlock a previously locked video buffer.
 
	 */
 
	virtual void UnlockVideoBuffer() {}
 

	
 
	/**
 
	 * Paint the window.
 
	 */
 
	virtual void Paint() {}
 

	
 
	/**
 
	 * Thread function for threaded drawing.
 
	 */
 
	virtual void PaintThread() {}
 

	
 
	std::chrono::steady_clock::duration GetGameInterval()
 
	{
 
		return std::chrono::milliseconds(MILLISECONDS_PER_TICK);
 
	}
 

	
 
	std::chrono::steady_clock::duration GetDrawInterval()
 
	{
 
		return std::chrono::microseconds(1000000 / _settings_client.gui.refresh_rate);
 
	}
 
};
 

	
 
#endif /* VIDEO_VIDEO_DRIVER_HPP */
src/video/win32_v.cpp
Show inline comments
 
@@ -287,168 +287,173 @@ bool VideoDriver_Win32::MakeWindow(bool 
 
		/* restore the resolution */
 
		_wnd.width = _bck_resolution.width;
 
		_wnd.height = _bck_resolution.height;
 
	}
 

	
 
	{
 
		RECT r;
 
		DWORD style, showstyle;
 
		int w, h;
 

	
 
		showstyle = SW_SHOWNORMAL;
 
		_wnd.fullscreen = full_screen;
 
		if (_wnd.fullscreen) {
 
			style = WS_POPUP;
 
			SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org);
 
		} else {
 
			style = WS_OVERLAPPEDWINDOW;
 
			/* On window creation, check if we were in maximize mode before */
 
			if (_window_maximize) showstyle = SW_SHOWMAXIMIZED;
 
			SetRect(&r, 0, 0, _wnd.width, _wnd.height);
 
		}
 

	
 
		AdjustWindowRect(&r, style, FALSE);
 
		w = r.right - r.left;
 
		h = r.bottom - r.top;
 

	
 
		if (_wnd.main_wnd != nullptr) {
 
			if (!_window_maximize) SetWindowPos(_wnd.main_wnd, 0, 0, 0, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE);
 
		} else {
 
			int x = (GetSystemMetrics(SM_CXSCREEN) - w) / 2;
 
			int y = (GetSystemMetrics(SM_CYSCREEN) - h) / 2;
 

	
 
			char window_title[64];
 
			seprintf(window_title, lastof(window_title), "OpenTTD %s", _openttd_revision);
 

	
 
			_wnd.main_wnd = CreateWindow(_T("OTTD"), MB_TO_WIDE(window_title), style, x, y, w, h, 0, 0, GetModuleHandle(nullptr), 0);
 
			if (_wnd.main_wnd == nullptr) usererror("CreateWindow failed");
 
			ShowWindow(_wnd.main_wnd, showstyle);
 
		}
 
	}
 

	
 
	BlitterFactory::GetCurrentBlitter()->PostResize();
 

	
 
	GameSizeChanged(); // invalidate all windows, force redraw
 
	return true; // the request succeeded
 
}
 

	
 
/** Do palette animation and blit to the window. */
 
static void PaintWindow()
 
void VideoDriver_Win32::Paint()
 
{
 
	PerformanceMeasurer framerate(PFE_VIDEO);
 

	
 
	if (IsEmptyRect(_dirty_rect)) return;
 

	
 
	/* Convert update region from logical to device coordinates. */
 
	POINT pt = {0, 0};
 
	ClientToScreen(_wnd.main_wnd, &pt);
 

	
 
	RECT r = { _dirty_rect.left, _dirty_rect.top, _dirty_rect.right, _dirty_rect.bottom };
 
	OffsetRect(&r, pt.x, pt.y);
 

	
 
	/* Create a device context that is clipped to the region we need to draw.
 
	 * GetDCEx 'consumes' the update region, so we may not destroy it ourself. */
 
	HRGN rgn = CreateRectRgnIndirect(&r);
 
	HDC dc = GetDCEx(_wnd.main_wnd, rgn, DCX_CLIPSIBLINGS | DCX_CLIPCHILDREN | DCX_INTERSECTRGN);
 

	
 
	HDC dc2 = CreateCompatibleDC(dc);
 
	HBITMAP old_bmp = (HBITMAP)SelectObject(dc2, _wnd.dib_sect);
 
	HPALETTE old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE);
 

	
 
	if (_cur_palette.count_dirty != 0) {
 
		Blitter *blitter = BlitterFactory::GetCurrentBlitter();
 

	
 
		switch (blitter->UsePaletteAnimation()) {
 
			case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
 
				UpdatePalette(dc2, _local_palette.first_dirty, _local_palette.count_dirty);
 
				break;
 

	
 
			case Blitter::PALETTE_ANIMATION_BLITTER:
 
				blitter->PaletteAnimate(_local_palette);
 
				break;
 

	
 
			case Blitter::PALETTE_ANIMATION_NONE:
 
				break;
 

	
 
			default:
 
				NOT_REACHED();
 
		}
 
		_cur_palette.count_dirty = 0;
 
	}
 

	
 
	BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
 
	SelectPalette(dc, old_palette, TRUE);
 
	SelectObject(dc2, old_bmp);
 
	DeleteDC(dc2);
 

	
 
	ReleaseDC(_wnd.main_wnd, dc);
 

	
 
	MemSetT(&_dirty_rect, 0);
 
}
 

	
 
static void PaintWindowThread()
 
void VideoDriver_Win32::PaintThread()
 
{
 
	/* First tell the main thread we're started */
 
	std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
 
	_draw_signal->notify_one();
 

	
 
	/* Now wait for the first thing to draw! */
 
	_draw_signal->wait(*_draw_mutex);
 

	
 
	while (_draw_continue) {
 
		PaintWindow();
 
		this->Paint();
 

	
 
		/* Flush GDI buffer to ensure drawing here doesn't conflict with any GDI usage in the main WndProc. */
 
		GdiFlush();
 

	
 
		_draw_signal->wait(*_draw_mutex);
 
	}
 
}
 

	
 
/* static */ void VideoDriver_Win32::PaintThreadThunk(VideoDriver_Win32 *drv)
 
{
 
	drv->PaintThread();
 
}
 

	
 
/** Forward key presses to the window system. */
 
static LRESULT HandleCharMsg(uint keycode, WChar charcode)
 
{
 
#if !defined(UNICODE)
 
	static char prev_char = 0;
 

	
 
	char input[2] = {(char)charcode, 0};
 
	int input_len = 1;
 

	
 
	if (prev_char != 0) {
 
		/* We stored a lead byte previously, combine it with this byte. */
 
		input[0] = prev_char;
 
		input[1] = (char)charcode;
 
		input_len = 2;
 
	} else if (IsDBCSLeadByte(charcode)) {
 
		/* We got a lead byte, store and exit. */
 
		prev_char = charcode;
 
		return 0;
 
	}
 
	prev_char = 0;
 

	
 
	wchar_t w[2]; // Can get up to two code points as a result.
 
	int len = MultiByteToWideChar(CP_ACP, 0, input, input_len, w, 2);
 
	switch (len) {
 
		case 1: // Normal unicode character.
 
			charcode = w[0];
 
			break;
 

	
 
		case 2: // Got an UTF-16 surrogate pair back.
 
			charcode = Utf16DecodeSurrogate(w[0], w[1]);
 
			break;
 

	
 
		default: // Some kind of error.
 
			DEBUG(driver, 1, "Invalid DBCS character sequence encountered, dropping input");
 
			charcode = 0;
 
			break;
 
	}
 
#else
 
	static WChar prev_char = 0;
 

	
 
	/* Did we get a lead surrogate? If yes, store and exit. */
 
	if (Utf16IsLeadSurrogate(charcode)) {
 
		if (prev_char != 0) DEBUG(driver, 1, "Got two UTF-16 lead surrogates, dropping the first one");
 
		prev_char = charcode;
 
		return 0;
 
	}
 

	
 
	/* Stored lead surrogate and incoming trail surrogate? Combine and forward to input handling. */
 
@@ -1131,175 +1136,175 @@ void VideoDriver_Win32::InputLoop()
 
	/* Speedup when pressing tab, except when using ALT+TAB
 
	 * to switch to another application. */
 
	if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0)
 
#endif
 
	{
 
		if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
 
	} else if (_fast_forward & 2) {
 
		_fast_forward = 0;
 
	}
 

	
 
	/* Determine which directional keys are down. */
 
	if (_wnd.has_focus) {
 
		_dirkeys =
 
			(GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) +
 
			(GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) +
 
			(GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) +
 
			(GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0);
 
	} else {
 
		_dirkeys = 0;
 
	}
 

	
 
	if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
 
}
 

	
 
void VideoDriver_Win32::MainLoop()
 
{
 
	MSG mesg;
 
	auto cur_ticks = std::chrono::steady_clock::now();
 
	auto last_realtime_tick = cur_ticks;
 
	auto next_game_tick = cur_ticks;
 
	auto next_draw_tick = cur_ticks;
 

	
 
	std::thread draw_thread;
 

	
 
	if (_draw_threaded) {
 
		/* Initialise the mutex first, because that's the thing we *need*
 
		 * directly in the newly created thread. */
 
		try {
 
			_draw_signal = new std::condition_variable_any();
 
			_draw_mutex = new std::recursive_mutex();
 
		} catch (...) {
 
			_draw_threaded = false;
 
		}
 

	
 
		if (_draw_threaded) {
 
			this->draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
 

	
 
			_draw_continue = true;
 
			_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &PaintWindowThread);
 
			_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &VideoDriver_Win32::PaintThreadThunk, this);
 

	
 
			/* Free the mutex if we won't be able to use it. */
 
			if (!_draw_threaded) {
 
				this->draw_lock.unlock();
 
				this->draw_lock.release();
 
				delete _draw_mutex;
 
				delete _draw_signal;
 
				_draw_mutex = nullptr;
 
				_draw_signal = nullptr;
 
			} else {
 
				DEBUG(driver, 1, "Threaded drawing enabled");
 
				/* Wait till the draw thread has started itself. */
 
				_draw_signal->wait(*_draw_mutex);
 
			}
 
		}
 
	}
 

	
 
	_wnd.running = true;
 

	
 
	CheckPaletteAnim();
 
	for (;;) {
 
		InteractiveRandom(); // randomness
 

	
 
		while (PeekMessage(&mesg, nullptr, 0, 0, PM_REMOVE)) {
 
			/* Convert key messages to char messages if we want text input. */
 
			if (EditBoxInGlobalFocus()) TranslateMessage(&mesg);
 
			DispatchMessage(&mesg);
 
		}
 
		if (_exit_game) break;
 

	
 
		cur_ticks = std::chrono::steady_clock::now();
 

	
 
		/* If more than a millisecond has passed, increase the _realtime_tick. */
 
		if (cur_ticks - last_realtime_tick > std::chrono::milliseconds(1)) {
 
			auto delta = std::chrono::duration_cast<std::chrono::milliseconds>(cur_ticks - last_realtime_tick);
 
			_realtime_tick += delta.count();
 
			last_realtime_tick += delta;
 
		}
 

	
 
		if (cur_ticks >= next_game_tick || (_fast_forward && !_pause_mode)) {
 
			if (_fast_forward && !_pause_mode) {
 
				next_game_tick = cur_ticks + this->GetGameInterval();
 
			} else {
 
				next_game_tick += this->GetGameInterval();
 
				/* Avoid next_game_tick getting behind more and more if it cannot keep up. */
 
				if (next_game_tick < cur_ticks - ALLOWED_DRIFT * this->GetGameInterval()) next_game_tick = cur_ticks;
 
			}
 

	
 
			/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
 
			GdiFlush();
 

	
 
			/* The game loop is the part that can run asynchronously.
 
			 * The rest except sleeping can't. */
 
			this->UnlockVideoBuffer();
 
			GameLoop();
 
			this->LockVideoBuffer();
 
		}
 

	
 
		/* Prevent drawing when switching mode, as windows can be removed when they should still appear. */
 
		if (cur_ticks >= next_draw_tick && (_switch_mode == SM_NONE || HasModalProgress())) {
 
			next_draw_tick += this->GetDrawInterval();
 
			/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
 
			if (next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) next_draw_tick = cur_ticks;
 

	
 
			if (_force_full_redraw) MarkWholeScreenDirty();
 

	
 
			/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
 
			GdiFlush();
 

	
 
			this->InputLoop();
 
			::InputLoop();
 
			UpdateWindows();
 
			CheckPaletteAnim();
 

	
 
			if (_draw_mutex != nullptr && !HasModalProgress()) {
 
				_draw_signal->notify_one();
 
			} else {
 
				PaintWindow();
 
				this->Paint();
 
			}
 
		}
 

	
 
		/* If we are not in fast-forward, create some time between calls to ease up CPU usage. */
 
		if (!_fast_forward || _pause_mode) {
 
			/* See how much time there is till we have to process the next event, and try to hit that as close as possible. */
 
			auto next_tick = std::min(next_draw_tick, next_game_tick);
 
			auto now = std::chrono::steady_clock::now();
 

	
 
			if (next_tick > now) {
 
				/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
 
				GdiFlush();
 

	
 
				this->UnlockVideoBuffer();
 
				std::this_thread::sleep_for(next_tick - now);
 
				this->LockVideoBuffer();
 
			}
 
		}
 
	}
 

	
 
	if (_draw_threaded) {
 
		_draw_continue = false;
 
		/* Sending signal if there is no thread blocked
 
		 * is very valid and results in noop */
 
		_draw_signal->notify_all();
 
		if (this->draw_lock.owns_lock()) this->draw_lock.unlock();
 
		this->draw_lock.release();
 
		draw_thread.join();
 

	
 
		delete _draw_mutex;
 
		delete _draw_signal;
 

	
 
		_draw_mutex = nullptr;
 
	}
 
}
 

	
 
bool VideoDriver_Win32::ChangeResolution(int w, int h)
 
{
 
	std::unique_lock<std::recursive_mutex> lock;
 
	if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
 

	
 
	if (_window_maximize) ShowWindow(_wnd.main_wnd, SW_SHOWNORMAL);
 

	
 
	_wnd.width = _wnd.width_org = w;
 
	_wnd.height = _wnd.height_org = h;
 

	
 
	return this->MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching
 
}
src/video/win32_v.h
Show inline comments
 
@@ -2,65 +2,67 @@
 
 * This file is part of OpenTTD.
 
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 
 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 
 */
 

	
 
/** @file win32_v.h Base of the Windows video driver. */
 

	
 
#ifndef VIDEO_WIN32_H
 
#define VIDEO_WIN32_H
 

	
 
#include "video_driver.hpp"
 

	
 
/** The video driver for windows. */
 
class VideoDriver_Win32 : public VideoDriver {
 
public:
 
	const char *Start(const StringList &param) override;
 

	
 
	void Stop() override;
 

	
 
	void MakeDirty(int left, int top, int width, int height) override;
 

	
 
	void MainLoop() override;
 

	
 
	bool ChangeResolution(int w, int h) override;
 

	
 
	bool ToggleFullscreen(bool fullscreen) override;
 

	
 
	bool AfterBlitterChange() override;
 

	
 
	void AcquireBlitterLock() override;
 

	
 
	void ReleaseBlitterLock() override;
 

	
 
	bool ClaimMousePointer() override;
 

	
 
	void EditBoxLostFocus() override;
 

	
 
	const char *GetName() const override { return "win32"; }
 

	
 
	bool MakeWindow(bool full_screen);
 

	
 
protected:
 
	Dimension GetScreenSize() const override;
 
	float GetDPIScale() override;
 
	void InputLoop() override;
 
	bool LockVideoBuffer() override;
 
	void UnlockVideoBuffer() override;
 
	void Paint() override;
 
	void PaintThread() override;
 

	
 
private:
 
	std::unique_lock<std::recursive_mutex> draw_lock;
 

	
 
	void CheckPaletteAnim();
 

	
 
	static void PaintThreadThunk(VideoDriver_Win32 *drv);
 
};
 

	
 
/** The factory for Windows' video driver. */
 
class FVideoDriver_Win32 : public DriverFactoryBase {
 
public:
 
	FVideoDriver_Win32() : DriverFactoryBase(Driver::DT_VIDEO, 10, "win32", "Win32 GDI Video Driver") {}
 
	Driver *CreateInstance() const override { return new VideoDriver_Win32(); }
 
};
 

	
 
#endif /* VIDEO_WIN32_H */
0 comments (0 inline, 0 general)