blob: 5947d3dc63e00c6121923414553a28c545a5c26f [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"
joshualitt24dd6872016-02-25 08:37:54 -080010
11#include "Request.h"
12
joshualitt7f6a1e02016-01-22 11:21:43 -080013#include "SkCanvas.h"
14#include "SkCommandLineFlags.h"
joshualitt609d9792016-01-27 11:07:23 -080015#include "SkJSONCanvas.h"
joshualitt792345f2016-02-02 13:02:33 -080016#include "SkPictureRecorder.h"
17#include "SkPixelSerializer.h"
joshualittcdad12f2016-02-08 07:08:21 -080018
joshualitt7f6a1e02016-01-22 11:21:43 -080019#include <sys/socket.h>
joshualitt7f6a1e02016-01-22 11:21:43 -080020#include <microhttpd.h>
ethannicholas546d6652016-02-16 11:03:04 -080021#include "png.h"
joshualitt7f6a1e02016-01-22 11:21:43 -080022
23// To get image decoders linked in we have to do the below magic
24#include "SkForceLinking.h"
25#include "SkImageDecoder.h"
26__SK_FORCE_IMAGE_DECODER_LINKING;
27
jcgregorio21ab1202016-01-28 06:24:19 -080028DEFINE_string(source, "https://debugger.skia.org", "Where to load the web UI from.");
29DEFINE_int32(port, 8888, "The port to listen on.");
joshualitt7f6a1e02016-01-22 11:21:43 -080030
31// TODO probably want to make this configurable
32static const int kImageWidth = 1920;
33static const int kImageHeight = 1080;
34
joshualitt7f6a1e02016-01-22 11:21:43 -080035SkString generateTemplate(SkString source) {
36 SkString debuggerTemplate;
37 debuggerTemplate.appendf(
38 "<!DOCTYPE html>\n"
39 "<html>\n"
40 "<head>\n"
41 " <title>SkDebugger</title>\n"
42 " <meta charset=\"utf-8\" />\n"
43 " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=egde,chrome=1\">\n"
44 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
45 " <script src=\"%s/res/js/core.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n"
jcgregorio50fe38e2016-02-25 07:02:13 -080046 " <link href=\"%s/res/vul/elements.html\" rel=\"import\" />\n"
47 " <link rel='shortcut icon' href='https://debugger.skia.org/res/img/favicon.ico' type='image/x-icon'/ >"
joshualitt7f6a1e02016-01-22 11:21:43 -080048 "</head>\n"
49 "<body class=\"fullbleed layout vertical\">\n"
50 " <debugger-app-sk>This is the app."
51 " </debugger-app-sk>\n"
52 "</body>\n"
53 "</html>", source.c_str(), source.c_str());
54 return debuggerTemplate;
55
56}
57
ethannicholas546d6652016-02-16 11:03:04 -080058static void write_png_callback(png_structp png_ptr, png_bytep data, png_size_t length) {
59 SkWStream* out = (SkWStream*) png_get_io_ptr(png_ptr);
60 out->write(data, length);
61}
62
63static void write_png(const png_bytep rgba, png_uint_32 width, png_uint_32 height, SkWStream& out) {
64 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
65 SkASSERT(png != nullptr);
66 png_infop info_ptr = png_create_info_struct(png);
67 SkASSERT(info_ptr != nullptr);
68 if (setjmp(png_jmpbuf(png))) {
69 SkFAIL("png encode error");
70 }
ethannicholascecbbe22016-02-17 11:49:43 -080071 png_set_IHDR(png, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
ethannicholas546d6652016-02-16 11:03:04 -080072 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
73 png_set_compression_level(png, 1);
74 png_bytepp rows = (png_bytepp) sk_malloc_throw(height * sizeof(png_byte*));
ethannicholascecbbe22016-02-17 11:49:43 -080075 png_bytep pixels = (png_bytep) sk_malloc_throw(width * height * 3);
ethannicholas546d6652016-02-16 11:03:04 -080076 for (png_size_t y = 0; y < height; ++y) {
ethannicholascecbbe22016-02-17 11:49:43 -080077 const png_bytep src = rgba + y * width * 4;
78 rows[y] = pixels + y * width * 3;
79 // convert from RGBA to RGB
80 for (png_size_t x = 0; x < width; ++x) {
81 rows[y][x * 3] = src[x * 4];
82 rows[y][x * 3 + 1] = src[x * 4 + 1];
83 rows[y][x * 3 + 2] = src[x * 4 + 2];
84 }
ethannicholas546d6652016-02-16 11:03:04 -080085 }
86 png_set_filter(png, 0, PNG_NO_FILTERS);
87 png_set_rows(png, info_ptr, &rows[0]);
88 png_set_write_fn(png, &out, write_png_callback, NULL);
89 png_write_png(png, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
90 png_destroy_write_struct(&png, NULL);
91 sk_free(rows);
92}
93
ethannicholas3ca1e1a2016-02-18 10:22:34 -080094SkBitmap* getBitmapFromCanvas(SkCanvas* canvas) {
95 SkBitmap* bmp = new SkBitmap();
96 SkImageInfo info = SkImageInfo::Make(kImageWidth, kImageHeight, kRGBA_8888_SkColorType,
97 kOpaque_SkAlphaType);
98 bmp->setInfo(info);
99 if (!canvas->readPixels(bmp, 0, 0)) {
ethannicholas272af6e2016-02-17 10:30:27 -0800100 fprintf(stderr, "Can't read pixels\n");
101 return nullptr;
102 }
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800103 return bmp;
104}
105
106SkData* writeCanvasToPng(SkCanvas* canvas) {
107 // capture pixels
108 SkAutoTDelete<SkBitmap> bmp(getBitmapFromCanvas(canvas));
109 SkASSERT(bmp);
ethannicholas272af6e2016-02-17 10:30:27 -0800110
joshualitt609d9792016-01-27 11:07:23 -0800111 // write to png
ethannicholas546d6652016-02-16 11:03:04 -0800112 SkDynamicMemoryWStream buffer;
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800113 write_png((const png_bytep) bmp->getPixels(), bmp->width(), bmp->height(), buffer);
ethannicholas546d6652016-02-16 11:03:04 -0800114 return buffer.copyToData();
joshualitt136f5172016-02-02 11:07:39 -0800115}
116
joshualitt6b3cf732016-02-17 11:20:26 -0800117SkCanvas* getCanvasFromRequest(Request* request) {
ethannicholasde8e54c2016-02-12 10:09:34 -0800118 GrContextFactory* factory = request->fContextFactory;
119 SkGLContext* gl = factory->getContextInfo(GrContextFactory::kNative_GLContextType,
120 GrContextFactory::kNone_GLContextOptions).fGLContext;
121 gl->makeCurrent();
122 SkASSERT(request->fDebugCanvas);
ethannicholas272af6e2016-02-17 10:30:27 -0800123 SkCanvas* target = request->fSurface->getCanvas();
joshualitt6b3cf732016-02-17 11:20:26 -0800124 return target;
125}
126
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800127void drawToCanvas(Request* request, int n) {
joshualitt6b3cf732016-02-17 11:20:26 -0800128 SkCanvas* target = getCanvasFromRequest(request);
ethannicholas272af6e2016-02-17 10:30:27 -0800129 request->fDebugCanvas->drawTo(target, n);
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800130}
131
132SkData* drawToPng(Request* request, int n) {
133 drawToCanvas(request, n);
134 return writeCanvasToPng(getCanvasFromRequest(request));
joshualitt609d9792016-01-27 11:07:23 -0800135}
136
ethannicholas85fca852016-02-19 08:40:59 -0800137SkSurface* createCPUSurface() {
joshualitt29e5a892016-02-04 06:08:33 -0800138 SkImageInfo info = SkImageInfo::Make(kImageWidth, kImageHeight, kN32_SkColorType,
139 kPremul_SkAlphaType);
140 return SkSurface::NewRaster(info);
141}
142
ethannicholas85fca852016-02-19 08:40:59 -0800143SkSurface* createGPUSurface(Request* request) {
144 GrContext* context = request->fContextFactory->get(GrContextFactory::kNative_GLContextType,
145 GrContextFactory::kNone_GLContextOptions);
146 int maxRTSize = context->caps()->maxRenderTargetSize();
147 SkImageInfo info = SkImageInfo::Make(SkTMin(kImageWidth, maxRTSize),
148 SkTMin(kImageHeight, maxRTSize),
149 kN32_SkColorType, kPremul_SkAlphaType);
150 uint32_t flags = 0;
151 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
bsalomon5ec26ae2016-02-25 08:33:02 -0800152 SkSurface* surface = SkSurface::NewRenderTarget(context, SkBudgeted::kNo, info, 0,
ethannicholas85fca852016-02-19 08:40:59 -0800153 &props);
154 return surface;
155}
156
joshualitt9a4e1882016-01-27 07:03:29 -0800157static const size_t kBufferSize = 1024;
158
159static int process_upload_data(void* cls, enum MHD_ValueKind kind,
160 const char* key, const char* filename,
161 const char* content_type, const char* transfer_encoding,
162 const char* data, uint64_t off, size_t size) {
163 struct UploadContext* uc = reinterpret_cast<UploadContext*>(cls);
164
165 if (0 != size) {
joshualitt609d9792016-01-27 11:07:23 -0800166 uc->fStream.write(data, size);
joshualitt9a4e1882016-01-27 07:03:29 -0800167 }
168 return MHD_YES;
169}
170
jcgregorio12d47ce2016-02-10 14:10:37 -0800171// SendOK just sends an empty response with a 200 OK status code.
172static int SendOK(MHD_Connection* connection) {
173 const char* data = "";
174
175 MHD_Response* response = MHD_create_response_from_buffer(strlen(data),
176 (void*)data,
177 MHD_RESPMEM_PERSISTENT);
178 int ret = MHD_queue_response(connection, 200, response);
179 MHD_destroy_response(response);
180 return ret;
181}
182
ethannicholas85fca852016-02-19 08:40:59 -0800183static int SendError(MHD_Connection* connection, const char* msg) {
184 MHD_Response* response = MHD_create_response_from_buffer(strlen(msg),
185 (void*) msg,
186 MHD_RESPMEM_PERSISTENT);
187 int ret = MHD_queue_response(connection, 500, response);
188 MHD_destroy_response(response);
189 return ret;
190}
191
joshualitt792345f2016-02-02 13:02:33 -0800192static int SendData(MHD_Connection* connection, const SkData* data, const char* type,
193 bool setContentDisposition = false, const char* dispositionString = nullptr) {
joshualittccfdaa52016-01-27 07:40:29 -0800194 MHD_Response* response = MHD_create_response_from_buffer(data->size(),
195 const_cast<void*>(data->data()),
196 MHD_RESPMEM_MUST_COPY);
joshualitt609d9792016-01-27 11:07:23 -0800197 MHD_add_response_header(response, "Content-Type", type);
joshualittccfdaa52016-01-27 07:40:29 -0800198
joshualitt792345f2016-02-02 13:02:33 -0800199 if (setContentDisposition) {
200 MHD_add_response_header(response, "Content-Disposition", dispositionString);
201 }
202
joshualittccfdaa52016-01-27 07:40:29 -0800203 int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
204 MHD_destroy_response(response);
205 return ret;
206}
207
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800208static int SendJSON(MHD_Connection* connection, Request* request, int n) {
209 SkCanvas* canvas = getCanvasFromRequest(request);
210 SkDebugCanvas* debugCanvas = request->fDebugCanvas;
211 UrlDataManager* urlDataManager = &request->fUrlDataManager;
joshualitt6b3cf732016-02-17 11:20:26 -0800212 Json::Value root = debugCanvas->toJSON(*urlDataManager, n, canvas);
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800213 root["mode"] = Json::Value(request->fGPUEnabled ? "gpu" : "cpu");
joshualitt609d9792016-01-27 11:07:23 -0800214 SkDynamicMemoryWStream stream;
joshualittdb6a2542016-02-11 07:09:51 -0800215 stream.writeText(Json::FastWriter().write(root).c_str());
joshualitt609d9792016-01-27 11:07:23 -0800216
217 SkAutoTUnref<SkData> data(stream.copyToData());
218 return SendData(connection, data, "application/json");
219}
220
221static int SendTemplate(MHD_Connection* connection, bool redirect = false,
222 const char* redirectUrl = nullptr) {
jcgregorio21ab1202016-01-28 06:24:19 -0800223 SkString debuggerTemplate = generateTemplate(SkString(FLAGS_source[0]));
joshualittccfdaa52016-01-27 07:40:29 -0800224
225 MHD_Response* response = MHD_create_response_from_buffer(
226 debuggerTemplate.size(),
227 (void*) const_cast<char*>(debuggerTemplate.c_str()),
228 MHD_RESPMEM_MUST_COPY);
jcgregorio6a2046e2016-01-28 05:31:31 -0800229 MHD_add_response_header (response, "Access-Control-Allow-Origin", "*");
joshualittccfdaa52016-01-27 07:40:29 -0800230
jcgregorio6f17bc52016-01-27 11:44:38 -0800231 int status = MHD_HTTP_OK;
232
joshualitt609d9792016-01-27 11:07:23 -0800233 if (redirect) {
234 MHD_add_response_header (response, "Location", redirectUrl);
jcgregorio6f17bc52016-01-27 11:44:38 -0800235 status = MHD_HTTP_SEE_OTHER;
joshualitt609d9792016-01-27 11:07:23 -0800236 }
237
jcgregorio6f17bc52016-01-27 11:44:38 -0800238 int ret = MHD_queue_response(connection, status, response);
joshualittccfdaa52016-01-27 07:40:29 -0800239 MHD_destroy_response(response);
240 return ret;
241}
242
joshualitt483b9012016-02-02 07:16:24 -0800243class UrlHandler {
244public:
245 virtual ~UrlHandler() {}
246 virtual bool canHandle(const char* method, const char* url) = 0;
247 virtual int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800248 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800249 const char* upload_data, size_t* upload_data_size) = 0;
250};
joshualittccfdaa52016-01-27 07:40:29 -0800251
joshualitt29e5a892016-02-04 06:08:33 -0800252class CmdHandler : public UrlHandler {
joshualitt483b9012016-02-02 07:16:24 -0800253public:
254 bool canHandle(const char* method, const char* url) override {
joshualitt136f5172016-02-02 11:07:39 -0800255 const char* kBasePath = "/cmd";
256 return 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800257 }
258
joshualitt483b9012016-02-02 07:16:24 -0800259 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800260 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800261 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800262 SkTArray<SkString> commands;
263 SkStrSplit(url, "/", &commands);
264
265 if (!request->fPicture.get() || commands.count() > 3) {
266 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800267 }
joshualitt136f5172016-02-02 11:07:39 -0800268
joshualittdb6a2542016-02-11 07:09:51 -0800269 // /cmd or /cmd/N
270 if (0 == strcmp(method, MHD_HTTP_METHOD_GET)) {
271 int n;
272 if (commands.count() == 1) {
ethannicholas0a0520a2016-02-12 12:06:53 -0800273 n = request->fDebugCanvas->getSize() - 1;
joshualittdb6a2542016-02-11 07:09:51 -0800274 } else {
275 sscanf(commands[1].c_str(), "%d", &n);
276 }
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800277 return SendJSON(connection, request, n);
joshualitt136f5172016-02-02 11:07:39 -0800278 }
279
280 // /cmd/N, for now only delete supported
281 if (commands.count() == 2 && 0 == strcmp(method, MHD_HTTP_METHOD_DELETE)) {
282 int n;
283 sscanf(commands[1].c_str(), "%d", &n);
284 request->fDebugCanvas->deleteDrawCommandAt(n);
jcgregorio12d47ce2016-02-10 14:10:37 -0800285 return SendOK(connection);
joshualitt136f5172016-02-02 11:07:39 -0800286 }
287
288 // /cmd/N/[0|1]
289 if (commands.count() == 3 && 0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
290 int n, toggle;
291 sscanf(commands[1].c_str(), "%d", &n);
292 sscanf(commands[2].c_str(), "%d", &toggle);
293 request->fDebugCanvas->toggleCommand(n, toggle);
jcgregorio12d47ce2016-02-10 14:10:37 -0800294 return SendOK(connection);
joshualitt136f5172016-02-02 11:07:39 -0800295 }
296
joshualitt483b9012016-02-02 07:16:24 -0800297 return MHD_NO;
298 }
299};
300
301class ImgHandler : public UrlHandler {
302public:
303 bool canHandle(const char* method, const char* url) override {
304 static const char* kBasePath = "/img";
305 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
306 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800307 }
308
joshualitt483b9012016-02-02 07:16:24 -0800309 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800310 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800311 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800312 SkTArray<SkString> commands;
313 SkStrSplit(url, "/", &commands);
314
315 if (!request->fPicture.get() || commands.count() > 2) {
316 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800317 }
joshualitta341b902016-02-02 07:37:21 -0800318
joshualitt136f5172016-02-02 11:07:39 -0800319 int n;
320 // /img or /img/N
321 if (commands.count() == 1) {
322 n = request->fDebugCanvas->getSize() - 1;
323 } else {
324 sscanf(commands[1].c_str(), "%d", &n);
325 }
326
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800327 SkAutoTUnref<SkData> data(drawToPng(request, n));
joshualitt136f5172016-02-02 11:07:39 -0800328 return SendData(connection, data, "image/png");
joshualitt483b9012016-02-02 07:16:24 -0800329 }
330};
joshualittccfdaa52016-01-27 07:40:29 -0800331
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800332class BreakHandler : public UrlHandler {
333public:
334 bool canHandle(const char* method, const char* url) override {
335 static const char* kBasePath = "/break";
336 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
337 0 == strncmp(url, kBasePath, strlen(kBasePath));
338 }
339
340 static SkColor GetPixel(Request* request, int x, int y) {
341 SkCanvas* canvas = getCanvasFromRequest(request);
342 canvas->flush();
343 SkAutoTDelete<SkBitmap> bitmap(getBitmapFromCanvas(canvas));
344 SkASSERT(bitmap);
345 bitmap->lockPixels();
346 uint8_t* start = ((uint8_t*) bitmap->getPixels()) + (y * kImageWidth + x) * 4;
347 SkColor result = SkColorSetARGB(start[3], start[0], start[1], start[2]);
348 bitmap->unlockPixels();
349 return result;
350 }
351
352 int handle(Request* request, MHD_Connection* connection,
353 const char* url, const char* method,
354 const char* upload_data, size_t* upload_data_size) override {
355 SkTArray<SkString> commands;
356 SkStrSplit(url, "/", &commands);
357
358 if (!request->fPicture.get() || commands.count() != 4) {
359 return MHD_NO;
360 }
361
362 // /break/<n>/<x>/<y>
363 int n;
364 sscanf(commands[1].c_str(), "%d", &n);
365 int x;
366 sscanf(commands[2].c_str(), "%d", &x);
367 int y;
368 sscanf(commands[3].c_str(), "%d", &y);
369
370 int count = request->fDebugCanvas->getSize();
371 SkASSERT(n < count);
372
373 SkCanvas* canvas = getCanvasFromRequest(request);
374 canvas->clear(SK_ColorWHITE);
375 int saveCount = canvas->save();
376 for (int i = 0; i <= n; ++i) {
377 request->fDebugCanvas->getDrawCommandAt(i)->execute(canvas);
378 }
379 SkColor target = GetPixel(request, x, y);
380 Json::Value response(Json::objectValue);
381 Json::Value startColor(Json::arrayValue);
382 startColor.append(Json::Value(SkColorGetR(target)));
383 startColor.append(Json::Value(SkColorGetG(target)));
384 startColor.append(Json::Value(SkColorGetB(target)));
385 startColor.append(Json::Value(SkColorGetA(target)));
386 response["startColor"] = startColor;
387 response["endColor"] = startColor;
388 response["endOp"] = Json::Value(n);
389 for (int i = n + 1; i < n + count; ++i) {
390 int index = i % count;
391 if (index == 0) {
392 // reset canvas for wraparound
393 canvas->restoreToCount(saveCount);
394 canvas->clear(SK_ColorWHITE);
395 saveCount = canvas->save();
396 }
397 request->fDebugCanvas->getDrawCommandAt(index)->execute(canvas);
398 SkColor current = GetPixel(request, x, y);
399 if (current != target) {
400 Json::Value endColor(Json::arrayValue);
401 endColor.append(Json::Value(SkColorGetR(current)));
402 endColor.append(Json::Value(SkColorGetG(current)));
403 endColor.append(Json::Value(SkColorGetB(current)));
404 endColor.append(Json::Value(SkColorGetA(current)));
405 response["endColor"] = endColor;
406 response["endOp"] = Json::Value(index);
407 break;
408 }
409 }
410 canvas->restoreToCount(saveCount);
411 SkDynamicMemoryWStream stream;
412 stream.writeText(Json::FastWriter().write(response).c_str());
413 SkAutoTUnref<SkData> data(stream.copyToData());
414 return SendData(connection, data, "application/json");
415 }
416};
417
ethannicholas0a0520a2016-02-12 12:06:53 -0800418/**
419 Updates the clip visualization alpha. On all subsequent /img requests, the clip will be drawn in
420 black with the specified alpha. 0 = no visible clip, 255 = fully opaque clip.
421 */
422class ClipAlphaHandler : public UrlHandler {
423public:
424 bool canHandle(const char* method, const char* url) override {
425 static const char* kBasePath = "/clipAlpha/";
jcgregorio3341d422016-02-16 10:31:07 -0800426 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
ethannicholas0a0520a2016-02-12 12:06:53 -0800427 0 == strncmp(url, kBasePath, strlen(kBasePath));
428 }
429
430 int handle(Request* request, MHD_Connection* connection,
431 const char* url, const char* method,
432 const char* upload_data, size_t* upload_data_size) override {
433 SkTArray<SkString> commands;
434 SkStrSplit(url, "/", &commands);
435
436 if (!request->fPicture.get() || commands.count() != 2) {
437 return MHD_NO;
438 }
439
440 int alpha;
441 sscanf(commands[1].c_str(), "%d", &alpha);
442
443 request->fDebugCanvas->setClipVizColor(SkColorSetARGB(alpha, 0, 0, 0));
444 return SendOK(connection);
445 }
446};
447
ethannicholas85fca852016-02-19 08:40:59 -0800448/**
449 Controls whether GPU rendering is enabled. Posting to /enableGPU/1 turns GPU on, /enableGPU/0
450 disables it.
451 */
452class EnableGPUHandler : public UrlHandler {
453public:
454 bool canHandle(const char* method, const char* url) override {
455 static const char* kBasePath = "/enableGPU/";
456 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
457 0 == strncmp(url, kBasePath, strlen(kBasePath));
458 }
459
460 int handle(Request* request, MHD_Connection* connection,
461 const char* url, const char* method,
462 const char* upload_data, size_t* upload_data_size) override {
463 SkTArray<SkString> commands;
464 SkStrSplit(url, "/", &commands);
465
466 if (commands.count() != 2) {
467 return MHD_NO;
468 }
469
470 int enable;
471 sscanf(commands[1].c_str(), "%d", &enable);
472
473 if (enable) {
474 SkSurface* surface = createGPUSurface(request);
475 if (surface) {
476 request->fSurface.reset(surface);
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800477 request->fGPUEnabled = true;
ethannicholas85fca852016-02-19 08:40:59 -0800478 return SendOK(connection);
479 }
480 return SendError(connection, "Unable to create GPU surface");
481 }
482 request->fSurface.reset(createCPUSurface());
ethannicholas3ff5d8c2016-02-22 08:59:57 -0800483 request->fGPUEnabled = false;
ethannicholas85fca852016-02-19 08:40:59 -0800484 return SendOK(connection);
485 }
486};
487
joshualitt483b9012016-02-02 07:16:24 -0800488class PostHandler : public UrlHandler {
489public:
490 bool canHandle(const char* method, const char* url) override {
491 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
492 0 == strcmp(url, "/new");
joshualittccfdaa52016-01-27 07:40:29 -0800493 }
494
joshualitt483b9012016-02-02 07:16:24 -0800495 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800496 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800497 const char* upload_data, size_t* upload_data_size) override {
498 UploadContext* uc = request->fUploadContext;
joshualittccfdaa52016-01-27 07:40:29 -0800499
joshualitt483b9012016-02-02 07:16:24 -0800500 // New connection
501 if (!uc) {
502 // TODO make this a method on request
503 uc = new UploadContext;
504 uc->connection = connection;
505 uc->fPostProcessor = MHD_create_post_processor(connection, kBufferSize,
506 &process_upload_data, uc);
507 SkASSERT(uc->fPostProcessor);
joshualitt609d9792016-01-27 11:07:23 -0800508
joshualitt483b9012016-02-02 07:16:24 -0800509 request->fUploadContext = uc;
510 return MHD_YES;
511 }
512
513 // in process upload
514 if (0 != *upload_data_size) {
515 SkASSERT(uc->fPostProcessor);
516 MHD_post_process(uc->fPostProcessor, upload_data, *upload_data_size);
517 *upload_data_size = 0;
518 return MHD_YES;
519 }
520
521 // end of upload
522 MHD_destroy_post_processor(uc->fPostProcessor);
523 uc->fPostProcessor = nullptr;
524
joshualitt136f5172016-02-02 11:07:39 -0800525 // parse picture from stream
526 request->fPicture.reset(
527 SkPicture::CreateFromStream(request->fUploadContext->fStream.detachAsStream()));
528 if (!request->fPicture.get()) {
529 fprintf(stderr, "Could not create picture from stream.\n");
530 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800531 }
532
joshualitt136f5172016-02-02 11:07:39 -0800533 // pour picture into debug canvas
534 request->fDebugCanvas.reset(new SkDebugCanvas(kImageWidth, kImageHeight));
535 request->fDebugCanvas->drawPicture(request->fPicture);
536
joshualitta341b902016-02-02 07:37:21 -0800537 // clear upload context
538 delete request->fUploadContext;
539 request->fUploadContext = nullptr;
540
joshualitt483b9012016-02-02 07:16:24 -0800541 return SendTemplate(connection, true, "/");
joshualittccfdaa52016-01-27 07:40:29 -0800542 }
joshualitt483b9012016-02-02 07:16:24 -0800543};
544
joshualitt792345f2016-02-02 13:02:33 -0800545class DownloadHandler : public UrlHandler {
546public:
547 bool canHandle(const char* method, const char* url) override {
548 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
549 0 == strcmp(url, "/download");
550 }
551
552 int handle(Request* request, MHD_Connection* connection,
553 const char* url, const char* method,
554 const char* upload_data, size_t* upload_data_size) override {
555 if (!request->fPicture.get()) {
556 return MHD_NO;
557 }
558
559 // TODO move to a function
560 // Playback into picture recorder
561 SkPictureRecorder recorder;
562 SkCanvas* canvas = recorder.beginRecording(kImageWidth, kImageHeight);
563
564 request->fDebugCanvas->draw(canvas);
565
566 SkAutoTUnref<SkPicture> picture(recorder.endRecording());
567
568 SkDynamicMemoryWStream outStream;
569
570 SkAutoTUnref<SkPixelSerializer> serializer(SkImageEncoder::CreatePixelSerializer());
571 picture->serialize(&outStream, serializer);
572
573 SkAutoTUnref<SkData> data(outStream.copyToData());
574
575 // TODO fancier name handling
576 return SendData(connection, data, "application/octet-stream", true,
577 "attachment; filename=something.skp;");
578 }
579};
580
joshualitt29e5a892016-02-04 06:08:33 -0800581class InfoHandler : public UrlHandler {
582public:
583 bool canHandle(const char* method, const char* url) override {
584 const char* kBaseName = "/info";
585 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
586 0 == strncmp(url, kBaseName, strlen(kBaseName));
587 }
588
589 int handle(Request* request, MHD_Connection* connection,
590 const char* url, const char* method,
591 const char* upload_data, size_t* upload_data_size) override {
592 SkTArray<SkString> commands;
593 SkStrSplit(url, "/", &commands);
594
595 if (!request->fPicture.get() || commands.count() > 2) {
596 return MHD_NO;
597 }
598
599 // drawTo
ethannicholas85fca852016-02-19 08:40:59 -0800600 SkAutoTUnref<SkSurface> surface(createCPUSurface());
joshualitt29e5a892016-02-04 06:08:33 -0800601 SkCanvas* canvas = surface->getCanvas();
602
603 int n;
604 // /info or /info/N
605 if (commands.count() == 1) {
606 n = request->fDebugCanvas->getSize() - 1;
607 } else {
608 sscanf(commands[1].c_str(), "%d", &n);
609 }
610
611 // TODO this is really slow and we should cache the matrix and clip
612 request->fDebugCanvas->drawTo(canvas, n);
613
614 // make some json
615 SkMatrix vm = request->fDebugCanvas->getCurrentMatrix();
616 SkIRect clip = request->fDebugCanvas->getCurrentClip();
617 Json::Value info(Json::objectValue);
618 info["ViewMatrix"] = SkJSONCanvas::MakeMatrix(vm);
619 info["ClipRect"] = SkJSONCanvas::MakeIRect(clip);
620
621 std::string json = Json::FastWriter().write(info);
622
623 // We don't want the null terminator so strlen is correct
624 SkAutoTUnref<SkData> data(SkData::NewWithCopy(json.c_str(), strlen(json.c_str())));
625 return SendData(connection, data, "application/json");
626 }
627};
628
joshualittcdad12f2016-02-08 07:08:21 -0800629class DataHandler : public UrlHandler {
630public:
631 bool canHandle(const char* method, const char* url) override {
632 static const char* kBaseUrl = "/data";
633 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
634 0 == strncmp(url, kBaseUrl, strlen(kBaseUrl));
635 }
636
637 int handle(Request* request, MHD_Connection* connection,
638 const char* url, const char* method,
639 const char* upload_data, size_t* upload_data_size) override {
640 SkTArray<SkString> commands;
641 SkStrSplit(url, "/", &commands);
642
643 if (!request->fPicture.get() || commands.count() != 2) {
644 return MHD_NO;
645 }
646
647 SkAutoTUnref<UrlDataManager::UrlData> urlData(
648 SkRef(request->fUrlDataManager.getDataFromUrl(SkString(url))));
649
650 if (urlData) {
651 return SendData(connection, urlData->fData.get(), urlData->fContentType.c_str());
652 }
653 return MHD_NO;
654 }
655};
joshualitt29e5a892016-02-04 06:08:33 -0800656
joshualitt483b9012016-02-02 07:16:24 -0800657class RootHandler : public UrlHandler {
658public:
659 bool canHandle(const char* method, const char* url) override {
660 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
661 0 == strcmp(url, "/");
662 }
663
664 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800665 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800666 const char* upload_data, size_t* upload_data_size) override {
667 return SendTemplate(connection);
668 }
669};
joshualittccfdaa52016-01-27 07:40:29 -0800670
671class UrlManager {
672public:
673 UrlManager() {
674 // Register handlers
joshualitt483b9012016-02-02 07:16:24 -0800675 fHandlers.push_back(new RootHandler);
676 fHandlers.push_back(new PostHandler);
677 fHandlers.push_back(new ImgHandler);
ethannicholas0a0520a2016-02-12 12:06:53 -0800678 fHandlers.push_back(new ClipAlphaHandler);
ethannicholas85fca852016-02-19 08:40:59 -0800679 fHandlers.push_back(new EnableGPUHandler);
joshualitt29e5a892016-02-04 06:08:33 -0800680 fHandlers.push_back(new CmdHandler);
joshualitt483b9012016-02-02 07:16:24 -0800681 fHandlers.push_back(new InfoHandler);
joshualitt792345f2016-02-02 13:02:33 -0800682 fHandlers.push_back(new DownloadHandler);
joshualittcdad12f2016-02-08 07:08:21 -0800683 fHandlers.push_back(new DataHandler);
ethannicholas3ca1e1a2016-02-18 10:22:34 -0800684 fHandlers.push_back(new BreakHandler);
joshualitt483b9012016-02-02 07:16:24 -0800685 }
686
687 ~UrlManager() {
688 for (int i = 0; i < fHandlers.count(); i++) { delete fHandlers[i]; }
joshualittccfdaa52016-01-27 07:40:29 -0800689 }
690
691 // This is clearly not efficient for a large number of urls and handlers
692 int invoke(Request* request, MHD_Connection* connection, const char* url, const char* method,
693 const char* upload_data, size_t* upload_data_size) const {
694 for (int i = 0; i < fHandlers.count(); i++) {
joshualitt483b9012016-02-02 07:16:24 -0800695 if (fHandlers[i]->canHandle(method, url)) {
joshualitt136f5172016-02-02 11:07:39 -0800696 return fHandlers[i]->handle(request, connection, url, method, upload_data,
697 upload_data_size);
joshualittccfdaa52016-01-27 07:40:29 -0800698 }
699 }
700 return MHD_NO;
701 }
702
703private:
joshualitt483b9012016-02-02 07:16:24 -0800704 SkTArray<UrlHandler*> fHandlers;
joshualittccfdaa52016-01-27 07:40:29 -0800705};
706
707const UrlManager kUrlManager;
708
joshualitt7f6a1e02016-01-22 11:21:43 -0800709int answer_to_connection(void* cls, struct MHD_Connection* connection,
710 const char* url, const char* method, const char* version,
711 const char* upload_data, size_t* upload_data_size,
712 void** con_cls) {
joshualitt9a4e1882016-01-27 07:03:29 -0800713 SkDebugf("New %s request for %s using version %s\n", method, url, version);
joshualitt7f6a1e02016-01-22 11:21:43 -0800714
joshualitt9a4e1882016-01-27 07:03:29 -0800715 Request* request = reinterpret_cast<Request*>(cls);
joshualitt483b9012016-02-02 07:16:24 -0800716 int result = kUrlManager.invoke(request, connection, url, method, upload_data,
717 upload_data_size);
718 if (MHD_NO == result) {
joshualitt873d6242016-02-08 13:57:44 -0800719 fprintf(stderr, "Invalid method and / or url: %s %s\n", method, url);
joshualitt483b9012016-02-02 07:16:24 -0800720 }
721 return result;
joshualitt7f6a1e02016-01-22 11:21:43 -0800722}
723
724int skiaserve_main() {
joshualittcdad12f2016-02-08 07:08:21 -0800725 Request request(SkString("/data")); // This simple server has one request
ethannicholas85fca852016-02-19 08:40:59 -0800726
727 // create surface
728 GrContextOptions grContextOpts;
729 request.fContextFactory.reset(new GrContextFactory(grContextOpts));
730 request.fSurface.reset(createCPUSurface());
731
joshualitt7f6a1e02016-01-22 11:21:43 -0800732 struct MHD_Daemon* daemon;
jcgregorio21ab1202016-01-28 06:24:19 -0800733 // TODO Add option to bind this strictly to an address, e.g. localhost, for security.
734 daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, FLAGS_port, nullptr, nullptr,
joshualitt9a4e1882016-01-27 07:03:29 -0800735 &answer_to_connection, &request,
736 MHD_OPTION_END);
joshualitt7f6a1e02016-01-22 11:21:43 -0800737 if (NULL == daemon) {
738 return 1;
739 }
740
741 getchar();
742 MHD_stop_daemon(daemon);
743 return 0;
744}
745
746#if !defined SK_BUILD_FOR_IOS
747int main(int argc, char** argv) {
748 SkCommandLineFlags::Parse(argc, argv);
749 return skiaserve_main();
750}
751#endif