Quartz2D display rendering driver for Mac

Implement a Quartz 2D (aka Core Graphics) output renderer for the Wx
interface as a subclass of BasicDrawingPanel called
Quartz2DDrawingPanel.

Split BasicDrawingPanel's DrawArea() into DrawArea() and DrawImage(),
with DrawImage() receiving both the wxPaintDC and the wxImage, the
wxImage is created with a direct pointer to the frame buffer when
possible (24bpp).

Implement Quartz2DDrawingPanel in macsupport.mm based on the code here:

http://www.cocoabuilder.com/archive/cocoa/309165-how-to-quickly-paint-to-cocoa-view-from-bitmap-in-memory.html

and here:

http://stackoverflow.com/questions/2261177/cgimage-from-byte-array

the GetData() method of wxImage is used to avoid copying the frame
buffer.

Add RND_QUARTZ2D to the renderers enum and update all config stuff and
the XRC to support it. As well as the DrawingPanel instantiation code in
GameArea::OnIdle().
This commit is contained in:
Rafael Kitover 2016-12-09 13:06:59 -08:00
parent 60e98506e7
commit 0e6c1b66e3
7 changed files with 125 additions and 20 deletions

View File

@ -9,6 +9,7 @@ public:
protected:
void DrawArea(wxWindowDC& dc);
virtual void DrawImage(wxWindowDC& dc, wxImage* im);
DECLARE_CLASS()
};
@ -64,4 +65,15 @@ protected:
};
#endif
#if defined(__WXMAC__)
class Quartz2DDrawingPanel : public BasicDrawingPanel {
public:
Quartz2DDrawingPanel(wxWindow* parent, int _width, int _height);
virtual void DrawImage(wxWindowDC& dc, wxImage* im);
protected:
DECLARE_CLASS()
};
#endif
#endif /* GAME_DRAWING_H */

View File

@ -3301,8 +3301,12 @@ bool MainFrame::BindControls()
// validator just for this, and spinctrl is good enough.
getgtc("DefaultScale", gopts.video_scale);
getsc("MaxScale", maxScale);
/// Advanced
/// Basic
getrbi("OutputSimple", gopts.render_method, RND_SIMPLE);
getrbi("OutputQuartz2D", gopts.render_method, RND_QUARTZ2D);
#if !defined(__WXMAC__)
rb->Hide();
#endif
getrbi("OutputOpenGL", gopts.render_method, RND_OPENGL);
#ifdef NO_OGL
rb->Hide();

View File

@ -1,7 +1,10 @@
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#include <wx/rawbmp.h>
#include "wxvbam.h"
#include "drawing.h"
double HiDPIAware::HiDPIScaleFactor()
{
@ -37,3 +40,66 @@ void HiDPIAware::GetRealPixelClientSize(int* x, int* y)
*x = backing_size.width;
*y = backing_size.height;
}
IMPLEMENT_CLASS(Quartz2DDrawingPanel, BasicDrawingPanel)
Quartz2DDrawingPanel::Quartz2DDrawingPanel(wxWindow* parent, int _width, int _height)
: BasicDrawingPanel(parent, _width, _height)
{
}
void Quartz2DDrawingPanel::DrawImage(wxWindowDC& dc, wxImage* im)
{
NSView* view = (NSView*)(GetWindow()->GetHandle());
size_t w = std::ceil(width * scale);
size_t h = std::ceil(height * scale);
size_t size = w * h * 3;
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, im->GetData(), size, NULL);
CGColorSpaceRef color_space = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
CGImageRef image = CGImageCreate(
w, h, 8, 24, w * 3, color_space,
kCGBitmapByteOrderDefault,
provider, NULL, true, kCGRenderingIntentDefault
);
// draw the image
[view lockFocus];
CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
CGContextSaveGState(context);
CGContextSetAllowsAntialiasing(context, false);
CGContextSetRGBFillColor(context, 0, 0, 0, 1.0);
CGContextSetRGBStrokeColor(context, 0, 0, 0, 1.0);
CGContextTranslateCTM(context, 0, view.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, view.bounds, image);
CGContextRestoreGState(context);
// have to draw something on the dc or it doesn't allow the frame to appear,
// I don't know of any better way to do this.
{
wxCoord w, h;
dc.GetSize(&w, &h);
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(w-2, h-2, w, h);
}
[view unlockFocus];
// and release everything
CGDataProviderRelease(provider);
CGColorSpaceRelease(color_space);
CGImageRelease(image);
}

View File

@ -157,6 +157,8 @@ opt_desc opts[] = {
INTOPT("Display/MaxThreads", "Multithread", wxTRANSLATE("Maximum number of threads to run filters in"), gopts.max_threads, 1, 8),
#ifdef __WXMSW__
ENUMOPT("Display/RenderMethod", "", wxTRANSLATE("Render method; if unsupported, simple method will be used"), gopts.render_method, wxTRANSLATE("simple|opengl|cairo|direct3d")),
#elif defined(__WXMAC__)
ENUMOPT("Display/RenderMethod", "", wxTRANSLATE("Render method; if unsupported, simple method will be used"), gopts.render_method, wxTRANSLATE("simple|opengl|cairo||quartz2d")),
#else
ENUMOPT("Display/RenderMethod", "", wxTRANSLATE("Render method; if unsupported, simple method will be used"), gopts.render_method, wxTRANSLATE("simple|opengl|cairo")),
#endif

View File

@ -989,20 +989,22 @@ void GameArea::OnIdle(wxIdleEvent& event)
case RND_SIMPLE:
panel = new BasicDrawingPanel(this, basic_width, basic_height);
break;
#ifdef __WXMAC__
case RND_QUARTZ2D:
panel = new Quartz2DDrawingPanel(this, basic_width, basic_height);
break;
#endif
#ifndef NO_OGL
case RND_OPENGL:
panel = new GLDrawingPanel(this, basic_width, basic_height);
break;
#endif
#ifndef NO_CAIRO
case RND_CAIRO:
panel = new CairoDrawingPanel(this, basic_width, basic_height);
break;
#endif
#ifdef __WXMSW__
case RND_DIRECT3D:
panel = new DXDrawingPanel(this, basic_width, basic_height);
break;
@ -1380,6 +1382,7 @@ void DrawingPanel::DrawingPanelInit()
// this is not 2.8 compatible, sorry
w->Bind(wxEVT_PAINT, &DrawingPanel::PaintEv, this);
w->Bind(wxEVT_ERASE_BACKGROUND, &DrawingPanel::EraseBackground, this);
did_init = true;
}
@ -1403,6 +1406,11 @@ void DrawingPanel::PaintEv(wxPaintEvent& ev)
DrawOSD(dc);
}
void DrawingPanel::EraseBackground(wxEraseEvent& ev)
{
// do nothing, do not allow propagation
}
// In order to run filters in parallel, they have to run from the method of
// a wxThread-derived class
// threads are only created once, if possible, to avoid thread creation
@ -1912,17 +1920,16 @@ BasicDrawingPanel::BasicDrawingPanel(wxWindow* parent, int _width, int _height)
void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
{
wxBitmap* bm;
wxImage* im;
if (systemColorDepth == 24) {
// never scaled, no borders, no transformations needed
wxImage im(width, height, todraw, true);
bm = new wxBitmap(im);
im = new wxImage(width, height, todraw, true);
} else if (out_16) {
// scaled by filters, top/right borders, transform to 24-bit
wxImage im(std::ceil(width * scale), std::ceil(height * scale), false);
im = new wxImage(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();
uint8_t* dst = im->GetData();
for (int y = 0; y < std::ceil(height * scale); y++) {
for (int x = 0; x < std::ceil(width * scale); x++, src++) {
@ -1933,14 +1940,12 @@ void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
src += 2; // skip rhs border
}
bm = new wxBitmap(im);
} else // 32-bit
{
// scaled by filters, top/right borders, transform to 24-bit
wxImage im(std::ceil(width * scale), std::ceil(height * scale), false);
im = new wxImage(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();
uint8_t* dst = im->GetData();
for (int y = 0; y < std::ceil(height * scale); y++) {
for (int x = 0; x < std::ceil(width * scale); x++, src++) {
@ -1951,18 +1956,23 @@ void BasicDrawingPanel::DrawArea(wxWindowDC& dc)
++src; // skip rhs border
}
bm = new wxBitmap(im);
}
DrawImage(dc, im);
delete im;
}
void BasicDrawingPanel::DrawImage(wxWindowDC& dc, wxImage* im)
{
double sx, sy;
int w, h;
GetClientSize(&w, &h);
sx = w / (width * scale);
sy = h / (height * scale);
dc.SetUserScale(sx, sy);
dc.DrawBitmap(*bm, 0, 0);
delete bm;
wxBitmap bm(*im);
dc.DrawBitmap(bm, 0, 0);
}
#ifndef NO_OGL

View File

@ -416,10 +416,13 @@ enum ifbfunc {
};
// make sure and keep this in sync with opts.cpp!
enum renderer { RND_SIMPLE,
enum renderer {
RND_SIMPLE,
RND_OPENGL,
RND_CAIRO,
RND_DIRECT3D };
RND_DIRECT3D,
RND_QUARTZ2D,
};
// likewise
enum audioapi { AUD_SDL,
@ -635,7 +638,8 @@ public:
virtual wxWindow* GetWindow() { return dynamic_cast<wxWindow*>(this); }
virtual void Delete() { (dynamic_cast<wxWindow*>(this))->Destroy(); }
void PaintEv(wxPaintEvent& ev);
virtual void PaintEv(wxPaintEvent& ev);
virtual void EraseBackground(wxEraseEvent& ev);
protected:
virtual void DrawArea(wxWindowDC&) = 0;
virtual void DrawOSD(wxWindowDC&);

View File

@ -35,6 +35,13 @@
<flag>wxALL</flag>
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxRadioButton" name="OutputQuartz2D">
<label>Quartz2D</label>
</object>
<flag>wxALL</flag>
<border>5</border>
</object>
<object class="sizeritem">
<object class="wxRadioButton" name="OutputOpenGL">
<label>OpenGL</label>