blob: fad8aa4acec211b6bc7eab81f3e07a99a28eb5fe [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"
joshualitt9a4e1882016-01-27 07:03:29 -080014#include "SkPicture.h"
joshualitt792345f2016-02-02 13:02:33 -080015#include "SkPictureRecorder.h"
16#include "SkPixelSerializer.h"
joshualitt7f6a1e02016-01-22 11:21:43 -080017#include "SkStream.h"
18#include "SkSurface.h"
19
joshualitt7f6a1e02016-01-22 11:21:43 -080020#include <sys/socket.h>
joshualitt7f6a1e02016-01-22 11:21:43 -080021#include <microhttpd.h>
22
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 -080035// TODO move to template file
36SkString generateTemplate(SkString source) {
37 SkString debuggerTemplate;
38 debuggerTemplate.appendf(
39 "<!DOCTYPE html>\n"
40 "<html>\n"
41 "<head>\n"
42 " <title>SkDebugger</title>\n"
43 " <meta charset=\"utf-8\" />\n"
44 " <meta http-equiv=\"X-UA-Compatible\" content=\"IE=egde,chrome=1\">\n"
45 " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
46 " <script src=\"%s/res/js/core.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n"
47 " <link href=\"%s/res/vul/elements.html\" rel=\"import\" />\n"
48 "</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
joshualitt9a4e1882016-01-27 07:03:29 -080058struct UploadContext {
joshualitt609d9792016-01-27 11:07:23 -080059 SkDynamicMemoryWStream fStream;
60 MHD_PostProcessor* fPostProcessor;
joshualitt9a4e1882016-01-27 07:03:29 -080061 MHD_Connection* connection;
62};
63
64struct Request {
65 Request() : fUploadContext(nullptr) {}
66 UploadContext* fUploadContext;
joshualitt609d9792016-01-27 11:07:23 -080067 SkAutoTUnref<SkPicture> fPicture;
joshualitt136f5172016-02-02 11:07:39 -080068 SkAutoTUnref<SkDebugCanvas> fDebugCanvas;
joshualitt9a4e1882016-01-27 07:03:29 -080069};
70
joshualitt609d9792016-01-27 11:07:23 -080071// TODO factor this out into functions, also handle CPU path
joshualitt136f5172016-02-02 11:07:39 -080072SkSurface* setupSurface(GrContextFactory* factory) {
joshualitt609d9792016-01-27 11:07:23 -080073 GrContext* context = factory->get(GrContextFactory::kNative_GLContextType,
74 GrContextFactory::kNone_GLContextOptions);
75 int maxRTSize = context->caps()->maxRenderTargetSize();
76 SkImageInfo info = SkImageInfo::Make(SkTMin(kImageWidth, maxRTSize),
77 SkTMin(kImageHeight, maxRTSize),
78 kN32_SkColorType, kPremul_SkAlphaType);
79 uint32_t flags = 0;
80 SkSurfaceProps props(flags, SkSurfaceProps::kLegacyFontHost_InitType);
joshualitt136f5172016-02-02 11:07:39 -080081 SkSurface* surface = SkSurface::NewRenderTarget(context, SkSurface::kNo_Budgeted, info, 0,
82 &props);
83 SkASSERT(surface);
joshualitt609d9792016-01-27 11:07:23 -080084
85 SkGLContext* gl = factory->getContextInfo(GrContextFactory::kNative_GLContextType,
86 GrContextFactory::kNone_GLContextOptions).fGLContext;
87 gl->makeCurrent();
joshualitt136f5172016-02-02 11:07:39 -080088 return surface;
89}
joshualitt609d9792016-01-27 11:07:23 -080090
joshualitt136f5172016-02-02 11:07:39 -080091SkData* writeCanvasToPng(SkCanvas* canvas) {
joshualitt609d9792016-01-27 11:07:23 -080092 // capture pixels
93 SkBitmap bmp;
94 bmp.setInfo(canvas->imageInfo());
95 if (!canvas->readPixels(&bmp, 0, 0)) {
joshualitt136f5172016-02-02 11:07:39 -080096 fprintf(stderr, "Can't read pixels\n");
97 return nullptr;
joshualitt609d9792016-01-27 11:07:23 -080098 }
99
100 // write to png
joshualitt136f5172016-02-02 11:07:39 -0800101 SkData* png = SkImageEncoder::EncodeData(bmp, SkImageEncoder::kPNG_Type, 100);
102 if (!png) {
103 fprintf(stderr, "Can't encode to png\n");
104 return nullptr;
joshualitt609d9792016-01-27 11:07:23 -0800105 }
joshualitt136f5172016-02-02 11:07:39 -0800106 return png;
107}
108
109SkData* setupAndDrawToCanvasReturnPng(SkDebugCanvas* debugCanvas, int n) {
110 GrContextOptions grContextOpts;
111 SkAutoTDelete<GrContextFactory> factory(new GrContextFactory(grContextOpts));
112 SkAutoTUnref<SkSurface> surface(setupSurface(factory.get()));
113
114 SkASSERT(debugCanvas);
115 SkCanvas* canvas = surface->getCanvas();
116 debugCanvas->drawTo(canvas, n);
117 return writeCanvasToPng(canvas);
joshualitt609d9792016-01-27 11:07:23 -0800118}
119
joshualitt9a4e1882016-01-27 07:03:29 -0800120static const size_t kBufferSize = 1024;
121
122static int process_upload_data(void* cls, enum MHD_ValueKind kind,
123 const char* key, const char* filename,
124 const char* content_type, const char* transfer_encoding,
125 const char* data, uint64_t off, size_t size) {
126 struct UploadContext* uc = reinterpret_cast<UploadContext*>(cls);
127
128 if (0 != size) {
joshualitt609d9792016-01-27 11:07:23 -0800129 uc->fStream.write(data, size);
joshualitt9a4e1882016-01-27 07:03:29 -0800130 }
131 return MHD_YES;
132}
133
joshualitt792345f2016-02-02 13:02:33 -0800134static int SendData(MHD_Connection* connection, const SkData* data, const char* type,
135 bool setContentDisposition = false, const char* dispositionString = nullptr) {
joshualittccfdaa52016-01-27 07:40:29 -0800136 MHD_Response* response = MHD_create_response_from_buffer(data->size(),
137 const_cast<void*>(data->data()),
138 MHD_RESPMEM_MUST_COPY);
joshualitt609d9792016-01-27 11:07:23 -0800139 MHD_add_response_header(response, "Content-Type", type);
joshualittccfdaa52016-01-27 07:40:29 -0800140
joshualitt792345f2016-02-02 13:02:33 -0800141 if (setContentDisposition) {
142 MHD_add_response_header(response, "Content-Disposition", dispositionString);
143 }
144
joshualittccfdaa52016-01-27 07:40:29 -0800145 int ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
146 MHD_destroy_response(response);
147 return ret;
148}
149
joshualitt136f5172016-02-02 11:07:39 -0800150static int SendJSON(MHD_Connection* connection, SkDebugCanvas* debugCanvas, int n) {
joshualitt609d9792016-01-27 11:07:23 -0800151 SkDynamicMemoryWStream stream;
152 SkAutoTUnref<SkJSONCanvas> jsonCanvas(new SkJSONCanvas(kImageWidth, kImageHeight, stream));
joshualitt136f5172016-02-02 11:07:39 -0800153 debugCanvas->drawTo(jsonCanvas, n);
joshualitt609d9792016-01-27 11:07:23 -0800154 jsonCanvas->finish();
155
156 SkAutoTUnref<SkData> data(stream.copyToData());
157 return SendData(connection, data, "application/json");
158}
159
160static int SendTemplate(MHD_Connection* connection, bool redirect = false,
161 const char* redirectUrl = nullptr) {
jcgregorio21ab1202016-01-28 06:24:19 -0800162 SkString debuggerTemplate = generateTemplate(SkString(FLAGS_source[0]));
joshualittccfdaa52016-01-27 07:40:29 -0800163
164 MHD_Response* response = MHD_create_response_from_buffer(
165 debuggerTemplate.size(),
166 (void*) const_cast<char*>(debuggerTemplate.c_str()),
167 MHD_RESPMEM_MUST_COPY);
jcgregorio6a2046e2016-01-28 05:31:31 -0800168 MHD_add_response_header (response, "Access-Control-Allow-Origin", "*");
joshualittccfdaa52016-01-27 07:40:29 -0800169
jcgregorio6f17bc52016-01-27 11:44:38 -0800170 int status = MHD_HTTP_OK;
171
joshualitt609d9792016-01-27 11:07:23 -0800172 if (redirect) {
173 MHD_add_response_header (response, "Location", redirectUrl);
jcgregorio6f17bc52016-01-27 11:44:38 -0800174 status = MHD_HTTP_SEE_OTHER;
joshualitt609d9792016-01-27 11:07:23 -0800175 }
176
jcgregorio6f17bc52016-01-27 11:44:38 -0800177 int ret = MHD_queue_response(connection, status, response);
joshualittccfdaa52016-01-27 07:40:29 -0800178 MHD_destroy_response(response);
179 return ret;
180}
181
joshualitt483b9012016-02-02 07:16:24 -0800182class UrlHandler {
183public:
184 virtual ~UrlHandler() {}
185 virtual bool canHandle(const char* method, const char* url) = 0;
186 virtual int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800187 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800188 const char* upload_data, size_t* upload_data_size) = 0;
189};
joshualittccfdaa52016-01-27 07:40:29 -0800190
joshualitt483b9012016-02-02 07:16:24 -0800191class InfoHandler : public UrlHandler {
192public:
193 bool canHandle(const char* method, const char* url) override {
joshualitt136f5172016-02-02 11:07:39 -0800194 const char* kBasePath = "/cmd";
195 return 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800196 }
197
joshualitt483b9012016-02-02 07:16:24 -0800198 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800199 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800200 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800201 SkTArray<SkString> commands;
202 SkStrSplit(url, "/", &commands);
203
204 if (!request->fPicture.get() || commands.count() > 3) {
205 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800206 }
joshualitt136f5172016-02-02 11:07:39 -0800207
208 // /cmd or /cmd/N or /cmd/N/[0|1]
209 if (commands.count() == 1 && 0 == strcmp(method, MHD_HTTP_METHOD_GET)) {
210 int n = request->fDebugCanvas->getSize() - 1;
211 return SendJSON(connection, request->fDebugCanvas, n);
212 }
213
214 // /cmd/N, for now only delete supported
215 if (commands.count() == 2 && 0 == strcmp(method, MHD_HTTP_METHOD_DELETE)) {
216 int n;
217 sscanf(commands[1].c_str(), "%d", &n);
218 request->fDebugCanvas->deleteDrawCommandAt(n);
219 return MHD_YES;
220 }
221
222 // /cmd/N/[0|1]
223 if (commands.count() == 3 && 0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
224 int n, toggle;
225 sscanf(commands[1].c_str(), "%d", &n);
226 sscanf(commands[2].c_str(), "%d", &toggle);
227 request->fDebugCanvas->toggleCommand(n, toggle);
228 return MHD_YES;
229 }
230
joshualitt483b9012016-02-02 07:16:24 -0800231 return MHD_NO;
232 }
233};
234
235class ImgHandler : public UrlHandler {
236public:
237 bool canHandle(const char* method, const char* url) override {
238 static const char* kBasePath = "/img";
239 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
240 0 == strncmp(url, kBasePath, strlen(kBasePath));
joshualittccfdaa52016-01-27 07:40:29 -0800241 }
242
joshualitt483b9012016-02-02 07:16:24 -0800243 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800244 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800245 const char* upload_data, size_t* upload_data_size) override {
joshualitt136f5172016-02-02 11:07:39 -0800246 SkTArray<SkString> commands;
247 SkStrSplit(url, "/", &commands);
248
249 if (!request->fPicture.get() || commands.count() > 2) {
250 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800251 }
joshualitta341b902016-02-02 07:37:21 -0800252
joshualitt136f5172016-02-02 11:07:39 -0800253 int n;
254 // /img or /img/N
255 if (commands.count() == 1) {
256 n = request->fDebugCanvas->getSize() - 1;
257 } else {
258 sscanf(commands[1].c_str(), "%d", &n);
259 }
260
261 SkAutoTUnref<SkData> data(setupAndDrawToCanvasReturnPng(request->fDebugCanvas, n));
262 return SendData(connection, data, "image/png");
joshualitt483b9012016-02-02 07:16:24 -0800263 }
264};
joshualittccfdaa52016-01-27 07:40:29 -0800265
joshualitt483b9012016-02-02 07:16:24 -0800266class PostHandler : public UrlHandler {
267public:
268 bool canHandle(const char* method, const char* url) override {
269 return 0 == strcmp(method, MHD_HTTP_METHOD_POST) &&
270 0 == strcmp(url, "/new");
joshualittccfdaa52016-01-27 07:40:29 -0800271 }
272
joshualitt483b9012016-02-02 07:16:24 -0800273 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800274 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800275 const char* upload_data, size_t* upload_data_size) override {
276 UploadContext* uc = request->fUploadContext;
joshualittccfdaa52016-01-27 07:40:29 -0800277
joshualitt483b9012016-02-02 07:16:24 -0800278 // New connection
279 if (!uc) {
280 // TODO make this a method on request
281 uc = new UploadContext;
282 uc->connection = connection;
283 uc->fPostProcessor = MHD_create_post_processor(connection, kBufferSize,
284 &process_upload_data, uc);
285 SkASSERT(uc->fPostProcessor);
joshualitt609d9792016-01-27 11:07:23 -0800286
joshualitt483b9012016-02-02 07:16:24 -0800287 request->fUploadContext = uc;
288 return MHD_YES;
289 }
290
291 // in process upload
292 if (0 != *upload_data_size) {
293 SkASSERT(uc->fPostProcessor);
294 MHD_post_process(uc->fPostProcessor, upload_data, *upload_data_size);
295 *upload_data_size = 0;
296 return MHD_YES;
297 }
298
299 // end of upload
300 MHD_destroy_post_processor(uc->fPostProcessor);
301 uc->fPostProcessor = nullptr;
302
joshualitt136f5172016-02-02 11:07:39 -0800303 // parse picture from stream
304 request->fPicture.reset(
305 SkPicture::CreateFromStream(request->fUploadContext->fStream.detachAsStream()));
306 if (!request->fPicture.get()) {
307 fprintf(stderr, "Could not create picture from stream.\n");
308 return MHD_NO;
joshualitt483b9012016-02-02 07:16:24 -0800309 }
310
joshualitt136f5172016-02-02 11:07:39 -0800311 // pour picture into debug canvas
312 request->fDebugCanvas.reset(new SkDebugCanvas(kImageWidth, kImageHeight));
313 request->fDebugCanvas->drawPicture(request->fPicture);
314
joshualitta341b902016-02-02 07:37:21 -0800315 // clear upload context
316 delete request->fUploadContext;
317 request->fUploadContext = nullptr;
318
joshualitt483b9012016-02-02 07:16:24 -0800319 return SendTemplate(connection, true, "/");
joshualittccfdaa52016-01-27 07:40:29 -0800320 }
joshualitt483b9012016-02-02 07:16:24 -0800321};
322
joshualitt792345f2016-02-02 13:02:33 -0800323class DownloadHandler : public UrlHandler {
324public:
325 bool canHandle(const char* method, const char* url) override {
326 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
327 0 == strcmp(url, "/download");
328 }
329
330 int handle(Request* request, MHD_Connection* connection,
331 const char* url, const char* method,
332 const char* upload_data, size_t* upload_data_size) override {
333 if (!request->fPicture.get()) {
334 return MHD_NO;
335 }
336
337 // TODO move to a function
338 // Playback into picture recorder
339 SkPictureRecorder recorder;
340 SkCanvas* canvas = recorder.beginRecording(kImageWidth, kImageHeight);
341
342 request->fDebugCanvas->draw(canvas);
343
344 SkAutoTUnref<SkPicture> picture(recorder.endRecording());
345
346 SkDynamicMemoryWStream outStream;
347
348 SkAutoTUnref<SkPixelSerializer> serializer(SkImageEncoder::CreatePixelSerializer());
349 picture->serialize(&outStream, serializer);
350
351 SkAutoTUnref<SkData> data(outStream.copyToData());
352
353 // TODO fancier name handling
354 return SendData(connection, data, "application/octet-stream", true,
355 "attachment; filename=something.skp;");
356 }
357};
358
joshualitt483b9012016-02-02 07:16:24 -0800359class RootHandler : public UrlHandler {
360public:
361 bool canHandle(const char* method, const char* url) override {
362 return 0 == strcmp(method, MHD_HTTP_METHOD_GET) &&
363 0 == strcmp(url, "/");
364 }
365
366 int handle(Request* request, MHD_Connection* connection,
joshualitt136f5172016-02-02 11:07:39 -0800367 const char* url, const char* method,
joshualitt483b9012016-02-02 07:16:24 -0800368 const char* upload_data, size_t* upload_data_size) override {
369 return SendTemplate(connection);
370 }
371};
joshualittccfdaa52016-01-27 07:40:29 -0800372
373class UrlManager {
374public:
375 UrlManager() {
376 // Register handlers
joshualitt483b9012016-02-02 07:16:24 -0800377 fHandlers.push_back(new RootHandler);
378 fHandlers.push_back(new PostHandler);
379 fHandlers.push_back(new ImgHandler);
380 fHandlers.push_back(new InfoHandler);
joshualitt792345f2016-02-02 13:02:33 -0800381 fHandlers.push_back(new DownloadHandler);
joshualitt483b9012016-02-02 07:16:24 -0800382 }
383
384 ~UrlManager() {
385 for (int i = 0; i < fHandlers.count(); i++) { delete fHandlers[i]; }
joshualittccfdaa52016-01-27 07:40:29 -0800386 }
387
388 // This is clearly not efficient for a large number of urls and handlers
389 int invoke(Request* request, MHD_Connection* connection, const char* url, const char* method,
390 const char* upload_data, size_t* upload_data_size) const {
391 for (int i = 0; i < fHandlers.count(); i++) {
joshualitt483b9012016-02-02 07:16:24 -0800392 if (fHandlers[i]->canHandle(method, url)) {
joshualitt136f5172016-02-02 11:07:39 -0800393 return fHandlers[i]->handle(request, connection, url, method, upload_data,
394 upload_data_size);
joshualittccfdaa52016-01-27 07:40:29 -0800395 }
396 }
397 return MHD_NO;
398 }
399
400private:
joshualitt483b9012016-02-02 07:16:24 -0800401 SkTArray<UrlHandler*> fHandlers;
joshualittccfdaa52016-01-27 07:40:29 -0800402};
403
404const UrlManager kUrlManager;
405
joshualitt7f6a1e02016-01-22 11:21:43 -0800406int answer_to_connection(void* cls, struct MHD_Connection* connection,
407 const char* url, const char* method, const char* version,
408 const char* upload_data, size_t* upload_data_size,
409 void** con_cls) {
joshualitt9a4e1882016-01-27 07:03:29 -0800410 SkDebugf("New %s request for %s using version %s\n", method, url, version);
joshualitt7f6a1e02016-01-22 11:21:43 -0800411
joshualitt9a4e1882016-01-27 07:03:29 -0800412 Request* request = reinterpret_cast<Request*>(cls);
joshualitt483b9012016-02-02 07:16:24 -0800413 int result = kUrlManager.invoke(request, connection, url, method, upload_data,
414 upload_data_size);
415 if (MHD_NO == result) {
416 fprintf(stderr, "Invalid method and / or url: %s %s)\n", method, url);
417 }
418 return result;
joshualitt7f6a1e02016-01-22 11:21:43 -0800419}
420
421int skiaserve_main() {
joshualitt9a4e1882016-01-27 07:03:29 -0800422 Request request; // This simple server has one request
joshualitt7f6a1e02016-01-22 11:21:43 -0800423 struct MHD_Daemon* daemon;
jcgregorio21ab1202016-01-28 06:24:19 -0800424 // TODO Add option to bind this strictly to an address, e.g. localhost, for security.
425 daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, FLAGS_port, nullptr, nullptr,
joshualitt9a4e1882016-01-27 07:03:29 -0800426 &answer_to_connection, &request,
427 MHD_OPTION_END);
joshualitt7f6a1e02016-01-22 11:21:43 -0800428 if (NULL == daemon) {
429 return 1;
430 }
431
432 getchar();
433 MHD_stop_daemon(daemon);
434 return 0;
435}
436
437#if !defined SK_BUILD_FOR_IOS
438int main(int argc, char** argv) {
439 SkCommandLineFlags::Parse(argc, argv);
440 return skiaserve_main();
441}
442#endif