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:
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -37,6 +37,7 @@ protected:
|
||||
void InputLoop() override;
|
||||
void Paint() override;
|
||||
void CheckPaletteAnim() override;
|
||||
bool PollEvent() override;
|
||||
};
|
||||
|
||||
/** Factory for the allegro video driver. */
|
||||
|
||||
@@ -6,3 +6,9 @@ add_files(
|
||||
cocoa_wnd.mm
|
||||
CONDITION APPLE
|
||||
)
|
||||
|
||||
add_files(
|
||||
cocoa_ogl.h
|
||||
cocoa_ogl.mm
|
||||
CONDITION APPLE AND OPENGL_FOUND
|
||||
)
|
||||
|
||||
59
src/video/cocoa/cocoa_ogl.h
Normal file
59
src/video/cocoa/cocoa_ogl.h
Normal 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 ¶m) 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 */
|
||||
317
src/video/cocoa/cocoa_ogl.mm
Normal file
317
src/video/cocoa/cocoa_ogl.mm
Normal 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 ¶m)
|
||||
{
|
||||
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 */
|
||||
@@ -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 ¶m) 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 ¶m) 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 */
|
||||
|
||||
@@ -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 ¶m)
|
||||
{
|
||||
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 */
|
||||
|
||||
@@ -55,6 +55,8 @@ extern NSString *OTTDMainLaunchGameEngine;
|
||||
@end
|
||||
|
||||
|
||||
extern bool _allow_hidpi_window;
|
||||
|
||||
bool CocoaSetupApplication();
|
||||
void CocoaExitApplication();
|
||||
|
||||
|
||||
@@ -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 ];
|
||||
|
||||
@@ -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
1464
src/video/opengl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
140
src/video/opengl.h
Normal file
140
src/video/opengl.h
Normal 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 */
|
||||
203
src/video/sdl2_default_v.cpp
Normal file
203
src/video/sdl2_default_v.cpp
Normal 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;
|
||||
}
|
||||
40
src/video/sdl2_default_v.h
Normal file
40
src/video/sdl2_default_v.h
Normal 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
179
src/video/sdl2_opengl_v.cpp
Normal 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 ¶m)
|
||||
{
|
||||
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
54
src/video/sdl2_opengl_v.h
Normal 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 ¶m) 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(); }
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ¶m) 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 */
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ¶m)
|
||||
|
||||
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 ¶m)
|
||||
{
|
||||
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 */
|
||||
|
||||
@@ -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 ¶m) 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 ¶m) 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 */
|
||||
|
||||
Reference in New Issue
Block a user