blob: 18a522a4c1b9170523f97741f5411356a2bdc77d [file] [log] [blame]
joshualitt7f6a1e02016-01-22 11:21:43 -08001/*
2 * Copyright 2016 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#include "GrCaps.h"
9#include "GrContextFactory.h"
10#include "SkCanvas.h"
11#include "SkCommandLineFlags.h"
joshualitt136f5172016-02-02 11:07:39 -080012#include "SkDebugCanvas.h"
joshualitt609d9792016-01-27 11:07:23 -080013#include "SkJSONCanvas.h"
joshualitt29e5a892016-02-04 06:08:33 -080014#include "SkJSONCPP.h"
joshualitt9a4e1882016-01-27 07:03:29 -080015#include "SkPicture.h"
joshualitt792345f2016-02-02 13:02:33 -080016#include "SkPictureRecorder.h"
17#include "SkPixelSerializer.h"
joshualitt7f6a1e02016-01-22 11:21:43 -080018#include "SkStream.h"
19#include "SkSurface.h"
20
joshualittcdad12f2016-02-08 07:08:21 -080021#include "UrlDataManager.h"
22
joshualitt7f6a1e02016-01-22 11:21:43 -080023#include <sys/socket.h>
joshualitt7f6a1e02016-01-22 11:21:43 -080024#include <microhttpd.h>
ethannicholas546d6652016-02-16 11:03:04 -080025#include "png.h"
joshualitt7f6a1e02016-01-22 11:21:43 -080026
27// To get image decoders linked in we have to do the below magic
28#include "SkForceLinking.h"
29#include "SkImageDecoder.h"
30__SK_FORCE_IMAGE_DECODER_LINKING;
31
jcgregorio21ab1202016-01-28 06:24:19 -080032DEFINE_string(source, "https://debugger.skia.org", "Where to load the web UI from.");
joshualitt873d6242016-02-08 13:57:44 -080033DEFINE_string(faviconDir, "tools/skiaserve", "The directory of the favicon");
jcgregorio21ab1202016-01-28 06:24:19 -080034DEFINE_int32(port, 8888, "The port to listen on.");
joshualitt7f6a1e02016-01-22 11:21:43 -080035
36// TODO probably want to make this configurable
37static const int kImageWidth = 1920;
38static const int kImageHeight = 1080;
39
joshualitt7f6a1e02016-01-22 11:21:43 -080040// TODO move to template file
41SkString generateTemplate(SkString source) {
42 SkString debuggerTemplate;
43 debuggerTemplate.appendf(
44 "<!DOCTYPE html>\n"
45 "<html>\n"
46 "<head>\n"
47 " <title>SkDebugger</title>\n"
48 " <meta charset=\"utf-8\" />\n"
49 " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=egde,chrome=1\">\n"
50 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
51 " <script src=\"%s/res/js/core.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n"
52 " <link href=\"%s/res/vul/elements.html\" rel=\"import\" />\n"
53 "</head>\n"
54 "<body class=\"fullbleed layout vertical\">\n"
55 " <debugger-app-sk>This is the app."
56 " </debugger-app-sk>\n"
57 "</body>\n"
58 "</html>", source.c_str(), source.c_str());
59 return debuggerTemplate;
60
61}
62
joshualitt9a4e1882016-01-27 07:03:29 -080063struct UploadContext {
joshualitt609d9792016-01-27 11:07:23 -080064 SkDynamicMemoryWStream fStream;
65 MHD_PostProcessor* fPostProcessor;
joshualitt9a4e1882016-01-27 07:03:29 -080066 MHD_Connection* connection;
67};
68
69struct Request {
joshualittcdad12f2016-02-08 07:08:21 -080070 Request(SkString rootUrl) : fUploadContext(nullptr), fUrlDataManager(rootUrl) {}
joshualitt9a4e1882016-01-27 07:03:29 -080071 UploadContext* fUploadContext;
joshualitt609d9792016-01-27 11:07:23 -080072 SkAutoTUnref<SkPicture> fPicture;
joshualitt136f5172016-02-02 11:07:39 -080073 SkAutoTUnref<SkDebugCanvas> fDebugCanvas;
ethannicholasde8e54c2016-02-12 10:09:34 -080074 SkAutoTDelete<GrContextFactory> fContextFactory;
ethannicholas272af6e2016-02-17 10:30:27 -080075 SkAutoTUnref<SkSurface> fSurface;
joshualittcdad12f2016-02-08 07:08:21 -080076 UrlDataManager fUrlDataManager;
joshualitt9a4e1882016-01-27 07:03:29 -080077};
78
ethannicholas546d6652016-02-16 11:03:04 -080079static void write_png_callback(png_structp png_ptr, png_bytep data, png_size_t length) {
80 SkWStream* out = (SkWStream*) png_get_io_ptr(png_ptr);
81 out->write(data, length);
82}
83
84static void write_png(const png_bytep rgba, png_uint_32 width, png_uint_32 height, SkWStream& out) {
85 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
86 SkASSERT(png != nullptr);
87 png_infop info_ptr = png_create_info_struct(png);
88 SkASSERT(info_ptr != nullptr);
89 if (setjmp(png_jmpbuf(png))) {
90 SkFAIL("png encode error");
91 }
ethannicholascecbbe22016-02-17 11:49:43 -080092 png_set_IHDR(png, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
ethannicholas546d6652016-02-16 11:03:04 -080093 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
94 png_set_compression_level(png, 1);
95 png_bytepp rows = (png_bytepp) sk_malloc_throw(height * sizeof(png_byte*));
ethannicholascecbbe22016-02-17 11:49:43 -080096 png_bytep pixels = (png_bytep) sk_malloc_throw(width * height * 3);
ethannicholas546d6652016-02-16 11:03:04 -080097 for (png_size_t y = 0; y < height; ++y) {
ethannicholascecbbe22016-02-17 11:49:43 -080098 const png_bytep src = rgba + y * width * 4;
99 rows[y] = pixels + y * width * 3;
100 // convert from RGBA to RGB
101 for (png_size_t x = 0; x < width; ++x) {
102 rows[y][x * 3] = src[x * 4];
103 rows[y][x * 3 + 1] = src[x * 4 + 1];
104 rows[y][x * 3 + 2] = src[x * 4 + 2];
105 }
ethannicholas546d6652016-02-16 11:03:04 -0800106 }
107 png_set_filter(png, 0, PNG_NO_FILTERS);
108 png_set_rows(png, info_ptr, &rows[0]);
109 png_set_write_fn(png, &out, write_png_callback, NULL);
110 png_write_png(png, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
111 png_destroy_write_struct(&png, NULL);
112 sk_free(rows);
113}
114
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800115SkBitmap* getBitmapFromCanvas(SkCanvas* canvas) {
116 SkBitmap* bmp = new SkBitmap();
117 SkImageInfo info = SkImageInfo::Make(kImageWidth, kImageHeight, kRGBA_8888_SkColorType,
118 kOpaque_SkAlphaType);
119 bmp->setInfo(info);
120 if (!canvas->readPixels(bmp, 0, 0)) {
ethannicholas272af6e2016-02-17 10:30:27 -0800121 fprintf(stderr, "Can't read pixels\n");
122 return nullptr;
123 }
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800124 return bmp;
125}
126
127SkData* writeCanvasToPng(SkCanvas* canvas) {
128 // capture pixels
129 SkAutoTDelete<SkBitmap> bmp(getBitmapFromCanvas(canvas));
130 SkASSERT(bmp);
ethannicholas272af6e2016-02-17 10:30:27 -0800131
joshualitt609d9792016-01-27 11:07:23 -0800132 // write to png
ethannicholas546d6652016-02-16 11:03:04 -0800133 SkDynamicMemoryWStream buffer;
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800134 write_png((const png_bytep) bmp->getPixels(), bmp->width(), bmp->height(), buffer);
ethannicholas546d6652016-02-16 11:03:04 -0800135 return buffer.copyToData();
joshualitt136f5172016-02-02 11:07:39 -0800136}
137
joshualitt6b3cf732016-02-17 11:20:26 -0800138SkCanvas* getCanvasFromRequest(Request* request) {
ethannicholasde8e54c2016-02-12 10:09:34 -0800139 GrContextFactory* factory = request->fContextFactory;
140 SkGLContext* gl = factory->getContextInfo(GrContextFactory::kNative_GLContextType,
141 GrContextFactory::kNone_GLContextOptions).fGLContext;
142 gl->makeCurrent();
143 SkASSERT(request->fDebugCanvas);
ethannicholas272af6e2016-02-17 10:30:27 -0800144 SkCanvas* target = request->fSurface->getCanvas();
joshualitt6b3cf732016-02-17 11:20:26 -0800145 return target;
146}
147
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800148void drawToCanvas(Request* request, int n) {
joshualitt6b3cf732016-02-17 11:20:26 -0800149 SkCanvas* target = getCanvasFromRequest(request);
ethannicholas272af6e2016-02-17 10:30:27 -0800150 request->fDebugCanvas->drawTo(target, n);
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800151}
152
153SkData* drawToPng(Request* request, int n) {
154 drawToCanvas(request, n);
155 return writeCanvasToPng(getCanvasFromRequest(request));
joshualitt609d9792016-01-27 11:07:23 -0800156}
157
ethannicholas85fca852016-02-19 08:40:59 -0800158SkSurface* createCPUSurface() {
joshualitt29e5a892016-02-04 06:08:33 -0800159 SkImageInfo info = SkImageInfo::Make(kImageWidth, kImageHeight, kN32_SkColorType,
160 kPremul_SkAlphaType);
161 return SkSurface::NewRaster(info);
162}
163
ethannicholas85fca852016-02-19 08:40:59 -0800164SkSurface* createGPUSurface(Request* request) {
165 GrContext* context = request->fContextFactory->get(GrContextFactory::kNative_GLContextType,
166 GrContextFactory::kNone_GLContextOptions);
167 int maxRTSize = context->caps()->maxRenderTargetSize();
168 SkImageInfo info = SkImageInfo::Make(SkTMin(kImageWidth, maxRTSize),
169 SkTMin(kImageHeight, maxRTSize),
170 kN32_SkColorType, kPremul_SkAlphaType);
171 uint32_t flags = 0;
172 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
173 SkSurface* surface = SkSurface::NewRenderTarget(context, SkSurface::kNo_Budgeted, info, 0,
174 &props);
175 return surface;
176}
177
joshualitt9a4e1882016-01-27 07:03:29 -0800178static const size_t kBufferSize = 1024;
179
180static int process_upload_data(void* cls, enum MHD_ValueKind kind,
181 const char* key, const char* filename,
182 const char* content_type, const char* transfer_encoding,
183 const char* data, uint64_t off, size_t size) {
184 struct UploadContext* uc = reinterpret_cast<UploadContext*>(cls);
185
186 if (0 != size) {
joshualitt609d9792016-01-27 11:07:23 -0800187 uc->fStream.write(data, size);
joshualitt9a4e1882016-01-27 07:03:29 -0800188 }
189 return MHD_YES;
190}
191
jcgregorio12d47ce2016-02-10 14:10:37 -0800192// SendOK just sends an empty response with a 200 OK status code.
193static int SendOK(MHD_Connection* connection) {
194 const char* data = "";
195
196 MHD_Response* response = MHD_create_response_from_buffer(strlen(data),
197 (void*)data,
198 MHD_RESPMEM_PERSISTENT);
199 int ret = MHD_queue_response(connection, 200, response);
200 MHD_destroy_response(response);
201 return ret;
202}
203
ethannicholas85fca852016-02-19 08:40:59 -0800204static int SendError(MHD_Connection* connection, const char* msg) {
205 MHD_Response* response = MHD_create_response_from_buffer(strlen(msg),
206 (void*) msg,
207 MHD_RESPMEM_PERSISTENT);
208 int ret = MHD_queue_response(connection, 500, response);
209 MHD_destroy_response(response);
210 return ret;
211}
212
joshualitt792345f2016-02-02 13:02:33 -0800213static int SendData(MHD_Connection* connection, const SkData* data, const char* type,
214 bool setContentDisposition = false, const char* dispositionString = nullptr) {
joshualittccfdaa52016-01-27 07:40:29 -0800215 MHD_Response* response = MHD_create_response_from_buffer(data->size(),
216 const_cast<void*>(data->data()),
217 MHD_RESPMEM_MUST_COPY);
joshualitt609d9792016-01-27 11:07:23 -0800218 MHD_add_response_header(response, "Content-Type", type);
joshualittccfdaa52016-01-27 07:40:29 -0800219
joshualitt792345f2016-02-02 13:02:33 -0800220 if (setContentDisposition) {
221 MHD_add_response_header(response, "Content-Disposition", dispositionString);
222 }
223
joshualittccfdaa52016-01-27 07:40:29 -0800224 int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
225 MHD_destroy_response(response);
226 return ret;
227}
228
joshualitt6b3cf732016-02-17 11:20:26 -0800229static int SendJSON(MHD_Connection* connection, SkCanvas* canvas, SkDebugCanvas* debugCanvas,
joshualittdb6a2542016-02-11 07:09:51 -0800230 UrlDataManager* urlDataManager, int n) {
joshualitt6b3cf732016-02-17 11:20:26 -0800231 Json::Value root = debugCanvas->toJSON(*urlDataManager, n, canvas);
joshualitt609d9792016-01-27 11:07:23 -0800232 SkDynamicMemoryWStream stream;
joshualittdb6a2542016-02-11 07:09:51 -0800233 stream.writeText(Json::FastWriter().write(root).c_str());
joshualitt609d9792016-01-27 11:07:23 -0800234
235 SkAutoTUnref<SkData> data(stream.copyToData());
236 return SendData(connection, data, "application/json");
237}
238
239static int SendTemplate(MHD_Connection* connection, bool redirect = false,
240 const char* redirectUrl = nullptr) {
jcgregorio21ab1202016-01-28 06:24:19 -0800241 SkString debuggerTemplate = generateTemplate(SkString(FLAGS_source[0]));
joshualittccfdaa52016-01-27 07:40:29 -0800242
243 MHD_Response* response = MHD_create_response_from_buffer(
244 debuggerTemplate.size(),
245 (void*) const_cast<char*>(debuggerTemplate.c_str()),
246 MHD_RESPMEM_MUST_COPY);
jcgregorio6a2046e2016-01-28 05:31:31 -0800247 MHD_add_response_header (response, "Access-Control-Allow-Origin", "*");
joshualittccfdaa52016-01-27 07:40:29 -0800248
jcgregorio6f17bc52016-01-27 11:44:38 -0800249 int status = MHD_HTTP_OK;
250
joshualitt609d9792016-01-27 11:07:23 -0800251 if (redirect) {
252 MHD_add_response_header (response, "Location", redirectUrl);
jcgregorio6f17bc52016-01-27 11:44:38 -0800253 status = MHD_HTTP_SEE_OTHER;
joshualitt609d9792016-01-27 11:07:23 -0800254 }
255
jcgregorio6f17bc52016-01-27 11:44:38 -0800256 int ret = MHD_queue_response(connection, status, response);
joshualittccfdaa52016-01-27 07:40:29 -0800257 MHD_destroy_response(response);
258 return ret;
259}
260
joshualitt483b9012016-02-02 07:16:24 -0800261class UrlHandler {
262public:
263 virtual ~UrlHandler() {}
264 virtual bool canHandle(const char* method, const char* url) = 0;
265 virtual int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800266 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800267 const char* upload_data, size_t* upload_data_size) = 0;
268};
joshualittccfdaa52016-01-27 07:40:29 -0800269
joshualitt29e5a892016-02-04 06:08:33 -0800270class CmdHandler : public UrlHandler {
joshualitt483b9012016-02-02 07:16:24 -0800271public:
272 bool canHandle(const char* method, const char* url) override {
joshualitt136f5172016-02-02 11:07:39 -0800273 const char* kBasePath = "/cmd";
274 return 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800275 }
276
joshualitt483b9012016-02-02 07:16:24 -0800277 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800278 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800279 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800280 SkTArray<SkString> commands;
281 SkStrSplit(url, "/", &commands);
282
283 if (!request->fPicture.get() || commands.count() > 3) {
284 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800285 }
joshualitt136f5172016-02-02 11:07:39 -0800286
joshualittdb6a2542016-02-11 07:09:51 -0800287 // /cmd or /cmd/N
288 if (0 == strcmp(method, MHD_HTTP_METHOD_GET)) {
289 int n;
290 if (commands.count() == 1) {
ethannicholas0a0520a2016-02-12 12:06:53 -0800291 n = request->fDebugCanvas->getSize() - 1;
joshualittdb6a2542016-02-11 07:09:51 -0800292 } else {
293 sscanf(commands[1].c_str(), "%d", &n);
294 }
joshualitt6b3cf732016-02-17 11:20:26 -0800295 return SendJSON(connection, getCanvasFromRequest(request), request->fDebugCanvas,
296 &request->fUrlDataManager, n);
joshualitt136f5172016-02-02 11:07:39 -0800297 }
298
299 // /cmd/N, for now only delete supported
300 if (commands.count() == 2 && 0 == strcmp(method, MHD_HTTP_METHOD_DELETE)) {
301 int n;
302 sscanf(commands[1].c_str(), "%d", &n);
303 request->fDebugCanvas->deleteDrawCommandAt(n);
jcgregorio12d47ce2016-02-10 14:10:37 -0800304 return SendOK(connection);
joshualitt136f5172016-02-02 11:07:39 -0800305 }
306
307 // /cmd/N/[0|1]
308 if (commands.count() == 3 && 0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
309 int n, toggle;
310 sscanf(commands[1].c_str(), "%d", &n);
311 sscanf(commands[2].c_str(), "%d", &toggle);
312 request->fDebugCanvas->toggleCommand(n, toggle);
jcgregorio12d47ce2016-02-10 14:10:37 -0800313 return SendOK(connection);
joshualitt136f5172016-02-02 11:07:39 -0800314 }
315
joshualitt483b9012016-02-02 07:16:24 -0800316 return MHD_NO;
317 }
318};
319
320class ImgHandler : public UrlHandler {
321public:
322 bool canHandle(const char* method, const char* url) override {
323 static const char* kBasePath = "/img";
324 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
325 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800326 }
327
joshualitt483b9012016-02-02 07:16:24 -0800328 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800329 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800330 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800331 SkTArray<SkString> commands;
332 SkStrSplit(url, "/", &commands);
333
334 if (!request->fPicture.get() || commands.count() > 2) {
335 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800336 }
joshualitta341b902016-02-02 07:37:21 -0800337
joshualitt136f5172016-02-02 11:07:39 -0800338 int n;
339 // /img or /img/N
340 if (commands.count() == 1) {
341 n = request->fDebugCanvas->getSize() - 1;
342 } else {
343 sscanf(commands[1].c_str(), "%d", &n);
344 }
345
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800346 SkAutoTUnref<SkData> data(drawToPng(request, n));
joshualitt136f5172016-02-02 11:07:39 -0800347 return SendData(connection, data, "image/png");
joshualitt483b9012016-02-02 07:16:24 -0800348 }
349};
joshualittccfdaa52016-01-27 07:40:29 -0800350
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800351class BreakHandler : public UrlHandler {
352public:
353 bool canHandle(const char* method, const char* url) override {
354 static const char* kBasePath = "/break";
355 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
356 0 == strncmp(url, kBasePath, strlen(kBasePath));
357 }
358
359 static SkColor GetPixel(Request* request, int x, int y) {
360 SkCanvas* canvas = getCanvasFromRequest(request);
361 canvas->flush();
362 SkAutoTDelete<SkBitmap> bitmap(getBitmapFromCanvas(canvas));
363 SkASSERT(bitmap);
364 bitmap->lockPixels();
365 uint8_t* start = ((uint8_t*) bitmap->getPixels()) + (y * kImageWidth + x) * 4;
366 SkColor result = SkColorSetARGB(start[3], start[0], start[1], start[2]);
367 bitmap->unlockPixels();
368 return result;
369 }
370
371 int handle(Request* request, MHD_Connection* connection,
372 const char* url, const char* method,
373 const char* upload_data, size_t* upload_data_size) override {
374 SkTArray<SkString> commands;
375 SkStrSplit(url, "/", &commands);
376
377 if (!request->fPicture.get() || commands.count() != 4) {
378 return MHD_NO;
379 }
380
381 // /break/<n>/<x>/<y>
382 int n;
383 sscanf(commands[1].c_str(), "%d", &n);
384 int x;
385 sscanf(commands[2].c_str(), "%d", &x);
386 int y;
387 sscanf(commands[3].c_str(), "%d", &y);
388
389 int count = request->fDebugCanvas->getSize();
390 SkASSERT(n < count);
391
392 SkCanvas* canvas = getCanvasFromRequest(request);
393 canvas->clear(SK_ColorWHITE);
394 int saveCount = canvas->save();
395 for (int i = 0; i <= n; ++i) {
396 request->fDebugCanvas->getDrawCommandAt(i)->execute(canvas);
397 }
398 SkColor target = GetPixel(request, x, y);
399 Json::Value response(Json::objectValue);
400 Json::Value startColor(Json::arrayValue);
401 startColor.append(Json::Value(SkColorGetR(target)));
402 startColor.append(Json::Value(SkColorGetG(target)));
403 startColor.append(Json::Value(SkColorGetB(target)));
404 startColor.append(Json::Value(SkColorGetA(target)));
405 response["startColor"] = startColor;
406 response["endColor"] = startColor;
407 response["endOp"] = Json::Value(n);
408 for (int i = n + 1; i < n + count; ++i) {
409 int index = i % count;
410 if (index == 0) {
411 // reset canvas for wraparound
412 canvas->restoreToCount(saveCount);
413 canvas->clear(SK_ColorWHITE);
414 saveCount = canvas->save();
415 }
416 request->fDebugCanvas->getDrawCommandAt(index)->execute(canvas);
417 SkColor current = GetPixel(request, x, y);
418 if (current != target) {
419 Json::Value endColor(Json::arrayValue);
420 endColor.append(Json::Value(SkColorGetR(current)));
421 endColor.append(Json::Value(SkColorGetG(current)));
422 endColor.append(Json::Value(SkColorGetB(current)));
423 endColor.append(Json::Value(SkColorGetA(current)));
424 response["endColor"] = endColor;
425 response["endOp"] = Json::Value(index);
426 break;
427 }
428 }
429 canvas->restoreToCount(saveCount);
430 SkDynamicMemoryWStream stream;
431 stream.writeText(Json::FastWriter().write(response).c_str());
432 SkAutoTUnref<SkData> data(stream.copyToData());
433 return SendData(connection, data, "application/json");
434 }
435};
436
ethannicholas0a0520a2016-02-12 12:06:53 -0800437/**
438 Updates the clip visualization alpha. On all subsequent /img requests, the clip will be drawn in
439 black with the specified alpha. 0 = no visible clip, 255 = fully opaque clip.
440 */
441class ClipAlphaHandler : public UrlHandler {
442public:
443 bool canHandle(const char* method, const char* url) override {
444 static const char* kBasePath = "/clipAlpha/";
jcgregorio3341d422016-02-16 10:31:07 -0800445 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
ethannicholas0a0520a2016-02-12 12:06:53 -0800446 0 == strncmp(url, kBasePath, strlen(kBasePath));
447 }
448
449 int handle(Request* request, MHD_Connection* connection,
450 const char* url, const char* method,
451 const char* upload_data, size_t* upload_data_size) override {
452 SkTArray<SkString> commands;
453 SkStrSplit(url, "/", &commands);
454
455 if (!request->fPicture.get() || commands.count() != 2) {
456 return MHD_NO;
457 }
458
459 int alpha;
460 sscanf(commands[1].c_str(), "%d", &alpha);
461
462 request->fDebugCanvas->setClipVizColor(SkColorSetARGB(alpha, 0, 0, 0));
463 return SendOK(connection);
464 }
465};
466
ethannicholas85fca852016-02-19 08:40:59 -0800467/**
468 Controls whether GPU rendering is enabled. Posting to /enableGPU/1 turns GPU on, /enableGPU/0
469 disables it.
470 */
471class EnableGPUHandler : public UrlHandler {
472public:
473 bool canHandle(const char* method, const char* url) override {
474 static const char* kBasePath = "/enableGPU/";
475 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
476 0 == strncmp(url, kBasePath, strlen(kBasePath));
477 }
478
479 int handle(Request* request, MHD_Connection* connection,
480 const char* url, const char* method,
481 const char* upload_data, size_t* upload_data_size) override {
482 SkTArray<SkString> commands;
483 SkStrSplit(url, "/", &commands);
484
485 if (commands.count() != 2) {
486 return MHD_NO;
487 }
488
489 int enable;
490 sscanf(commands[1].c_str(), "%d", &enable);
491
492 if (enable) {
493 SkSurface* surface = createGPUSurface(request);
494 if (surface) {
495 request->fSurface.reset(surface);
496 return SendOK(connection);
497 }
498 return SendError(connection, "Unable to create GPU surface");
499 }
500 request->fSurface.reset(createCPUSurface());
501 return SendOK(connection);
502 }
503};
504
joshualitt483b9012016-02-02 07:16:24 -0800505class PostHandler : public UrlHandler {
506public:
507 bool canHandle(const char* method, const char* url) override {
508 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
509 0 == strcmp(url, "/new");
joshualittccfdaa52016-01-27 07:40:29 -0800510 }
511
joshualitt483b9012016-02-02 07:16:24 -0800512 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800513 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800514 const char* upload_data, size_t* upload_data_size) override {
515 UploadContext* uc = request->fUploadContext;
joshualittccfdaa52016-01-27 07:40:29 -0800516
joshualitt483b9012016-02-02 07:16:24 -0800517 // New connection
518 if (!uc) {
519 // TODO make this a method on request
520 uc = new UploadContext;
521 uc->connection = connection;
522 uc->fPostProcessor = MHD_create_post_processor(connection, kBufferSize,
523 &process_upload_data, uc);
524 SkASSERT(uc->fPostProcessor);
joshualitt609d9792016-01-27 11:07:23 -0800525
joshualitt483b9012016-02-02 07:16:24 -0800526 request->fUploadContext = uc;
527 return MHD_YES;
528 }
529
530 // in process upload
531 if (0 != *upload_data_size) {
532 SkASSERT(uc->fPostProcessor);
533 MHD_post_process(uc->fPostProcessor, upload_data, *upload_data_size);
534 *upload_data_size = 0;
535 return MHD_YES;
536 }
537
538 // end of upload
539 MHD_destroy_post_processor(uc->fPostProcessor);
540 uc->fPostProcessor = nullptr;
541
joshualitt136f5172016-02-02 11:07:39 -0800542 // parse picture from stream
543 request->fPicture.reset(
544 SkPicture::CreateFromStream(request->fUploadContext->fStream.detachAsStream()));
545 if (!request->fPicture.get()) {
546 fprintf(stderr, "Could not create picture from stream.\n");
547 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800548 }
549
joshualitt136f5172016-02-02 11:07:39 -0800550 // pour picture into debug canvas
551 request->fDebugCanvas.reset(new SkDebugCanvas(kImageWidth, kImageHeight));
552 request->fDebugCanvas->drawPicture(request->fPicture);
553
joshualitta341b902016-02-02 07:37:21 -0800554 // clear upload context
555 delete request->fUploadContext;
556 request->fUploadContext = nullptr;
557
joshualitt483b9012016-02-02 07:16:24 -0800558 return SendTemplate(connection, true, "/");
joshualittccfdaa52016-01-27 07:40:29 -0800559 }
joshualitt483b9012016-02-02 07:16:24 -0800560};
561
joshualitt792345f2016-02-02 13:02:33 -0800562class DownloadHandler : public UrlHandler {
563public:
564 bool canHandle(const char* method, const char* url) override {
565 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
566 0 == strcmp(url, "/download");
567 }
568
569 int handle(Request* request, MHD_Connection* connection,
570 const char* url, const char* method,
571 const char* upload_data, size_t* upload_data_size) override {
572 if (!request->fPicture.get()) {
573 return MHD_NO;
574 }
575
576 // TODO move to a function
577 // Playback into picture recorder
578 SkPictureRecorder recorder;
579 SkCanvas* canvas = recorder.beginRecording(kImageWidth, kImageHeight);
580
581 request->fDebugCanvas->draw(canvas);
582
583 SkAutoTUnref<SkPicture> picture(recorder.endRecording());
584
585 SkDynamicMemoryWStream outStream;
586
587 SkAutoTUnref<SkPixelSerializer> serializer(SkImageEncoder::CreatePixelSerializer());
588 picture->serialize(&outStream, serializer);
589
590 SkAutoTUnref<SkData> data(outStream.copyToData());
591
592 // TODO fancier name handling
593 return SendData(connection, data, "application/octet-stream", true,
594 "attachment; filename=something.skp;");
595 }
596};
597
joshualitt29e5a892016-02-04 06:08:33 -0800598class InfoHandler : public UrlHandler {
599public:
600 bool canHandle(const char* method, const char* url) override {
601 const char* kBaseName = "/info";
602 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
603 0 == strncmp(url, kBaseName, strlen(kBaseName));
604 }
605
606 int handle(Request* request, MHD_Connection* connection,
607 const char* url, const char* method,
608 const char* upload_data, size_t* upload_data_size) override {
609 SkTArray<SkString> commands;
610 SkStrSplit(url, "/", &commands);
611
612 if (!request->fPicture.get() || commands.count() > 2) {
613 return MHD_NO;
614 }
615
616 // drawTo
ethannicholas85fca852016-02-19 08:40:59 -0800617 SkAutoTUnref<SkSurface> surface(createCPUSurface());
joshualitt29e5a892016-02-04 06:08:33 -0800618 SkCanvas* canvas = surface->getCanvas();
619
620 int n;
621 // /info or /info/N
622 if (commands.count() == 1) {
623 n = request->fDebugCanvas->getSize() - 1;
624 } else {
625 sscanf(commands[1].c_str(), "%d", &n);
626 }
627
628 // TODO this is really slow and we should cache the matrix and clip
629 request->fDebugCanvas->drawTo(canvas, n);
630
631 // make some json
632 SkMatrix vm = request->fDebugCanvas->getCurrentMatrix();
633 SkIRect clip = request->fDebugCanvas->getCurrentClip();
634 Json::Value info(Json::objectValue);
635 info["ViewMatrix"] = SkJSONCanvas::MakeMatrix(vm);
636 info["ClipRect"] = SkJSONCanvas::MakeIRect(clip);
637
638 std::string json = Json::FastWriter().write(info);
639
640 // We don't want the null terminator so strlen is correct
641 SkAutoTUnref<SkData> data(SkData::NewWithCopy(json.c_str(), strlen(json.c_str())));
642 return SendData(connection, data, "application/json");
643 }
644};
645
joshualittcdad12f2016-02-08 07:08:21 -0800646class DataHandler : public UrlHandler {
647public:
648 bool canHandle(const char* method, const char* url) override {
649 static const char* kBaseUrl = "/data";
650 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
651 0 == strncmp(url, kBaseUrl, strlen(kBaseUrl));
652 }
653
654 int handle(Request* request, MHD_Connection* connection,
655 const char* url, const char* method,
656 const char* upload_data, size_t* upload_data_size) override {
657 SkTArray<SkString> commands;
658 SkStrSplit(url, "/", &commands);
659
660 if (!request->fPicture.get() || commands.count() != 2) {
661 return MHD_NO;
662 }
663
664 SkAutoTUnref<UrlDataManager::UrlData> urlData(
665 SkRef(request->fUrlDataManager.getDataFromUrl(SkString(url))));
666
667 if (urlData) {
668 return SendData(connection, urlData->fData.get(), urlData->fContentType.c_str());
669 }
670 return MHD_NO;
671 }
672};
joshualitt29e5a892016-02-04 06:08:33 -0800673
joshualitt873d6242016-02-08 13:57:44 -0800674class FaviconHandler : public UrlHandler {
675public:
676 bool canHandle(const char* method, const char* url) override {
677 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
678 0 == strcmp(url, "/favicon.ico");
679 }
680
681 int handle(Request* request, MHD_Connection* connection,
682 const char* url, const char* method,
683 const char* upload_data, size_t* upload_data_size) override {
684 SkString dir(FLAGS_faviconDir[0]);
685 dir.append("/favicon.ico");
686 FILE* ico = fopen(dir.c_str(), "r");
687
688 SkAutoTUnref<SkData> data(SkData::NewFromFILE(ico));
689 int ret = SendData(connection, data, "image/vnd.microsoft.icon");
690 fclose(ico);
691 return ret;
692 }
693};
694
695
joshualitt483b9012016-02-02 07:16:24 -0800696class RootHandler : public UrlHandler {
697public:
698 bool canHandle(const char* method, const char* url) override {
699 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
700 0 == strcmp(url, "/");
701 }
702
703 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800704 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800705 const char* upload_data, size_t* upload_data_size) override {
706 return SendTemplate(connection);
707 }
708};
joshualittccfdaa52016-01-27 07:40:29 -0800709
710class UrlManager {
711public:
712 UrlManager() {
713 // Register handlers
joshualitt483b9012016-02-02 07:16:24 -0800714 fHandlers.push_back(new RootHandler);
715 fHandlers.push_back(new PostHandler);
716 fHandlers.push_back(new ImgHandler);
ethannicholas0a0520a2016-02-12 12:06:53 -0800717 fHandlers.push_back(new ClipAlphaHandler);
ethannicholas85fca852016-02-19 08:40:59 -0800718 fHandlers.push_back(new EnableGPUHandler);
joshualitt29e5a892016-02-04 06:08:33 -0800719 fHandlers.push_back(new CmdHandler);
joshualitt483b9012016-02-02 07:16:24 -0800720 fHandlers.push_back(new InfoHandler);
joshualitt792345f2016-02-02 13:02:33 -0800721 fHandlers.push_back(new DownloadHandler);
joshualittcdad12f2016-02-08 07:08:21 -0800722 fHandlers.push_back(new DataHandler);
joshualitt873d6242016-02-08 13:57:44 -0800723 fHandlers.push_back(new FaviconHandler);
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800724 fHandlers.push_back(new BreakHandler);
joshualitt483b9012016-02-02 07:16:24 -0800725 }
726
727 ~UrlManager() {
728 for (int i = 0; i < fHandlers.count(); i++) { delete fHandlers[i]; }
joshualittccfdaa52016-01-27 07:40:29 -0800729 }
730
731 // This is clearly not efficient for a large number of urls and handlers
732 int invoke(Request* request, MHD_Connection* connection, const char* url, const char* method,
733 const char* upload_data, size_t* upload_data_size) const {
734 for (int i = 0; i < fHandlers.count(); i++) {
joshualitt483b9012016-02-02 07:16:24 -0800735 if (fHandlers[i]->canHandle(method, url)) {
joshualitt136f5172016-02-02 11:07:39 -0800736 return fHandlers[i]->handle(request, connection, url, method, upload_data,
737 upload_data_size);
joshualittccfdaa52016-01-27 07:40:29 -0800738 }
739 }
740 return MHD_NO;
741 }
742
743private:
joshualitt483b9012016-02-02 07:16:24 -0800744 SkTArray<UrlHandler*> fHandlers;
joshualittccfdaa52016-01-27 07:40:29 -0800745};
746
747const UrlManager kUrlManager;
748
joshualitt7f6a1e02016-01-22 11:21:43 -0800749int answer_to_connection(void* cls, struct MHD_Connection* connection,
750 const char* url, const char* method, const char* version,
751 const char* upload_data, size_t* upload_data_size,
752 void** con_cls) {
joshualitt9a4e1882016-01-27 07:03:29 -0800753 SkDebugf("New %s request for %s using version %s\n", method, url, version);
joshualitt7f6a1e02016-01-22 11:21:43 -0800754
joshualitt9a4e1882016-01-27 07:03:29 -0800755 Request* request = reinterpret_cast<Request*>(cls);
joshualitt483b9012016-02-02 07:16:24 -0800756 int result = kUrlManager.invoke(request, connection, url, method, upload_data,
757 upload_data_size);
758 if (MHD_NO == result) {
joshualitt873d6242016-02-08 13:57:44 -0800759 fprintf(stderr, "Invalid method and / or url: %s %s\n", method, url);
joshualitt483b9012016-02-02 07:16:24 -0800760 }
761 return result;
joshualitt7f6a1e02016-01-22 11:21:43 -0800762}
763
764int skiaserve_main() {
joshualittcdad12f2016-02-08 07:08:21 -0800765 Request request(SkString("/data")); // This simple server has one request
ethannicholas85fca852016-02-19 08:40:59 -0800766
767 // create surface
768 GrContextOptions grContextOpts;
769 request.fContextFactory.reset(new GrContextFactory(grContextOpts));
770 request.fSurface.reset(createCPUSurface());
771
joshualitt7f6a1e02016-01-22 11:21:43 -0800772 struct MHD_Daemon* daemon;
jcgregorio21ab1202016-01-28 06:24:19 -0800773 // TODO Add option to bind this strictly to an address, e.g. localhost, for security.
774 daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, FLAGS_port, nullptr, nullptr,
joshualitt9a4e1882016-01-27 07:03:29 -0800775 &answer_to_connection, &request,
776 MHD_OPTION_END);
joshualitt7f6a1e02016-01-22 11:21:43 -0800777 if (NULL == daemon) {
778 return 1;
779 }
780
781 getchar();
782 MHD_stop_daemon(daemon);
783 return 0;
784}
785
786#if !defined SK_BUILD_FOR_IOS
787int main(int argc, char** argv) {
788 SkCommandLineFlags::Parse(argc, argv);
789 return skiaserve_main();
790}
791#endif