Merge tag '1.11.0-beta2' into jgrpp

# Conflicts:
#	.github/workflows/ci-build.yml
#	.github/workflows/release.yml
#	CMakeLists.txt
#	src/blitter/32bpp_optimized.cpp
#	src/debug.cpp
#	src/gfx.cpp
#	src/gfx_func.h
#	src/lang/czech.txt
#	src/lang/english.txt
#	src/lang/italian.txt
#	src/lang/swedish.txt
#	src/lang/ukrainian.txt
#	src/network/network_server.cpp
#	src/os/windows/crashlog_win.cpp
#	src/os/windows/win32.cpp
#	src/pathfinder/follow_track.hpp
#	src/screenshot.cpp
#	src/settings_type.h
#	src/spritecache.cpp
#	src/vehicle_gui.cpp
#	src/video/sdl2_v.cpp
#	src/video/video_driver.cpp
#	src/video/video_driver.hpp
#	src/video/win32_v.cpp
This commit is contained in:
Jonathan G Rennison
2021-03-02 11:59:03 +00:00
148 changed files with 19670 additions and 1452 deletions

View File

@@ -7,6 +7,12 @@ if(NOT OPTION_DEDICATED)
CONDITION Allegro_FOUND
)
add_files(
opengl.cpp
opengl.h
CONDITION OPENGL_FOUND
)
add_files(
sdl_v.cpp
sdl_v.h
@@ -16,9 +22,17 @@ if(NOT OPTION_DEDICATED)
add_files(
sdl2_v.cpp
sdl2_v.h
sdl2_default_v.cpp
sdl2_default_v.h
CONDITION SDL2_FOUND
)
add_files(
sdl2_opengl_v.cpp
sdl2_opengl_v.h
CONDITION SDL2_FOUND AND OPENGL_FOUND
)
add_files(
win32_v.cpp
win32_v.h

View File

@@ -328,7 +328,7 @@ static uint32 ConvertAllegroKeyIntoMy(WChar *character)
static const uint LEFT_BUTTON = 0;
static const uint RIGHT_BUTTON = 1;
static void PollEvent()
bool VideoDriver_Allegro::PollEvent()
{
poll_mouse();
@@ -402,6 +402,8 @@ static void PollEvent()
uint keycode = ConvertAllegroKeyIntoMy(&character);
HandleKeypress(keycode, character);
}
return false;
}
/**
@@ -475,9 +477,6 @@ void VideoDriver_Allegro::InputLoop()
void VideoDriver_Allegro::MainLoop()
{
for (;;) {
InteractiveRandom(); // randomness
PollEvent();
if (_exit_game) return;
if (this->Tick()) {

View File

@@ -37,6 +37,7 @@ protected:
void InputLoop() override;
void Paint() override;
void CheckPaletteAnim() override;
bool PollEvent() override;
};
/** Factory for the allegro video driver. */

View File

@@ -6,3 +6,9 @@ add_files(
cocoa_wnd.mm
CONDITION APPLE
)
add_files(
cocoa_ogl.h
cocoa_ogl.mm
CONDITION APPLE AND OPENGL_FOUND
)

View File

@@ -0,0 +1,59 @@
/*
* 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 cocoa_ogl.h The Cocoa OpenGL video driver. */
#ifndef VIDEO_COCOA_OGL_H
#define VIDEO_COCOA_OGL_H
#include "cocoa_v.h"
@class OTTD_OpenGLView;
class VideoDriver_CocoaOpenGL : public VideoDriver_Cocoa {
CGLContextObj gl_context;
uint8 *anim_buffer; ///< Animation buffer from OpenGL back-end.
const char *AllocateContext(bool allow_software);
public:
VideoDriver_CocoaOpenGL() : gl_context(nullptr), anim_buffer(nullptr) {}
const char *Start(const StringList &param) override;
void Stop() override;
bool HasEfficient8Bpp() const override { return true; }
bool UseSystemCursor() override { return true; }
void ClearSystemSprites() override;
bool HasAnimBuffer() override { return true; }
uint8 *GetAnimBuffer() override { return this->anim_buffer; }
/** Return driver name */
const char *GetName() const override { return "cocoa-opengl"; }
void AllocateBackingStore(bool force = false) override;
protected:
void Paint() override;
void *GetVideoPointer() override;
void ReleaseVideoPointer() override;
NSView* AllocateDrawView() override;
};
class FVideoDriver_CocoaOpenGL : public DriverFactoryBase {
public:
FVideoDriver_CocoaOpenGL() : DriverFactoryBase(Driver::DT_VIDEO, 9, "cocoa-opengl", "Cocoa OpenGL Video Driver") {}
Driver *CreateInstance() const override { return new VideoDriver_CocoaOpenGL(); }
};
#endif /* VIDEO_COCOA_OGL_H */

View File

@@ -0,0 +1,317 @@
/*
* 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 cocoa_ogl.mm Code related to the cocoa OpengL video driver. */
#ifdef WITH_COCOA
#include "../../stdafx.h"
#include "../../os/macosx/macos.h"
#define Rect OTTDRect
#define Point OTTDPoint
#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>
#undef Rect
#undef Point
#include "../../openttd.h"
#include "../../debug.h"
#include "../../core/geometry_func.hpp"
#include "../../core/math_func.hpp"
#include "../../core/mem_func.hpp"
#include "cocoa_ogl.h"
#include "cocoa_wnd.h"
#include "../../blitter/factory.hpp"
#include "../../gfx_func.h"
#include "../../framerate_type.h"
#include "../opengl.h"
#import <dlfcn.h>
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl3.h>
/**
* Important notice regarding all modifications!!!!!!!
* There are certain limitations because the file is objective C++.
* gdb has limitations.
* C++ and objective C code can't be joined in all cases (classes stuff).
* Read http://developer.apple.com/releasenotes/Cocoa/Objective-C++.html for more information.
*/
/** Platform-specific callback to get an OpenGL funtion pointer. */
static OGLProc GetOGLProcAddressCallback(const char *proc)
{
static void *dl = nullptr;
if (dl == nullptr) {
dl = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_LAZY);
}
return reinterpret_cast<OGLProc>(dlsym(dl, proc));
}
@interface OTTD_CGLLayer : CAOpenGLLayer {
@private
CGLContextObj _context;
}
@property (class) bool allowSoftware;
+ (CGLPixelFormatObj)defaultPixelFormat;
- (instancetype)initWithContext:(CGLContextObj)context;
@end
@implementation OTTD_CGLLayer
static bool _allowSoftware;
+ (bool)allowSoftware
{
return _allowSoftware;
}
+ (void)setAllowSoftware:(bool)newVal
{
_allowSoftware = newVal;
}
- (instancetype)initWithContext:(CGLContextObj)context
{
if (self = [ super init ]) {
self->_context = context;
self.opaque = YES;
self.magnificationFilter = kCAFilterNearest;
}
return self;
}
+ (CGLPixelFormatObj)defaultPixelFormat
{
CGLPixelFormatAttribute attribs[] = {
kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core,
kCGLPFAColorSize, (CGLPixelFormatAttribute)24,
kCGLPFAAlphaSize, (CGLPixelFormatAttribute)0,
kCGLPFADepthSize, (CGLPixelFormatAttribute)0,
kCGLPFADoubleBuffer,
kCGLPFAAllowOfflineRenderers,
kCGLPFASupportsAutomaticGraphicsSwitching,
kCGLPFANoRecovery,
_allowSoftware ? (CGLPixelFormatAttribute)0 : kCGLPFAAccelerated,
(CGLPixelFormatAttribute)0
};
CGLPixelFormatObj pxfmt = nullptr;
GLint numPixelFormats;
CGLChoosePixelFormat(attribs, &pxfmt, &numPixelFormats);
return pxfmt;
}
- (CGLPixelFormatObj)copyCGLPixelFormatForDisplayMask:(uint32_t)mask
{
return [ OTTD_CGLLayer defaultPixelFormat ];
}
- (CGLContextObj)copyCGLContextForPixelFormat:(CGLPixelFormatObj)pf
{
CGLContextObj ctx;
CGLCreateContext(pf, self->_context, &ctx);
/* Set context state that is not shared. */
CGLSetCurrentContext(ctx);
OpenGLBackend::Get()->PrepareContext();
return ctx;
}
- (void)drawInCGLContext:(CGLContextObj)ctx pixelFormat:(CGLPixelFormatObj)pf forLayerTime:(CFTimeInterval)t displayTime:(nullable const CVTimeStamp *)ts
{
CGLSetCurrentContext(ctx);
OpenGLBackend::Get()->Paint();
if (_cursor.in_window) OpenGLBackend::Get()->DrawMouseCursor();
[ super drawInCGLContext:ctx pixelFormat:pf forLayerTime:t displayTime:ts ];
}
@end
@interface OTTD_CGLLayerView : NSView
- (instancetype)initWithFrame:(NSRect)frameRect context:(CGLContextObj)context;
@end
@implementation OTTD_CGLLayerView
- (instancetype)initWithFrame:(NSRect)frameRect context:(CGLContextObj)context
{
if (self = [ super initWithFrame:frameRect ]) {
/* We manage our content updates ourselves. */
self.wantsBestResolutionOpenGLSurface = _allow_hidpi_window ? YES : NO;
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
/* Create backing layer. */
CALayer *l = [ [ OTTD_CGLLayer alloc ] initWithContext:context ];
self.layer = l;
self.wantsLayer = YES;
[ l release ];
}
return self;
}
- (BOOL)acceptsFirstResponder
{
return NO;
}
- (BOOL)isOpaque
{
return YES;
}
- (void)viewDidChangeBackingProperties
{
self.layer.contentsScale = _allow_hidpi_window && [ self.window respondsToSelector:@selector(backingScaleFactor) ] ? [ self.window backingScaleFactor ] : 1.0f;
}
@end
static FVideoDriver_CocoaOpenGL iFVideoDriver_CocoaOpenGL;
const char *VideoDriver_CocoaOpenGL::Start(const StringList &param)
{
const char *err = this->Initialize();
if (err != nullptr) return err;
int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
if (bpp != 8 && bpp != 32) {
this->Stop();
return "The cocoa OpenGL subdriver only supports 8 and 32 bpp.";
}
/* Try to allocate GL context. */
err = this->AllocateContext(GetDriverParamBool(param, "software"));
if (err != nullptr) {
this->Stop();
return err;
}
bool fullscreen = _fullscreen;
if (!this->MakeWindow(_cur_resolution.width, _cur_resolution.height)) {
this->Stop();
return "Could not create window";
}
this->AllocateBackingStore(true);
if (fullscreen) this->ToggleFullscreen(fullscreen);
this->GameSizeChanged();
this->UpdateVideoModes();
MarkWholeScreenDirty();
return nullptr;
}
void VideoDriver_CocoaOpenGL::Stop()
{
this->VideoDriver_Cocoa::Stop();
CGLSetCurrentContext(this->gl_context);
OpenGLBackend::Destroy();
CGLReleaseContext(this->gl_context);
}
void VideoDriver_CocoaOpenGL::ClearSystemSprites()
{
CGLSetCurrentContext(this->gl_context);
OpenGLBackend::Get()->ClearCursorCache();
}
const char *VideoDriver_CocoaOpenGL::AllocateContext(bool allow_software)
{
[ OTTD_CGLLayer setAllowSoftware:allow_software ];
CGLPixelFormatObj pxfmt = [ OTTD_CGLLayer defaultPixelFormat ];
if (pxfmt == nullptr) return "No suitable pixel format found";
CGLCreateContext(pxfmt, nullptr, &this->gl_context);
CGLDestroyPixelFormat(pxfmt);
if (this->gl_context == nullptr) return "Can't create a rendering context";
CGLSetCurrentContext(this->gl_context);
return OpenGLBackend::Create(&GetOGLProcAddressCallback);
}
NSView *VideoDriver_CocoaOpenGL::AllocateDrawView()
{
return [ [ OTTD_CGLLayerView alloc ] initWithFrame:this->cocoaview.bounds context:this->gl_context ];
}
/** Resize the window. */
void VideoDriver_CocoaOpenGL::AllocateBackingStore(bool force)
{
if (this->window == nil || this->setup) return;
if (_screen.dst_ptr != nullptr) this->ReleaseVideoPointer();
CGLSetCurrentContext(this->gl_context);
NSRect frame = [ this->cocoaview getRealRect:[ this->cocoaview frame ] ];
OpenGLBackend::Get()->Resize(frame.size.width, frame.size.height, force);
_screen.dst_ptr = this->GetVideoPointer();
this->dirty_rect = {};
/* Redraw screen */
this->GameSizeChanged();
}
void *VideoDriver_CocoaOpenGL::GetVideoPointer()
{
CGLSetCurrentContext(this->gl_context);
if (BlitterFactory::GetCurrentBlitter()->NeedsAnimationBuffer()) {
this->anim_buffer = OpenGLBackend::Get()->GetAnimBuffer();
}
return OpenGLBackend::Get()->GetVideoBuffer();
}
void VideoDriver_CocoaOpenGL::ReleaseVideoPointer()
{
CGLSetCurrentContext(this->gl_context);
if (this->anim_buffer != nullptr) OpenGLBackend::Get()->ReleaseAnimBuffer(this->dirty_rect);
OpenGLBackend::Get()->ReleaseVideoBuffer(this->dirty_rect);
this->dirty_rect = {};
_screen.dst_ptr = nullptr;
this->anim_buffer = nullptr;
}
void VideoDriver_CocoaOpenGL::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
if (_cur_palette.count_dirty != 0) {
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
/* Always push a changed palette to OpenGL. */
CGLSetCurrentContext(this->gl_context);
OpenGLBackend::Get()->UpdatePalette(_cur_palette.palette, _cur_palette.first_dirty, _cur_palette.count_dirty);
if (blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_BLITTER) {
blitter->PaletteAnimate(_cur_palette);
}
_cur_palette.count_dirty = 0;
}
[ CATransaction begin ];
[ this->cocoaview.subviews[0].layer setNeedsDisplay ];
[ CATransaction commit ];
}
#endif /* WITH_COCOA */

View File

@@ -24,32 +24,18 @@ class VideoDriver_Cocoa : public VideoDriver {
private:
Dimension orig_res; ///< Saved window size for non-fullscreen mode.
int window_width; ///< Current window width in pixel
int window_height; ///< Current window height in pixel
int window_pitch;
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
Rect dirty_rect; ///< Region of the screen that needs redrawing.
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;
@@ -61,40 +47,83 @@ public:
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 MainLoopReal();
void AllocateBackingStore();
virtual void AllocateBackingStore(bool force = false) = 0;
protected:
Rect dirty_rect; ///< Region of the screen that needs redrawing.
bool buffer_locked; ///< Video buffer was locked by the main thread.
Dimension GetScreenSize() const override;
float GetDPIScale() override;
void InputLoop() override;
void Paint() override;
void CheckPaletteAnim() override;
bool LockVideoBuffer() override;
void UnlockVideoBuffer() override;
bool PollEvent() override;
private:
bool PollEvent();
bool IsFullscreen();
void GameSizeChanged();
const char *Initialize();
void UpdateVideoModes();
bool MakeWindow(int width, int height);
void UpdatePalette(uint first_color, uint num_colors);
virtual NSView* AllocateDrawView() = 0;
void BlitIndexedToView32(int left, int top, int right, int bottom);
/** Get a pointer to the video buffer. */
virtual void *GetVideoPointer() = 0;
/** Hand video buffer back to the drawing backend. */
virtual void ReleaseVideoPointer() {}
private:
bool IsFullscreen();
};
class FVideoDriver_Cocoa : public DriverFactoryBase {
class VideoDriver_CocoaQuartz : public VideoDriver_Cocoa {
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
int window_width; ///< Current window width in pixel
int window_height; ///< Current window height in pixel
int window_pitch;
uint32 palette[256]; ///< Colour Palette
void BlitIndexedToView32(int left, int top, int right, int bottom);
void UpdatePalette(uint first_color, uint num_colors);
public:
FVideoDriver_Cocoa() : DriverFactoryBase(Driver::DT_VIDEO, 10, "cocoa", "Cocoa Video Driver") {}
Driver *CreateInstance() const override { return new VideoDriver_Cocoa(); }
CGContextRef cgcontext; ///< Context reference for Quartz subdriver
VideoDriver_CocoaQuartz();
const char *Start(const StringList &param) override;
void Stop() override;
/** Return driver name */
const char *GetName() const override { return "cocoa"; }
void AllocateBackingStore(bool force = false) override;
protected:
void Paint() override;
void CheckPaletteAnim() override;
NSView* AllocateDrawView() override;
void *GetVideoPointer() override { return this->buffer_depth == 8 ? this->pixel_buffer : this->window_buffer; }
};
class FVideoDriver_CocoaQuartz : public DriverFactoryBase {
public:
FVideoDriver_CocoaQuartz() : DriverFactoryBase(Driver::DT_VIDEO, 10, "cocoa", "Cocoa Video Driver") {}
Driver *CreateInstance() const override { return new VideoDriver_CocoaQuartz(); }
};
#endif /* VIDEO_COCOA_H */

View File

@@ -80,33 +80,17 @@ static const Dimension _default_resolutions[] = {
{ 2560, 1440 }
};
static FVideoDriver_Cocoa iFVideoDriver_Cocoa;
/** Subclass of NSView for drawing to screen. */
@interface OTTD_QuartzView : NSView {
VideoDriver_Cocoa *driver;
}
- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv;
@end
VideoDriver_Cocoa::VideoDriver_Cocoa()
{
this->window_width = 0;
this->window_height = 0;
this->window_pitch = 0;
this->buffer_depth = 0;
this->window_buffer = nullptr;
this->pixel_buffer = nullptr;
this->setup = false;
this->buffer_locked = false;
this->window = nil;
this->cocoaview = nil;
this->delegate = nil;
this->color_space = nullptr;
this->cgcontext = nullptr;
this->dirty_rect = {};
}
@@ -123,17 +107,13 @@ void VideoDriver_Cocoa::Stop()
[ this->cocoaview release ];
[ this->delegate release ];
CGContextRelease(this->cgcontext);
CGColorSpaceRelease(this->color_space);
free(this->window_buffer);
free(this->pixel_buffer);
_cocoa_video_started = false;
}
/** Try to start Cocoa video driver. */
const char *VideoDriver_Cocoa::Start(const StringList &parm)
/** Common driver initialization. */
const char *VideoDriver_Cocoa::Initialize()
{
if (!MacOSVersionIsAtLeast(10, 7, 0)) return "The Cocoa video driver requires Mac OS X 10.7 or later.";
@@ -146,23 +126,6 @@ const char *VideoDriver_Cocoa::Start(const StringList &parm)
this->UpdateAutoResolution();
this->orig_res = _cur_resolution;
int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
if (bpp != 8 && bpp != 32) {
Stop();
return "The cocoa quartz subdriver only supports 8 and 32 bpp.";
}
bool fullscreen = _fullscreen;
if (!this->MakeWindow(_cur_resolution.width, _cur_resolution.height)) {
Stop();
return "Could not create window";
}
if (fullscreen) this->ToggleFullscreen(fullscreen);
this->GameSizeChanged();
this->UpdateVideoModes();
return nullptr;
}
@@ -216,9 +179,6 @@ bool VideoDriver_Cocoa::ChangeResolution(int w, int h)
[ this->cocoaview setFrameSize:contentRect.size ];
}
this->window_width = w;
this->window_height = h;
[ (OTTD_CocoaWindow *)this->window center ];
this->AllocateBackingStore();
@@ -250,7 +210,6 @@ bool VideoDriver_Cocoa::ToggleFullscreen(bool full_screen)
bool VideoDriver_Cocoa::AfterBlitterChange()
{
this->ChangeResolution(_cur_resolution.width, _cur_resolution.height);
this->UpdatePalette(0, 256);
return true;
}
@@ -279,6 +238,30 @@ float VideoDriver_Cocoa::GetDPIScale()
return this->cocoaview != nil ? [ this->cocoaview getContentsScale ] : 1.0f;
}
/** Lock video buffer for drawing if it isn't already mapped. */
bool VideoDriver_Cocoa::LockVideoBuffer()
{
if (this->buffer_locked) return false;
this->buffer_locked = true;
_screen.dst_ptr = this->GetVideoPointer();
assert(_screen.dst_ptr != nullptr);
return true;
}
/** Unlock video buffer. */
void VideoDriver_Cocoa::UnlockVideoBuffer()
{
if (_screen.dst_ptr != nullptr) {
/* Hand video buffer back to the drawing backend. */
this->ReleaseVideoPointer();
_screen.dst_ptr = nullptr;
}
this->buffer_locked = false;
}
/**
* Are we in fullscreen mode?
* @return whether fullscreen mode is currently used
@@ -293,12 +276,6 @@ bool VideoDriver_Cocoa::IsFullscreen()
*/
void VideoDriver_Cocoa::GameSizeChanged()
{
/* Tell the game that the resolution has changed */
_screen.width = this->window_width;
_screen.height = this->window_height;
_screen.pitch = this->buffer_depth == 8 ? this->window_width : this->window_pitch;
_screen.dst_ptr = this->buffer_depth == 8 ? this->pixel_buffer : this->window_buffer;
/* Store old window size if we entered fullscreen mode. */
bool fullscreen = this->IsFullscreen();
if (fullscreen && !_fullscreen) this->orig_res = _cur_resolution;
@@ -391,7 +368,7 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height)
[ this->cocoaview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable ];
/* Create content view. */
NSView *draw_view = [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->cocoaview bounds ] andDriver:this ];
NSView *draw_view = this->AllocateDrawView();
if (draw_view == nil) {
DEBUG(driver, 0, "Could not create the drawing view.");
this->setup = false;
@@ -413,171 +390,9 @@ bool VideoDriver_Cocoa::MakeWindow(int width, int height)
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]];
}
}
}
/**
* Paint window.
* @param force_update Whether to redraw unconditionally
*/
void VideoDriver_Cocoa::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
/* Check if we need to do anything */
if (IsEmptyRect(this->dirty_rect) || [ this->window isMiniaturized ]) return;
/* 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_rect.left,
this->dirty_rect.top,
this->dirty_rect.right,
this->dirty_rect.bottom
);
}
NSRect dirtyrect;
dirtyrect.origin.x = this->dirty_rect.left;
dirtyrect.origin.y = this->window_height - this->dirty_rect.bottom;
dirtyrect.size.width = this->dirty_rect.right - this->dirty_rect.left;
dirtyrect.size.height = this->dirty_rect.bottom - this->dirty_rect.top;
/* Notify OS X that we have new content to show. */
[ this->cocoaview setNeedsDisplayInRect:[ this->cocoaview getVirtualRect:dirtyrect ] ];
/* Tell the OS to get our contents to screen as soon as possible. */
[ CATransaction flush ];
this->dirty_rect = {};
}
/** 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->MakeDirty(0, 0, _screen.width, _screen.height);
}
/** 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. */
ClearWindowBuffer((uint32 *)this->window_buffer, this->window_pitch, this->window_height);
CGContextRelease(this->cgcontext);
this->cgcontext = CGBitmapContextCreate(
this->window_buffer, // data
this->window_width, // width
this->window_height, // height
8, // bits per component
this->window_pitch * 4, // bytes per row
this->color_space, // color space
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host
);
assert(this->cgcontext != NULL);
CGContextSetShouldAntialias(this->cgcontext, FALSE);
CGContextSetAllowsAntialiasing(this->cgcontext, FALSE);
CGContextSetInterpolationQuality(this->cgcontext, kCGInterpolationNone);
if (this->buffer_depth == 8) {
free(this->pixel_buffer);
this->pixel_buffer = malloc(this->window_width * this->window_height);
if (this->pixel_buffer == nullptr) usererror("Out of memory allocating pixel buffer");
} else {
free(this->pixel_buffer);
this->pixel_buffer = nullptr;
}
/* Redraw screen */
this->GameSizeChanged();
this->MakeDirty(0, 0, _screen.width, _screen.height);
}
/** Check if palette updates need to be performed. */
void VideoDriver_Cocoa::CheckPaletteAnim()
{
if (_cur_palette.count_dirty != 0) {
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
switch (blitter->UsePaletteAnimation()) {
case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
this->UpdatePalette(_cur_palette.first_dirty, _cur_palette.count_dirty);
break;
case Blitter::PALETTE_ANIMATION_BLITTER:
blitter->PaletteAnimate(_cur_palette);
break;
case Blitter::PALETTE_ANIMATION_NONE:
break;
default:
NOT_REACHED();
}
_cur_palette.count_dirty = 0;
}
}
/**
* Poll and handle a single event from the OS.
@@ -615,15 +430,10 @@ void VideoDriver_Cocoa::InputLoop()
}
/** Main game loop. */
void VideoDriver_Cocoa::GameLoop()
void VideoDriver_Cocoa::MainLoopReal()
{
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;
@@ -639,9 +449,16 @@ void VideoDriver_Cocoa::GameLoop()
}
/* Subclass of OTTD_CocoaView to fix Quartz rendering */
@interface OTTD_QuartzView : NSView {
VideoDriver_CocoaQuartz *driver;
}
- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_CocoaQuartz *)drv;
@end
@implementation OTTD_QuartzView
- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_Cocoa *)drv
- (instancetype)initWithFrame:(NSRect)frameRect andDriver:(VideoDriver_CocoaQuartz *)drv
{
if (self = [ super initWithFrame:frameRect ]) {
self->driver = drv;
@@ -690,4 +507,225 @@ void VideoDriver_Cocoa::GameLoop()
@end
static FVideoDriver_CocoaQuartz iFVideoDriver_CocoaQuartz;
/** 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;
}
}
}
VideoDriver_CocoaQuartz::VideoDriver_CocoaQuartz()
{
this->window_width = 0;
this->window_height = 0;
this->window_pitch = 0;
this->buffer_depth = 0;
this->window_buffer = nullptr;
this->pixel_buffer = nullptr;
this->cgcontext = nullptr;
}
const char *VideoDriver_CocoaQuartz::Start(const StringList &param)
{
const char *err = this->Initialize();
if (err != nullptr) return err;
int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
if (bpp != 8 && bpp != 32) {
Stop();
return "The cocoa quartz subdriver only supports 8 and 32 bpp.";
}
bool fullscreen = _fullscreen;
if (!this->MakeWindow(_cur_resolution.width, _cur_resolution.height)) {
Stop();
return "Could not create window";
}
this->AllocateBackingStore(true);
if (fullscreen) this->ToggleFullscreen(fullscreen);
this->GameSizeChanged();
this->UpdateVideoModes();
return nullptr;
}
void VideoDriver_CocoaQuartz::Stop()
{
this->VideoDriver_Cocoa::Stop();
CGContextRelease(this->cgcontext);
free(this->window_buffer);
free(this->pixel_buffer);
}
NSView *VideoDriver_CocoaQuartz::AllocateDrawView()
{
return [ [ OTTD_QuartzView alloc ] initWithFrame:[ this->cocoaview bounds ] andDriver:this ];
}
/** Resize the window. */
void VideoDriver_CocoaQuartz::AllocateBackingStore(bool force)
{
if (this->window == nil || this->cocoaview == nil || this->setup) return;
this->UpdatePalette(0, 256);
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. */
ClearWindowBuffer((uint32 *)this->window_buffer, this->window_pitch, this->window_height);
CGContextRelease(this->cgcontext);
this->cgcontext = CGBitmapContextCreate(
this->window_buffer, // data
this->window_width, // width
this->window_height, // height
8, // bits per component
this->window_pitch * 4, // bytes per row
this->color_space, // color space
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host
);
assert(this->cgcontext != NULL);
CGContextSetShouldAntialias(this->cgcontext, FALSE);
CGContextSetAllowsAntialiasing(this->cgcontext, FALSE);
CGContextSetInterpolationQuality(this->cgcontext, kCGInterpolationNone);
if (this->buffer_depth == 8) {
free(this->pixel_buffer);
this->pixel_buffer = malloc(this->window_width * this->window_height);
if (this->pixel_buffer == nullptr) usererror("Out of memory allocating pixel buffer");
} else {
free(this->pixel_buffer);
this->pixel_buffer = nullptr;
}
/* Tell the game that the resolution has changed */
_screen.width = this->window_width;
_screen.height = this->window_height;
_screen.pitch = this->buffer_depth == 8 ? this->window_width : this->window_pitch;
_screen.dst_ptr = this->GetVideoPointer();
/* Redraw screen */
this->MakeDirty(0, 0, _screen.width, _screen.height);
this->GameSizeChanged();
}
/**
* This function copies 8bpp pixels from 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_CocoaQuartz::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]];
}
}
}
/** Update the palette */
void VideoDriver_CocoaQuartz::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->MakeDirty(0, 0, _screen.width, _screen.height);
}
void VideoDriver_CocoaQuartz::CheckPaletteAnim()
{
if (_cur_palette.count_dirty != 0) {
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
switch (blitter->UsePaletteAnimation()) {
case Blitter::PALETTE_ANIMATION_VIDEO_BACKEND:
this->UpdatePalette(_cur_palette.first_dirty, _cur_palette.count_dirty);
break;
case Blitter::PALETTE_ANIMATION_BLITTER:
blitter->PaletteAnimate(_cur_palette);
break;
case Blitter::PALETTE_ANIMATION_NONE:
break;
default:
NOT_REACHED();
}
_cur_palette.count_dirty = 0;
}
}
/** Draw window */
void VideoDriver_CocoaQuartz::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
/* Check if we need to do anything */
if (IsEmptyRect(this->dirty_rect) || [ this->window isMiniaturized ]) return;
/* 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_rect.left,
this->dirty_rect.top,
this->dirty_rect.right,
this->dirty_rect.bottom
);
}
NSRect dirtyrect;
dirtyrect.origin.x = this->dirty_rect.left;
dirtyrect.origin.y = this->window_height - this->dirty_rect.bottom;
dirtyrect.size.width = this->dirty_rect.right - this->dirty_rect.left;
dirtyrect.size.height = this->dirty_rect.bottom - this->dirty_rect.top;
/* Notify OS X that we have new content to show. */
[ this->cocoaview setNeedsDisplayInRect:[ this->cocoaview getVirtualRect:dirtyrect ] ];
/* Tell the OS to get our contents to screen as soon as possible. */
[ CATransaction flush ];
this->dirty_rect = {};
}
#endif /* WITH_COCOA */

View File

@@ -55,6 +55,8 @@ extern NSString *OTTDMainLaunchGameEngine;
@end
extern bool _allow_hidpi_window;
bool CocoaSetupApplication();
void CocoaExitApplication();

View File

@@ -152,7 +152,7 @@ static std::vector<WChar> NSStringToUTF32(NSString *s)
[ e release ];
/* Hand off to main application code. */
drv->GameLoop();
drv->MainLoopReal();
/* We are done, thank you for playing. */
[ self performSelectorOnMainThread:@selector(stopEngine) withObject:nil waitUntilDone:FALSE ];

View File

@@ -151,7 +151,7 @@ const char *VideoDriver_Dedicated::Start(const StringList &parm)
/* For win32 we need to allocate a console (debug mode does the same) */
CreateConsole();
CreateWindowsConsoleThread();
SetConsoleTitle(_T("OpenTTD Dedicated Server"));
SetConsoleTitle(L"OpenTTD Dedicated Server");
#endif
#ifdef _MSC_VER
@@ -275,8 +275,6 @@ void VideoDriver_Dedicated::MainLoop()
}
while (!_exit_game) {
InteractiveRandom(); // randomness
if (!_dedicated_forks) DedicatedHandleKeyInput();
ChangeGameSpeed(_ddc_fastforward);

1464
src/video/opengl.cpp Normal file

File diff suppressed because it is too large Load Diff

140
src/video/opengl.h Normal file
View File

@@ -0,0 +1,140 @@
/* $Id$ */
/*
* 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 opengl.h OpenGL video driver support. */
#ifndef VIDEO_OPENGL_H
#define VIDEO_OPENGL_H
#include "../core/alloc_type.hpp"
#include "../core/geometry_type.hpp"
#include "../gfx_type.h"
#include "../spriteloader/spriteloader.hpp"
#include "../misc/lrucache.hpp"
typedef void (*OGLProc)();
typedef OGLProc (*GetOGLProcAddressProc)(const char *proc);
bool IsOpenGLVersionAtLeast(byte major, byte minor);
const char *FindStringInExtensionList(const char *string, const char *substring);
class OpenGLSprite;
/** Platform-independent back-end class for OpenGL video drivers. */
class OpenGLBackend : public ZeroedMemoryAllocator, SpriteEncoder {
private:
static OpenGLBackend *instance; ///< Singleton instance pointer.
bool persistent_mapping_supported; ///< Persistent pixel buffer mapping supported.
GLsync sync_vid_mapping; ///< Sync object for the persistently mapped video buffer.
GLsync sync_anim_mapping; ///< Sync object for the persistently mapped animation buffer.
void *vid_buffer; ///< Pointer to the mapped video buffer.
GLuint vid_pbo; ///< Pixel buffer object storing the memory used for the video driver to draw to.
GLuint vid_texture; ///< Texture handle for the video buffer texture.
GLuint vid_program; ///< Shader program for rendering a RGBA video buffer.
GLuint pal_program; ///< Shader program for rendering a paletted video buffer.
GLuint vao_quad; ///< Vertex array object storing the rendering state for the fullscreen quad.
GLuint vbo_quad; ///< Vertex buffer with a fullscreen quad.
GLuint pal_texture; ///< Palette lookup texture.
void *anim_buffer; ///< Pointer to the mapped animation buffer.
GLuint anim_pbo; ///< Pixel buffer object storing the memory used for the animation buffer.
GLuint anim_texture; ///< Texture handle for the animation buffer texture.
GLuint remap_program; ///< Shader program for blending and rendering a RGBA + remap texture.
GLint remap_sprite_loc; ///< Uniform location for sprite parameters.
GLint remap_screen_loc; ///< Uniform location for screen size;
GLint remap_zoom_loc; ///< Uniform location for sprite zoom;
GLint remap_rgb_loc; ///< Uniform location for RGB mode flag;
GLuint sprite_program; ///< Shader program for blending and rendering a sprite to the video buffer.
GLint sprite_sprite_loc; ///< Uniform location for sprite parameters.
GLint sprite_screen_loc; ///< Uniform location for screen size;
GLint sprite_zoom_loc; ///< Uniform location for sprite zoom;
GLint sprite_rgb_loc; ///< Uniform location for RGB mode flag;
GLint sprite_crash_loc; ///< Uniform location for crash remap mode flag;
LRUCache<SpriteID, Sprite> cursor_cache; ///< Cache of encoded cursor sprites.
PaletteID last_sprite_pal = (PaletteID)-1; ///< Last uploaded remap palette.
OpenGLBackend();
~OpenGLBackend();
const char *Init();
bool InitShaders();
void RenderOglSprite(OpenGLSprite *gl_sprite, PaletteID pal, int x, int y, ZoomLevel zoom);
public:
/** Get singleton instance of this class. */
static inline OpenGLBackend *Get()
{
return OpenGLBackend::instance;
}
static const char *Create(GetOGLProcAddressProc get_proc);
static void Destroy();
void PrepareContext();
void UpdatePalette(const Colour *pal, uint first, uint length);
bool Resize(int w, int h, bool force = false);
void Paint();
void DrawMouseCursor();
void ClearCursorCache();
void *GetVideoBuffer();
uint8 *GetAnimBuffer();
void ReleaseVideoBuffer(const Rect &update_rect);
void ReleaseAnimBuffer(const Rect &update_rect);
/* SpriteEncoder */
bool Is32BppSupported() override { return true; }
uint GetSpriteAlignment() override { return 1u << (ZOOM_LVL_COUNT - 1); }
Sprite *Encode(const SpriteLoader::Sprite *sprite, AllocatorProc *allocator) override;
};
/** Class that encapsulates a RGBA texture together with a paletted remap texture. */
class OpenGLSprite {
private:
/** Enum of all used OpenGL texture objects. */
enum Texture {
TEX_RGBA, ///< RGBA texture part.
TEX_REMAP, ///< Remap texture part.
NUM_TEX
};
Dimension dim;
GLuint tex[NUM_TEX]; ///< The texture objects.
static GLuint dummy_tex[NUM_TEX]; ///< 1x1 dummy textures to substitute for unused sprite components.
static GLuint pal_identity; ///< Identity texture mapping.
static GLuint pal_tex; ///< Texture for palette remap.
static GLuint pal_pbo; ///< Pixel buffer object for remap upload.
static bool Create();
static void Destroy();
bool BindTextures();
public:
OpenGLSprite(uint width, uint height, uint levels, SpriteColourComponent components);
~OpenGLSprite();
void Update(uint width, uint height, uint level, const SpriteLoader::CommonPixel *data);
Dimension GetSize(ZoomLevel level) const;
friend class OpenGLBackend;
};
#endif /* VIDEO_OPENGL_H */

View File

@@ -0,0 +1,203 @@
/*
* 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_default_v.cpp Implementation of the default backend for SDL2 video driver. */
#include "../stdafx.h"
#include "../openttd.h"
#include "../gfx_func.h"
#include "../rev.h"
#include "../blitter/factory.hpp"
#include "../network/network.h"
#include "../thread.h"
#include "../progress.h"
#include "../core/random_func.hpp"
#include "../core/math_func.hpp"
#include "../core/mem_func.hpp"
#include "../core/geometry_func.hpp"
#include "../fileio_func.h"
#include "../framerate_type.h"
#include "../window_func.h"
#include "sdl2_default_v.h"
#include <SDL.h>
#include <mutex>
#include <condition_variable>
#if defined(__MINGW32__)
#include "../3rdparty/mingw-std-threads/mingw.mutex.h"
#include "../3rdparty/mingw-std-threads/mingw.condition_variable.h"
#endif
#ifdef __EMSCRIPTEN__
# include <emscripten.h>
# include <emscripten/html5.h>
#endif
#include "../safeguards.h"
static FVideoDriver_SDL_Default iFVideoDriver_SDL_Default;
static SDL_Surface *_sdl_surface;
static SDL_Surface *_sdl_rgb_surface;
static SDL_Surface *_sdl_real_surface;
static SDL_Palette *_sdl_palette;
void VideoDriver_SDL_Default::UpdatePalette()
{
SDL_Color pal[256];
for (int i = 0; i != this->local_palette.count_dirty; i++) {
pal[i].r = this->local_palette.palette[this->local_palette.first_dirty + i].r;
pal[i].g = this->local_palette.palette[this->local_palette.first_dirty + i].g;
pal[i].b = this->local_palette.palette[this->local_palette.first_dirty + i].b;
pal[i].a = 0;
}
SDL_SetPaletteColors(_sdl_palette, pal, this->local_palette.first_dirty, this->local_palette.count_dirty);
SDL_SetSurfacePalette(_sdl_surface, _sdl_palette);
}
void VideoDriver_SDL_Default::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;
this->local_palette = _cur_palette;
this->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_Default::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
if (IsEmptyRect(this->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:
this->UpdatePalette();
break;
case Blitter::PALETTE_ANIMATION_BLITTER: {
bool need_buf = _screen.dst_ptr == nullptr;
if (need_buf) _screen.dst_ptr = this->GetVideoPointer();
blitter->PaletteAnimate(this->local_palette);
if (need_buf) {
this->ReleaseVideoPointer();
_screen.dst_ptr = nullptr;
}
break;
}
case Blitter::PALETTE_ANIMATION_NONE:
break;
default:
NOT_REACHED();
}
_cur_palette.count_dirty = 0;
}
SDL_Rect r = { this->dirty_rect.left, this->dirty_rect.top, this->dirty_rect.right - this->dirty_rect.left, this->dirty_rect.bottom - this->dirty_rect.top };
if (_sdl_surface != _sdl_real_surface) {
SDL_BlitSurface(_sdl_surface, &r, _sdl_real_surface, &r);
}
SDL_UpdateWindowSurfaceRects(this->sdl_window, &r, 1);
this->dirty_rect = {};
}
void VideoDriver_SDL_Default::PaintThread()
{
/* First tell the main thread we're started */
std::unique_lock<std::recursive_mutex> lock(*this->draw_mutex);
this->draw_signal->notify_one();
/* Now wait for the first thing to draw! */
this->draw_signal->wait(*this->draw_mutex);
while (this->draw_continue) {
/* Then just draw and wait till we stop */
this->Paint();
this->draw_signal->wait(lock);
}
}
bool VideoDriver_SDL_Default::AllocateBackingStore(int w, int h, bool force)
{
int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
_sdl_real_surface = SDL_GetWindowSurface(this->sdl_window);
if (_sdl_real_surface == nullptr) usererror("SDL2: Couldn't get window surface: %s", SDL_GetError());
if (!force && w == _sdl_real_surface->w && h == _sdl_real_surface->h) return false;
/* Free any previously allocated rgb surface. */
if (_sdl_rgb_surface != nullptr) {
SDL_FreeSurface(_sdl_rgb_surface);
_sdl_rgb_surface = nullptr;
}
if (bpp == 8) {
_sdl_rgb_surface = SDL_CreateRGBSurface(0, w, h, 8, 0, 0, 0, 0);
if (_sdl_rgb_surface == nullptr) usererror("SDL2: Couldn't allocate shadow surface: %s", SDL_GetError());
_sdl_surface = _sdl_rgb_surface;
} else {
_sdl_surface = _sdl_real_surface;
}
/* X11 doesn't appreciate it if we invalidate areas outside the window
* if shared memory is enabled (read: it crashes). So, as we might have
* gotten smaller, reset our dirty rects. GameSizeChanged() a bit lower
* will mark the whole screen dirty again anyway, but this time with the
* new dimensions. */
this->dirty_rect = {};
_screen.width = _sdl_surface->w;
_screen.height = _sdl_surface->h;
_screen.pitch = _sdl_surface->pitch / (bpp / 8);
_screen.dst_ptr = this->GetVideoPointer();
this->MakePalette();
return true;
}
void *VideoDriver_SDL_Default::GetVideoPointer()
{
return _sdl_surface->pixels;
}

View File

@@ -0,0 +1,40 @@
/*
* 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_default_v.h Default backend of the SDL2 video driver. */
#ifndef VIDEO_SDL2_DEFAULT_H
#define VIDEO_SDL2_DEFAULT_H
#include "sdl2_v.h"
/** The SDL video driver using default SDL backend. */
class VideoDriver_SDL_Default : public VideoDriver_SDL_Base {
public:
const char *GetName() const override { return "sdl"; }
protected:
bool AllocateBackingStore(int w, int h, bool force = false) override;
void *GetVideoPointer() override;
void Paint() override;
void PaintThread() override;
void ReleaseVideoPointer() override {}
private:
void UpdatePalette();
void MakePalette();
};
/** Factory for the SDL video driver. */
class FVideoDriver_SDL_Default : public DriverFactoryBase {
public:
FVideoDriver_SDL_Default() : DriverFactoryBase(Driver::DT_VIDEO, 5, "sdl", "SDL Video Driver") {}
Driver *CreateInstance() const override { return new VideoDriver_SDL_Default(); }
};
#endif /* VIDEO_SDL2_DEFAULT_H */

179
src/video/sdl2_opengl_v.cpp Normal file
View File

@@ -0,0 +1,179 @@
/*
* 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_opengl_v.cpp Implementation of the OpenGL backend for SDL2 video driver. */
/* XXX -- Temporary hack for Windows compile */
#define WINGDIAPI
#define APIENTRY
#include "../stdafx.h"
#include "../openttd.h"
#include "../gfx_func.h"
#include "../rev.h"
#include "../blitter/factory.hpp"
#include "../network/network.h"
#include "../thread.h"
#include "../progress.h"
#include "../core/random_func.hpp"
#include "../core/math_func.hpp"
#include "../core/mem_func.hpp"
#include "../core/geometry_func.hpp"
#include "../fileio_func.h"
#include "../framerate_type.h"
#include "../window_func.h"
#include "sdl2_opengl_v.h"
#include <SDL.h>
#include <mutex>
#include <condition_variable>
#if defined(__MINGW32__)
#include "../3rdparty/mingw-std-threads/mingw.mutex.h"
#include "../3rdparty/mingw-std-threads/mingw.condition_variable.h"
#endif
#include <GL/gl.h>
#include "../3rdparty/opengl/glext.h"
#include "opengl.h"
#ifdef __EMSCRIPTEN__
# include <emscripten.h>
# include <emscripten/html5.h>
#endif
#include "../safeguards.h"
static FVideoDriver_SDL_OpenGL iFVideoDriver_SDL_OpenGL;
/** Platform-specific callback to get an OpenGL funtion pointer. */
static OGLProc GetOGLProcAddressCallback(const char *proc)
{
return reinterpret_cast<OGLProc>(SDL_GL_GetProcAddress(proc));
}
bool VideoDriver_SDL_OpenGL::CreateMainWindow(uint w, uint h, uint flags)
{
return this->VideoDriver_SDL_Base::CreateMainWindow(w, h, SDL_WINDOW_OPENGL);
}
const char *VideoDriver_SDL_OpenGL::Start(const StringList &param)
{
const char *error = VideoDriver_SDL_Base::Start(param);
if (error != nullptr) return error;
error = this->AllocateContext();
if (error != nullptr) {
this->Stop();
return error;
}
/* Now we have a OpenGL context, force a client-size-changed event,
* so all buffers are allocated correctly. */
int w, h;
SDL_GetWindowSize(this->sdl_window, &w, &h);
this->ClientSizeChanged(w, h, true);
SDL_GL_SetSwapInterval(GetDriverParamBool(param, "vsync") ? 1 : 0);
this->draw_threaded = false;
return nullptr;
}
void VideoDriver_SDL_OpenGL::Stop()
{
this->DestroyContext();
this->VideoDriver_SDL_Base::Stop();
}
void VideoDriver_SDL_OpenGL::DestroyContext()
{
OpenGLBackend::Destroy();
if (this->gl_context != nullptr) {
SDL_GL_DeleteContext(this->gl_context);
this->gl_context = nullptr;
}
}
const char *VideoDriver_SDL_OpenGL::AllocateContext()
{
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
if (_debug_driver_level >= 8) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
}
this->gl_context = SDL_GL_CreateContext(this->sdl_window);
if (this->gl_context == nullptr) return "SDL2: Can't active GL context";
return OpenGLBackend::Create(&GetOGLProcAddressCallback);
}
void VideoDriver_SDL_OpenGL::ClearSystemSprites()
{
OpenGLBackend::Get()->ClearCursorCache();
}
bool VideoDriver_SDL_OpenGL::AllocateBackingStore(int w, int h, bool force)
{
if (this->gl_context == nullptr) return false;
if (_screen.dst_ptr != nullptr) this->ReleaseVideoPointer();
w = std::max(w, 64);
h = std::max(h, 64);
MemSetT(&this->dirty_rect, 0);
bool res = OpenGLBackend::Get()->Resize(w, h, force);
_screen.dst_ptr = this->GetVideoPointer();
_cur_palette.first_dirty = 0;
_cur_palette.count_dirty = 256;
this->local_palette = _cur_palette;
return res;
}
void *VideoDriver_SDL_OpenGL::GetVideoPointer()
{
if (BlitterFactory::GetCurrentBlitter()->NeedsAnimationBuffer()) {
this->anim_buffer = OpenGLBackend::Get()->GetAnimBuffer();
}
return OpenGLBackend::Get()->GetVideoBuffer();
}
void VideoDriver_SDL_OpenGL::ReleaseVideoPointer()
{
if (this->anim_buffer != nullptr) OpenGLBackend::Get()->ReleaseAnimBuffer(this->dirty_rect);
OpenGLBackend::Get()->ReleaseVideoBuffer(this->dirty_rect);
MemSetT(&this->dirty_rect, 0);
this->anim_buffer = nullptr;
}
void VideoDriver_SDL_OpenGL::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
if (_cur_palette.count_dirty != 0) {
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
/* Always push a changed palette to OpenGL. */
OpenGLBackend::Get()->UpdatePalette(this->local_palette.palette, this->local_palette.first_dirty, this->local_palette.count_dirty);
if (blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_BLITTER) {
blitter->PaletteAnimate(this->local_palette);
}
_cur_palette.count_dirty = 0;
}
OpenGLBackend::Get()->Paint();
if (_cursor.in_window) OpenGLBackend::Get()->DrawMouseCursor();
SDL_GL_SwapWindow(this->sdl_window);
}

54
src/video/sdl2_opengl_v.h Normal file
View File

@@ -0,0 +1,54 @@
/*
* 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_opengl_v.h OpenGL backend of the SDL2 video driver. */
#include "sdl2_v.h"
/** The OpenGL video driver for windows. */
class VideoDriver_SDL_OpenGL : public VideoDriver_SDL_Base {
public:
VideoDriver_SDL_OpenGL() : gl_context(nullptr), anim_buffer(nullptr) {}
const char *Start(const StringList &param) override;
void Stop() override;
bool HasEfficient8Bpp() const override { return true; }
bool UseSystemCursor() override { return true; }
void ClearSystemSprites() override;
bool HasAnimBuffer() override { return true; }
uint8 *GetAnimBuffer() override { return this->anim_buffer; }
const char *GetName() const override { return "sdl-opengl"; }
protected:
bool AllocateBackingStore(int w, int h, bool force = false) override;
void *GetVideoPointer() override;
void ReleaseVideoPointer() override;
void Paint() override;
bool CreateMainWindow(uint w, uint h, uint flags) override;
void PaintThread() override {}
private:
void *gl_context; ///< OpenGL context.
uint8 *anim_buffer; ///< Animation buffer from OpenGL back-end.
const char *AllocateContext();
void DestroyContext();
};
/** The factory for SDL' OpenGL video driver. */
class FVideoDriver_SDL_OpenGL : public DriverFactoryBase {
public:
FVideoDriver_SDL_OpenGL() : DriverFactoryBase(Driver::DT_VIDEO, 8, "sdl-opengl", "SDL OpenGL Video Driver") {}
/* virtual */ Driver *CreateInstance() const override { return new VideoDriver_SDL_OpenGL(); }
};

View File

@@ -23,12 +23,10 @@
#include "../scope.h"
#include "sdl2_v.h"
#include <SDL.h>
#include <mutex>
#include <condition_variable>
#include <algorithm>
#include <mutex>
#if defined(__MINGW32__)
#include "../3rdparty/mingw-std-threads/mingw.mutex.h"
#include "../3rdparty/mingw-std-threads/mingw.condition_variable.h"
#endif
#ifdef __EMSCRIPTEN__
# include <emscripten.h>
@@ -47,31 +45,11 @@
#include "../safeguards.h"
static FVideoDriver_SDL iFVideoDriver_SDL;
static SDL_Window *_sdl_window;
static SDL_Surface *_sdl_surface;
static SDL_Surface *_sdl_rgb_surface;
static SDL_Surface *_sdl_real_surface;
/** Whether the drawing is/may be done in a separate thread. */
static bool _draw_threaded;
/** Mutex to keep the access to the shared memory controlled. */
static std::recursive_mutex *_draw_mutex = nullptr;
/** Signal to draw the next frame. */
static std::condition_variable_any *_draw_signal = nullptr;
/** Should we keep continue drawing? */
static volatile bool _draw_continue;
static Palette _local_palette;
static SDL_Palette *_sdl_palette;
#ifdef __EMSCRIPTEN__
/** Whether we just had a window-enter event. */
static bool _cursor_new_in_window = false;
#endif
static Rect _dirty_rect;
static std::string _editing_text;
static void SetTextInputRect();
@@ -83,6 +61,7 @@ bool EditBoxInGlobalFocus();
void InputLoop();
#if defined(WITH_FCITX)
static SDL_Window *_fcitx_sdl_window;
static bool _fcitx_mode = false;
static char _fcitx_service_name[64];
static char _fcitx_ic_name[64];
@@ -271,126 +250,21 @@ const static bool _fcitx_mode = false;
const static bool _suppress_text_event = false;
#endif
void VideoDriver_SDL::MakeDirty(int left, int top, int width, int height)
void VideoDriver_SDL_Base::MakeDirty(int left, int top, int width, int height)
{
Rect r = {left, top, left + width, top + height};
_dirty_rect = BoundingRect(_dirty_rect, r);
this->dirty_rect = BoundingRect(this->dirty_rect, r);
}
static void UpdatePalette()
{
SDL_Color pal[256];
for (int i = 0; i != _local_palette.count_dirty; i++) {
pal[i].r = _local_palette.palette[_local_palette.first_dirty + i].r;
pal[i].g = _local_palette.palette[_local_palette.first_dirty + i].g;
pal[i].b = _local_palette.palette[_local_palette.first_dirty + i].b;
pal[i].a = 0;
}
SDL_SetPaletteColors(_sdl_palette, pal, _local_palette.first_dirty, _local_palette.count_dirty);
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()
void VideoDriver_SDL_Base::CheckPaletteAnim()
{
if (_cur_palette.count_dirty == 0) return;
_local_palette = _cur_palette;
this->local_palette = _cur_palette;
this->MakeDirty(0, 0, _screen.width, _screen.height);
}
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);
_dirty_rect = {};
}
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 */
this->Paint();
_draw_signal->wait(lock);
}
}
/* static */ void VideoDriver_SDL::PaintThreadThunk(VideoDriver_SDL *drv)
/* static */ void VideoDriver_SDL_Base::PaintThreadThunk(VideoDriver_SDL_Base *drv)
{
drv->PaintThread();
}
@@ -473,11 +347,26 @@ static uint FindStartupDisplay(uint startup_display)
return 0;
}
bool VideoDriver_SDL::CreateMainWindow(uint w, uint h)
void VideoDriver_SDL_Base::ClientSizeChanged(int w, int h, bool force)
{
if (_sdl_window != nullptr) return true;
/* Allocate backing store of the new size. */
if (this->AllocateBackingStore(w, h, force)) {
/* Mark all palette colours dirty. */
_cur_palette.first_dirty = 0;
_cur_palette.count_dirty = 256;
this->local_palette = _cur_palette;
Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
BlitterFactory::GetCurrentBlitter()->PostResize();
GameSizeChanged();
}
}
bool VideoDriver_SDL_Base::CreateMainWindow(uint w, uint h, uint flags)
{
if (this->sdl_window != nullptr) return true;
flags |= SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
if (_fullscreen) {
flags |= SDL_WINDOW_FULLSCREEN;
@@ -492,13 +381,16 @@ bool VideoDriver_SDL::CreateMainWindow(uint w, uint h)
char caption[50];
seprintf(caption, lastof(caption), "OpenTTD %s", _openttd_revision);
_sdl_window = SDL_CreateWindow(
this->sdl_window = SDL_CreateWindow(
caption,
x, y,
w, h,
flags);
#if defined(WITH_FCITX)
_fcitx_sdl_window = this->sdl_window;
#endif
if (_sdl_window == nullptr) {
if (this->sdl_window == nullptr) {
DEBUG(driver, 0, "SDL2: Couldn't allocate a window to draw on: %s", SDL_GetError());
return false;
}
@@ -512,7 +404,7 @@ bool VideoDriver_SDL::CreateMainWindow(uint w, uint h)
uint32 rgbmap = SDL_MapRGB(icon->format, 255, 0, 255);
SDL_SetColorKey(icon, SDL_TRUE, rgbmap);
SDL_SetWindowIcon(_sdl_window, icon);
SDL_SetWindowIcon(this->sdl_window, icon);
SDL_FreeSurface(icon);
}
}
@@ -520,67 +412,24 @@ bool VideoDriver_SDL::CreateMainWindow(uint w, uint h)
return true;
}
bool VideoDriver_SDL::CreateMainSurface(uint w, uint h, bool resize)
bool VideoDriver_SDL_Base::CreateMainSurface(uint w, uint h, bool resize)
{
int bpp = BlitterFactory::GetCurrentBlitter()->GetScreenDepth();
GetAvailableVideoMode(&w, &h);
DEBUG(driver, 1, "SDL2: using mode %ux%ux%d", w, h, bpp);
DEBUG(driver, 1, "SDL2: using mode %ux%u", w, h);
if (!this->CreateMainWindow(w, h)) return false;
if (resize) SDL_SetWindowSize(_sdl_window, w, h);
_sdl_real_surface = SDL_GetWindowSurface(_sdl_window);
if (_sdl_real_surface == nullptr) {
DEBUG(driver, 0, "SDL2: Couldn't get window surface: %s", SDL_GetError());
return false;
}
/* Free any previously allocated rgb surface. */
if (_sdl_rgb_surface != nullptr) {
SDL_FreeSurface(_sdl_rgb_surface);
_sdl_rgb_surface = nullptr;
}
if (bpp == 8) {
_sdl_rgb_surface = SDL_CreateRGBSurface(0, w, h, 8, 0, 0, 0, 0);
if (_sdl_rgb_surface == nullptr) {
DEBUG(driver, 0, "SDL2: Couldn't allocate shadow surface: %s", SDL_GetError());
return false;
}
_sdl_surface = _sdl_rgb_surface;
} else {
_sdl_surface = _sdl_real_surface;
}
/* X11 doesn't appreciate it if we invalidate areas outside the window
* if shared memory is enabled (read: it crashes). So, as we might have
* gotten smaller, reset our dirty rects. GameSizeChanged() a bit lower
* will mark the whole screen dirty again anyway, but this time with the
* new dimensions. */
_dirty_rect = {};
_screen.width = _sdl_surface->w;
_screen.height = _sdl_surface->h;
_screen.pitch = _sdl_surface->pitch / (bpp / 8);
_screen.dst_ptr = _sdl_surface->pixels;
MakePalette();
if (resize) SDL_SetWindowSize(this->sdl_window, w, h);
this->ClientSizeChanged(w, h, true);
/* When in full screen, we will always have the mouse cursor
* within the window, even though SDL does not give us the
* appropriate event to know this. */
if (_fullscreen) _cursor.in_window = true;
BlitterFactory::GetCurrentBlitter()->PostResize();
GameSizeChanged();
return true;
}
bool VideoDriver_SDL::ClaimMousePointer()
bool VideoDriver_SDL_Base::ClaimMousePointer()
{
SDL_ShowCursor(0);
#ifdef __EMSCRIPTEN__
@@ -603,7 +452,7 @@ static void SetTextInputRect()
if (_fcitx_mode) {
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
if (!SDL_GetWindowWMInfo(_sdl_window, &info)) {
if (!SDL_GetWindowWMInfo(_fcitx_sdl_window, &info)) {
return;
}
int x = 0;
@@ -616,7 +465,7 @@ static void SetTextInputRect()
Window unused;
XTranslateCoordinates(x_disp, x_win, attrib.root, 0, 0, &x, &y, &unused);
} else {
SDL_GetWindowPosition(_sdl_window, &x, &y);
SDL_GetWindowPosition(_fcitx_sdl_window, &x, &y);
}
x += winrect.x;
y += winrect.y;
@@ -639,7 +488,7 @@ static void SetTextInputRect()
/**
* This is called to indicate that an edit box has gained focus, text input mode should be enabled.
*/
void VideoDriver_SDL::EditBoxGainedFocus()
void VideoDriver_SDL_Base::EditBoxGainedFocus()
{
if (!this->edit_box_focused) {
SDL_StartTextInput();
@@ -651,7 +500,7 @@ void VideoDriver_SDL::EditBoxGainedFocus()
/**
* This is called to indicate that an edit box has lost focus, text input mode should be disabled.
*/
void VideoDriver_SDL::EditBoxLostFocus()
void VideoDriver_SDL_Base::EditBoxLostFocus()
{
if (this->edit_box_focused) {
if (_fcitx_mode) {
@@ -793,14 +642,14 @@ static uint ConvertSdlKeycodeIntoMy(SDL_Keycode kc)
return key;
}
int VideoDriver_SDL::PollEvent()
bool VideoDriver_SDL_Base::PollEvent()
{
#if defined(WITH_FCITX)
if (_fcitx_mode) FcitxPoll();
#endif
SDL_Event ev;
if (!SDL_PollEvent(&ev)) return -2;
if (!SDL_PollEvent(&ev)) return false;
switch (ev.type) {
case SDL_MOUSEMOTION:
@@ -822,7 +671,7 @@ int VideoDriver_SDL::PollEvent()
}
#else
if (_cursor.UpdateCursorPosition(ev.motion.x, ev.motion.y, true)) {
SDL_WarpMouseInWindow(_sdl_window, _cursor.pos.x, _cursor.pos.y);
SDL_WarpMouseInWindow(this->sdl_window, _cursor.pos.x, _cursor.pos.y);
}
#endif
HandleMouseEvents();
@@ -988,13 +837,12 @@ int VideoDriver_SDL::PollEvent()
#endif
}
}
return -1;
return true;
}
const char *VideoDriver_SDL::Start(const StringList &parm)
static const char *InitializeSDL()
{
if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
#if defined(WITH_FCITX)
FcitxInit();
#endif
@@ -1005,18 +853,32 @@ const char *VideoDriver_SDL::Start(const StringList &parm)
SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "0");
SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1");
/* Just on the offchance the audio subsystem started before the video system,
* check whether any part of SDL has been initialised before getting here.
* Slightly duplicated with sound/sdl_s.cpp */
int ret_code = 0;
if (SDL_WasInit(SDL_INIT_VIDEO) == 0) {
ret_code = SDL_InitSubSystem(SDL_INIT_VIDEO);
}
if (ret_code < 0) return SDL_GetError();
/* Check if the video-driver is already initialized. */
if (SDL_WasInit(SDL_INIT_VIDEO) != 0) return nullptr;
if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) return SDL_GetError();
return nullptr;
}
const char *VideoDriver_SDL_Base::Initialize()
{
this->UpdateAutoResolution();
const char *error = InitializeSDL();
if (error != nullptr) return error;
FindResolutions();
DEBUG(driver, 2, "Resolution for display: %ux%u", _cur_resolution.width, _cur_resolution.height);
return nullptr;
}
const char *VideoDriver_SDL_Base::Start(const StringList &parm)
{
if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
const char *error = this->Initialize();
if (error != nullptr) return error;
this->startup_display = FindStartupDisplay(GetDriverParamInt(parm, "display", -1));
@@ -1029,17 +891,17 @@ const char *VideoDriver_SDL::Start(const StringList &parm)
MarkWholeScreenDirty();
_draw_threaded = !GetDriverParamBool(parm, "no_threads") && !GetDriverParamBool(parm, "no_thread");
this->draw_threaded = !GetDriverParamBool(parm, "no_threads") && !GetDriverParamBool(parm, "no_thread");
/* Wayland SDL video driver uses EGL to render the game. SDL created the
* EGL context from the main-thread, and with EGL you are not allowed to
* draw in another thread than the context was created. The function of
* _draw_threaded is to do exactly this: draw in another thread than the
* draw_threaded is to do exactly this: draw in another thread than the
* window was created, and as such, this fails on Wayland SDL video
* driver. So, we disable threading by default if Wayland SDL video
* driver is detected.
*/
if (strcmp(dname, "wayland") == 0) {
_draw_threaded = false;
this->draw_threaded = false;
}
SDL_StopTextInput();
@@ -1052,7 +914,7 @@ const char *VideoDriver_SDL::Start(const StringList &parm)
return nullptr;
}
void VideoDriver_SDL::Stop()
void VideoDriver_SDL_Base::Stop()
{
#if defined(WITH_FCITX)
FcitxDeinit();
@@ -1063,7 +925,7 @@ void VideoDriver_SDL::Stop()
}
}
void VideoDriver_SDL::InputLoop()
void VideoDriver_SDL_Base::InputLoop()
{
uint32 mod = SDL_GetModState();
const Uint8 *keys = SDL_GetKeyboardState(NULL);
@@ -1093,11 +955,8 @@ void VideoDriver_SDL::InputLoop()
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
}
void VideoDriver_SDL::LoopOnce()
void VideoDriver_SDL_Base::LoopOnce()
{
InteractiveRandom(); // randomness
while (PollEvent() == -1) {}
if (_exit_game) {
#ifdef __EMSCRIPTEN__
/* Emscripten is event-driven, and as such the main loop is inside
@@ -1113,8 +972,8 @@ void VideoDriver_SDL::LoopOnce()
}
if (VideoDriver::Tick()) {
if (_draw_mutex != nullptr && !HasModalProgress()) {
_draw_signal->notify_one();
if (this->draw_mutex != nullptr && !HasModalProgress()) {
this->draw_signal->notify_one();
} else {
this->Paint();
}
@@ -1127,37 +986,37 @@ void VideoDriver_SDL::LoopOnce()
#endif
}
void VideoDriver_SDL::MainLoop()
void VideoDriver_SDL_Base::MainLoop()
{
if (_draw_threaded) {
if (this->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;
this->draw_mutex = new std::recursive_mutex();
if (this->draw_mutex == nullptr) {
this->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_lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex);
this->draw_signal = new std::condition_variable_any();
this->draw_continue = true;
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &VideoDriver_SDL::PaintThreadThunk, this);
this->draw_threaded = StartNewThread(&draw_thread, "ottd:draw-sdl", &VideoDriver_SDL_Base::PaintThreadThunk, this);
/* Free the mutex if we won't be able to use it. */
if (!_draw_threaded) {
if (!this->draw_threaded) {
draw_lock.unlock();
draw_lock.release();
delete _draw_mutex;
delete _draw_signal;
_draw_mutex = nullptr;
_draw_signal = nullptr;
delete this->draw_mutex;
delete this->draw_signal;
this->draw_mutex = nullptr;
this->draw_signal = nullptr;
} else {
/* Wait till the draw mutex has started itself. */
_draw_signal->wait(*_draw_mutex);
this->draw_signal->wait(*this->draw_mutex);
}
}
}
DEBUG(driver, 1, "SDL2: using %sthreads", _draw_threaded ? "" : "no ");
DEBUG(driver, 1, "SDL2: using %sthreads", this->draw_threaded ? "" : "no ");
#ifdef __EMSCRIPTEN__
/* Run the main loop event-driven, based on RequestAnimationFrame. */
@@ -1171,22 +1030,22 @@ void VideoDriver_SDL::MainLoop()
#endif
}
void VideoDriver_SDL::MainLoopCleanup()
void VideoDriver_SDL_Base::MainLoopCleanup()
{
if (_draw_mutex != nullptr) {
_draw_continue = false;
if (this->draw_mutex != nullptr) {
this->draw_continue = false;
/* Sending signal if there is no thread blocked
* is very valid and results in noop */
_draw_signal->notify_one();
this->draw_signal->notify_one();
if (draw_lock.owns_lock()) draw_lock.unlock();
draw_lock.release();
draw_thread.join();
delete _draw_mutex;
delete _draw_signal;
delete this->draw_mutex;
delete this->draw_signal;
_draw_mutex = nullptr;
_draw_signal = nullptr;
this->draw_mutex = nullptr;
this->draw_signal = nullptr;
}
#ifdef __EMSCRIPTEN__
@@ -1199,40 +1058,40 @@ void VideoDriver_SDL::MainLoopCleanup()
#endif
}
bool VideoDriver_SDL::ChangeResolution(int w, int h)
bool VideoDriver_SDL_Base::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 (this->draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex);
return CreateMainSurface(w, h, true);
}
bool VideoDriver_SDL::ToggleFullscreen(bool fullscreen)
bool VideoDriver_SDL_Base::ToggleFullscreen(bool fullscreen)
{
std::unique_lock<std::recursive_mutex> lock;
if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
if (this->draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex);
int w, h;
/* Remember current window size */
if (fullscreen) {
SDL_GetWindowSize(_sdl_window, &w, &h);
SDL_GetWindowSize(this->sdl_window, &w, &h);
/* Find fullscreen window size */
SDL_DisplayMode dm;
if (SDL_GetCurrentDisplayMode(0, &dm) < 0) {
DEBUG(driver, 0, "SDL_GetCurrentDisplayMode() failed: %s", SDL_GetError());
} else {
SDL_SetWindowSize(_sdl_window, dm.w, dm.h);
SDL_SetWindowSize(this->sdl_window, dm.w, dm.h);
}
}
DEBUG(driver, 1, "SDL2: Setting %s", fullscreen ? "fullscreen" : "windowed");
int ret = SDL_SetWindowFullscreen(_sdl_window, fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
int ret = SDL_SetWindowFullscreen(this->sdl_window, fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
if (ret == 0) {
/* Switching resolution succeeded, set fullscreen value of window. */
_fullscreen = fullscreen;
if (!fullscreen) SDL_SetWindowSize(_sdl_window, w, h);
if (!fullscreen) SDL_SetWindowSize(this->sdl_window, w, h);
} else {
DEBUG(driver, 0, "SDL_SetWindowFullscreen() failed: %s", SDL_GetError());
}
@@ -1240,25 +1099,25 @@ bool VideoDriver_SDL::ToggleFullscreen(bool fullscreen)
return ret == 0;
}
bool VideoDriver_SDL::AfterBlitterChange()
bool VideoDriver_SDL_Base::AfterBlitterChange()
{
assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
int w, h;
SDL_GetWindowSize(_sdl_window, &w, &h);
SDL_GetWindowSize(this->sdl_window, &w, &h);
return CreateMainSurface(w, h, false);
}
void VideoDriver_SDL::AcquireBlitterLock()
void VideoDriver_SDL_Base::AcquireBlitterLock()
{
if (_draw_mutex != nullptr) _draw_mutex->lock();
if (this->draw_mutex != nullptr) this->draw_mutex->lock();
}
void VideoDriver_SDL::ReleaseBlitterLock()
void VideoDriver_SDL_Base::ReleaseBlitterLock()
{
if (_draw_mutex != nullptr) _draw_mutex->unlock();
if (this->draw_mutex != nullptr) this->draw_mutex->unlock();
}
Dimension VideoDriver_SDL::GetScreenSize() const
Dimension VideoDriver_SDL_Base::GetScreenSize() const
{
SDL_DisplayMode mode;
if (SDL_GetCurrentDisplayMode(this->startup_display, &mode) != 0) return VideoDriver::GetScreenSize();
@@ -1266,13 +1125,27 @@ Dimension VideoDriver_SDL::GetScreenSize() const
return { static_cast<uint>(mode.w), static_cast<uint>(mode.h) };
}
bool VideoDriver_SDL::LockVideoBuffer()
bool VideoDriver_SDL_Base::LockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.lock();
if (this->buffer_locked) return false;
this->buffer_locked = true;
if (this->draw_threaded) this->draw_lock.lock();
_screen.dst_ptr = this->GetVideoPointer();
assert(_screen.dst_ptr != nullptr);
return true;
}
void VideoDriver_SDL::UnlockVideoBuffer()
void VideoDriver_SDL_Base::UnlockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.unlock();
if (_screen.dst_ptr != nullptr) {
/* Hand video buffer back to the drawing backend. */
this->ReleaseVideoPointer();
_screen.dst_ptr = nullptr;
}
if (this->draw_threaded) this->draw_lock.unlock();
this->buffer_locked = false;
}

View File

@@ -10,11 +10,18 @@
#ifndef VIDEO_SDL_H
#define VIDEO_SDL_H
#include <condition_variable>
#if defined(__MINGW32__)
#include "../3rdparty/mingw-std-threads/mingw.condition_variable.h"
#endif
#include "video_driver.hpp"
/** The SDL video driver. */
class VideoDriver_SDL : public VideoDriver {
class VideoDriver_SDL_Base : public VideoDriver {
public:
VideoDriver_SDL_Base() : sdl_window(nullptr) {}
const char *Start(const StringList &param) override;
void Stop() override;
@@ -42,24 +49,43 @@ public:
const char *GetName() const override { return "sdl"; }
protected:
struct SDL_Window *sdl_window; ///< Main SDL window.
Palette local_palette; ///< Copy of _cur_palette.
bool draw_threaded; ///< Whether the drawing is/may be done in a separate thread.
std::recursive_mutex *draw_mutex = nullptr; ///< Mutex to keep the access to the shared memory controlled.
std::condition_variable_any *draw_signal = nullptr; ///< Signal to draw the next frame.
volatile bool draw_continue; ///< Should we keep continue drawing?
bool buffer_locked; ///< Video buffer was locked by the main thread.
Rect dirty_rect; ///< Rectangle encompassing the dirty area of the video buffer.
Dimension GetScreenSize() const override;
void InputLoop() override;
bool LockVideoBuffer() override;
void UnlockVideoBuffer() override;
void Paint() override;
void PaintThread() override;
void CheckPaletteAnim() override;
bool PollEvent() override;
/** Indicate to the driver the client-side might have changed. */
void ClientSizeChanged(int w, int h, bool force);
/** (Re-)create the backing store. */
virtual bool AllocateBackingStore(int w, int h, bool force = false) = 0;
/** Get a pointer to the video buffer. */
virtual void *GetVideoPointer() = 0;
/** Hand video buffer back to the painting backend. */
virtual void ReleaseVideoPointer() = 0;
/** Create the main window. */
virtual bool CreateMainWindow(uint w, uint h, uint flags = 0);
private:
int PollEvent();
void LoopOnce();
void MainLoopCleanup();
bool CreateMainSurface(uint w, uint h, bool resize);
bool CreateMainWindow(uint w, uint h);
const char *Initialize();
#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(); }
static void EmscriptenLoop(void *self) { ((VideoDriver_SDL_Base *)self)->LoopOnce(); }
#endif
/**
@@ -71,14 +97,7 @@ private:
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(); }
static void PaintThreadThunk(VideoDriver_SDL_Base *drv);
};
#endif /* VIDEO_SDL_H */

View File

@@ -512,11 +512,11 @@ static uint ConvertSdlKeyIntoMy(SDL_keysym *sym, WChar *character)
return key;
}
int VideoDriver_SDL::PollEvent()
bool VideoDriver_SDL::PollEvent()
{
SDL_Event ev;
if (!SDL_PollEvent(&ev)) return -2;
if (!SDL_PollEvent(&ev)) return false;
switch (ev.type) {
case SDL_MOUSEMOTION:
@@ -603,7 +603,8 @@ int VideoDriver_SDL::PollEvent()
break;
}
}
return -1;
return true;
}
const char *VideoDriver_SDL::Start(const StringList &parm)
@@ -719,9 +720,6 @@ void VideoDriver_SDL::MainLoop()
DEBUG(driver, 1, "SDL: using %sthreads", _draw_threaded ? "" : "no ");
for (;;) {
InteractiveRandom(); // randomness
while (PollEvent() == -1) {}
if (_exit_game) break;
if (this->Tick()) {

View File

@@ -43,12 +43,12 @@ protected:
void UnlockVideoBuffer() override;
void Paint() override;
void PaintThread() override;
void CheckPaletteAnim();
void CheckPaletteAnim() override;
bool PollEvent() override;
private:
std::unique_lock<std::recursive_mutex> draw_lock;
int PollEvent();
bool CreateMainSurface(uint w, uint h);
void SetupKeyboard();

View File

@@ -9,6 +9,7 @@
#include "../stdafx.h"
#include "../debug.h"
#include "../core/random_func.hpp"
#include "../network/network.h"
#include "../gfx_func.h"
#include "../progress.h"
@@ -46,6 +47,11 @@ bool VideoDriver::Tick()
/* Avoid next_draw_tick getting behind more and more if it cannot keep up. */
if (this->next_draw_tick < cur_ticks - ALLOWED_DRIFT * this->GetDrawInterval()) this->next_draw_tick = cur_ticks;
/* Keep the interactive randomizer a bit more random by requesting
* new values when-ever we can. */
InteractiveRandom();
while (this->PollEvent()) {}
this->InputLoop();
/* Check if the fast-forward button is still pressed. */

View File

@@ -86,6 +86,20 @@ public:
return true;
}
/**
* Get whether the mouse cursor is drawn by the video driver.
* @return True if cursor drawing is done by the video driver.
*/
virtual bool UseSystemCursor()
{
return false;
}
/**
* Clear all cached sprites.
*/
virtual void ClearSystemSprites() {}
/**
* Whether the driver has a graphical user interface with the end user.
* Or in other words, whether we should spawn a thread for world generation
@@ -100,15 +114,42 @@ public:
}
/**
* An edit box gained the input focus
* Has this video driver an efficient code path for palette animated 8-bpp sprites?
* @return True if the driver has an efficient code path for 8-bpp.
*/
virtual void EditBoxGainedFocus() {}
virtual bool HasEfficient8Bpp() const
{
return false;
}
/**
* Does this video driver support a separate animation buffer in addition to the colour buffer?
* @return True if a separate animation buffer is supported.
*/
virtual bool HasAnimBuffer()
{
return false;
}
/**
* Get a pointer to the animation buffer of the video back-end.
* @return Pointer to the buffer or nullptr if no animation buffer is supported.
*/
virtual uint8 *GetAnimBuffer()
{
return nullptr;
}
/**
* An edit box lost the input focus. Abort character compositing if necessary.
*/
virtual void EditBoxLostFocus() {}
/**
* An edit box gained the input focus
*/
virtual void EditBoxGainedFocus() {}
/**
* Get a suggested default GUI zoom taking screen DPI into account.
*/
@@ -128,6 +169,25 @@ public:
return static_cast<VideoDriver*>(*DriverFactoryBase::GetActiveDriver(Driver::DT_VIDEO));
}
/**
* Helper struct to ensure the video buffer is locked and ready for drawing. The destructor
* will make sure the buffer is unlocked no matter how the scope is exited.
*/
struct VideoBufferLocker {
VideoBufferLocker()
{
this->unlock = VideoDriver::GetInstance()->LockVideoBuffer();
}
~VideoBufferLocker()
{
if (this->unlock) VideoDriver::GetInstance()->UnlockVideoBuffer();
}
private:
bool unlock; ///< Stores if the lock did anything that has to be undone.
};
protected:
const uint ALLOWED_DRIFT = 5; ///< How many times videodriver can miss deadlines without it being overly compensated.
@@ -190,6 +250,12 @@ protected:
*/
virtual void CheckPaletteAnim() {}
/**
* Process a single system event.
* @returns False if there are no more events to process.
*/
virtual bool PollEvent() { return false; };
/**
* Run the game for a single tick, processing boththe game-tick and draw-tick.
* @returns True if the driver should redraw the screen.

View File

@@ -25,12 +25,6 @@
#include "win32_v.h"
#include <windows.h>
#include <imm.h>
#include <mutex>
#include <condition_variable>
#if defined(__MINGW32__)
#include "../3rdparty/mingw-std-threads/mingw.mutex.h"
#include "../3rdparty/mingw-std-threads/mingw.condition_variable.h"
#endif
#include <algorithm>
#include "../safeguards.h"
@@ -44,32 +38,12 @@
#define PM_QS_INPUT 0x20000
#endif
static struct {
void *buffer_bits; ///< Internal rendering buffer.
int width; ///< Width in pixels of our display surface.
int height; ///< Height in pixels of our display surface.
int width_org; ///< Original monitor resolution width, before we changed it.
int height_org; ///< Original monitor resolution height, before we changed it.
bool has_focus; ///< Does our window have system focus?
bool running; ///< Is the main loop running?
} _wnd;
bool _window_maximize;
static Dimension _bck_resolution;
DWORD _imm_props;
/** Whether the drawing is/may be done in a separate thread. */
static bool _draw_threaded;
/** Mutex to keep the access to the shared memory controlled. */
static std::recursive_mutex *_draw_mutex = nullptr;
/** Signal to draw the next frame. */
static std::condition_variable_any *_draw_signal = nullptr;
/** Should we keep continue drawing? */
static volatile bool _draw_continue;
/** Local copy of the palette for use in the drawing thread. */
static Palette _local_palette;
/** Region of the screen that needs redrawing. */
static Rect _dirty_rect;
bool VideoDriver_Win32Base::ClaimMousePointer()
{
@@ -179,8 +153,8 @@ bool VideoDriver_Win32Base::MakeWindow(bool full_screen)
DM_PELSWIDTH |
DM_PELSHEIGHT;
settings.dmBitsPerPel = this->GetFullscreenBpp();
settings.dmPelsWidth = _wnd.width_org;
settings.dmPelsHeight = _wnd.height_org;
settings.dmPelsWidth = this->width_org;
settings.dmPelsHeight = this->height_org;
/* Check for 8 bpp support. */
if (settings.dmBitsPerPel == 8 && ChangeDisplaySettings(&settings, CDS_FULLSCREEN | CDS_TEST) != DISP_CHANGE_SUCCESSFUL) {
@@ -206,8 +180,8 @@ bool VideoDriver_Win32Base::MakeWindow(bool full_screen)
/* restore display? */
ChangeDisplaySettings(nullptr, 0);
/* restore the resolution */
_wnd.width = _bck_resolution.width;
_wnd.height = _bck_resolution.height;
this->width = _bck_resolution.width;
this->height = _bck_resolution.height;
}
{
@@ -219,12 +193,12 @@ bool VideoDriver_Win32Base::MakeWindow(bool full_screen)
this->fullscreen = full_screen;
if (this->fullscreen) {
style = WS_POPUP;
SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org);
SetRect(&r, 0, 0, this->width_org, this->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);
SetRect(&r, 0, 0, this->width, this->height);
}
AdjustWindowRect(&r, style, FALSE);
@@ -240,7 +214,7 @@ bool VideoDriver_Win32Base::MakeWindow(bool full_screen)
char window_title[64];
seprintf(window_title, lastof(window_title), "OpenTTD %s", _openttd_revision);
this->main_wnd = CreateWindow(_T("OTTD"), MB_TO_WIDE(window_title), style, x, y, w, h, 0, 0, GetModuleHandle(nullptr), this);
this->main_wnd = CreateWindow(L"OTTD", OTTD2FS(window_title), style, x, y, w, h, 0, 0, GetModuleHandle(nullptr), this);
if (this->main_wnd == nullptr) usererror("CreateWindow failed");
ShowWindow(this->main_wnd, showstyle);
}
@@ -260,41 +234,6 @@ bool VideoDriver_Win32Base::MakeWindow(bool full_screen)
/** 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. */
@@ -313,7 +252,6 @@ static LRESULT HandleCharMsg(uint keycode, WChar charcode)
}
}
prev_char = 0;
#endif /* UNICODE */
HandleKeypress(keycode, charcode);
@@ -391,9 +329,9 @@ static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
if (lParam & GCS_RESULTSTR) {
/* Read result string from the IME. */
LONG len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
wchar_t *str = (wchar_t *)_alloca(len + sizeof(wchar_t));
len = ImmGetCompositionString(hIMC, GCS_RESULTSTR, str, len);
str[len / sizeof(TCHAR)] = '\0';
str[len / sizeof(wchar_t)] = '\0';
/* Transmit text to windowing system. */
if (len > 0) {
@@ -409,9 +347,9 @@ static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
if ((lParam & GCS_COMPSTR) && DrawIMECompositionString()) {
/* Read composition string from the IME. */
LONG len = ImmGetCompositionString(hIMC, GCS_COMPSTR, nullptr, 0); // Length is always in bytes, even in UNICODE build.
TCHAR *str = (TCHAR *)_alloca(len + sizeof(TCHAR));
wchar_t *str = (wchar_t *)_alloca(len + sizeof(wchar_t));
len = ImmGetCompositionString(hIMC, GCS_COMPSTR, str, len);
str[len / sizeof(TCHAR)] = '\0';
str[len / sizeof(wchar_t)] = '\0';
if (len > 0) {
static char utf8_buf[1024];
@@ -420,13 +358,9 @@ static LRESULT HandleIMEComposition(HWND hwnd, WPARAM wParam, LPARAM lParam)
/* Convert caret position from bytes in the input string to a position in the UTF-8 encoded string. */
LONG caret_bytes = ImmGetCompositionString(hIMC, GCS_CURSORPOS, nullptr, 0);
const char *caret = utf8_buf;
for (const TCHAR *c = str; *c != '\0' && *caret != '\0' && caret_bytes > 0; c++, caret_bytes--) {
for (const wchar_t *c = str; *c != '\0' && *caret != '\0' && caret_bytes > 0; c++, caret_bytes--) {
/* Skip DBCS lead bytes or leading surrogates. */
#ifdef UNICODE
if (Utf16IsLeadSurrogate(*c)) {
#else
if (IsDBCSLeadByte(*c)) {
#endif
c++;
caret_bytes--;
}
@@ -595,16 +529,6 @@ LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
if (wParam == IMN_OPENCANDIDATE) SetCandidatePos(hwnd);
break;
#if !defined(UNICODE)
case WM_IME_CHAR:
if (GB(wParam, 8, 8) != 0) {
/* DBCS character, send lead byte first. */
HandleCharMsg(0, GB(wParam, 8, 8));
}
HandleCharMsg(0, GB(wParam, 0, 8));
return 0;
#endif
case WM_DEADCHAR:
console = GB(lParam, 16, 8) == 41;
return 0;
@@ -775,12 +699,12 @@ LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
}
case WM_SETFOCUS:
_wnd.has_focus = true;
video_driver->has_focus = true;
SetCompositionPos(hwnd);
break;
case WM_KILLFOCUS:
_wnd.has_focus = false;
video_driver->has_focus = false;
break;
case WM_ACTIVATE: {
@@ -824,7 +748,7 @@ static void RegisterWndClass()
LoadCursor(nullptr, IDC_ARROW),
0,
0,
_T("OTTD")
L"OTTD"
};
registered = true;
@@ -868,14 +792,12 @@ void VideoDriver_Win32Base::Initialize()
{
this->UpdateAutoResolution();
memset(&_wnd, 0, sizeof(_wnd));
RegisterWndClass();
FindResolutions(this->GetFullscreenBpp());
/* fullscreen uses those */
_wnd.width = _wnd.width_org = _cur_resolution.width;
_wnd.height = _wnd.height_org = _cur_resolution.height;
this->width = this->width_org = _cur_resolution.width;
this->height = this->height_org = _cur_resolution.height;
DEBUG(driver, 2, "Resolution for display: %ux%u", _cur_resolution.width, _cur_resolution.height);
}
@@ -890,7 +812,7 @@ void VideoDriver_Win32Base::Stop()
void VideoDriver_Win32Base::MakeDirty(int left, int top, int width, int height)
{
Rect r = {left, top, left + width, top + height};
_dirty_rect = BoundingRect(_dirty_rect, r);
this->dirty_rect = BoundingRect(this->dirty_rect, r);
}
void VideoDriver_Win32Base::CheckPaletteAnim()
@@ -906,19 +828,19 @@ void VideoDriver_Win32Base::InputLoop()
bool old_ctrl_pressed = _ctrl_pressed;
bool old_shift_pressed = _shift_pressed;
_ctrl_pressed = (_wnd.has_focus && GetAsyncKeyState(VK_CONTROL) < 0) != _invert_ctrl;
_shift_pressed = (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0) != _invert_shift;
_ctrl_pressed = (this->has_focus && GetAsyncKeyState(VK_CONTROL) < 0) != _invert_ctrl;
_shift_pressed = (this->has_focus && GetAsyncKeyState(VK_SHIFT) < 0) != _invert_shift;
#if defined(_DEBUG)
this->fast_forward_key_pressed = _shift_pressed;
#else
/* Speedup when pressing tab, except when using ALT+TAB
* to switch to another application. */
this->fast_forward_key_pressed = _wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0;
this->fast_forward_key_pressed = this->has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0;
#endif
/* Determine which directional keys are down. */
if (_wnd.has_focus) {
if (this->has_focus) {
_dirkeys =
(GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) +
(GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) +
@@ -932,62 +854,64 @@ void VideoDriver_Win32Base::InputLoop()
if (old_shift_pressed != _shift_pressed) HandleShiftChanged();
}
void VideoDriver_Win32Base::MainLoop()
bool VideoDriver_Win32Base::PollEvent()
{
MSG mesg;
if (!PeekMessage(&mesg, nullptr, 0, 0, PM_REMOVE)) return false;
/* Convert key messages to char messages if we want text input. */
if (EditBoxInGlobalFocus()) TranslateMessage(&mesg);
DispatchMessage(&mesg);
return true;
}
void VideoDriver_Win32Base::MainLoop()
{
std::thread draw_thread;
if (_draw_threaded) {
if (this->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();
this->draw_signal = new std::condition_variable_any();
this->draw_mutex = new std::recursive_mutex();
} catch (...) {
_draw_threaded = false;
this->draw_threaded = false;
}
if (_draw_threaded) {
this->draw_lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
if (this->draw_threaded) {
this->draw_lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex);
_draw_continue = true;
_draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &VideoDriver_Win32Base::PaintThreadThunk, this);
this->draw_continue = true;
this->draw_threaded = StartNewThread(&draw_thread, "ottd:draw-win32", &VideoDriver_Win32Base::PaintThreadThunk, this);
/* Free the mutex if we won't be able to use it. */
if (!_draw_threaded) {
if (!this->draw_threaded) {
this->draw_lock.unlock();
this->draw_lock.release();
delete _draw_mutex;
delete _draw_signal;
_draw_mutex = nullptr;
_draw_signal = nullptr;
delete this->draw_mutex;
delete this->draw_signal;
this->draw_mutex = nullptr;
this->draw_signal = nullptr;
} else {
DEBUG(driver, 1, "Threaded drawing enabled");
/* Wait till the draw thread has started itself. */
_draw_signal->wait(*_draw_mutex);
this->draw_signal->wait(*this->draw_mutex);
}
}
}
_wnd.running = true;
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;
/* Flush GDI buffer to ensure we don't conflict with the drawing thread. */
GdiFlush();
if (this->Tick()) {
if (_draw_mutex != nullptr && !HasModalProgress()) {
_draw_signal->notify_one();
if (this->draw_mutex != nullptr && !HasModalProgress()) {
this->draw_signal->notify_one();
} else {
this->Paint();
}
@@ -995,26 +919,26 @@ void VideoDriver_Win32Base::MainLoop()
this->SleepTillNextTick();
}
if (_draw_threaded) {
_draw_continue = false;
if (this->draw_threaded) {
this->draw_continue = false;
/* Sending signal if there is no thread blocked
* is very valid and results in noop */
_draw_signal->notify_all();
this->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;
delete this->draw_mutex;
delete this->draw_signal;
_draw_mutex = nullptr;
this->draw_mutex = nullptr;
}
}
void VideoDriver_Win32Base::ClientSizeChanged(int w, int h)
void VideoDriver_Win32Base::ClientSizeChanged(int w, int h, bool force)
{
/* Allocate backing store of the new size. */
if (this->AllocateBackingStore(w, h)) {
if (this->AllocateBackingStore(w, h, force)) {
/* Mark all palette colours dirty. */
_cur_palette.first_dirty = 0;
_cur_palette.count_dirty = 256;
@@ -1029,12 +953,12 @@ void VideoDriver_Win32Base::ClientSizeChanged(int w, int h)
bool VideoDriver_Win32Base::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 (this->draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex);
if (_window_maximize) ShowWindow(this->main_wnd, SW_SHOWNORMAL);
_wnd.width = _wnd.width_org = w;
_wnd.height = _wnd.height_org = h;
this->width = this->width_org = w;
this->height = this->height_org = h;
return this->MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching
}
@@ -1042,25 +966,25 @@ bool VideoDriver_Win32Base::ChangeResolution(int w, int h)
bool VideoDriver_Win32Base::ToggleFullscreen(bool full_screen)
{
std::unique_lock<std::recursive_mutex> lock;
if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
if (this->draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex);
return this->MakeWindow(full_screen);
}
void VideoDriver_Win32Base::AcquireBlitterLock()
{
if (_draw_mutex != nullptr) _draw_mutex->lock();
if (this->draw_mutex != nullptr) this->draw_mutex->lock();
}
void VideoDriver_Win32Base::ReleaseBlitterLock()
{
if (_draw_mutex != nullptr) _draw_mutex->unlock();
if (this->draw_mutex != nullptr) this->draw_mutex->unlock();
}
void VideoDriver_Win32Base::EditBoxLostFocus()
{
std::unique_lock<std::recursive_mutex> lock;
if (_draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*_draw_mutex);
if (this->draw_mutex != nullptr) lock = std::unique_lock<std::recursive_mutex>(*this->draw_mutex);
CancelIMEComposition(this->main_wnd);
SetCompositionPos(this->main_wnd);
@@ -1086,9 +1010,9 @@ float VideoDriver_Win32Base::GetDPIScale()
if (!init_done) {
init_done = true;
_GetDpiForWindow = (PFNGETDPIFORWINDOW)GetProcAddress(GetModuleHandle(_T("User32")), "GetDpiForWindow");
_GetDpiForSystem = (PFNGETDPIFORSYSTEM)GetProcAddress(GetModuleHandle(_T("User32")), "GetDpiForSystem");
_GetDpiForMonitor = (PFNGETDPIFORMONITOR)GetProcAddress(LoadLibrary(_T("Shcore.dll")), "GetDpiForMonitor");
_GetDpiForWindow = (PFNGETDPIFORWINDOW)GetProcAddress(GetModuleHandle(L"User32"), "GetDpiForWindow");
_GetDpiForSystem = (PFNGETDPIFORSYSTEM)GetProcAddress(GetModuleHandle(L"User32"), "GetDpiForSystem");
_GetDpiForMonitor = (PFNGETDPIFORMONITOR)GetProcAddress(LoadLibrary(L"Shcore.dll"), "GetDpiForMonitor");
}
UINT cur_dpi = 0;
@@ -1114,13 +1038,28 @@ float VideoDriver_Win32Base::GetDPIScale()
bool VideoDriver_Win32Base::LockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.lock();
if (this->buffer_locked) return false;
this->buffer_locked = true;
if (this->draw_threaded) this->draw_lock.lock();
_screen.dst_ptr = this->GetVideoPointer();
assert(_screen.dst_ptr != nullptr);
return true;
}
void VideoDriver_Win32Base::UnlockVideoBuffer()
{
if (_draw_threaded) this->draw_lock.unlock();
assert(_screen.dst_ptr != nullptr);
if (_screen.dst_ptr != nullptr) {
/* Hand video buffer back to the drawing backend. */
this->ReleaseVideoPointer();
_screen.dst_ptr = nullptr;
}
if (this->draw_threaded) this->draw_lock.unlock();
this->buffer_locked = false;
}
@@ -1138,7 +1077,7 @@ const char *VideoDriver_Win32GDI::Start(const StringList &param)
MarkWholeScreenDirty();
_draw_threaded = !GetDriverParam(param, "no_threads") && !GetDriverParam(param, "no_thread") && std::thread::hardware_concurrency() > 1;
this->draw_threaded = !GetDriverParam(param, "no_threads") && !GetDriverParam(param, "no_thread") && std::thread::hardware_concurrency() > 1;
return nullptr;
}
@@ -1164,8 +1103,8 @@ bool VideoDriver_Win32GDI::AllocateBackingStore(int w, int h, bool force)
memset(bi, 0, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 256);
bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi->bmiHeader.biWidth = _wnd.width = w;
bi->bmiHeader.biHeight = -(_wnd.height = h);
bi->bmiHeader.biWidth = this->width = w;
bi->bmiHeader.biHeight = -(this->height = h);
bi->bmiHeader.biPlanes = 1;
bi->bmiHeader.biBitCount = bpp;
@@ -1174,14 +1113,14 @@ bool VideoDriver_Win32GDI::AllocateBackingStore(int w, int h, bool force)
if (this->dib_sect) DeleteObject(this->dib_sect);
HDC dc = GetDC(0);
this->dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID **)&_wnd.buffer_bits, nullptr, 0);
this->dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID **)&this->buffer_bits, nullptr, 0);
if (this->dib_sect == nullptr) usererror("CreateDIBSection failed");
ReleaseDC(0, dc);
_screen.width = w;
_screen.pitch = (bpp == 8) ? Align(w, 4) : w;
_screen.height = h;
_screen.dst_ptr = _wnd.buffer_bits;
_screen.dst_ptr = this->GetVideoPointer();
return true;
}
@@ -1243,7 +1182,7 @@ void VideoDriver_Win32GDI::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
if (IsEmptyRect(_dirty_rect)) return;
if (IsEmptyRect(this->dirty_rect)) return;
HDC dc = GetDC(this->main_wnd);
HDC dc2 = CreateCompatibleDC(dc);
@@ -1259,9 +1198,16 @@ void VideoDriver_Win32GDI::Paint()
this->UpdatePalette(dc2, _local_palette.first_dirty, _local_palette.count_dirty);
break;
case Blitter::PALETTE_ANIMATION_BLITTER:
case Blitter::PALETTE_ANIMATION_BLITTER: {
bool need_buf = _screen.dst_ptr == nullptr;
if (need_buf) _screen.dst_ptr = this->GetVideoPointer();
blitter->PaletteAnimate(_local_palette);
if (need_buf) {
this->ReleaseVideoPointer();
_screen.dst_ptr = nullptr;
}
break;
}
case Blitter::PALETTE_ANIMATION_NONE:
break;
@@ -1272,32 +1218,32 @@ void VideoDriver_Win32GDI::Paint()
_cur_palette.count_dirty = 0;
}
BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY);
BitBlt(dc, 0, 0, this->width, this->height, dc2, 0, 0, SRCCOPY);
SelectPalette(dc, old_palette, TRUE);
SelectObject(dc2, old_bmp);
DeleteDC(dc2);
ReleaseDC(this->main_wnd, dc);
_dirty_rect = {};
this->dirty_rect = {};
}
void VideoDriver_Win32GDI::PaintThread()
{
/* First tell the main thread we're started */
std::unique_lock<std::recursive_mutex> lock(*_draw_mutex);
_draw_signal->notify_one();
std::unique_lock<std::recursive_mutex> lock(*this->draw_mutex);
this->draw_signal->notify_one();
/* Now wait for the first thing to draw! */
_draw_signal->wait(*_draw_mutex);
this->draw_signal->wait(*this->draw_mutex);
while (_draw_continue) {
while (this->draw_continue) {
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);
this->draw_signal->wait(*this->draw_mutex);
}
}
@@ -1309,14 +1255,287 @@ void VideoDriver_Win32GDI::PaintThread()
{
static int _fooctr;
_screen.dst_ptr = _wnd.buffer_bits;
UpdateWindows();
VideoDriver_Win32GDI *drv = static_cast<VideoDriver_Win32GDI *>(VideoDriver::GetInstance());
_screen.dst_ptr = drv->GetVideoPointer();
UpdateWindows();
drv->Paint();
GdiFlush();
return _fooctr++;
}
#endif
#ifdef WITH_OPENGL
#include <GL/gl.h>
#include "../3rdparty/opengl/glext.h"
#include "../3rdparty/opengl/wglext.h"
#include "opengl.h"
#ifndef PFD_SUPPORT_COMPOSITION
# define PFD_SUPPORT_COMPOSITION 0x00008000
#endif
static PFNWGLCREATECONTEXTATTRIBSARBPROC _wglCreateContextAttribsARB = nullptr;
static PFNWGLSWAPINTERVALEXTPROC _wglSwapIntervalEXT = nullptr;
static bool _hasWGLARBCreateContextProfile = false; ///< Is WGL_ARB_create_context_profile supported?
/** Platform-specific callback to get an OpenGL function pointer. */
static OGLProc GetOGLProcAddressCallback(const char *proc)
{
OGLProc ret = reinterpret_cast<OGLProc>(wglGetProcAddress(proc));
if (ret == nullptr) {
/* Non-extension GL function? Try normal loading. */
ret = reinterpret_cast<OGLProc>(GetProcAddress(GetModuleHandle(L"opengl32"), proc));
}
return ret;
}
/**
* Set the pixel format of a window-
* @param dc Device context to set the pixel format of.
* @param fullscreen Should the pixel format be used for fullscreen drawing?
* @return nullptr on success, error message otherwise.
*/
static const char *SelectPixelFormat(HDC dc, bool fullscreen)
{
PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // Size of this struct.
1, // Version of this struct.
PFD_DRAW_TO_WINDOW | // Require window support.
PFD_SUPPORT_OPENGL | // Require OpenGL support.
PFD_DOUBLEBUFFER | // Use double buffering.
PFD_DEPTH_DONTCARE,
PFD_TYPE_RGBA, // Request RGBA format.
24, // 24 bpp (excluding alpha).
0, 0, 0, 0, 0, 0, 0, 0, // Colour bits and shift ignored.
0, 0, 0, 0, 0, // No accumulation buffer.
0, 0, // No depth/stencil buffer.
0, // No aux buffers.
PFD_MAIN_PLANE, // Main layer.
0, 0, 0, 0 // Ignored/reserved.
};
if (IsWindowsVistaOrGreater()) pfd.dwFlags |= PFD_SUPPORT_COMPOSITION; // Make OpenTTD compatible with Aero.
/* Choose a suitable pixel format. */
int format = ChoosePixelFormat(dc, &pfd);
if (format == 0) return "No suitable pixel format found";
if (!SetPixelFormat(dc, format, &pfd)) return "Can't set pixel format";
return nullptr;
}
/** Bind all WGL extension functions we need. */
static void LoadWGLExtensions()
{
/* Querying the supported WGL extensions and loading the matching
* functions requires a valid context, even for the extensions
* regarding context creation. To get around this, we create
* a dummy window with a dummy context. The extension functions
* remain valid even after this context is destroyed. */
HWND wnd = CreateWindow(_T("STATIC"), _T("dummy"), WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
HDC dc = GetDC(wnd);
/* Set pixel format of the window. */
if (SelectPixelFormat(dc, false) == nullptr) {
/* Create rendering context. */
HGLRC rc = wglCreateContext(dc);
if (rc != nullptr) {
wglMakeCurrent(dc, rc);
/* Get list of WGL extensions. */
PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress("wglGetExtensionsStringARB");
if (wglGetExtensionsStringARB != nullptr) {
const char *wgl_exts = wglGetExtensionsStringARB(dc);
/* Bind supported functions. */
if (FindStringInExtensionList(wgl_exts, "WGL_ARB_create_context") != nullptr) {
_wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
}
_hasWGLARBCreateContextProfile = FindStringInExtensionList(wgl_exts, "WGL_ARB_create_context_profile") != nullptr;
if (FindStringInExtensionList(wgl_exts, "WGL_EXT_swap_control") != nullptr) {
_wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT");
}
}
wglMakeCurrent(nullptr, nullptr);
wglDeleteContext(rc);
}
}
ReleaseDC(wnd, dc);
DestroyWindow(wnd);
}
static FVideoDriver_Win32OpenGL iFVideoDriver_Win32OpenGL;
const char *VideoDriver_Win32OpenGL::Start(const StringList &param)
{
if (BlitterFactory::GetCurrentBlitter()->GetScreenDepth() == 0) return "Only real blitters supported";
Dimension old_res = _cur_resolution; // Save current screen resolution in case of errors, as MakeWindow invalidates it.
this->vsync = GetDriverParamBool(param, "vsync");
LoadWGLExtensions();
this->Initialize();
this->MakeWindow(_fullscreen);
/* Create and initialize OpenGL context. */
const char *err = this->AllocateContext();
if (err != nullptr) {
this->Stop();
_cur_resolution = old_res;
return err;
}
this->ClientSizeChanged(this->width, this->height, true);
this->draw_threaded = false;
MarkWholeScreenDirty();
return nullptr;
}
void VideoDriver_Win32OpenGL::Stop()
{
this->DestroyContext();
this->VideoDriver_Win32Base::Stop();
}
void VideoDriver_Win32OpenGL::DestroyContext()
{
OpenGLBackend::Destroy();
wglMakeCurrent(nullptr, nullptr);
if (this->gl_rc != nullptr) {
wglDeleteContext(this->gl_rc);
this->gl_rc = nullptr;
}
if (this->dc != nullptr) {
ReleaseDC(this->main_wnd, this->dc);
this->dc = nullptr;
}
}
const char *VideoDriver_Win32OpenGL::AllocateContext()
{
this->dc = GetDC(this->main_wnd);
const char *err = SelectPixelFormat(this->dc, this->fullscreen);
if (err != nullptr) return err;
HGLRC rc = nullptr;
/* Create OpenGL device context. Try to get an 3.2+ context if possible. */
if (_wglCreateContextAttribsARB != nullptr) {
int attribs[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 2,
WGL_CONTEXT_FLAGS_ARB, _debug_driver_level >= 8 ? WGL_CONTEXT_DEBUG_BIT_ARB : 0,
_hasWGLARBCreateContextProfile ? WGL_CONTEXT_PROFILE_MASK_ARB : 0, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, // Terminate list if WGL_ARB_create_context_profile isn't supported.
0
};
rc = _wglCreateContextAttribsARB(this->dc, nullptr, attribs);
}
if (rc == nullptr) {
/* Old OpenGL or old driver, let's hope for the best. */
rc = wglCreateContext(this->dc);
if (rc == nullptr) return "Can't create OpenGL context";
}
if (!wglMakeCurrent(this->dc, rc)) return "Can't active GL context";
/* Enable/disable Vsync if supported. */
if (_wglSwapIntervalEXT != nullptr) {
_wglSwapIntervalEXT(this->vsync ? 1 : 0);
} else if (vsync) {
DEBUG(driver, 0, "OpenGL: Vsync requested, but not supported by driver");
}
this->gl_rc = rc;
return OpenGLBackend::Create(&GetOGLProcAddressCallback);
}
bool VideoDriver_Win32OpenGL::ToggleFullscreen(bool full_screen)
{
if (_screen.dst_ptr != nullptr) this->ReleaseVideoPointer();
this->DestroyContext();
bool res = this->VideoDriver_Win32Base::ToggleFullscreen(full_screen);
res &= this->AllocateContext() == nullptr;
this->ClientSizeChanged(this->width, this->height, true);
return res;
}
bool VideoDriver_Win32OpenGL::AfterBlitterChange()
{
assert(BlitterFactory::GetCurrentBlitter()->GetScreenDepth() != 0);
this->ClientSizeChanged(this->width, this->height, true);
return true;
}
void VideoDriver_Win32OpenGL::ClearSystemSprites()
{
OpenGLBackend::Get()->ClearCursorCache();
}
bool VideoDriver_Win32OpenGL::AllocateBackingStore(int w, int h, bool force)
{
if (!force && w == _screen.width && h == _screen.height) return false;
this->width = w = std::max(w, 64);
this->height = h = std::max(h, 64);
if (this->gl_rc == nullptr) return false;
if (_screen.dst_ptr != nullptr) this->ReleaseVideoPointer();
this->dirty_rect = {};
bool res = OpenGLBackend::Get()->Resize(w, h, force);
_screen.dst_ptr = this->GetVideoPointer();
return res;
}
void *VideoDriver_Win32OpenGL::GetVideoPointer()
{
if (BlitterFactory::GetCurrentBlitter()->NeedsAnimationBuffer()) {
this->anim_buffer = OpenGLBackend::Get()->GetAnimBuffer();
}
return OpenGLBackend::Get()->GetVideoBuffer();
}
void VideoDriver_Win32OpenGL::ReleaseVideoPointer()
{
if (this->anim_buffer != nullptr) OpenGLBackend::Get()->ReleaseAnimBuffer(this->dirty_rect);
OpenGLBackend::Get()->ReleaseVideoBuffer(this->dirty_rect);
this->dirty_rect = {};
_screen.dst_ptr = nullptr;
this->anim_buffer = nullptr;
}
void VideoDriver_Win32OpenGL::Paint()
{
PerformanceMeasurer framerate(PFE_VIDEO);
if (_cur_palette.count_dirty != 0) {
Blitter *blitter = BlitterFactory::GetCurrentBlitter();
/* Always push a changed palette to OpenGL. */
OpenGLBackend::Get()->UpdatePalette(_local_palette.palette, _local_palette.first_dirty, _local_palette.count_dirty);
if (blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_BLITTER) {
blitter->PaletteAnimate(_local_palette);
}
_cur_palette.count_dirty = 0;
}
OpenGLBackend::Get()->Paint();
if (_cursor.in_window) OpenGLBackend::Get()->DrawMouseCursor();
SwapBuffers(this->dc);
}
#endif /* WITH_OPENGL */

View File

@@ -11,11 +11,17 @@
#define VIDEO_WIN32_H
#include "video_driver.hpp"
#include <mutex>
#include <condition_variable>
#if defined(__MINGW32__)
#include "../3rdparty/mingw-std-threads/mingw.mutex.h"
#include "../3rdparty/mingw-std-threads/mingw.condition_variable.h"
#endif
/** Base class for Windows video drivers. */
class VideoDriver_Win32Base : public VideoDriver {
public:
VideoDriver_Win32Base() : main_wnd(nullptr), fullscreen(false) {}
VideoDriver_Win32Base() : main_wnd(nullptr), fullscreen(false), draw_mutex(nullptr), draw_signal(nullptr) {}
void Stop() override;
@@ -36,8 +42,21 @@ public:
void EditBoxLostFocus() override;
protected:
HWND main_wnd; ///< Handle to system window.
bool fullscreen; ///< Whether to use (true) fullscreen mode.
HWND main_wnd; ///< Handle to system window.
bool fullscreen; ///< Whether to use (true) fullscreen mode.
bool has_focus = false; ///< Does our window have system focus?
Rect dirty_rect; ///< Region of the screen that needs redrawing.
int width = 0; ///< Width in pixels of our display surface.
int height = 0; ///< Height in pixels of our display surface.
int width_org = 0; ///< Original monitor resolution width, before we changed it.
int height_org = 0; ///< Original monitor resolution height, before we changed it.
bool draw_threaded; ///< Whether the drawing is/may be done in a separate thread.
bool buffer_locked; ///< Video buffer was locked by the main thread.
volatile bool draw_continue; ///< Should we keep continue drawing?
std::recursive_mutex *draw_mutex; ///< Mutex to keep the access to the shared memory controlled.
std::condition_variable_any *draw_signal; ///< Signal to draw the next frame.
Dimension GetScreenSize() const override;
float GetDPIScale() override;
@@ -45,21 +64,26 @@ protected:
bool LockVideoBuffer() override;
void UnlockVideoBuffer() override;
void CheckPaletteAnim() override;
bool PollEvent() override;
void Initialize();
bool MakeWindow(bool full_screen);
virtual uint8 GetFullscreenBpp();
void ClientSizeChanged(int w, int h, bool force = false);
/** Get screen depth to use for fullscreen mode. */
virtual uint8 GetFullscreenBpp();
/** (Re-)create the backing store. */
virtual bool AllocateBackingStore(int w, int h, bool force = false) = 0;
/** Get a pointer to the video buffer. */
virtual void *GetVideoPointer() = 0;
/** Hand video buffer back to the painting backend. */
virtual void ReleaseVideoPointer() {}
/** Palette of the window has changed. */
virtual void PaletteChanged(HWND hWnd) = 0;
private:
std::unique_lock<std::recursive_mutex> draw_lock;
void ClientSizeChanged(int w, int h);
static void PaintThreadThunk(VideoDriver_Win32Base *drv);
friend LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
@@ -67,7 +91,7 @@ private:
/** The GDI video driver for windows. */
class VideoDriver_Win32GDI : public VideoDriver_Win32Base {
public:
VideoDriver_Win32GDI() : dib_sect(nullptr), gdi_palette(nullptr) {}
VideoDriver_Win32GDI() : dib_sect(nullptr), gdi_palette(nullptr), buffer_bits(nullptr) {}
const char *Start(const StringList &param) override;
@@ -80,8 +104,10 @@ public:
protected:
HBITMAP dib_sect; ///< System bitmap object referencing our rendering buffer.
HPALETTE gdi_palette; ///< Palette object for 8bpp blitter.
void *buffer_bits; ///< Internal rendering buffer.
void Paint() override;
void *GetVideoPointer() override { return this->buffer_bits; }
void PaintThread() override;
bool AllocateBackingStore(int w, int h, bool force = false) override;
@@ -98,8 +124,63 @@ public:
/** The factory for Windows' video driver. */
class FVideoDriver_Win32GDI : public DriverFactoryBase {
public:
FVideoDriver_Win32GDI() : DriverFactoryBase(Driver::DT_VIDEO, 10, "win32", "Win32 GDI Video Driver") {}
FVideoDriver_Win32GDI() : DriverFactoryBase(Driver::DT_VIDEO, 9, "win32", "Win32 GDI Video Driver") {}
Driver *CreateInstance() const override { return new VideoDriver_Win32GDI(); }
};
#ifdef WITH_OPENGL
/** The OpenGL video driver for windows. */
class VideoDriver_Win32OpenGL : public VideoDriver_Win32Base {
public:
VideoDriver_Win32OpenGL() : dc(nullptr), gl_rc(nullptr), anim_buffer(nullptr) {}
const char *Start(const StringList &param) override;
void Stop() override;
bool ToggleFullscreen(bool fullscreen) override;
bool AfterBlitterChange() override;
bool HasEfficient8Bpp() const override { return true; }
bool UseSystemCursor() override { return true; }
void ClearSystemSprites() override;
bool HasAnimBuffer() override { return true; }
uint8 *GetAnimBuffer() override { return this->anim_buffer; }
const char *GetName() const override { return "win32-opengl"; }
protected:
HDC dc; ///< Window device context.
HGLRC gl_rc; ///< OpenGL context.
bool vsync; ///< Enable VSync?
uint8 *anim_buffer; ///< Animation buffer from OpenGL back-end.
uint8 GetFullscreenBpp() override { return 32; } // OpenGL is always 32 bpp.
void Paint() override;
void PaintThread() override {}
bool AllocateBackingStore(int w, int h, bool force = false) override;
void *GetVideoPointer() override;
void ReleaseVideoPointer() override;
void PaletteChanged(HWND hWnd) override {}
const char *AllocateContext();
void DestroyContext();
};
/** The factory for Windows' OpenGL video driver. */
class FVideoDriver_Win32OpenGL : public DriverFactoryBase {
public:
FVideoDriver_Win32OpenGL() : DriverFactoryBase(Driver::DT_VIDEO, 10, "win32-opengl", "Win32 OpenGL Video Driver") {}
/* virtual */ Driver *CreateInstance() const override { return new VideoDriver_Win32OpenGL(); }
};
#endif /* WITH_OPENGL */
#endif /* VIDEO_WIN32_H */