/* | |
* Copyright 2011 Google Inc. | |
* | |
* Use of this source code is governed by a BSD-style license that can be | |
* found in the LICENSE file. | |
*/ | |
#include "SkTypes.h" | |
#if defined(SK_BUILD_FOR_WIN) | |
#include <GL/gl.h> | |
#include <d3d9.h> | |
#include <WindowsX.h> | |
#include "SkWGL.h" | |
#include "SkWindow.h" | |
#include "SkCanvas.h" | |
#include "SkOSMenu.h" | |
#include "SkTime.h" | |
#include "SkUtils.h" | |
#include "SkGraphics.h" | |
#if SK_ANGLE | |
#include "GLES2/gl2.h" | |
#endif | |
#define INVALIDATE_DELAY_MS 200 | |
static SkOSWindow* gCurrOSWin; | |
static HWND gEventTarget; | |
#define WM_EVENT_CALLBACK (WM_USER+0) | |
void post_skwinevent() | |
{ | |
PostMessage(gEventTarget, WM_EVENT_CALLBACK, 0, 0); | |
} | |
SkOSWindow::SkOSWindow(void* hWnd) | |
: fHWND(hWnd) | |
#if SK_ANGLE | |
, fDisplay(EGL_NO_DISPLAY) | |
, fContext(EGL_NO_CONTEXT) | |
, fSurface(EGL_NO_SURFACE) | |
#endif | |
, fHGLRC(NULL) | |
, fD3D9Device(NULL) | |
, fAttached(kNone_BackEndType) { | |
gEventTarget = (HWND)hWnd; | |
} | |
SkOSWindow::~SkOSWindow() { | |
if (NULL != fD3D9Device) { | |
((IDirect3DDevice9*)fD3D9Device)->Release(); | |
} | |
if (NULL != fHGLRC) { | |
wglDeleteContext((HGLRC)fHGLRC); | |
} | |
#if SK_ANGLE | |
if (EGL_NO_CONTEXT != fContext) { | |
angle::eglDestroyContext(fDisplay, fContext); | |
fContext = EGL_NO_CONTEXT; | |
} | |
if (EGL_NO_SURFACE != fSurface) { | |
angle::eglDestroySurface(fDisplay, fSurface); | |
fSurface = EGL_NO_SURFACE; | |
} | |
if (EGL_NO_DISPLAY != fDisplay) { | |
angle::eglTerminate(fDisplay); | |
fDisplay = EGL_NO_DISPLAY; | |
} | |
#endif | |
} | |
static SkKey winToskKey(WPARAM vk) { | |
static const struct { | |
WPARAM fVK; | |
SkKey fKey; | |
} gPair[] = { | |
{ VK_BACK, kBack_SkKey }, | |
{ VK_CLEAR, kBack_SkKey }, | |
{ VK_RETURN, kOK_SkKey }, | |
{ VK_UP, kUp_SkKey }, | |
{ VK_DOWN, kDown_SkKey }, | |
{ VK_LEFT, kLeft_SkKey }, | |
{ VK_RIGHT, kRight_SkKey } | |
}; | |
for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) { | |
if (gPair[i].fVK == vk) { | |
return gPair[i].fKey; | |
} | |
} | |
return kNONE_SkKey; | |
} | |
bool SkOSWindow::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { | |
switch (message) { | |
case WM_KEYDOWN: { | |
SkKey key = winToskKey(wParam); | |
if (kNONE_SkKey != key) { | |
this->handleKey(key); | |
return true; | |
} | |
} break; | |
case WM_KEYUP: { | |
SkKey key = winToskKey(wParam); | |
if (kNONE_SkKey != key) { | |
this->handleKeyUp(key); | |
return true; | |
} | |
} break; | |
case WM_UNICHAR: | |
this->handleChar(wParam); | |
return true; | |
case WM_CHAR: { | |
this->handleChar(SkUTF8_ToUnichar((char*)&wParam)); | |
return true; | |
} break; | |
case WM_SIZE: | |
this->resize(lParam & 0xFFFF, lParam >> 16); | |
break; | |
case WM_PAINT: { | |
PAINTSTRUCT ps; | |
HDC hdc = BeginPaint(hWnd, &ps); | |
this->doPaint(hdc); | |
EndPaint(hWnd, &ps); | |
return true; | |
} break; | |
case WM_TIMER: { | |
RECT* rect = (RECT*)wParam; | |
InvalidateRect(hWnd, rect, FALSE); | |
KillTimer(hWnd, (UINT_PTR)rect); | |
delete rect; | |
return true; | |
} break; | |
case WM_LBUTTONDOWN: | |
this->handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), Click::kDown_State); | |
return true; | |
case WM_MOUSEMOVE: | |
this->handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), Click::kMoved_State); | |
return true; | |
case WM_LBUTTONUP: | |
this->handleClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), Click::kUp_State); | |
return true; | |
case WM_EVENT_CALLBACK: | |
if (SkEvent::ProcessEvent()) { | |
post_skwinevent(); | |
} | |
return true; | |
} | |
return false; | |
} | |
void SkOSWindow::doPaint(void* ctx) { | |
this->update(NULL); | |
if (kNone_BackEndType == fAttached) | |
{ | |
HDC hdc = (HDC)ctx; | |
const SkBitmap& bitmap = this->getBitmap(); | |
BITMAPINFO bmi; | |
memset(&bmi, 0, sizeof(bmi)); | |
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); | |
bmi.bmiHeader.biWidth = bitmap.width(); | |
bmi.bmiHeader.biHeight = -bitmap.height(); // top-down image | |
bmi.bmiHeader.biPlanes = 1; | |
bmi.bmiHeader.biBitCount = 32; | |
bmi.bmiHeader.biCompression = BI_RGB; | |
bmi.bmiHeader.biSizeImage = 0; | |
// | |
// Do the SetDIBitsToDevice. | |
// | |
// TODO(wjmaclean): | |
// Fix this call to handle SkBitmaps that have rowBytes != width, | |
// i.e. may have padding at the end of lines. The SkASSERT below | |
// may be ignored by builds, and the only obviously safe option | |
// seems to be to copy the bitmap to a temporary (contiguous) | |
// buffer before passing to SetDIBitsToDevice(). | |
SkASSERT(bitmap.width() * bitmap.bytesPerPixel() == bitmap.rowBytes()); | |
bitmap.lockPixels(); | |
int iRet = SetDIBitsToDevice(hdc, | |
0, 0, | |
bitmap.width(), bitmap.height(), | |
0, 0, | |
0, bitmap.height(), | |
bitmap.getPixels(), | |
&bmi, | |
DIB_RGB_COLORS); | |
bitmap.unlockPixels(); | |
} | |
} | |
#if 0 | |
void SkOSWindow::updateSize() | |
{ | |
RECT r; | |
GetWindowRect((HWND)this->getHWND(), &r); | |
this->resize(r.right - r.left, r.bottom - r.top); | |
} | |
#endif | |
void SkOSWindow::onHandleInval(const SkIRect& r) { | |
RECT* rect = new RECT; | |
rect->left = r.fLeft; | |
rect->top = r.fTop; | |
rect->right = r.fRight; | |
rect->bottom = r.fBottom; | |
SetTimer((HWND)fHWND, (UINT_PTR)rect, INVALIDATE_DELAY_MS, NULL); | |
} | |
void SkOSWindow::onAddMenu(const SkOSMenu* sk_menu) | |
{ | |
} | |
void SkOSWindow::onSetTitle(const char title[]){ | |
SetWindowTextA((HWND)fHWND, title); | |
} | |
enum { | |
SK_MacReturnKey = 36, | |
SK_MacDeleteKey = 51, | |
SK_MacEndKey = 119, | |
SK_MacLeftKey = 123, | |
SK_MacRightKey = 124, | |
SK_MacDownKey = 125, | |
SK_MacUpKey = 126, | |
SK_Mac0Key = 0x52, | |
SK_Mac1Key = 0x53, | |
SK_Mac2Key = 0x54, | |
SK_Mac3Key = 0x55, | |
SK_Mac4Key = 0x56, | |
SK_Mac5Key = 0x57, | |
SK_Mac6Key = 0x58, | |
SK_Mac7Key = 0x59, | |
SK_Mac8Key = 0x5b, | |
SK_Mac9Key = 0x5c | |
}; | |
static SkKey raw2key(uint32_t raw) | |
{ | |
static const struct { | |
uint32_t fRaw; | |
SkKey fKey; | |
} gKeys[] = { | |
{ SK_MacUpKey, kUp_SkKey }, | |
{ SK_MacDownKey, kDown_SkKey }, | |
{ SK_MacLeftKey, kLeft_SkKey }, | |
{ SK_MacRightKey, kRight_SkKey }, | |
{ SK_MacReturnKey, kOK_SkKey }, | |
{ SK_MacDeleteKey, kBack_SkKey }, | |
{ SK_MacEndKey, kEnd_SkKey }, | |
{ SK_Mac0Key, k0_SkKey }, | |
{ SK_Mac1Key, k1_SkKey }, | |
{ SK_Mac2Key, k2_SkKey }, | |
{ SK_Mac3Key, k3_SkKey }, | |
{ SK_Mac4Key, k4_SkKey }, | |
{ SK_Mac5Key, k5_SkKey }, | |
{ SK_Mac6Key, k6_SkKey }, | |
{ SK_Mac7Key, k7_SkKey }, | |
{ SK_Mac8Key, k8_SkKey }, | |
{ SK_Mac9Key, k9_SkKey } | |
}; | |
for (unsigned i = 0; i < SK_ARRAY_COUNT(gKeys); i++) | |
if (gKeys[i].fRaw == raw) | |
return gKeys[i].fKey; | |
return kNONE_SkKey; | |
} | |
/////////////////////////////////////////////////////////////////////////////////////// | |
void SkEvent::SignalNonEmptyQueue() | |
{ | |
post_skwinevent(); | |
//SkDebugf("signal nonempty\n"); | |
} | |
static UINT_PTR gTimer; | |
VOID CALLBACK sk_timer_proc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) | |
{ | |
SkEvent::ServiceQueueTimer(); | |
//SkDebugf("timer task fired\n"); | |
} | |
void SkEvent::SignalQueueTimer(SkMSec delay) | |
{ | |
if (gTimer) | |
{ | |
KillTimer(NULL, gTimer); | |
gTimer = NULL; | |
} | |
if (delay) | |
{ | |
gTimer = SetTimer(NULL, 0, delay, sk_timer_proc); | |
//SkDebugf("SetTimer of %d returned %d\n", delay, gTimer); | |
} | |
} | |
#define USE_MSAA 0 | |
HGLRC create_gl(HWND hwnd) { | |
HDC dc = GetDC(hwnd); | |
SkWGLExtensions extensions; | |
if (!extensions.hasExtension(dc, "WGL_ARB_pixel_format")) { | |
return NULL; | |
} | |
HDC prevDC = wglGetCurrentDC(); | |
HGLRC prevGLRC = wglGetCurrentContext(); | |
PIXELFORMATDESCRIPTOR pfd; | |
int format = 0; | |
GLint iattrs[] = { | |
SK_WGL_DRAW_TO_WINDOW, TRUE, | |
SK_WGL_DOUBLE_BUFFER, TRUE, | |
SK_WGL_ACCELERATION, SK_WGL_FULL_ACCELERATION, | |
SK_WGL_SUPPORT_OPENGL, TRUE, | |
SK_WGL_COLOR_BITS, 24, | |
SK_WGL_ALPHA_BITS, 8, | |
SK_WGL_STENCIL_BITS, 8, | |
// these must be kept last | |
SK_WGL_SAMPLE_BUFFERS, TRUE, | |
SK_WGL_SAMPLES, 0, | |
0,0 | |
}; | |
static const int kSampleBuffersValueIdx = SK_ARRAY_COUNT(iattrs) - 5; | |
static const int kSamplesValueIdx = SK_ARRAY_COUNT(iattrs) - 3; | |
if (USE_MSAA && extensions.hasExtension(dc, "WGL_ARB_multisample")) { | |
for (int samples = 16; samples > 1; --samples) { | |
iattrs[kSamplesValueIdx] = samples; | |
GLfloat fattrs[] = {0,0}; | |
GLuint num; | |
int formats[64]; | |
extensions.choosePixelFormat(dc, iattrs, fattrs, 64, formats, &num); | |
num = min(num,64); | |
for (GLuint i = 0; i < num; ++i) { | |
DescribePixelFormat(dc, formats[i], sizeof(pfd), &pfd); | |
if (SetPixelFormat(dc, formats[i], &pfd)) { | |
format = formats[i]; | |
break; | |
} | |
} | |
} | |
} | |
if (0 == format) { | |
iattrs[kSampleBuffersValueIdx-1] = iattrs[kSampleBuffersValueIdx] = 0; | |
iattrs[kSamplesValueIdx-1] = iattrs[kSamplesValueIdx] = 0; | |
GLfloat fattrs[] = {0,0}; | |
GLuint num; | |
extensions.choosePixelFormat(dc, iattrs, fattrs, 1, &format, &num); | |
DescribePixelFormat(dc, format, sizeof(pfd), &pfd); | |
BOOL set = SetPixelFormat(dc, format, &pfd); | |
SkASSERT(TRUE == set); | |
} | |
HGLRC glrc = wglCreateContext(dc); | |
SkASSERT(glrc); | |
wglMakeCurrent(prevDC, prevGLRC); | |
return glrc; | |
} | |
bool SkOSWindow::attachGL() { | |
if (NULL == fHGLRC) { | |
fHGLRC = create_gl((HWND)fHWND); | |
if (NULL == fHGLRC) { | |
return false; | |
} | |
glClearStencil(0); | |
glClearColor(0, 0, 0, 0); | |
glStencilMask(0xffffffff); | |
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); | |
} | |
if (wglMakeCurrent(GetDC((HWND)fHWND), (HGLRC)fHGLRC)) { | |
glViewport(0, 0, SkScalarRound(this->width()), | |
SkScalarRound(this->height())); | |
return true; | |
} | |
return false; | |
} | |
void SkOSWindow::detachGL() { | |
wglMakeCurrent(GetDC((HWND)fHWND), 0); | |
wglDeleteContext((HGLRC)fHGLRC); | |
fHGLRC = NULL; | |
} | |
void SkOSWindow::presentGL() { | |
glFlush(); | |
SwapBuffers(GetDC((HWND)fHWND)); | |
} | |
#if SK_ANGLE | |
bool create_ANGLE(EGLNativeWindowType hWnd, angle::EGLDisplay* eglDisplay, | |
angle::EGLContext* eglContext, angle::EGLSurface* eglSurface) { | |
EGLint contextAttribs[] = { | |
EGL_CONTEXT_CLIENT_VERSION, 2, | |
EGL_NONE, EGL_NONE | |
}; | |
EGLint configAttribList[] = { | |
EGL_RED_SIZE, 8, | |
EGL_GREEN_SIZE, 8, | |
EGL_BLUE_SIZE, 8, | |
EGL_ALPHA_SIZE, 8, | |
EGL_DEPTH_SIZE, 8, | |
EGL_STENCIL_SIZE, 8, | |
EGL_NONE | |
}; | |
EGLint surfaceAttribList[] = { | |
EGL_NONE, EGL_NONE | |
}; | |
angle::EGLDisplay display = angle::eglGetDisplay(GetDC(hWnd)); | |
if (display == EGL_NO_DISPLAY ) { | |
return false; | |
} | |
// Initialize EGL | |
EGLint majorVersion, minorVersion; | |
if (!angle::eglInitialize(display, &majorVersion, &minorVersion)) { | |
return false; | |
} | |
EGLint numConfigs; | |
if (!angle::eglGetConfigs(display, NULL, 0, &numConfigs)) { | |
return false; | |
} | |
// Choose config | |
angle::EGLConfig config; | |
if (!angle::eglChooseConfig(display, configAttribList, | |
&config, 1, &numConfigs)) { | |
return false; | |
} | |
// Create a surface | |
angle::EGLSurface surface = angle::eglCreateWindowSurface(display, config, | |
(EGLNativeWindowType)hWnd, | |
surfaceAttribList); | |
if (surface == EGL_NO_SURFACE) { | |
return false; | |
} | |
// Create a GL context | |
angle::EGLContext context = angle::eglCreateContext(display, config, | |
EGL_NO_CONTEXT, | |
contextAttribs ); | |
if (context == EGL_NO_CONTEXT ) { | |
return false; | |
} | |
// Make the context current | |
if (!angle::eglMakeCurrent(display, surface, surface, context)) { | |
return false; | |
} | |
*eglDisplay = display; | |
*eglContext = context; | |
*eglSurface = surface; | |
return true; | |
} | |
bool SkOSWindow::attachANGLE() { | |
if (EGL_NO_DISPLAY == fDisplay) { | |
bool bResult = create_ANGLE((HWND)fHWND, &fDisplay, &fContext, &fSurface); | |
if (false == bResult) { | |
return false; | |
} | |
angle::glClearStencil(0); | |
angle::glClearColor(0, 0, 0, 0); | |
angle::glStencilMask(0xffffffff); | |
angle::glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); | |
} | |
if (angle::eglMakeCurrent(fDisplay, fSurface, fSurface, fContext)) { | |
angle::glViewport(0, 0, SkScalarRound(this->width()), | |
SkScalarRound(this->height())); | |
return true; | |
} | |
return false; | |
} | |
void SkOSWindow::detachANGLE() { | |
angle::eglMakeCurrent(fDisplay, EGL_NO_SURFACE , EGL_NO_SURFACE , EGL_NO_CONTEXT); | |
angle::eglDestroyContext(fDisplay, fContext); | |
fContext = EGL_NO_CONTEXT; | |
angle::eglDestroySurface(fDisplay, fSurface); | |
fSurface = EGL_NO_SURFACE; | |
angle::eglTerminate(fDisplay); | |
fDisplay = EGL_NO_DISPLAY; | |
} | |
void SkOSWindow::presentANGLE() { | |
angle::glFlush(); | |
angle::eglSwapBuffers(fDisplay, fSurface); | |
} | |
#endif | |
IDirect3DDevice9* create_d3d9_device(HWND hwnd) { | |
HRESULT hr; | |
IDirect3D9* d3d9; | |
d3d9 = Direct3DCreate9(D3D_SDK_VERSION); | |
if (NULL == d3d9) { | |
return NULL; | |
} | |
D3DDEVTYPE devType = D3DDEVTYPE_HAL; | |
//D3DDEVTYPE devType = D3DDEVTYPE_REF; | |
DWORD qLevels; | |
DWORD qLevelsDepth; | |
D3DMULTISAMPLE_TYPE type; | |
for (type = D3DMULTISAMPLE_16_SAMPLES; | |
type >= D3DMULTISAMPLE_NONMASKABLE; --(*(DWORD*)&type)) { | |
hr = d3d9->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, | |
devType, D3DFMT_D24S8, TRUE, | |
type, &qLevels); | |
qLevels = (hr == D3D_OK) ? qLevels : 0; | |
hr = d3d9->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT, | |
devType, D3DFMT_A8R8G8B8, TRUE, | |
type, &qLevelsDepth); | |
qLevelsDepth = (hr == D3D_OK) ? qLevelsDepth : 0; | |
qLevels = min(qLevels,qLevelsDepth); | |
if (qLevels > 0) { | |
break; | |
} | |
} | |
qLevels = 0; | |
IDirect3DDevice9* d3d9Device; | |
D3DPRESENT_PARAMETERS pres; | |
memset(&pres, 0, sizeof(pres)); | |
pres.EnableAutoDepthStencil = TRUE; | |
pres.AutoDepthStencilFormat = D3DFMT_D24S8; | |
pres.BackBufferCount = 2; | |
pres.BackBufferFormat = D3DFMT_A8R8G8B8; | |
pres.BackBufferHeight = 0; | |
pres.BackBufferWidth = 0; | |
if (qLevels > 0) { | |
pres.MultiSampleType = type; | |
pres.MultiSampleQuality = qLevels-1; | |
} else { | |
pres.MultiSampleType = D3DMULTISAMPLE_NONE; | |
pres.MultiSampleQuality = 0; | |
} | |
pres.SwapEffect = D3DSWAPEFFECT_DISCARD; | |
pres.Windowed = TRUE; | |
pres.hDeviceWindow = hwnd; | |
pres.PresentationInterval = 1; | |
pres.Flags = 0; | |
hr = d3d9->CreateDevice(D3DADAPTER_DEFAULT, | |
devType, | |
hwnd, | |
D3DCREATE_HARDWARE_VERTEXPROCESSING, | |
&pres, | |
&d3d9Device); | |
D3DERR_INVALIDCALL; | |
if (SUCCEEDED(hr)) { | |
d3d9Device->Clear(0, NULL, D3DCLEAR_TARGET, 0xFFFFFFFF, 0, 0); | |
return d3d9Device; | |
} | |
return NULL; | |
} | |
// This needs some improvement. D3D doesn't have the same notion of attach/detach | |
// as GL. However, just allowing GDI to write to the window after creating the | |
// D3D device seems to work. | |
// We need to handle resizing. On XP and earlier Reset() will trash all our textures | |
// so we would need to inform the SkGpu/caches or just recreate them. On Vista+ we | |
// could use an IDirect3DDevice9Ex and call ResetEx() to resize without trashing | |
// everything. Currently we do nothing and the D3D9 image gets stretched/compressed | |
// when resized. | |
bool SkOSWindow::attachD3D9() { | |
if (NULL == fD3D9Device) { | |
fD3D9Device = (void*) create_d3d9_device((HWND)fHWND); | |
} | |
if (NULL != fD3D9Device) { | |
((IDirect3DDevice9*)fD3D9Device)->BeginScene(); | |
return true; | |
} | |
return false; | |
} | |
void SkOSWindow::detachD3D9() { | |
if (NULL != fD3D9Device) { | |
((IDirect3DDevice9*)fD3D9Device)->EndScene(); | |
} | |
} | |
void SkOSWindow::presentD3D9() { | |
if (NULL != fD3D9Device) { | |
HRESULT hr; | |
hr = ((IDirect3DDevice9*)fD3D9Device)->EndScene(); | |
SkASSERT(SUCCEEDED(hr)); | |
hr = ((IDirect3DDevice9*)d3d9Device())->Present(NULL, NULL, NULL, NULL); | |
SkASSERT(SUCCEEDED(hr)); | |
hr = ((IDirect3DDevice9*)fD3D9Device)->Clear(0,NULL,D3DCLEAR_TARGET | | |
D3DCLEAR_STENCIL, 0x0, 0, | |
0); | |
SkASSERT(SUCCEEDED(hr)); | |
hr = ((IDirect3DDevice9*)fD3D9Device)->BeginScene(); | |
SkASSERT(SUCCEEDED(hr)); | |
} | |
} | |
// return true on success | |
bool SkOSWindow::attach(SkBackEndTypes attachType) { | |
// attach doubles as "windowResize" so we need to allo | |
// already bound states to pass through again | |
// TODO: split out the resize functionality | |
// SkASSERT(kNone_BackEndType == fAttached); | |
bool result = true; | |
switch (attachType) { | |
case kNone_BackEndType: | |
// nothing to do | |
break; | |
case kNativeGL_BackEndType: | |
result = attachGL(); | |
break; | |
#if SK_ANGLE | |
case kANGLE_BackEndType: | |
result = attachANGLE(); | |
break; | |
#endif | |
case kD3D9_BackEndType: | |
result = attachD3D9(); | |
break; | |
default: | |
SkASSERT(false); | |
result = false; | |
break; | |
} | |
if (result) { | |
fAttached = attachType; | |
} | |
return result; | |
} | |
void SkOSWindow::detach() { | |
switch (fAttached) { | |
case kNone_BackEndType: | |
// nothing to do | |
break; | |
case kNativeGL_BackEndType: | |
detachGL(); | |
break; | |
#if SK_ANGLE | |
case kANGLE_BackEndType: | |
detachANGLE(); | |
break; | |
#endif | |
case kD3D9_BackEndType: | |
detachD3D9(); | |
break; | |
default: | |
SkASSERT(false); | |
break; | |
} | |
fAttached = kNone_BackEndType; | |
} | |
void SkOSWindow::present() { | |
switch (fAttached) { | |
case kNone_BackEndType: | |
// nothing to do | |
return; | |
case kNativeGL_BackEndType: | |
presentGL(); | |
break; | |
#if SK_ANGLE | |
case kANGLE_BackEndType: | |
presentANGLE(); | |
break; | |
#endif | |
case kD3D9_BackEndType: | |
presentD3D9(); | |
break; | |
default: | |
SkASSERT(false); | |
break; | |
} | |
} | |
#endif | |