Mac OS X HiDPI fixes + other misc

Use a high-res surface on HiDPI (e.g. retina) Macs for the OpenGL
renderer, and scale window accordingly. Also fix fullscreen toggle not
working in HiDPI mode. And some other stuff.

Aside from the .app not being linked statically, the app is now fully
functional on Mac and ready to ship.

Full change details:

* add -DDEBUG or -DNDEBUG based on CMAKE_BUILD_TYPE (-DNDEBUG for
  Release and -DDEBUG for Debug)

* properly search for homebrew/macports/fink directories, and only add
  them if they exist

* fix building with wx debug support, when available

* use explicit OpenGL context on Mac too for new versions of wx

* add main_frame pointer in GameArea back to MainFrame, and a
  SetMainFrame(MainFrame* parent) public method to set it in guinit

* add full support for reading and writing double value config options
  (the GUI still needs to be updated for options that can take doubles,
  such as video_scale)

* change video_scale option (Display/Scale) to a double, putting a
  double value such as 3.6 in the config file works correctly

* add a HiDPIAware mixin class for GameArea and DrawingPanel, with the
  method HiDPIScaleFactor which returns the current window's
  backingScaleFactor

* change the GameArea sizing methods (DelBorder(), AdjustMinSize() and
  AdjustSize()) to divide the window size by the
  HiDPIAware::HiDPIScaleFactor so that the window is scaled properly

* change GameArea::ShowFullScreen to ignore fullscreen events for a
  maximized window on Mac, because maximized windows on OS X are
  actually native fullscreen windows

* change scale variables to double from int, and use std::ceil() to
  round scaled pixel or memory size values

* make a default base class DrawingPane::DrawingPanelInit() virtual
  method, call by all concrete class constructors based on the did_init
  flag

* call setWantsBestResolutionOpenGLSurface:YES on the view backing the
  wxGLCanvas for the OpenGL renderer (GLDrawingPanel) to get a high res
  OpenGL surface in HiDPI mode

* remove GLDrawingPanel::OnSize event, the OpenGL viewport resizes
  automatically without the need to call glViewport()

* do not hide the mouse pointer if the main frame has menus or dialogs
  open

* add variadic vbamDebug(const char* format, ...) function, active only
  #ifdef DEBUG, which sets the wx log target to STDERR and logs a
  message to it

* use full name of app "visualboyadvance-m" instead of "vbam" when
  getting configuration paths, this way the config is saved to
  ~/Library/Application Support/visualboyadvance-m rather than
  ~/Library/Application Support/vbam

* listen to the MENU_HIGHLIGHT_ALL event as well, as an extra way to
  check when the menus are open

* add public MainFrame::SetMenusOpened(bool state) method to force the
  main frame to change the internal menus_opened state, this is
  necessary because in HiDPI mode on Mac the keyboard accelerator for
  toggle fullscreen sends a menu open event, but not a menu close event,
  so on switch to fullscreen the state is changed to menus closed and
  the emu is unpaused

TODO:

* GUI option to change toggle fullscreen behavior between native and
  non-native fullscreen on Mac

* GUI support for double config values like Display/Scale

* add HiDPI support to simple renderer

* fix SDL sound, or disable the option

* fix Cairo suport on Mac, or disable the option

* use dynamic_cast<> to implement GetWindow() for DrawingPanel instead
  of pure virtual method, likewise for Delete()

* link .app statically by default so it can be shipped

* add Homebrew formula
This commit is contained in:
Rafael Kitover 2016-10-22 05:20:46 -07:00
parent 258c7fec40
commit 7a7a72f2bd
9 changed files with 273 additions and 95 deletions

View File

@ -59,7 +59,11 @@ IF( NOT VERSION )
endif(EXISTS ${PROJECT_SOURCE_DIR}/.git)
ENDIF( NOT VERSION )
IF(CMAKE_BUILD_TYPE STREQUAL "Debug")
ADD_DEFINITIONS(-DDEBUG)
ELSE()
ADD_DEFINITIONS(-NDEBUG)
ENDIF()
# Fill in SDLMAIN_LIBRARY on OS X manually to avoid using SDLMain.m
# OS X users will have to compile and install SDL from source.
@ -68,18 +72,38 @@ if( APPLE AND ENABLE_SDL )
SET(SDL2MAIN_LIBRARY "-lSDL2main")
endif( APPLE AND ENABLE_SDL )
# Add support for MacPorts and Homebrew on OS X
# and ObjectiveC code
# Add support for Homebrew, MacPorts and Fink on OS X
# as well as for ObjectiveC code
IF(APPLE)
SET(CMAKE_INCLUDE_PATH "${CMAKE_INCLUDE_PATH};/usr/local/include;/opt/local/include")
SET(CMAKE_LIBRARY_PATH "${CMAKE_LIBRARY_PATH};/usr/local/lib;/opt/local/lib")
SET(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};/usr/local/bin;/opt/local/bin")
IF(EXISTS /usr/local/include)
SET(CMAKE_INCLUDE_PATH "${CMAKE_INCLUDE_PATH};/usr/local/include")
INCLUDE_DIRECTORIES("/usr/local/include")
ELSEIF(EXISTS /opt/local/include)
SET(CMAKE_INCLUDE_PATH "${CMAKE_INCLUDE_PATH};/opt/local/include")
INCLUDE_DIRECTORIES("/opt/local/include")
ELSEIF(EXISTS /sw/include)
SET(CMAKE_INCLUDE_PATH "${CMAKE_INCLUDE_PATH};/sw/include")
INCLUDE_DIRECTORIES("/sw/include")
ENDIF()
link_directories("/usr/local/lib")
include_directories("/usr/local/include")
IF(EXISTS /usr/local/lib)
SET(CMAKE_LIBRARY_PATH "${CMAKE_LIBRARY_PATH};/usr/local/lib")
LINK_DIRECTORIES("/usr/local/lib")
ELSEIF(EXISTS /opt/local/lib)
SET(CMAKE_LIBRARY_PATH "${CMAKE_LIBRARY_PATH};/opt/local/lib")
LINK_DIRECTORIES("/opt/local/lib")
ELSEIF(EXISTS /sw/lib)
SET(CMAKE_LIBRARY_PATH "${CMAKE_LIBRARY_PATH};/sw/lib")
LINK_DIRECTORIES("/sw/lib")
ENDIF()
link_directories("/opt/local/lib")
include_directories("/opt/local/include")
IF(EXISTS /usr/local/bin)
SET(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};/usr/local/bin")
ELSEIF(EXISTS /opt/local/bin)
SET(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};/opt/local/bin")
ELSEIF(EXISTS /sw/bin)
SET(CMAKE_PROGRAM_PATH "${CMAKE_PROGRAM_PATH};/sw/bin")
ENDIF()
# and compile as Objective-C++ for ObjectiveC #ifdefs
SET(CMAKE_CXX_COMPILE_OBJECT "<CMAKE_CXX_COMPILER> -x objective-c++ <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> -c <SOURCE>")

View File

@ -29,10 +29,15 @@ else(ENABLE_OPENAL)
ADD_DEFINITIONS (-DNO_OAL)
endif(ENABLE_OPENAL)
IF(CMAKE_BUILD_TYPE STREQUAL "Debug")
SET(wxWidgets_USE_DEBUG ON) # noop if wx is compiled with --disable-debug, like in Mac Homebrew atm
IF(CMAKE_BUILD_TYPE EQUAL "Debug")
SET(wxWidgets_USE_DEBUG ON)
# and if this is the case, we can't set debug level without link failing
IF(NOT wxWidgets_DEFINITIONS MATCHES "-DwxDEBUG_LEVEL=0")
ADD_DEFINITIONS(-DwxDEBUG_LEVEL=1)
ENDIF()
ENDIF()
SET(wxWidgets_USE_UNICODE ON)
# adv is for wxAboutBox
# xml, html is for xrc

View File

@ -47,13 +47,11 @@ protected:
{
PaintEv(ev);
}
void OnSize(wxSizeEvent&);
void DrawArea(wxWindowDC& dc);
#if wxCHECK_VERSION(2, 9, 0) || !defined(__WXMAC__)
wxGLContext ctx;
#if wxCHECK_VERSION(2, 9, 0)
wxGLContext* ctx;
#endif
bool did_init;
void Init();
void DrawingPanelInit();
GLuint texid, vlist;
int texsize;
@ -81,8 +79,7 @@ protected:
PaintEv(ev);
}
void DrawArea(wxWindowDC&);
bool did_init;
void Init();
void DrawingPanelInit();
DECLARE_CLASS()
DECLARE_EVENT_TABLE()

View File

@ -2522,6 +2522,8 @@ bool MainFrame::BindControls()
return false;
}
panel->SetMainFrame(this);
panel->AdjustSize(false);
// only the panel does idle events (the emulator loop)
// however, do not enable until end of init, since errors will start

View File

@ -21,6 +21,10 @@
{ \
wxT(c), (n), d, NULL, &v, NULL, min, max \
}
#define DOUBLEOPT(c, n, d, v, min, max) \
{ \
wxT(c), (n), d, NULL, NULL, NULL, min, max, NULL, &v \
}
#define BOOLOPT(c, n, d, v) \
{ \
wxT(c), (n), d, NULL, NULL, NULL, 0, 0, &v \
@ -156,7 +160,7 @@ opt_desc opts[] = {
#else
ENUMOPT("Display/RenderMethod", "", wxTRANSLATE("Render method; if unsupported, simple method will be used"), gopts.render_method, wxTRANSLATE("simple|opengl|cairo")),
#endif
INTOPT("Display/Scale", "", wxTRANSLATE("Default scale factor"), gopts.video_scale, 1, 6),
DOUBLEOPT("Display/Scale", "", wxTRANSLATE("Default scale factor"), gopts.video_scale, 1, 6),
BOOLOPT("Display/Stretch", "RetainAspect", wxTRANSLATE("Retain aspect ratio when resizing"), gopts.retain_aspect),
/// GB
@ -523,6 +527,13 @@ void load_opts()
wxLogWarning(_("Invalid value %d for option %s; valid values are %d - %d"), opt.curint, opt.opt, opt.min, opt.max);
} else
*opt.intopt = opt.curint;
} else if (opt.doubleopt) {
cfg->Read(opt.opt, &opt.curdouble, *opt.doubleopt);
if (opt.curdouble < opt.min || opt.curdouble > opt.max) {
wxLogWarning(_("Invalid value %f for option %s; valid values are %f - %f"), opt.curdouble, opt.opt, opt.min, opt.max);
} else
*opt.doubleopt = opt.curdouble;
} else if (opt.boolopt) {
cfg->Read(opt.opt, opt.boolopt, *opt.boolopt);
opt.curbool = *opt.boolopt;
@ -648,6 +659,9 @@ void update_opts()
} else if (opt.intopt) {
if (*opt.intopt != opt.curint)
cfg->Write(opt.opt, (opt.curint = *opt.intopt));
} else if (opt.doubleopt) {
if (*opt.doubleopt != opt.curdouble)
cfg->Write(opt.opt, (opt.curdouble = *opt.doubleopt));
} else if (opt.boolopt) {
if (*opt.boolopt != opt.curbool)
cfg->Write(opt.opt, (opt.curbool = *opt.boolopt));
@ -805,6 +819,14 @@ bool opt_set(const wxChar* name, const wxChar* val)
wxLogWarning(_("Invalid value %d for option %s; valid values are %d - %d"), ival, name, opt->min, opt->max);
else
*opt->intopt = ival;
} else if (opt->doubleopt) {
const wxString s(val);
double dval;
if (!s.ToDouble(&dval) || dval < opt->min || dval > opt->max)
wxLogWarning(_("Invalid value %f for option %s; valid values are %f - %f"), dval, name, opt->min, opt->max);
else
*opt->doubleopt = dval;
} else {
// GB/Palette[0-2] is virtual
for (int i = 0; i < 3; i++) {

View File

@ -18,7 +18,7 @@ extern struct opts_t {
wxVideoMode fs_mode;
int max_threads;
int render_method;
int video_scale;
double video_scale;
bool retain_aspect;
bool keep_on_top;
@ -90,11 +90,13 @@ extern struct opt_desc {
wxString* stropt;
int* intopt;
const wxChar* enumvals;
int min, max;
double min, max;
bool* boolopt;
double* doubleopt;
// current configured value
wxString curstr;
int curint;
double curdouble;
#define curbool curint
} opts[];
extern const int num_opts;

View File

@ -1,5 +1,10 @@
#include <wx/dcbuffer.h>
#ifdef __WXMAC__
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#endif
#include "../../version.h"
#include "../common/ConfigManager.h"
#include "../common/Patch.h"
@ -13,6 +18,19 @@
int emulating;
double HiDPIAware::HiDPIScaleFactor()
{
if (hidpi_scale_factor == 0) {
#ifdef __WXMAC__
hidpi_scale_factor = [[(NSView*)GetWindow()->GetHandle() window] backingScaleFactor];
#else
hidpi_scale_factor = 1.0;
#endif
}
return hidpi_scale_factor;
}
IMPLEMENT_DYNAMIC_CLASS(GameArea, wxPanel)
GameArea::GameArea()
@ -703,10 +721,13 @@ void GameArea::DelBorder()
void GameArea::AdjustMinSize()
{
wxWindow* frame = wxGetApp().frame;
wxWindow* frame = wxGetApp().frame;
double hidpi_scale_factor = HiDPIScaleFactor();
// note: could safely set min size to 1x or less regardless of video_scale
// but setting it to scaled size makes resizing to default easier
wxSize sz(basic_width * gopts.video_scale, basic_height * gopts.video_scale);
wxSize sz((std::ceil(basic_width * gopts.video_scale) / hidpi_scale_factor),
(std::ceil(basic_height * gopts.video_scale) / hidpi_scale_factor));
SetMinSize(sz);
#if wxCHECK_VERSION(2, 8, 8)
sz = frame->ClientToWindowSize(sz);
@ -718,8 +739,12 @@ void GameArea::AdjustMinSize()
void GameArea::LowerMinSize()
{
wxWindow* frame = wxGetApp().frame;
wxSize sz(basic_width, basic_height);
wxWindow* frame = wxGetApp().frame;
double hidpi_scale_factor = HiDPIScaleFactor();
wxSize sz(std::ceil(basic_width / hidpi_scale_factor),
std::ceil(basic_height / hidpi_scale_factor));
SetMinSize(sz);
// do not take decorations into account
frame->SetMinSize(sz);
@ -732,7 +757,9 @@ void GameArea::AdjustSize(bool force)
if (fullscreen)
return;
const wxSize newsz(basic_width * gopts.video_scale, basic_height * gopts.video_scale);
double hidpi_scale_factor = HiDPIScaleFactor();
const wxSize newsz((std::ceil(basic_width * gopts.video_scale) / hidpi_scale_factor),
(std::ceil(basic_height * gopts.video_scale) / hidpi_scale_factor));
if (!force) {
wxSize sz = GetSize();
@ -756,6 +783,15 @@ void GameArea::ShowFullScreen(bool full)
return;
}
// Some kbd accels can send a menu open event without a close event,
// this happens on Mac in HiDPI mode for the fullscreen toggle accel.
main_frame->SetMenusOpened(false);
// on Mac maximize is native fullscreen, so ignore fullscreen requests
#ifdef __WXMAC__
if (full && main_frame->IsMaximized()) return;
#endif
fullscreen = full;
// just in case screen mode is going to change, go ahead and preemptively
@ -1300,12 +1336,12 @@ DrawingPanel::DrawingPanel(int _width, int _height)
// unused (as is rpi->Handle)
_rpi->Output = (RENDPLUG_Output)filt_plugin.GetSymbol(wxT("RenderPluginOutput"));
scale = (_rpi->Flags & RPI_OUT_SCLMSK) >> RPI_OUT_SCLSH;
scale *= (_rpi->Flags & RPI_OUT_SCLMSK) >> RPI_OUT_SCLSH;
rpi = _rpi;
gopts.filter = FF_PLUGIN; // now that there is a valid plugin
} while (0);
} else {
scale = builtin_ff_scale(gopts.filter);
scale *= builtin_ff_scale(gopts.filter);
#define out_16 (systemColorDepth == 16)
systemColorDepth = 32;
}
@ -1336,6 +1372,11 @@ DrawingPanel::DrawingPanel(int _width, int _height)
utilUpdateSystemColorMaps(false);
}
void DrawingPanel::DrawingPanelInit()
{
did_init = true;
}
void DrawingPanel::PaintEv(wxPaintEvent& ev)
{
wxPaintDC dc(GetWindow());
@ -1388,7 +1429,8 @@ public:
// Set these params before running
int nthreads, threadno;
int width, height, scale;
int width, height;
double scale;
const RENDER_PLUGIN_INFO* rpi;
uint8_t *dst, *delta;
@ -1407,10 +1449,10 @@ public:
int instride = (width + inrb) * inbpp;
int outbpp = out_16 ? 2 : systemColorDepth == 24 ? 3 : 4;
int outrb = systemColorDepth == 24 ? 0 : 4;
int outstride = width * outbpp * scale + outrb;
int outstride = std::ceil(width * outbpp * scale) + outrb;
delta += instride * procy;
// + 1 for stupid top border
dst += outstride * (procy + 1) * scale;
dst += (int)std::ceil(outstride * (procy + 1) * scale);
while (nthreads == 1 || sig.Wait() == wxCOND_NO_ERROR) {
if (!src /* && nthreads > 1 */) {
@ -1561,10 +1603,10 @@ public:
outdesc.SrcH = height; // + scale / 2
outdesc.DstPtr = dst;
outdesc.DstPitch = outstride;
outdesc.DstW = width * scale;
outdesc.DstW = std::ceil(width * scale);
// on the other hand, there is at least 1 line below, so I'll add
// that to dest in case safety checks in plugin use < instead of <=
outdesc.DstH = height * scale + 1; // + scale * (scale / 2)
outdesc.DstH = std::ceil(height * scale) + 1; // + scale * (scale / 2)
rpi->Output(&outdesc);
break;
@ -1588,18 +1630,18 @@ void DrawingPanel::DrawArea(uint8_t** data)
// if not filtering, we still retain current image for redraws
int outbpp = out_16 ? 2 : systemColorDepth == 24 ? 3 : 4;
int outrb = systemColorDepth == 24 ? 0 : 4;
int outstride = width * outbpp * scale + outrb;
int outstride = std::ceil(width * outbpp * scale) + outrb;
if (!pixbuf2) {
int allocstride = outstride, alloch = height;
// gb may write borders, so allocate enough for them
if (width == GameArea::GBWidth && height == GameArea::GBHeight) {
allocstride = GameArea::SGBWidth * outbpp * scale + outrb;
allocstride = std::ceil(GameArea::SGBWidth * outbpp * scale) + outrb;
alloch = GameArea::SGBHeight;
}
pixbuf2 = (uint8_t*)calloc(allocstride, (alloch + 2) * scale);
pixbuf2 = (uint8_t*)calloc(allocstride, std::ceil((alloch + 2) * scale));
}
if (gopts.filter == FF_NONE) {
@ -1701,7 +1743,7 @@ void DrawingPanel::DrawArea(uint8_t** data)
if (!disableStatusMessages && !panel->osdtext.empty()) {
if (systemGetClock() - panel->osdtime < OSD_TIME) {
std::string message = ToString(panel->osdtext);
int linelen = (width * scale - 20) / 8;
int linelen = std::ceil(width * scale - 20) / 8;
int nlines = (message.length() + linelen - 1) / linelen;
int cury = height - 14 - nlines * 10;
char* ptr = const_cast<char*>(message.c_str());
@ -1862,6 +1904,7 @@ BasicDrawingPanel::BasicDrawingPanel(wxWindow* parent, int _width, int _height)
if (gopts.filter == FF_NONE && gopts.ifb == IFB_NONE)
// changing from 32 to 24 does not require regenerating color tables
systemColorDepth = 24;
if (!did_init) DrawingPanelInit();
}
void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
@ -1874,12 +1917,12 @@ void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
bm = new wxBitmap(im);
} else if (out_16) {
// scaled by filters, top/right borders, transform to 24-bit
wxImage im(width * scale, height * scale, false);
uint16_t* src = (uint16_t*)todraw + (width + 2) * scale; // skip top border
wxImage im(std::ceil(width * scale), std::ceil(height * scale), false);
uint16_t* src = (uint16_t*)todraw + (int)std::ceil((width + 2) * scale); // skip top border
uint8_t* dst = im.GetData();
for (int y = 0; y < height * scale; y++) {
for (int x = 0; x < width * scale; x++, src++) {
for (int y = 0; y < std::ceil(height * scale); y++) {
for (int x = 0; x < std::ceil(width * scale); x++, src++) {
*dst++ = ((*src >> systemRedShift) & 0x1f) << 3;
*dst++ = ((*src >> systemGreenShift) & 0x1f) << 3;
*dst++ = ((*src >> systemBlueShift) & 0x1f) << 3;
@ -1892,12 +1935,12 @@ void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
} else // 32-bit
{
// scaled by filters, top/right borders, transform to 24-bit
wxImage im(width * scale, height * scale, false);
uint32_t* src = (uint32_t*)todraw + (width + 1) * scale; // skip top border
wxImage im(std::ceil(width * scale), std::ceil(height * scale), false);
uint32_t* src = (uint32_t*)todraw + (int)std::ceil((width + 1) * scale); // skip top border
uint8_t* dst = im.GetData();
for (int y = 0; y < height * scale; y++) {
for (int x = 0; x < width * scale; x++, src++) {
for (int y = 0; y < std::ceil(height * scale); y++) {
for (int x = 0; x < std::ceil(width * scale); x++, src++) {
*dst++ = *src >> (systemRedShift - 3);
*dst++ = *src >> (systemGreenShift - 3);
*dst++ = *src >> (systemBlueShift - 3);
@ -1912,8 +1955,8 @@ void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
double sx, sy;
int w, h;
GetClientSize(&w, &h);
sx = (double)w / (double)(width * scale);
sy = (double)h / (double)(height * scale);
sx = w / (width * scale);
sy = h / (height * scale);
dc.SetUserScale(sx, sy);
dc.DrawBitmap(*bm, 0, 0);
delete bm;
@ -1936,7 +1979,6 @@ IMPLEMENT_CLASS2(GLDrawingPanel, DrawingPanel, wxGLCanvas)
// this would be easier in 2.9
BEGIN_EVENT_TABLE(GLDrawingPanel, wxGLCanvas)
EVT_PAINT(GLDrawingPanel::PaintEv2)
EVT_SIZE(GLDrawingPanel::OnSize)
END_EVENT_TABLE()
// This is supposed to be the default, but DOUBLEBUFFER doesn't seem to be
@ -1945,7 +1987,7 @@ static int glopts[] = {
WX_GL_RGBA, WX_GL_DOUBLEBUFFER, 0
};
#if wxCHECK_VERSION(2, 9, 0) || !defined(__WXMAC__)
#if wxCHECK_VERSION(2, 9, 0)
#define glc wxGLCanvas
#else
// shuffled parms for 2.9 indicates non-auto glcontext
@ -1957,23 +1999,30 @@ GLDrawingPanel::GLDrawingPanel(wxWindow* parent, int _width, int _height)
: glc(parent, wxID_ANY, glopts, wxPoint(0, 0), parent->GetSize(),
wxFULL_REPAINT_ON_RESIZE)
, DrawingPanel(_width, _height)
, did_init(false)
#if wxCHECK_VERSION(2, 9, 0) || !defined(__WXMAC__)
, ctx(this)
#endif
{
#ifdef __WXMAC__
[(NSView *)GetHandle() setWantsBestResolutionOpenGLSurface:YES];
#endif
#if wxCHECK_VERSION(2, 9, 0)
ctx = new wxGLContext(this);
SetCurrent(*ctx);
#endif
if (!did_init) DrawingPanelInit();
}
GLDrawingPanel::~GLDrawingPanel()
{
#if wxCHECK_VERSION(2, 9, 0)
delete ctx;
#endif
#if 0
// this should be automatically deleted w/ context
// it's also unsafe if panel no longer displayed
if (did_init)
{
#if wxCHECK_VERSION(2, 9, 0) || !defined(__WXMAC__)
SetContext(ctx);
#if wxCHECK_VERSION(2, 9, 0)
SetContext(*ctx);
#else
SetContext();
#endif
@ -1984,8 +2033,21 @@ GLDrawingPanel::~GLDrawingPanel()
#endif
}
void GLDrawingPanel::Init()
void GLDrawingPanel::DrawingPanelInit()
{
#if wxCHECK_VERSION(2, 9, 0)
SetCurrent(*ctx);
#endif
DrawingPanel::DrawingPanelInit();
#ifdef DEBUG
// you can use this to check that the gl surface is indeed high res
GLint m_viewport[4];
glGetIntegerv(GL_VIEWPORT, m_viewport);
vbamDebug("GL VIEWPORT: %d, %d, %d, %d", m_viewport[0], m_viewport[1], m_viewport[2], m_viewport[3]);
#endif
// taken from GTK front end almost verbatim
glDisable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);
@ -2013,12 +2075,13 @@ void GLDrawingPanel::Init()
gopts.bilinear ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
gopts.bilinear ? GL_LINEAR : GL_NEAREST);
#define int_fmt out_16 ? GL_RGB5 : GL_RGB
#define tex_fmt out_16 ? GL_BGRA : GL_RGBA, \
out_16 ? GL_UNSIGNED_SHORT_1_5_5_5_REV : GL_UNSIGNED_BYTE
#if 0
texsize = width > height ? width : height;
texsize *= scale;
texsize = std::ceil(texsize * scale);
// texsize = 1 << ffs(texsize);
texsize = texsize | (texsize >> 1);
texsize = texsize | (texsize >> 2);
@ -2029,7 +2092,7 @@ void GLDrawingPanel::Init()
#else
// but really, most cards support non-p2 and rect
// if not, use cairo or wx renderer
glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, width * scale, height * scale, 0, tex_fmt, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, std::ceil(width * scale), std::ceil(height * scale), 0, tex_fmt, NULL);
#endif
glClearColor(0.0, 0.0, 0.0, 1.0);
// non-portable vsync code
@ -2062,22 +2125,21 @@ void GLDrawingPanel::Init()
#endif
#endif
#endif
did_init = true;
}
void GLDrawingPanel::DrawArea(wxWindowDC& dc)
{
#if wxCHECK_VERSION(2, 9, 0) || !defined(__WXMAC__)
SetCurrent(ctx);
#if wxCHECK_VERSION(2, 9, 0)
SetCurrent(*ctx);
#else
SetCurrent();
#endif
if (!did_init)
Init();
DrawingPanelInit();
if (todraw) {
int rowlen = width * scale + (out_16 ? 2 : 1);
int rowlen = std::ceil(width * scale) + (out_16 ? 2 : 1);
glPixelStorei(GL_UNPACK_ROW_LENGTH, rowlen);
#if wxBYTE_ORDER == wxBIG_ENDIAN
@ -2086,30 +2148,14 @@ void GLDrawingPanel::DrawArea(wxWindowDC& dc)
glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_TRUE);
#endif
glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, width * scale, height * scale,
0, tex_fmt, todraw + rowlen * (out_16 ? 2 : 4) * scale);
glTexImage2D(GL_TEXTURE_2D, 0, int_fmt, std::ceil(width * scale), (int)std::ceil(height * scale),
0, tex_fmt, todraw + (int)std::ceil(rowlen * (out_16 ? 2 : 4) * scale));
glCallList(vlist);
} else
glClear(GL_COLOR_BUFFER_BIT);
SwapBuffers();
}
void GLDrawingPanel::OnSize(wxSizeEvent& ev)
{
if (!did_init)
return;
int w, h;
GetClientSize(&w, &h);
#if wxCHECK_VERSION(2, 9, 0) || !defined(__WXMAC__)
SetCurrent(ctx);
#else
SetCurrent();
#endif
glViewport(0, 0, w, h);
ev.Skip(); // propagate to parent
}
#endif
#ifndef NO_CAIRO
@ -2141,6 +2187,8 @@ CairoDrawingPanel::CairoDrawingPanel(wxWindow* parent, int _width, int _height)
// FIXME: should be "true" for GBA carts if lcd mode selected
utilUpdateSystemColorMaps(false);
if (!did_init) DrawingPanelInit();
}
CairoDrawingPanel::~CairoDrawingPanel()
@ -2194,8 +2242,8 @@ void CairoDrawingPanel::DrawArea(wxWindowDC& dc)
else {
if (!conv_surf)
conv_surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
width * scale,
height * scale);
std::ceil(width * scale),
std::ceil(height * scale));
if (!conv_surf) {
wxLogError(_("Cannot create conversion buffer"));
@ -2203,11 +2251,11 @@ void CairoDrawingPanel::DrawArea(wxWindowDC& dc)
}
surf = cairo_surface_reference(conv_surf);
uint16_t* src = (uint16_t*)todraw + (width + 2) * scale; // skip top border
uint16_t* src = (uint16_t*)todraw + (int)std::ceil((width + 2) * scale); // skip top border
uint32_t* dst = (uint32_t*)cairo_image_surface_get_data(surf);
for (int y = 0; y < height * scale; y++) {
for (int x = 0; x < width * scale; x++, src++) {
for (int y = 0; y < std::ceil(height * scale); y++) {
for (int x = 0; x < std::ceil(width * scale); x++, src++) {
*dst++ = (((*src >> systemRedShift) & 0x1f) << 19) | (((*src >> systemGreenShift) & 0x1f) << 11) | (((*src >> systemBlueShift) & 0x1f) << 3);
}
@ -2222,8 +2270,8 @@ void CairoDrawingPanel::DrawArea(wxWindowDC& dc)
double sx, sy;
int w, h;
GetClientSize(&w, &h);
sx = (double)width / (double)w;
sy = (double)height / (double)h;
sx = (double)width / w;
sy = (double)height / h;
cairo_matrix_t mat;
cairo_matrix_init_scale(&mat, sx, sy);
cairo_pattern_set_matrix(pat, &mat);
@ -2253,6 +2301,7 @@ DXDrawingPanel::DXDrawingPanel(wxWindow* parent, int _width, int _height)
, DrawingPanel(_width, _height)
{
// FIXME: implement
if (!did_init) DrawingPanelInit();
}
void DXDrawingPanel::DrawArea(wxWindowDC& dc)
@ -2401,7 +2450,8 @@ void GameArea::HidePointer()
return;
// FIXME: make time configurable
if (fullscreen || (systemGetClock() - mouse_active_time) > 3000) {
if ((fullscreen || (systemGetClock() - mouse_active_time) > 3000) &&
!(main_frame && (main_frame->MenusOpened() || main_frame->DialogOpened()))) {
pointer_blanked = true;
SetCursor(wxCursor(wxCURSOR_BLANK));

View File

@ -30,9 +30,31 @@
IMPLEMENT_APP(wxvbamApp)
IMPLEMENT_DYNAMIC_CLASS(MainFrame, wxFrame)
// For spewing stuff to terminal when debugging
void vbamDebug(const char* format, ...) {
#ifdef DEBUG
wxLog *active_log = wxLog::GetActiveTarget();
wxLogStderr log_to_stderr;
wxLog::SetActiveTarget(&log_to_stderr);
va_list argptr;
va_start(argptr, format);
wxVLogDebug(format, argptr);
va_end(argptr);
wxLog::SetActiveTarget(active_log);
#endif
}
// generate config file path
static void get_config_path(wxPathList& path, bool exists = true)
{
// we want paths with "visualboyadvance-m" not "vbam", so change appname temporarily
wxString current_app_name = wxGetApp().GetAppName();
wxGetApp().SetAppName(_("visualboyadvance-m"));
// local config dir first, then global
// locale-specific res first, then main
wxStandardPathsBase& stdp = wxStandardPaths::Get();
@ -44,6 +66,15 @@ static void get_config_path(wxPathList& path, bool exists = true)
if ((wxDirExists(s) && wxIsWritable(s)) || ((!exists || !wxDirExists(s)) && parent.IsDirWritable())) \
path.Add(s); \
} while (0)
vbamDebug("GetUserLocalDataDir(): %s", static_cast<const char*>(stdp.GetUserLocalDataDir().utf8_str()));
vbamDebug("GetUserDataDir(): %s", static_cast<const char*>(stdp.GetUserDataDir().utf8_str()));
vbamDebug("GetLocalizedResourcesDir(wxGetApp().locale.GetCanonicalName()): %s", static_cast<const char*>(stdp.GetLocalizedResourcesDir(wxGetApp().locale.GetCanonicalName()).utf8_str()));
vbamDebug("GetResourcesDir(): %s", static_cast<const char*>(stdp.GetResourcesDir().utf8_str()));
vbamDebug("GetDataDir(): %s", static_cast<const char*>(stdp.GetDataDir().utf8_str()));
vbamDebug("GetLocalDataDir(): %s", static_cast<const char*>(stdp.GetLocalDataDir().utf8_str()));
vbamDebug("GetPluginsDir(): %s", static_cast<const char*>(stdp.GetPluginsDir().utf8_str()));
// NOTE: this does not support XDG (freedesktop.org) paths
add_path(GetUserLocalDataDir());
add_path(GetUserDataDir());
@ -52,6 +83,8 @@ static void get_config_path(wxPathList& path, bool exists = true)
add_path(GetDataDir());
add_path(GetLocalDataDir());
add_path(GetPluginsDir());
wxGetApp().SetAppName(current_app_name);
}
static void tack_full_path(wxString& s, const wxString& app = wxEmptyString)
@ -459,7 +492,7 @@ bool wxvbamApp::OnCmdLineParsed(wxCmdLineParser& cl)
for (int i = 0; i < num_opts; i++) {
wxPrintf(wxT("%s (%s"), opts[i].opt,
opts[i].boolopt ? (const wxChar*)wxT("flag") : opts[i].stropt ? (const wxChar*)wxT("string") : opts[i].enumvals ? opts[i].enumvals : opts[i].intopt ? (const wxChar*)wxT("int") : (const wxChar*)wxT("string"));
opts[i].boolopt ? (const wxChar*)wxT("flag") : opts[i].stropt ? (const wxChar*)wxT("string") : opts[i].enumvals ? opts[i].enumvals : opts[i].intopt ? (const wxChar*)wxT("int") : opts[i].doubleopt ? (const wxChar*)wxT("decimal") : (const wxChar*)wxT("string"));
if (opts[i].enumvals) {
const wxChar* evx = wxGetTranslation(opts[i].enumvals);
@ -557,6 +590,7 @@ EVT_DROP_FILES(MainFrame::OnDropFile)
// pause game if menu pops up
EVT_MENU_OPEN(MainFrame::MenuPopped)
EVT_MENU_CLOSE(MainFrame::MenuPopped)
EVT_MENU_HIGHLIGHT_ALL(MainFrame::MenuPopped)
END_EVENT_TABLE()
void MainFrame::OnActivate(wxActivateEvent& event)
@ -950,7 +984,7 @@ int MainFrame::newest_state_slot()
// Removing the nesting counter may help, but on wxGTK I still get lockups.
void MainFrame::MenuPopped(wxMenuEvent& evt)
{
bool popped = evt.GetEventType() == wxEVT_MENU_OPEN;
bool popped = evt.GetEventType() != wxEVT_MENU_CLOSE;
#if 0
if (popped)
@ -985,6 +1019,21 @@ void MainFrame::MenuPopped(wxMenuEvent& evt)
panel->Resume();
}
void MainFrame::SetMenusOpened(bool state)
{
if (state) {
menus_opened = 1;
paused = true;
panel->Pause();
}
else {
menus_opened = 0;
paused = false;
pause_next = false;
panel->Resume();
}
}
// ShowModal that also disables emulator loop
// uses dialog_opened as a nesting counter
int MainFrame::ShowModal(wxDialog* dlg)

View File

@ -38,6 +38,9 @@ void CheckPointer(T pointer)
}
}
// For spewing stuff to terminal
void vbamDebug(const char* format, ...);
/// Helper functions to convert WX's crazy string types to std::string
inline std::string ToString(wxCharBuffer aString)
@ -278,6 +281,12 @@ public:
// Check for online updates to the emulator
bool CheckForUpdates();
virtual bool MenusOpened() { return menus_opened != 0; }
virtual void SetMenusOpened(bool state);
virtual bool DialogOpened() { return dialog_opened != 0; }
// required for building from xrc
DECLARE_DYNAMIC_CLASS();
// required for event handling
@ -333,6 +342,15 @@ private:
#include "cmdhandlers.h"
};
// helper class to add HiDPI awareness (mostly for Mac OS X)
class HiDPIAware {
public:
double HiDPIScaleFactor();
virtual wxWindow* GetWindow() = 0;
private:
double hidpi_scale_factor = 0;
};
// a helper class to avoid forgetting StopModal()
class ModalPause {
public:
@ -420,11 +438,13 @@ enum audioapi { AUD_SDL,
class DrawingPanel;
class GameArea : public wxPanel {
class GameArea : public wxPanel, public HiDPIAware {
public:
GameArea();
virtual ~GameArea();
virtual void SetMainFrame(MainFrame* parent) { main_frame = parent; }
// set to game title + link info
void SetFrameTitle();
@ -540,7 +560,11 @@ public:
void StartGamePlayback(const wxString& fname);
void StopGamePlayback();
virtual wxWindow* GetWindow() { return this; }
protected:
MainFrame* main_frame;
// set minsize of frame & panel to unscaled screen size
void LowerMinSize();
// set minsize of frame & panel to scaled screen size
@ -599,7 +623,7 @@ extern bool cmditem_lt(const struct cmditem& cmd1, const struct cmditem& cmd2);
class FilterThread;
class DrawingPanel : public wxObject {
class DrawingPanel : public wxObject, public HiDPIAware {
public:
DrawingPanel(int _width, int _height);
~DrawingPanel();
@ -611,7 +635,10 @@ public:
protected:
virtual void DrawArea(wxWindowDC&) = 0;
virtual void DrawOSD(wxWindowDC&);
int width, height, scale;
int width, height;
double scale;
virtual void DrawingPanelInit();
bool did_init = false;
uint8_t* todraw;
uint8_t *pixbuf1, *pixbuf2;
FilterThread* threads;