| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stdio.h> |
| |
| #include <vector> |
| |
| #if USE_MOLD |
| #define DEMO_CLASS_HEADER "common/demo_app_mold.h" |
| #define DEMO_CLASS_NAME DemoAppMold |
| #define ONLY_IF_MOLD(...) __VA_ARGS__ |
| #define ONLY_IF_SPINEL(...) /* nothing */ |
| #else // !USE_MOLD |
| #define DEMO_CLASS_HEADER "common/demo_app_spinel.h" |
| #define DEMO_CLASS_NAME DemoAppSpinel |
| #define ONLY_IF_MOLD(...) /* nothing */ |
| #define ONLY_IF_SPINEL(...) __VA_ARGS__ |
| #endif // !USE_MOLD |
| |
| // This weird include is to work-around the fact that gn --check doesn't |
| // seem to properly understand conditional includes when they appear in |
| // #ifdef .. #endif blocks. Note that this is standard cpp behaviour! |
| #include DEMO_CLASS_HEADER |
| |
| #include "common/demo_image.h" |
| #include "common/demo_image_group.h" |
| #include "common/demo_utils.h" |
| #include "tests/common/affine_transform.h" |
| #include "tests/common/argparse.h" |
| #include "tests/common/spinel/svg_spinel_image.h" |
| #include "tests/common/svg/scoped_svg.h" |
| #include "tests/common/svg/svg_bounds.h" |
| #include "tests/common/utils.h" |
| |
| #define DEMO_SURFACE_WIDTH 1024 |
| #define DEMO_SURFACE_HEIGHT 1024 |
| |
| #ifndef PROGRAM_NAME |
| #define PROGRAM_NAME "svg_demo" |
| #endif |
| |
| // |
| // |
| // |
| |
| namespace { |
| |
| // A DemoImage derived class to display a single SVG document. |
| // |
| // TODO(digit): For simplicity, each instance has its own set of path handles. |
| // It might be useful to share these between several instances, but this requires |
| // non-trivial changes to the SvgSpinelImage class. |
| class SvgDemoImage : public DemoImage { |
| // Type of a callback used to compute a transform to apply to a given frame |
| // based on its counter value. |
| using FrameTransformFunc = std::function<spn_transform_t(uint32_t)>; |
| |
| public: |
| SvgDemoImage(const DemoImage::Config & config, const svg * svg, FrameTransformFunc transform_func) |
| : transform_func_(transform_func) |
| { |
| svg_image_.init(svg, config.context, config.surface_width, config.surface_height); |
| svg_image_.setupPaths(); |
| } |
| |
| ~SvgDemoImage() |
| { |
| svg_image_.reset(); |
| } |
| |
| void |
| setup(uint32_t frame_counter) override |
| { |
| spn_transform_t transform = { .sx = 1., .sy = 1. }; |
| if (transform_func_) |
| transform = transform_func_(frame_counter); |
| |
| svg_image_.setupRasters(&transform); |
| svg_image_.setupLayers(); |
| } |
| |
| void |
| render(void * submit_ext, uint32_t clip_width, uint32_t clip_height) override |
| { |
| svg_image_.render(submit_ext, clip_width, clip_height); |
| svg_image_.resetRasters(); |
| } |
| |
| void |
| flush() override |
| { |
| svg_image_.resetLayers(); |
| } |
| |
| protected: |
| SvgSpinelImage svg_image_ = {}; |
| FrameTransformFunc transform_func_; |
| }; |
| |
| } // namespace |
| |
| // |
| // |
| // |
| |
| int |
| main(int argc, const char ** argv) |
| { |
| // clang-format off |
| |
| #define MY_OPTIONS_LIST(param) \ |
| ARGPARSE_OPTION_DOUBLE(param, scale, 's', "scale", "Apply affine scale to the image.") \ |
| ARGPARSE_OPTION_DOUBLE(param, fixed_scale, 'S', "fixed-scale", \ |
| "Fix animation scale to specific value. Useful for " \ |
| "replicating rendering bugs. Implies --fixed-rotation=0 if that option is not used.") \ |
| ARGPARSE_OPTION_DOUBLE(param, fixed_rotation, 'R', "fixed-rotation", \ |
| "Fix animation rotation to specific angle value in degrees. Useful for " \ |
| "replicating rendering bugs. Implies --fixed-scale=1 if that option is not used.") \ |
| ARGPARSE_OPTION_FLAG(param, debug, 'D', "debug", \ |
| "Enable debug messages and Vulkan validation layers.") \ |
| ARGPARSE_OPTION_STRING(param, window, '\0', "window", "Set window dimensions (e.g. 800x600).") \ |
| ARGPARSE_OPTION_STRING(param, device, '\0', "device", "Device selection (vendor:device) IDs.") \ |
| ARGPARSE_OPTION_STRING(param, format, '\0', "format", "Force pixel format [RGBA, BGRA].") \ |
| ARGPARSE_OPTION_FLAG(param, fps, '\0', "fps", "Print frames per seconds to stdout.") \ |
| ARGPARSE_OPTION_FLAG(param, no_vsync, '\0', "no-vsync", \ |
| "Disable vsync synchronization. Useful for benchmarking. Note that this will disable " \ |
| "presentation on Fuchsia as well.") \ |
| ARGPARSE_OPTION_FLAG(param, no_clear, '\0', "no-clear", \ |
| "Disable image clear before rendering. Useful for benchmarking raw rendering performance.") |
| |
| // clang-format on |
| ARGPARSE_DEFINE_OPTIONS_STRUCT(options, MY_OPTIONS_LIST); |
| if (!ARGPARSE_PARSE_ARGS(argc, argv, options, MY_OPTIONS_LIST)) |
| { |
| if (options.help_needed) |
| ARGPARSE_PRINT_HELP(PROGRAM_NAME, "A short demo of Spinel rendering", MY_OPTIONS_LIST); |
| exit(options.help_needed ? EXIT_SUCCESS : EXIT_FAILURE); |
| } |
| |
| uint32_t vendor_id, device_id; |
| if (!parseDeviceOption(options.device, &vendor_id, &device_id)) |
| return EXIT_FAILURE; |
| |
| uint32_t window_width, window_height; |
| if (!parseWindowOption(options.window, |
| DEMO_SURFACE_WIDTH, |
| DEMO_SURFACE_HEIGHT, |
| &window_width, |
| &window_height)) |
| return EXIT_FAILURE; |
| |
| double svg_scale = 1.0; |
| if (options.scale.used) |
| { |
| svg_scale = options.scale.value; |
| } |
| |
| // Parse the SVG input document. |
| if (argc < 2) |
| { |
| fprintf(stderr, "This program requires an input svg file path!\n"); |
| return EXIT_FAILURE; |
| } |
| ScopedSvg svg = ScopedSvg::parseFile(argv[1]); |
| ASSERT_MSG(svg.get(), "Could not parse input SVG file: %s\n", argv[1]); |
| |
| VkOffset2D image_center = {}; |
| { |
| double svg_xmin, svg_ymin, svg_xmax, svg_ymax; |
| svg_estimate_bounds(svg.get(), nullptr, &svg_xmin, &svg_ymin, &svg_xmax, &svg_ymax); |
| |
| if (options.debug) |
| { |
| printf("Image bounds min=(%g,%g) max=(%g,%g) width=%g height=%g\n", |
| svg_xmin, |
| svg_ymin, |
| svg_xmax, |
| svg_ymax, |
| svg_xmax - svg_xmin, |
| svg_ymax - svg_ymin); |
| } |
| |
| if (svg_xmin >= svg_xmax || svg_ymin >= svg_ymax) |
| { |
| fprintf(stderr, "WARNING: Could not compute bounds of SVG document!\n"); |
| } |
| else |
| { |
| image_center.x = (int32_t)((svg_xmin + svg_xmax) / 2.); |
| image_center.y = (int32_t)((svg_ymin + svg_ymax) / 2.); |
| } |
| } |
| |
| // Create the application window. |
| DEMO_CLASS_NAME::Config config = { |
| .app = { |
| .app_name = PROGRAM_NAME, |
| .window_width = window_width, |
| .window_height = window_height, |
| .verbose = options.debug, |
| .debug = options.debug, |
| .disable_vsync = options.no_vsync, |
| .print_fps = options.fps, |
| }, |
| .no_clear = options.no_clear, |
| }; |
| |
| DEMO_CLASS_NAME demo(config); |
| |
| // Determine per-frame transform / animation. |
| VkExtent2D swapchain_extent = demo.window_extent(); |
| |
| auto per_frame_transform = |
| [options, svg_scale, swapchain_extent, image_center](uint32_t frame_counter) { |
| double angle; |
| double scale; |
| |
| if (options.fixed_scale.used || options.fixed_rotation.used) |
| { |
| angle = options.fixed_rotation.used ? options.fixed_rotation.value * M_PI / 180. : 0.; |
| scale = options.fixed_scale.used ? options.fixed_scale.value : 1.; |
| } |
| else |
| { |
| angle = (frame_counter / 60.) * M_PI; |
| scale = (1. + 0.25 * (1. + sin(M_PI * frame_counter / 20.))); |
| } |
| scale *= svg_scale; |
| |
| affine_transform_t affine; |
| |
| affine = affine_transform_make_translation(-image_center.x, -image_center.y); |
| affine = affine_transform_multiply_by_value(affine_transform_make_rotation(angle), affine); |
| affine = affine_transform_multiply_by_value(affine_transform_make_scale(scale), affine); |
| affine = affine_transform_multiply_by_value( |
| affine_transform_make_translation(swapchain_extent.width / 2, swapchain_extent.height / 2), |
| affine); |
| |
| return spn_transform_t{ |
| .sx = (float)affine.sx, |
| .shx = (float)affine.shx, |
| .tx = (float)affine.tx, |
| .shy = (float)affine.shy, |
| .sy = (float)affine.sy, |
| .ty = (float)affine.ty, |
| }; |
| }; |
| |
| demo.setImageFactory([&](const DemoImage::Config & config) { |
| return std::make_unique<SvgDemoImage>(config, svg.get(), per_frame_transform); |
| }); |
| |
| demo.run(); |
| |
| return EXIT_SUCCESS; |
| } |