1/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 *
7 */
8
9#include "GrBackendSurface.h"
10#include "GrContext.h"
11#include "SDL.h"
12#include "SkCanvas.h"
13#include "SkRandom.h"
14#include "SkSurface.h"
15
16#include "gl/GrGLInterface.h"
17#include "gl/GrGLUtil.h"
18
19#if defined(SK_BUILD_FOR_ANDROID)
20#include <GLES/gl.h>
21#elif defined(SK_BUILD_FOR_UNIX)
22#include <GL/gl.h>
23#elif defined(SK_BUILD_FOR_MAC)
24#include <OpenGL/gl.h>
25#elif defined(SK_BUILD_FOR_IOS)
26#include <OpenGLES/ES2/gl.h>
27#endif
28
29/*
30 * This application is a simple example of how to combine SDL and Skia it demonstrates:
31 *   how to setup gpu rendering to the main window
32 *   how to perform cpu-side rendering and draw the result to the gpu-backed screen
33 *   draw simple primitives (rectangles)
34 *   draw more complex primitives (star)
35 */
36
37struct ApplicationState {
38    ApplicationState() : fQuit(false) {}
39    // Storage for the user created rectangles. The last one may still be being edited.
40    SkTArray<SkRect> fRects;
41    bool fQuit;
42};
43
44static void handle_error() {
45    const char* error = SDL_GetError();
46    SkDebugf("SDL Error: %s\n", error);
47    SDL_ClearError();
48}
49
50static void handle_events(ApplicationState* state, SkCanvas* canvas) {
51    SDL_Event event;
52    while(SDL_PollEvent(&event)) {
53        switch (event.type) {
54            case SDL_MOUSEMOTION:
55                if (event.motion.state == SDL_PRESSED) {
56                    SkRect& rect = state->fRects.back();
57                    rect.fRight = event.motion.x;
58                    rect.fBottom = event.motion.y;
59                }
60                break;
61            case SDL_MOUSEBUTTONDOWN:
62                if (event.button.state == SDL_PRESSED) {
63                    state->fRects.push_back() = SkRect::MakeLTRB(SkIntToScalar(event.button.x),
64                                                                 SkIntToScalar(event.button.y),
65                                                                 SkIntToScalar(event.button.x),
66                                                                 SkIntToScalar(event.button.y));
67                }
68                break;
69            case SDL_KEYDOWN: {
70                SDL_Keycode key = event.key.keysym.sym;
71                if (key == SDLK_ESCAPE) {
72                    state->fQuit = true;
73                }
74                break;
75            }
76            case SDL_QUIT:
77                state->fQuit = true;
78                break;
79            default:
80                break;
81        }
82    }
83}
84
85// Creates a star type shape using a SkPath
86static SkPath create_star() {
87    static const int kNumPoints = 5;
88    SkPath concavePath;
89    SkPoint points[kNumPoints] = {{0, SkIntToScalar(-50)} };
90    SkMatrix rot;
91    rot.setRotate(SkIntToScalar(360) / kNumPoints);
92    for (int i = 1; i < kNumPoints; ++i) {
93        rot.mapPoints(points + i, points + i - 1, 1);
94    }
95    concavePath.moveTo(points[0]);
96    for (int i = 0; i < kNumPoints; ++i) {
97        concavePath.lineTo(points[(2 * i) % kNumPoints]);
98    }
99    concavePath.setFillType(SkPath::kEvenOdd_FillType);
100    SkASSERT(!concavePath.isConvex());
101    concavePath.close();
102    return concavePath;
103}
104
105#if defined(SK_BUILD_FOR_ANDROID)
106int SDL_main(int argc, char** argv) {
107#else
108int main(int argc, char** argv) {
109#endif
110    uint32_t windowFlags = 0;
111
112    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
113    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
114
115    SDL_GLContext glContext = nullptr;
116#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_IOS)
117    // For Android/iOS we need to set up for OpenGL ES and we make the window hi res & full screen
118    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
119    windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE |
120                  SDL_WINDOW_BORDERLESS | SDL_WINDOW_FULLSCREEN_DESKTOP |
121                  SDL_WINDOW_ALLOW_HIGHDPI;
122#else
123    // For all other clients we use the core profile and operate in a window
124    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
125
126    windowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
127#endif
128    static const int kStencilBits = 8;  // Skia needs 8 stencil bits
129    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
130    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
131    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
132    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
133    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
134    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, kStencilBits);
135
136    SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
137
138    // If you want multisampling, uncomment the below lines and set a sample count
139    static const int kMsaaSampleCount = 0; //4;
140    // SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
141    // SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, kMsaaSampleCount);
142
143    /*
144     * In a real application you might want to initialize more subsystems
145     */
146    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
147        handle_error();
148        return 1;
149    }
150
151    // Setup window
152    // This code will create a window with the same resolution as the user's desktop.
153    SDL_DisplayMode dm;
154    if (SDL_GetDesktopDisplayMode(0, &dm) != 0) {
155        handle_error();
156        return 1;
157    }
158
159    SDL_Window* window = SDL_CreateWindow("SDL Window", SDL_WINDOWPOS_CENTERED,
160                                          SDL_WINDOWPOS_CENTERED, dm.w, dm.h, windowFlags);
161
162    if (!window) {
163        handle_error();
164        return 1;
165    }
166
167    // To go fullscreen
168    // SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
169
170    // try and setup a GL context
171    glContext = SDL_GL_CreateContext(window);
172    if (!glContext) {
173        handle_error();
174        return 1;
175    }
176
177    int success =  SDL_GL_MakeCurrent(window, glContext);
178    if (success != 0) {
179        handle_error();
180        return success;
181    }
182
183    uint32_t windowFormat = SDL_GetWindowPixelFormat(window);
184    int contextType;
185    SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &contextType);
186
187
188    int dw, dh;
189    SDL_GL_GetDrawableSize(window, &dw, &dh);
190
191    glViewport(0, 0, dw, dh);
192    glClearColor(1, 1, 1, 1);
193    glClearStencil(0);
194    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
195
196    // setup GrContext
197    auto interface = GrGLMakeNativeInterface();
198
199    // setup contexts
200    sk_sp<GrContext> grContext(GrContext::MakeGL(interface));
201    SkASSERT(grContext);
202
203    // Wrap the frame buffer object attached to the screen in a Skia render target so Skia can
204    // render to it
205    GrGLint buffer;
206    GR_GL_GetIntegerv(interface.get(), GR_GL_FRAMEBUFFER_BINDING, &buffer);
207    GrGLFramebufferInfo info;
208    info.fFBOID = (GrGLuint) buffer;
209    SkColorType colorType;
210
211    if (SDL_PIXELFORMAT_RGBA8888 == windowFormat) {
212        info.fFormat = GR_GL_RGBA8;
213        colorType = kRGBA_8888_SkColorType;
214    } else {
215        SkASSERT(SDL_PIXELFORMAT_BGRA8888);
216        colorType = kBGRA_8888_SkColorType;
217        if (SDL_GL_CONTEXT_PROFILE_ES == contextType) {
218            info.fFormat = GR_GL_BGRA8;
219        } else {
220            // We assume the internal format is RGBA8 on desktop GL
221            info.fFormat = GR_GL_RGBA8;
222        }
223    }
224
225    GrBackendRenderTarget target(dw, dh, kMsaaSampleCount, kStencilBits, info);
226
227    // setup SkSurface
228    // To use distance field text, use commented out SkSurfaceProps instead
229    // SkSurfaceProps props(SkSurfaceProps::kUseDeviceIndependentFonts_Flag,
230    //                      SkSurfaceProps::kLegacyFontHost_InitType);
231    SkSurfaceProps props(SkSurfaceProps::kLegacyFontHost_InitType);
232
233    sk_sp<SkSurface> surface(SkSurface::MakeFromBackendRenderTarget(grContext.get(), target,
234                                                                    kBottomLeft_GrSurfaceOrigin,
235                                                                    colorType, nullptr, &props));
236
237    SkCanvas* canvas = surface->getCanvas();
238    canvas->scale((float)dw/dm.w, (float)dh/dm.h);
239
240    ApplicationState state;
241
242    const char* helpMessage = "Click and drag to create rects.  Press esc to quit.";
243
244    SkPaint paint;
245
246    // create a surface for CPU rasterization
247    sk_sp<SkSurface> cpuSurface(SkSurface::MakeRaster(canvas->imageInfo()));
248
249    SkCanvas* offscreen = cpuSurface->getCanvas();
250    offscreen->save();
251    offscreen->translate(50.0f, 50.0f);
252    offscreen->drawPath(create_star(), paint);
253    offscreen->restore();
254
255    sk_sp<SkImage> image = cpuSurface->makeImageSnapshot();
256
257    int rotation = 0;
258    while (!state.fQuit) { // Our application loop
259        SkRandom rand;
260        canvas->clear(SK_ColorWHITE);
261        handle_events(&state, canvas);
262
263        paint.setColor(SK_ColorBLACK);
264        canvas->drawText(helpMessage, strlen(helpMessage), SkIntToScalar(100),
265                         SkIntToScalar(100), paint);
266        for (int i = 0; i < state.fRects.count(); i++) {
267            paint.setColor(rand.nextU() | 0x44808080);
268            canvas->drawRect(state.fRects[i], paint);
269        }
270
271        // draw offscreen canvas
272        canvas->save();
273        canvas->translate(dm.w / 2.0, dm.h / 2.0);
274        canvas->rotate(rotation++);
275        canvas->drawImage(image, -50.0f, -50.0f);
276        canvas->restore();
277
278        canvas->flush();
279        SDL_GL_SwapWindow(window);
280    }
281
282    if (glContext) {
283        SDL_GL_DeleteContext(glContext);
284    }
285
286    //Destroy window
287    SDL_DestroyWindow(window);
288
289    //Quit SDL subsystems
290    SDL_Quit();
291    return 0;
292}
293