From f17eb06604c21209baa18e1a91f6efbbf7587859 Mon Sep 17 00:00:00 2001 From: luigi-rosso Date: Sat, 16 Jul 2022 04:21:44 +0000 Subject: [PATCH] Multi backend viewer Re-works viewer to use GL, Metal, and optionally in the future D3D. Also introduces a new renderer and allows the viewer to be compiled with either Skia or the new example Tess renderer (with either GL or Metal). I want to re-work the dependency building to be more similar to what Mike did with the third_party directory in a separate PR. It's pretty similar to what's been done here, but generalized at the top level which I think is nice and more re-usable. Also removes the need for some of my path building shell scripts... There's a new README.md in the viewer directory with some details. ``` cd packages/runtime/viewer/build/macosx ``` Build and run with a Metal backed Skia Renderer: ``` ./build_viewer.sh metal skia run ``` Build and run with a GL backed Skia Renderer: ``` ./build_viewer.sh gl skia run ``` Build and run with a Metal backed Tess Renderer: ``` ./build_viewer.sh metal tess run ``` ... and so on You can also clean directly from the same command: ``` ./build_viewer.sh metal tess run clean ``` Diffs= 86d5b0924 Cleanup skia build 83506229a Fix skia dir. 7d4a3bdcf Remove tess to simplify PR. 14346ad9e Nuke old viewer fba2696cf See clang-format after update 15b8fd753 Remove mat4 test from main rive project 69a2ce9ad Render font cleanup abd8fa89b Missed files. e690870a4 Move shader.h and remove formatting for generated files. afc437b17 Tweaks for failed tests. 2f9a936fc Moving mat4 to tess renderer. 27a3606d6 Cleanup a4034ff0f Adding viewer readme bebcd8abe Adding text viewers. 5a1da4ed2 Getting image rendering working in with tess 2c81fee81 Silence deprecation warning. 52f410dfc Getting mesh rendering working. 8de842c7f Starting to use sokol to render meshes. Adding Mat4 class. dbd5c79b5 Updating clang format for objc ca0ea9d84 Getting image content working too and fixing contention with sokol 79405a77d Starting to add handle draw support. 8b9f14a16 Adding gl renderer for skia. af23274ae Adding support for rendering Skia with Metal. 92139ce08 Adding tess renderer. dc3e1b49b Adding dependencies at top level 22fa8e6ce Updating build scripts b5cb990aa Merging with latest master. d08ac18e2 Reorg a828e92da Updating project json 086890736 Cleanup unused glfw and gl3w for new viewer 55161f36c Using sokol. 72b62b563 Separating GL and TessRenderer. 611b991b6 Adding limits import to fix compilation on all platforms. 820ab8e2f Missed build files. 0964b4416 Stubbing out a generalized viewer and alternate renderer. --- .lua-format | 32 ++ .rive_head | 2 +- .vscode/settings.json | 3 +- build/macosx/build_rive.sh | 34 +++ dependencies/macosx/.gitignore | 1 + dependencies/macosx/config_directories.sh | 4 + dependencies/macosx/get_imgui.sh | 18 ++ dependencies/macosx/get_libpng.sh | 35 +++ dependencies/macosx/get_premake5.sh | 15 + dependencies/macosx/get_sokol.sh | 28 ++ dependencies/macosx/make_viewer_skia.sh | 123 ++++++++ include/rive/math/aabb.hpp | 18 ++ include/rive/math/cubic_utilities.hpp | 52 ++++ .../build/macosx/build_skia_renderer.sh | 60 ++++ skia/renderer/build/premake5.lua | 40 ++- skia/renderer/src/renderfont_coretext.cpp | 5 +- skia/renderer/src/renderfont_hb.cpp | 3 +- skia/renderer/src/renderfont_skia.cpp | 3 +- skia/viewer/build.sh | 26 -- skia/viewer/build/premake5.lua | 100 ------- skia/viewer/run.sh | 20 -- skia/viewer/src/main.cpp | 204 ------------- skia/viewer/src/scene_content.cpp | 281 ------------------ src/math/aabb.cpp | 17 ++ src/shapes/metrics_path.cpp | 39 +-- viewer/README.md | 22 ++ viewer/build/macosx/build_viewer.sh | 73 +++++ viewer/build/premake5_libpng.lua | 85 ++++++ viewer/build/premake5_viewer.lua | 213 +++++++++++++ .../viewer/skia/viewer_skia_renderer.hpp | 13 + viewer/include/viewer/tess/bitmap_decoder.hpp | 40 +++ viewer/include/viewer/tess/pnglibconf.h | 1 + .../viewer/tess/viewer_sokol_factory.hpp | 10 + viewer/include/viewer/viewer.hpp | 20 ++ .../include/viewer}/viewer_content.hpp | 40 ++- viewer/src/platform/imgui_sokol_impl.cpp | 5 + viewer/src/platform/viewer_gl.mm | 17 ++ viewer/src/platform/viewer_mac.m | 4 + viewer/src/skia/viewer_skia_gl.cpp | 30 ++ viewer/src/skia/viewer_skia_metal.mm | 87 ++++++ viewer/src/stats.cpp | 71 +++++ viewer/src/tess/bitmap_decoder.cpp | 105 +++++++ viewer/src/tess/decode_png.cpp | 118 ++++++++ viewer/src/tess/viewer_sokol_factory.cpp | 37 +++ viewer/src/viewer.cpp | 218 ++++++++++++++ .../src/viewer_content}/image_content.cpp | 10 +- viewer/src/viewer_content/scene_content.cpp | 249 ++++++++++++++++ .../src/viewer_content}/text_content.cpp | 26 +- .../src/viewer_content}/textpath_content.cpp | 65 ++-- .../src/viewer_content}/viewer_content.cpp | 49 +-- 50 files changed, 2011 insertions(+), 760 deletions(-) create mode 100644 .lua-format create mode 100755 build/macosx/build_rive.sh create mode 100644 dependencies/macosx/.gitignore create mode 100755 dependencies/macosx/config_directories.sh create mode 100755 dependencies/macosx/get_imgui.sh create mode 100755 dependencies/macosx/get_libpng.sh create mode 100755 dependencies/macosx/get_premake5.sh create mode 100755 dependencies/macosx/get_sokol.sh create mode 100755 dependencies/macosx/make_viewer_skia.sh create mode 100644 include/rive/math/cubic_utilities.hpp create mode 100755 skia/renderer/build/macosx/build_skia_renderer.sh delete mode 100755 skia/viewer/build.sh delete mode 100644 skia/viewer/build/premake5.lua delete mode 100755 skia/viewer/run.sh delete mode 100644 skia/viewer/src/main.cpp delete mode 100644 skia/viewer/src/scene_content.cpp create mode 100644 viewer/README.md create mode 100755 viewer/build/macosx/build_viewer.sh create mode 100644 viewer/build/premake5_libpng.lua create mode 100644 viewer/build/premake5_viewer.lua create mode 100644 viewer/include/viewer/skia/viewer_skia_renderer.hpp create mode 100644 viewer/include/viewer/tess/bitmap_decoder.hpp create mode 100644 viewer/include/viewer/tess/pnglibconf.h create mode 100644 viewer/include/viewer/tess/viewer_sokol_factory.hpp create mode 100644 viewer/include/viewer/viewer.hpp rename {skia/viewer/src => viewer/include/viewer}/viewer_content.hpp (51%) create mode 100644 viewer/src/platform/imgui_sokol_impl.cpp create mode 100644 viewer/src/platform/viewer_gl.mm create mode 100644 viewer/src/platform/viewer_mac.m create mode 100644 viewer/src/skia/viewer_skia_gl.cpp create mode 100644 viewer/src/skia/viewer_skia_metal.mm create mode 100644 viewer/src/stats.cpp create mode 100644 viewer/src/tess/bitmap_decoder.cpp create mode 100644 viewer/src/tess/decode_png.cpp create mode 100644 viewer/src/tess/viewer_sokol_factory.cpp create mode 100644 viewer/src/viewer.cpp rename {skia/viewer/src => viewer/src/viewer_content}/image_content.cpp (75%) create mode 100644 viewer/src/viewer_content/scene_content.cpp rename {skia/viewer/src => viewer/src/viewer_content}/text_content.cpp (91%) rename {skia/viewer/src => viewer/src/viewer_content}/textpath_content.cpp (86%) rename {skia/viewer/src => viewer/src/viewer_content}/viewer_content.cpp (73%) diff --git a/.lua-format b/.lua-format new file mode 100644 index 00000000..9a550421 --- /dev/null +++ b/.lua-format @@ -0,0 +1,32 @@ +column_limit: 80 +indent_width: 4 +use_tab: false +tab_width: 4 +continuation_indent_width: 4 +spaces_before_call: 1 +keep_simple_control_block_one_line: true +keep_simple_function_one_line: true +align_args: true +break_after_functioncall_lp: false +break_before_functioncall_rp: false +spaces_inside_functioncall_parens: false +spaces_inside_functiondef_parens: false +align_parameter: true +chop_down_parameter: false +break_after_functiondef_lp: false +break_before_functiondef_rp: false +align_table_field: true +break_after_table_lb: true +break_before_table_rb: true +chop_down_table: true +chop_down_kv_table: true +table_sep: "," +column_table_limit: column_limit +extra_sep_at_table_end: false +spaces_inside_table_braces: false +break_after_operator: true +double_quote_to_single_quote: false +single_quote_to_double_quote: false +spaces_around_equals_in_field: true +line_breaks_after_function_body: 1 +line_separator: input diff --git a/.rive_head b/.rive_head index 7d2d4352..92fd62da 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -ac811aaa4b35e861838711db9b92e08649fb1c8a +86d5b0924f0696e86940dc907954861436ffbc19 diff --git a/.vscode/settings.json b/.vscode/settings.json index e1a12267..27abf3da 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -101,7 +101,8 @@ "concepts": "cpp", "scoped_allocator": "cpp", "span": "cpp", - "typeindex": "cpp" + "typeindex": "cpp", + "filesystem": "cpp" }, "git.ignoreLimitWarning": true } \ No newline at end of file diff --git a/build/macosx/build_rive.sh b/build/macosx/build_rive.sh new file mode 100755 index 00000000..9e371f57 --- /dev/null +++ b/build/macosx/build_rive.sh @@ -0,0 +1,34 @@ +#!/bin/sh +set -e + +source ../../dependencies/macosx/config_directories.sh + +CONFIG=debug + +for var in "$@"; do + if [[ $var = "release" ]]; then + CONFIG=release + fi +done + +if [[ ! -f "$DEPENDENCIES/bin/premake5" ]]; then + pushd $DEPENDENCIES_SCRIPTS + ./get_premake5.sh + popd +fi + +export PREMAKE=$DEPENDENCIES/bin/premake5 +pushd .. + +$PREMAKE --file=./premake5.lua gmake2 + +for var in "$@"; do + if [[ $var = "clean" ]]; then + make clean + make config=release clean + fi +done + +make config=$CONFIG -j$(($(sysctl -n hw.physicalcpu) + 1)) + +popd diff --git a/dependencies/macosx/.gitignore b/dependencies/macosx/.gitignore new file mode 100644 index 00000000..06cf6539 --- /dev/null +++ b/dependencies/macosx/.gitignore @@ -0,0 +1 @@ +cache diff --git a/dependencies/macosx/config_directories.sh b/dependencies/macosx/config_directories.sh new file mode 100755 index 00000000..7bcf2cbf --- /dev/null +++ b/dependencies/macosx/config_directories.sh @@ -0,0 +1,4 @@ +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +export DEPENDENCIES_SCRIPTS=$SCRIPT_DIR +export DEPENDENCIES=$SCRIPT_DIR/cache +mkdir -p $DEPENDENCIES diff --git a/dependencies/macosx/get_imgui.sh b/dependencies/macosx/get_imgui.sh new file mode 100755 index 00000000..97aa7887 --- /dev/null +++ b/dependencies/macosx/get_imgui.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +set -e + +if [[ -z "${DEPENDENCIES}" ]]; then + echo "DEPENDENCIES env variable must be set. This script is usually called by other scripts." + exit 1 +fi +pushd $DEPENDENCIES +IMGUI_REPO=https://github.com/ocornut/imgui +IMGUI_STABLE_BRANCH=master + +if [ ! -d imgui ]; then + echo "Cloning ImGui." + git clone $IMGUI_REPO +fi + +cd imgui && git checkout $IMGUI_STABLE_BRANCH && git fetch && git pull diff --git a/dependencies/macosx/get_libpng.sh b/dependencies/macosx/get_libpng.sh new file mode 100755 index 00000000..b22d8f0d --- /dev/null +++ b/dependencies/macosx/get_libpng.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +set -e + +if [[ -z "${DEPENDENCIES}" ]]; then + echo "DEPENDENCIES env variable must be set. This script is usually called by other scripts." + exit 1 +fi + +pushd $DEPENDENCIES +LIBPNG_REPO=https://github.com/glennrp/libpng +LIBPNG_STABLE_BRANCH=libpng16 + +if [ ! -d libpng ]; then + echo "Cloning libpng." + git clone $LIBPNG_REPO +fi + +pushd libpng +git checkout $LIBPNG_STABLE_BRANCH && git fetch && git pull +popd + +ZLIB_REPO=https://github.com/madler/zlib +ZLIB_STABLE_BRANCH=master + +if [ ! -d zlib ]; then + echo "Cloning zlib." + git clone $ZLIB_REPO +fi + +pushd zlib +git checkout $ZLIB_STABLE_BRANCH && git fetch && git pull +popd + +popd diff --git a/dependencies/macosx/get_premake5.sh b/dependencies/macosx/get_premake5.sh new file mode 100755 index 00000000..bb9e65e6 --- /dev/null +++ b/dependencies/macosx/get_premake5.sh @@ -0,0 +1,15 @@ +#!/bin/sh +set -e +if [[ -z "${DEPENDENCIES}" ]]; then + echo "DEPENDENCIES env variable must be set. This script is usually called by other scripts." + exit 1 +fi + +mkdir -p $DEPENDENCIES/bin +echo Downloading Premake5 +curl https://github.com/premake/premake-core/releases/download/v5.0.0-beta1/premake-5.0.0-beta1-macosx.tar.gz -L -o $DEPENDENCIES//bin/premake_macosx.tar.gz +cd $DEPENDENCIES/bin +# Export premake5 into bin +tar -xvf premake_macosx.tar.gz 2>/dev/null +# Delete downloaded archive +rm premake_macosx.tar.gz diff --git a/dependencies/macosx/get_sokol.sh b/dependencies/macosx/get_sokol.sh new file mode 100755 index 00000000..241c1e18 --- /dev/null +++ b/dependencies/macosx/get_sokol.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +if [[ -z "${DEPENDENCIES}" ]]; then + echo "DEPENDENCIES env variable must be set. This script is usually called by other scripts." + exit 1 +fi + +pushd $DEPENDENCIES +SOKOL_REPO=https://github.com/luigi-rosso/sokol +SOKOL_STABLE_BRANCH=support_transparent_framebuffer + +if [ ! -d sokol ]; then + echo "Cloning sokol." + git clone $SOKOL_REPO + + if [ $(arch) == arm64 ]; then + SOKOL_SHDC=https://github.com/floooh/sokol-tools-bin/raw/master/bin/osx_arm64/sokol-shdc + else + SOKOL_SHDC=https://github.com/floooh/sokol-tools-bin/raw/master/bin/osx/sokol-shdc + fi + curl $SOKOL_SHDC -L -o ./bin/sokol-shdc + chmod +x ./bin/sokol-shdc +fi + +cd sokol && git checkout $SOKOL_STABLE_BRANCH && git fetch && git pull +popd diff --git a/dependencies/macosx/make_viewer_skia.sh b/dependencies/macosx/make_viewer_skia.sh new file mode 100755 index 00000000..f0294366 --- /dev/null +++ b/dependencies/macosx/make_viewer_skia.sh @@ -0,0 +1,123 @@ +#!/bin/sh + +set -e + +if [[ -z "${DEPENDENCIES}" ]]; then + echo "DEPENDENCIES env variable must be set. This script is usually called by other scripts." + exit 1 +fi + +pushd $DEPENDENCIES + +if [ ! -d skia ]; then + git clone https://github.com/google/skia skia + cd skia + git checkout chrome/m99 +else + cd skia +fi + +python tools/git-sync-deps + +CONFIG=debug +RENDERER= +SKIA_USE_GL=false +SKIA_USE_METAL=false + +for var in "$@"; do + if [[ $var = "release" ]]; then + CONFIG=release + fi + if [[ $var = "gl" ]]; then + SKIA_USE_GL=true + RENDERER=gl + fi + if [[ $var = "metal" ]]; then + SKIA_USE_METAL=true + RENDERER=metal + fi +done + +if [[ $CONFIG = "debug" ]]; then + bin/gn gen out/$RENDERER/debug --type=static_library --args=" \ + extra_cflags=[\"-fno-rtti\", \"-DSK_DISABLE_SKPICTURE\", \"-DSK_DISABLE_TEXT\", \"-DRIVE_OPTIMIZED\", \"-DSK_DISABLE_LEGACY_SHADERCONTEXT\", \"-DSK_DISABLE_LOWP_RASTER_PIPELINE\", \"-DSK_FORCE_RASTER_PIPELINE_BLITTER\", \"-DSK_DISABLE_AAA\", \"-DSK_DISABLE_EFFECT_DESERIALIZATION\"] \ + + is_official_build=false \ + skia_use_gl=$SKIA_USE_GL \ + skia_use_zlib=true \ + skia_enable_gpu=true \ + skia_enable_fontmgr_empty=false \ + skia_use_libpng_encode=true \ + skia_use_libpng_decode=true \ + skia_enable_skgpu_v1=true \ + + skia_use_dng_sdk=false \ + skia_use_egl=false \ + skia_use_expat=false \ + skia_use_fontconfig=false \ + skia_use_freetype=false \ + skia_use_icu=false \ + skia_use_libheif=false \ + skia_use_system_libpng=false \ + skia_use_system_libjpeg_turbo=false \ + skia_use_libjpeg_turbo_encode=false \ + skia_use_libjpeg_turbo_decode=true \ + skia_use_libwebp_encode=false \ + skia_use_libwebp_decode=true \ + skia_use_system_libwebp=false \ + skia_use_lua=false \ + skia_use_piex=false \ + skia_use_vulkan=false \ + skia_use_metal=$SKIA_USE_METAL \ + skia_use_angle=false \ + skia_use_system_zlib=false \ + skia_enable_spirv_validation=false \ + skia_enable_pdf=false \ + skia_enable_skottie=false \ + skia_enable_tools=false \ + " + ninja -C out/$RENDERER/debug + du -hs out/$RENDERER/debug/libskia.a +fi + +if [[ $CONFIG = "release" ]]; then + bin/gn gen out/$RENDERER/release --type=static_library --args=" \ + extra_cflags=[\"-fno-rtti\", \"-flto=full\", \"-DSK_DISABLE_SKPICTURE\", \"-DSK_DISABLE_TEXT\", \"-DRIVE_OPTIMIZED\", \"-DSK_DISABLE_LEGACY_SHADERCONTEXT\", \"-DSK_DISABLE_LOWP_RASTER_PIPELINE\", \"-DSK_FORCE_RASTER_PIPELINE_BLITTER\", \"-DSK_DISABLE_AAA\", \"-DSK_DISABLE_EFFECT_DESERIALIZATION\"] \ + + is_official_build=true \ + skia_use_gl=true \ + skia_use_zlib=true \ + skia_enable_gpu=true \ + skia_enable_fontmgr_empty=false \ + skia_use_libpng_encode=true \ + skia_use_libpng_decode=true \ + skia_enable_skgpu_v1=true \ + + skia_use_dng_sdk=false \ + skia_use_egl=false \ + skia_use_expat=false \ + skia_use_fontconfig=false \ + skia_use_freetype=false \ + skia_use_icu=false \ + skia_use_libheif=false \ + skia_use_system_libpng=false \ + skia_use_system_libjpeg_turbo=false \ + skia_use_libjpeg_turbo_encode=false \ + skia_use_libjpeg_turbo_decode=true \ + skia_use_libwebp_encode=false \ + skia_use_libwebp_decode=true \ + skia_use_system_libwebp=false \ + skia_use_lua=false \ + skia_use_piex=false \ + skia_use_vulkan=false \ + skia_use_metal=true \ + skia_use_angle=false \ + skia_use_system_zlib=false \ + skia_enable_spirv_validation=false \ + skia_enable_pdf=false \ + skia_enable_skottie=false \ + skia_enable_tools=false \ + " + ninja -C out/$RENDERER/release + du -hs out/$RENDERER/release/libskia.a +fi diff --git a/include/rive/math/aabb.hpp b/include/rive/math/aabb.hpp index 16b8a1b0..30566d68 100644 --- a/include/rive/math/aabb.hpp +++ b/include/rive/math/aabb.hpp @@ -5,6 +5,7 @@ #include "rive/math/mat2d.hpp" #include "rive/math/vec2d.hpp" #include +#include namespace rive { struct IAABB { @@ -60,6 +61,23 @@ namespace rive { } IAABB round() const; + + /// + /// Initialize an AABB to values that represent an invalid/collapsed + /// AABB that can then expand to points that are added to it. + /// + inline static AABB forExpansion() { + return AABB(std::numeric_limits::max(), + std::numeric_limits::max(), + -std::numeric_limits::max(), + -std::numeric_limits::max()); + } + + /// + /// Grow the AABB to fit the point. + /// + static void expandTo(AABB& out, const Vec2D& point); + static void expandTo(AABB& out, float x, float y); }; } // namespace rive diff --git a/include/rive/math/cubic_utilities.hpp b/include/rive/math/cubic_utilities.hpp new file mode 100644 index 00000000..c913120d --- /dev/null +++ b/include/rive/math/cubic_utilities.hpp @@ -0,0 +1,52 @@ +#ifndef _RIVE_CUBIC_UTILITIES_HPP_ +#define _RIVE_CUBIC_UTILITIES_HPP_ + +#include "rive/math/vec2d.hpp" +#include + +namespace rive { + /// + /// Utility functions for recursively subdividing a cubic. + /// + class CubicUtilities { + public: + static void computeHull(const Vec2D& from, + const Vec2D& fromOut, + const Vec2D& toIn, + const Vec2D& to, + float t, + Vec2D* hull) { + hull[0] = Vec2D::lerp(from, fromOut, t); + hull[1] = Vec2D::lerp(fromOut, toIn, t); + hull[2] = Vec2D::lerp(toIn, to, t); + + hull[3] = Vec2D::lerp(hull[0], hull[1], t); + hull[4] = Vec2D::lerp(hull[1], hull[2], t); + + hull[5] = Vec2D::lerp(hull[3], hull[4], t); + } + + static bool tooFar(const Vec2D& a, const Vec2D& b, float threshold) { + return std::max(std::abs(a.x - b.x), std::abs(a.y - b.y)) > threshold; + } + + static bool shouldSplitCubic(const Vec2D& from, + const Vec2D& fromOut, + const Vec2D& toIn, + const Vec2D& to, + float threshold) { + + Vec2D oneThird = Vec2D::lerp(from, to, 1.0f / 3.0f); + Vec2D twoThird = Vec2D::lerp(from, to, 2.0f / 3.0f); + return tooFar(fromOut, oneThird, threshold) || tooFar(toIn, twoThird, threshold); + } + + static float cubicAt(float t, float a, float b, float c, float d) { + float ti = 1.0f - t; + float value = + ti * ti * ti * a + 3.0f * ti * ti * t * b + 3.0f * ti * t * t * c + t * t * t * d; + return value; + } + }; +} // namespace rive +#endif \ No newline at end of file diff --git a/skia/renderer/build/macosx/build_skia_renderer.sh b/skia/renderer/build/macosx/build_skia_renderer.sh new file mode 100755 index 00000000..05601b1f --- /dev/null +++ b/skia/renderer/build/macosx/build_skia_renderer.sh @@ -0,0 +1,60 @@ +#!/bin/sh +set -e + +source ../../../../dependencies/macosx/config_directories.sh + +CONFIG=debug +GRAPHICS=gl +OTHER_OPTIONS= + +for var in "$@"; do + if [[ $var = "release" ]]; then + CONFIG=release + fi + if [[ $var = "gl" ]]; then + GRAPHICS=gl + fi + if [[ $var = "d3d" ]]; then + GRAPHICS=d3d + fi + if [[ $var = "metal" ]]; then + GRAPHICS=metal + fi + if [[ $var = "text" ]]; then + OTHER_OPTIONS+=--with-text + fi +done + +if [[ ! -f "$DEPENDENCIES/bin/premake5" ]]; then + pushd $DEPENDENCIES_SCRIPTS + ./get_premake5.sh + popd +fi + +if [[ ! -d "$DEPENDENCIES/sokol" ]]; then + pushd $DEPENDENCIES_SCRIPTS + ./get_sokol.sh + popd +fi + +if [[ ! -f "$DEPENDENCIES/skia/out/$GRAPHICS/$CONFIG/libskia.a" ]]; then + pushd $DEPENDENCIES_SCRIPTS + ./make_viewer_skia.sh $GRAPHICS $CONFIG + popd +fi + +export PREMAKE=$DEPENDENCIES/bin/premake5 +pushd .. + +$PREMAKE --file=./premake5.lua gmake2 $OTHER_OPTIONS + +for var in "$@"; do + if [[ $var = "clean" ]]; then + make clean + make config=release clean + fi +done + +make config=$CONFIG -j$(($(sysctl -n hw.physicalcpu) + 1)) + +popd diff --git a/skia/renderer/build/premake5.lua b/skia/renderer/build/premake5.lua index e1e16c8e..b750ce3d 100644 --- a/skia/renderer/build/premake5.lua +++ b/skia/renderer/build/premake5.lua @@ -2,6 +2,13 @@ workspace "rive" configurations {"debug", "release"} SKIA_DIR = os.getenv('SKIA_DIR') or 'skia' +dependencies = os.getenv('DEPENDENCIES') + +if dependencies ~= nil then + SKIA_DIR = dependencies .. '/skia' +else + SKIA_DIR = "../../dependencies/" .. SKIA_DIR +end project "rive_skia_renderer" kind "StaticLib" @@ -35,25 +42,24 @@ project "rive_skia_renderer" libdirs {"../../../build/%{cfg.system}/bin/%{cfg.buildcfg}"} files { - "../src/skia_factory.cpp", - "../src/cg_skia_factory.cpp", + "../src/**.cpp" } buildoptions {"-Wall", "-fno-exceptions", "-fno-rtti", "-Werror=format"} filter {"system:macosx" } buildoptions {"-flto=full"} - includedirs {"../../dependencies/" .. SKIA_DIR} - libdirs {"../../dependencies/" .. SKIA_DIR.. "/out/static"} + includedirs {SKIA_DIR} + libdirs {SKIA_DIR.. "/out/static"} filter {"system:linux or windows" } - includedirs {"../../dependencies/" .. SKIA_DIR} - libdirs {"../../dependencies/" .. SKIA_DIR.. "/out/static"} + includedirs {SKIA_DIR} + libdirs {SKIA_DIR.. "/out/static"} filter {"system:ios" } buildoptions {"-flto=full"} - includedirs {"../../dependencies/".. SKIA_DIR} - libdirs {"../../dependencies/".. SKIA_DIR.. "/out/static"} + includedirs {SKIA_DIR} + libdirs {SKIA_DIR.. "/out/static"} filter {"system:ios", "options:variant=system" } buildoptions {"-mios-version-min=10.0 -fembed-bitcode -arch armv7 -arch arm64 -arch arm64e -isysroot " .. (os.getenv("IOS_SYSROOT") or "")} @@ -68,27 +74,27 @@ project "rive_skia_renderer" -- Is there a way to pass 'arch' as a variable here? filter { "system:android" } - includedirs {"../../dependencies/".. SKIA_DIR} + includedirs {SKIA_DIR} filter { "system:android", "options:arch=x86" } targetdir "%{cfg.system}/x86/bin/%{cfg.buildcfg}" objdir "%{cfg.system}/x86/obj/%{cfg.buildcfg}" - libdirs {"../../dependencies/" .. SKIA_DIR.. "/out/x86"} + libdirs {SKIA_DIR.. "/out/x86"} filter { "system:android", "options:arch=x64" } targetdir "%{cfg.system}/x64/bin/%{cfg.buildcfg}" objdir "%{cfg.system}/x64/obj/%{cfg.buildcfg}" - libdirs {"../../dependencies/" .. SKIA_DIR.. "/out/x64"} + libdirs {SKIA_DIR.. "/out/x64"} filter { "system:android", "options:arch=arm" } targetdir "%{cfg.system}/arm/bin/%{cfg.buildcfg}" objdir "%{cfg.system}/arm/obj/%{cfg.buildcfg}" - libdirs {"../../dependencies/" .. SKIA_DIR.. "/out/arm"} + libdirs {SKIA_DIR.. "/out/arm"} filter { "system:android", "options:arch=arm64" } targetdir "%{cfg.system}/arm64/bin/%{cfg.buildcfg}" objdir "%{cfg.system}/arm64/obj/%{cfg.buildcfg}" - libdirs {"../../dependencies/" .. SKIA_DIR.. "/out/arm64"} + libdirs {SKIA_DIR.. "/out/arm64"} filter "configurations:debug" buildoptions {"-g"} @@ -99,6 +105,14 @@ project "rive_skia_renderer" defines {"RELEASE", "NDEBUG"} optimize "On" + filter {"options:with-text" } + defines {"RIVE_TEXT"} + +newoption { + trigger = "with-text", + description = "Enables text experiments" +} + newoption { trigger = "variant", value = "type", diff --git a/skia/renderer/src/renderfont_coretext.cpp b/skia/renderer/src/renderfont_coretext.cpp index d0da4c02..64176162 100644 --- a/skia/renderer/src/renderfont_coretext.cpp +++ b/skia/renderer/src/renderfont_coretext.cpp @@ -1,7 +1,8 @@ /* * Copyright 2022 Rive */ - +#include "rive/rive_types.hpp" +#if defined(RIVE_BUILD_FOR_APPLE) && defined(RIVE_TEXT) #include "renderfont_coretext.hpp" #include "mac_utils.hpp" @@ -9,8 +10,6 @@ #include "rive/render_text.hpp" #include "rive/core/type_conversions.hpp" -#ifdef RIVE_BUILD_FOR_APPLE - #if defined(RIVE_BUILD_FOR_OSX) #include #elif defined(RIVE_BUILD_FOR_IOS) diff --git a/skia/renderer/src/renderfont_hb.cpp b/skia/renderer/src/renderfont_hb.cpp index 80e98b1b..2fe63563 100644 --- a/skia/renderer/src/renderfont_hb.cpp +++ b/skia/renderer/src/renderfont_hb.cpp @@ -1,7 +1,7 @@ /* * Copyright 2022 Rive */ - +#ifdef RIVE_TEXT #include "renderfont_hb.hpp" #include "rive/factory.hpp" @@ -221,3 +221,4 @@ HBRenderFont::onShapeText(rive::Span text, return gruns; } +#endif \ No newline at end of file diff --git a/skia/renderer/src/renderfont_skia.cpp b/skia/renderer/src/renderfont_skia.cpp index 1f31eded..e3672d1d 100644 --- a/skia/renderer/src/renderfont_skia.cpp +++ b/skia/renderer/src/renderfont_skia.cpp @@ -1,7 +1,7 @@ /* * Copyright 2022 Rive */ - +#ifdef RIVE_TEXT #include "rive/factory.hpp" #include "rive/render_text.hpp" #include "renderfont_skia.hpp" @@ -176,3 +176,4 @@ SkiaRenderFont::onShapeText(rive::Span text, return gruns; } +#endif \ No newline at end of file diff --git a/skia/viewer/build.sh b/skia/viewer/build.sh deleted file mode 100755 index a9d726b1..00000000 --- a/skia/viewer/build.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -# dir=$(pwd) - -# cd ../renderer -# ./build.sh $@ - -# cd $dir - -cd build - -OPTION=$1 - -if [ "$OPTION" = 'help' ]; then - echo build.sh - build debug library - echo build.sh clean - clean the build - echo build.sh release - build release library -elif [ "$OPTION" = "clean" ]; then - echo Cleaning project ... - # TODO: fix premake5 clean to bubble the clean command to dependent projects - premake5 gmake && make clean -elif [ "$OPTION" = "release" ]; then - premake5 gmake && make config=release -j7 -else - premake5 gmake && make config=debug -j7 -fi diff --git a/skia/viewer/build/premake5.lua b/skia/viewer/build/premake5.lua deleted file mode 100644 index c0f8ee00..00000000 --- a/skia/viewer/build/premake5.lua +++ /dev/null @@ -1,100 +0,0 @@ -workspace "rive" -configurations {"debug", "release"} - -BASE_DIR = path.getabsolute("../../../build") -location("./") -dofile(path.join(BASE_DIR, "premake5.lua")) - -BASE_DIR = path.getabsolute("../../../../../third_party/harfbuzz/build") -location("./") -dofile(path.join(BASE_DIR, "premake5.lua")) - -BASE_DIR = path.getabsolute("../../renderer/build") -location("./") -dofile(path.join(BASE_DIR, "premake5.lua")) - -project "rive_viewer" - kind "ConsoleApp" - language "C++" - cppdialect "C++17" - targetdir "%{cfg.system}/bin/%{cfg.buildcfg}" - objdir "%{cfg.system}/obj/%{cfg.buildcfg}" - includedirs { - "../include", - "../../../include", - "../../renderer/include", - "../../dependencies/glfw/include", - "../../dependencies/skia", - "../../dependencies/skia/include/core", - "../../dependencies/skia/include/effects", - "../../dependencies/skia/include/gpu", - "../../dependencies/skia/include/config", - "../../dependencies/imgui", - "../../dependencies", - "../../dependencies/gl3w/build/include", - "../../../../../third_party/externals/harfbuzz/src", - } - - links { - "Cocoa.framework", - "IOKit.framework", - "CoreVideo.framework", - "rive", - "rive_harfbuzz", - "skia", - "rive_skia_renderer", - "glfw3" - } - - libdirs { - "../../../../../third_party/harfbuzz/build/%{cfg.buildcfg}/bin", - "../../../build/%{cfg.system}/bin/%{cfg.buildcfg}", - "../../dependencies/glfw_build/src", - "../../dependencies/skia/out/static", - "../../renderer/build/%{cfg.system}/bin/%{cfg.buildcfg}" - } - - files { - "../src/**.cpp", - - "../../renderer/src/line_breaker.cpp", - "../../renderer/src/renderfont_coretext.cpp", - "../../renderer/src/renderfont_hb.cpp", - "../../renderer/src/renderfont_skia.cpp", - - "../../dependencies/gl3w/build/src/gl3w.c", - "../../dependencies/imgui/backends/imgui_impl_glfw.cpp", - "../../dependencies/imgui/backends/imgui_impl_opengl3.cpp", - "../../dependencies/imgui/imgui_widgets.cpp", - "../../dependencies/imgui/imgui.cpp", - "../../dependencies/imgui/imgui_tables.cpp", - "../../dependencies/imgui/imgui_draw.cpp" - } - - buildoptions {"-Wall", "-fno-exceptions", "-fno-rtti"} - - filter "configurations:debug" - buildoptions {"-g"} - defines {"DEBUG"} - symbols "On" - - filter "configurations:release" - buildoptions {"-flto=full"} - defines {"RELEASE"} - defines {"NDEBUG"} - optimize "On" - --- Clean Function -- -newaction { - trigger = "clean", - description = "clean the build", - execute = function() - print("clean the build...") - os.rmdir("./bin") - os.rmdir("./obj") - os.remove("Makefile") - -- no wildcards in os.remove, so use shell - os.execute("rm *.make") - print("build cleaned") - end -} diff --git a/skia/viewer/run.sh b/skia/viewer/run.sh deleted file mode 100755 index 4b4c2922..00000000 --- a/skia/viewer/run.sh +++ /dev/null @@ -1,20 +0,0 @@ - -OPTION=$1 -unameSystem="$(uname -s)" -case "${unameSystem}" in - Linux*) system=linux;; - Darwin*) system=macosx;; - *) system="unknown:${unameSystem}" -esac - -if [ "$OPTION" = 'help' ]; then - echo build.sh - build debug library - echo build.sh clean - clean the build - echo build.sh release - build release library -elif [ "$OPTION" = "release" ]; then - ./build/$system/bin/release/rive_viewer -elif [ "$OPTION" = "lldb" ]; then - lldb build/$system/bin/debug/rive_viewer -else - ./build/$system/bin/debug/rive_viewer -fi diff --git a/skia/viewer/src/main.cpp b/skia/viewer/src/main.cpp deleted file mode 100644 index 990fa7ab..00000000 --- a/skia/viewer/src/main.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2022 Rive - */ - -// Makes ure gl3w is included before glfw3 -#include "GL/gl3w.h" - -#define SK_GL -#include "GLFW/glfw3.h" - -#include "GrBackendSurface.h" -#include "GrDirectContext.h" -#include "SkCanvas.h" -#include "SkColorSpace.h" -#include "SkSurface.h" - -#include "gl/GrGLInterface.h" -#include "imgui/backends/imgui_impl_glfw.h" -#include "imgui/backends/imgui_impl_opengl3.h" - -#include -#include - -#include "viewer_content.hpp" - -int lastScreenWidth = 0, lastScreenHeight = 0; - -std::unique_ptr gContent; - -static void glfwCursorPosCallback(GLFWwindow* window, double x, double y) { - if (gContent) { - float xscale, yscale; - glfwGetWindowContentScale(window, &xscale, &yscale); - gContent->handlePointerMove(x * xscale, y * yscale); - } -} - -void glfwMouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { - if (gContent) { - switch (action) { - case GLFW_PRESS: - gContent->handlePointerDown(); - break; - case GLFW_RELEASE: - gContent->handlePointerUp(); - break; - } - } -} - -void glfwErrorCallback(int error, const char* description) { puts(description); } - -void glfwDropCallback(GLFWwindow* window, int count, const char** paths) { - // Just get the last dropped file for now... - const char* filename = paths[count - 1]; - - auto newContent = ViewerContent::FindHandler(filename); - if (newContent) { - gContent = std::move(newContent); - gContent->handleResize(lastScreenWidth, lastScreenHeight); - } else { - fprintf(stderr, "No handler found for %s\n", filename); - } -} - -int main() { - if (!glfwInit()) { - fprintf(stderr, "Failed to initialize glfw.\n"); - return 1; - } - glfwSetErrorCallback(glfwErrorCallback); - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - GLFWwindow* window = glfwCreateWindow(1280, 720, "Rive Viewer", NULL, NULL); - if (window == nullptr) { - fprintf(stderr, "Failed to make window or GL.\n"); - glfwTerminate(); - return 1; - } - - glfwSetDropCallback(window, glfwDropCallback); - glfwSetCursorPosCallback(window, glfwCursorPosCallback); - glfwSetMouseButtonCallback(window, glfwMouseButtonCallback); - glfwMakeContextCurrent(window); - if (gl3wInit() != 0) { - fprintf(stderr, "Failed to make initialize gl3w.\n"); - glfwTerminate(); - return 1; - } - // Enable VSYNC. - glfwSwapInterval(1); - - // Setup ImGui - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - (void)io; - - ImGui::StyleColorsDark(); - ImGui_ImplGlfw_InitForOpenGL(window, true); - ImGui_ImplOpenGL3_Init("#version 150"); - io.Fonts->AddFontDefault(); - - // Setup Skia - GrContextOptions options; - sk_sp context = GrDirectContext::MakeGL(nullptr, options); - GrGLFramebufferInfo framebufferInfo; - framebufferInfo.fFBOID = 0; - framebufferInfo.fFormat = GL_RGBA8; - - sk_sp surface; - SkCanvas* canvas = nullptr; - - // Render loop. - int width = 0, height = 0; - double lastTime = glfwGetTime(); - while (!glfwWindowShouldClose(window)) { - glfwGetFramebufferSize(window, &width, &height); - - // Update surface. - if (!surface || width != lastScreenWidth || height != lastScreenHeight) { - lastScreenWidth = width; - lastScreenHeight = height; - - if (gContent) { - gContent->handleResize(width, height); - } - - SkColorType colorType = - kRGBA_8888_SkColorType; // GrColorTypeToSkColorType(GrPixelConfigToColorType(kRGBA_8888_GrPixelConfig)); - // - // if (kRGBA_8888_GrPixelConfig == kSkia8888_GrPixelConfig) - // { - // colorType = kRGBA_8888_SkColorType; - // } - // else - // { - // colorType = kBGRA_8888_SkColorType; - // } - - GrBackendRenderTarget backendRenderTarget(width, - height, - 0, // sample count - 0, // stencil bits - framebufferInfo); - - surface = SkSurface::MakeFromBackendRenderTarget(context.get(), - backendRenderTarget, - kBottomLeft_GrSurfaceOrigin, - colorType, - nullptr, - nullptr); - if (!surface) { - fprintf(stderr, "Failed to create Skia surface\n"); - return 1; - } - canvas = surface->getCanvas(); - } - - double time = glfwGetTime(); - float elapsed = (float)(time - lastTime); - lastTime = time; - - // Clear screen. - SkPaint paint; - paint.setColor(SK_ColorDKGRAY); - canvas->drawPaint(paint); - - if (gContent) { - SkAutoCanvasRestore acr(canvas, true); - gContent->handleDraw(canvas, elapsed); - } - - context->flush(); - - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - if (gContent) { - gContent->handleImgui(); - } - - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(window); - glfwPollEvents(); - } - - gContent = nullptr; // force delete now, so we can clean up - - // Cleanup Skia. - surface = nullptr; - context = nullptr; - - ImGui_ImplGlfw_Shutdown(); - - // Cleanup GLFW. - glfwDestroyWindow(window); - glfwTerminate(); - - return 0; -} diff --git a/skia/viewer/src/scene_content.cpp b/skia/viewer/src/scene_content.cpp deleted file mode 100644 index 04b61b18..00000000 --- a/skia/viewer/src/scene_content.cpp +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2022 Rive - */ - -#include "rive/animation/linear_animation_instance.hpp" -#include "rive/animation/state_machine_instance.hpp" -#include "rive/animation/state_machine_input_instance.hpp" -#include "rive/animation/state_machine_number.hpp" -#include "rive/animation/state_machine_bool.hpp" -#include "rive/animation/state_machine_trigger.hpp" -#include "rive/artboard.hpp" -#include "rive/file.hpp" -#include "rive/layout.hpp" -#include "rive/math/aabb.hpp" - -#include "cg_skia_factory.hpp" -#include "skia_renderer.hpp" -#include "viewer_content.hpp" - -// ImGui wants raw pointers to names, but our public API returns -// names as strings (by value), so we cache these names each time we -// load a file -std::vector animationNames; -std::vector stateMachineNames; - -constexpr int REQUEST_DEFAULT_SCENE = -1; - -#include -double GetSecondsToday() { - time_t m_time; - time(&m_time); - struct tm tstruct; - gmtime_r(&m_time, &tstruct); - - int hours = tstruct.tm_hour - 4; - if (hours < 0) { - hours += 12; - } else if (hours >= 12) { - hours -= 12; - } - - auto secs = (double)hours * 60 * 60 + (double)tstruct.tm_min * 60 + (double)tstruct.tm_sec; - // printf("%d %d %d\n", tstruct.tm_sec, tstruct.tm_min, hours); - // printf("%g %g %g\n", secs, secs/60, secs/60/60); - return secs; -} - -// We hold onto the file's bytes for the lifetime of the file, in case we want -// to change animations or state-machines, we just rebuild the rive::File from -// it. -std::vector fileBytes; - -static void loadNames(const rive::Artboard* ab) { - animationNames.clear(); - stateMachineNames.clear(); - if (ab) { - for (size_t i = 0; i < ab->animationCount(); ++i) { - animationNames.push_back(ab->animationNameAt(i)); - } - for (size_t i = 0; i < ab->stateMachineCount(); ++i) { - stateMachineNames.push_back(ab->stateMachineNameAt(i)); - } - } -} - -class SceneContent : public ViewerContent { - std::string m_filename; - std::unique_ptr m_file; - - std::unique_ptr m_artboardInstance; - std::unique_ptr m_currentScene; - int m_animationIndex = 0; - int m_stateMachineIndex = -1; - - int m_width = 0, m_height = 0; - rive::Vec2D m_lastPointer; - rive::Mat2D m_inverseViewTransform; - - void initStateMachine(int index) { - m_stateMachineIndex = -1; - m_animationIndex = -1; - m_currentScene = nullptr; - m_artboardInstance = nullptr; - - m_artboardInstance = m_file->artboardDefault(); - m_artboardInstance->advance(0.0f); - loadNames(m_artboardInstance.get()); - - if (index < 0) { - m_currentScene = m_artboardInstance->defaultStateMachine(); - index = m_artboardInstance->defaultStateMachineIndex(); - } - if (!m_currentScene) { - if (index >= m_artboardInstance->stateMachineCount()) { - index = 0; - } - m_currentScene = m_artboardInstance->stateMachineAt(index); - } - if (!m_currentScene) { - index = -1; - m_currentScene = m_artboardInstance->animationAt(0); - m_animationIndex = 0; - } - m_stateMachineIndex = index; - - if (m_currentScene) { - m_currentScene->inputCount(); - } - - DumpCounters("After loading file"); - } - - void initAnimation(int index) { - m_stateMachineIndex = -1; - m_animationIndex = -1; - m_currentScene = nullptr; - m_artboardInstance = nullptr; - - m_artboardInstance = m_file->artboardDefault(); - m_artboardInstance->advance(0.0f); - loadNames(m_artboardInstance.get()); - - if (index >= 0 && index < m_artboardInstance->animationCount()) { - m_animationIndex = index; - m_currentScene = m_artboardInstance->animationAt(index); - m_currentScene->inputCount(); - } - - DumpCounters("After loading file"); - } - -public: - SceneContent(const char filename[], std::unique_ptr file) : - m_filename(filename), m_file(std::move(file)) { - initStateMachine(REQUEST_DEFAULT_SCENE); - } - - void handlePointerMove(float x, float y) override { - m_lastPointer = m_inverseViewTransform * rive::Vec2D(x, y); - if (m_currentScene) { - m_currentScene->pointerMove(m_lastPointer); - } - } - void handlePointerDown() override { - if (m_currentScene) { - m_currentScene->pointerDown(m_lastPointer); - } - } - void handlePointerUp() override { - if (m_currentScene) { - m_currentScene->pointerUp(m_lastPointer); - } - } - - void handleResize(int width, int height) override { - m_width = width; - m_height = height; - } - - void handleDraw(SkCanvas* canvas, double elapsed) override { - - if (m_currentScene) { - // See if we can "set the time" e.g. clock statemachine - if (auto num = m_currentScene->getNumber("isTime")) { - num->value(GetSecondsToday() / 60 / 60); - } - - m_currentScene->advanceAndApply(elapsed); - - rive::SkiaRenderer renderer(canvas); - renderer.save(); - - auto viewTransform = rive::computeAlignment(rive::Fit::contain, - rive::Alignment::center, - rive::AABB(0, 0, m_width, m_height), - m_currentScene->bounds()); - renderer.transform(viewTransform); - // Store the inverse view so we can later go from screen to world. - m_inverseViewTransform = viewTransform.invertOrIdentity(); - // post_mouse_event(artboard.get(), canvas->getTotalMatrix()); - - m_currentScene->draw(&renderer); - renderer.restore(); - } - } - - void handleImgui() override { - if (m_artboardInstance != nullptr) { - ImGui::Begin(m_filename.c_str(), nullptr); - if (ImGui::ListBox( - "Animations", - &m_animationIndex, - [](void* data, int index, const char** name) { - *name = animationNames[index].c_str(); - return true; - }, - m_artboardInstance.get(), - animationNames.size(), - 4)) - { - m_stateMachineIndex = -1; - initAnimation(m_animationIndex); - } - if (ImGui::ListBox( - "State Machines", - &m_stateMachineIndex, - [](void* data, int index, const char** name) { - *name = stateMachineNames[index].c_str(); - return true; - }, - m_artboardInstance.get(), - stateMachineNames.size(), - 4)) - { - m_animationIndex = -1; - initStateMachine(m_stateMachineIndex); - } - if (m_currentScene != nullptr) { - - ImGui::Columns(2); - ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.6666); - - for (int i = 0; i < m_currentScene->inputCount(); i++) { - auto inputInstance = m_currentScene->input(i); - - if (inputInstance->input()->is()) { - // ImGui requires names as id's, use ## to hide the - // label but still give it an id. - char label[256]; - snprintf(label, 256, "##%u", i); - - auto number = static_cast(inputInstance); - float v = number->value(); - ImGui::InputFloat(label, &v, 1.0f, 2.0f, "%.3f"); - number->value(v); - ImGui::NextColumn(); - } else if (inputInstance->input()->is()) { - // ImGui requires names as id's, use ## to hide the - // label but still give it an id. - char label[256]; - snprintf(label, 256, "Fire##%u", i); - if (ImGui::Button(label)) { - auto trigger = static_cast(inputInstance); - trigger->fire(); - } - ImGui::NextColumn(); - } else if (inputInstance->input()->is()) { - // ImGui requires names as id's, use ## to hide the - // label but still give it an id. - char label[256]; - snprintf(label, 256, "##%u", i); - auto boolInput = static_cast(inputInstance); - bool value = boolInput->value(); - - ImGui::Checkbox(label, &value); - boolInput->value(value); - ImGui::NextColumn(); - } - ImGui::Text("%s", inputInstance->input()->name().c_str()); - ImGui::NextColumn(); - } - - ImGui::Columns(1); - } - ImGui::End(); - - } else { - ImGui::Text("Drop a .riv file to preview."); - } - } -}; - -rive::CGSkiaFactory skiaFactory; - -std::unique_ptr ViewerContent::Scene(const char filename[]) { - auto bytes = LoadFile(filename); - if (auto file = rive::File::import(rive::toSpan(bytes), &skiaFactory)) { - return std::make_unique(filename, std::move(file)); - } - return nullptr; -} \ No newline at end of file diff --git a/src/math/aabb.cpp b/src/math/aabb.cpp index f4ae1931..624ca4d4 100644 --- a/src/math/aabb.cpp +++ b/src/math/aabb.cpp @@ -36,3 +36,20 @@ IAABB AABB::round() const { graphics_round(bottom()), }; } + +void AABB::expandTo(AABB& out, const Vec2D& point) { expandTo(out, point.x, point.y); } + +void AABB::expandTo(AABB& out, float x, float y) { + if (x < out.minX) { + out.minX = x; + } + if (x > out.maxX) { + out.maxX = x; + } + if (y < out.minY) { + out.minY = y; + } + if (y > out.maxY) { + out.maxY = y; + } +} \ No newline at end of file diff --git a/src/shapes/metrics_path.cpp b/src/shapes/metrics_path.cpp index bbcc8caf..c2f164d3 100644 --- a/src/shapes/metrics_path.cpp +++ b/src/shapes/metrics_path.cpp @@ -1,6 +1,7 @@ #include "rive/core/type_conversions.hpp" #include "rive/shapes/metrics_path.hpp" #include "rive/renderer.hpp" +#include "rive/math/cubic_utilities.hpp" using namespace rive; @@ -46,37 +47,9 @@ void MetricsPath::cubicTo(float ox, float oy, float ix, float iy, float x, float void MetricsPath::close() {} -static void computeHull(const Vec2D& from, - const Vec2D& fromOut, - const Vec2D& toIn, - const Vec2D& to, - float t, - Vec2D* hull) { - hull[0] = Vec2D::lerp(from, fromOut, t); - hull[1] = Vec2D::lerp(fromOut, toIn, t); - hull[2] = Vec2D::lerp(toIn, to, t); - - hull[3] = Vec2D::lerp(hull[0], hull[1], t); - hull[4] = Vec2D::lerp(hull[1], hull[2], t); - - hull[5] = Vec2D::lerp(hull[3], hull[4], t); -} - static const float minSegmentLength = 0.05f; static const float distTooFar = 1.0f; -static bool tooFar(const Vec2D& a, const Vec2D& b) { - auto diff = a - b; - return std::max(std::abs(diff.x), std::abs(diff.y)) > distTooFar; -} - -static bool -shouldSplitCubic(const Vec2D& from, const Vec2D& fromOut, const Vec2D& toIn, const Vec2D& to) { - const Vec2D oneThird = Vec2D::lerp(from, to, 1.0f / 3.0f), - twoThird = Vec2D::lerp(from, to, 2.0f / 3.0f); - return tooFar(fromOut, oneThird) || tooFar(toIn, twoThird); -} - static float segmentCubic(const Vec2D& from, const Vec2D& fromOut, const Vec2D& toIn, @@ -86,11 +59,11 @@ static float segmentCubic(const Vec2D& from, float t2, std::vector& segments) { - if (shouldSplitCubic(from, fromOut, toIn, to)) { + if (CubicUtilities::shouldSplitCubic(from, fromOut, toIn, to, distTooFar)) { float halfT = (t1 + t2) / 2.0f; Vec2D hull[6]; - computeHull(from, fromOut, toIn, to, 0.5f, hull); + CubicUtilities::computeHull(from, fromOut, toIn, to, 0.5f, hull); runningLength = segmentCubic(from, hull[0], hull[3], hull[5], runningLength, t1, halfT, segments); @@ -315,14 +288,14 @@ void MetricsPath::extractSubPart( if (startT == 0.0f) { // Start is 0, so split at end and keep the left side. - computeHull(from, fromOut, toIn, to, endT, hull); + CubicUtilities::computeHull(from, fromOut, toIn, to, endT, hull); if (moveTo) { result->move(from); } result->cubic(hull[0], hull[3], hull[5]); } else { // Split at start since it's non 0. - computeHull(from, fromOut, toIn, to, startT, hull); + CubicUtilities::computeHull(from, fromOut, toIn, to, startT, hull); if (moveTo) { // Move to first point on the right side. result->move(hull[5]); @@ -334,7 +307,7 @@ void MetricsPath::extractSubPart( } else { // End is not 1, so split again and cubic to the left side // of the split and remap endT to the new curve range - computeHull( + CubicUtilities::computeHull( hull[5], hull[4], hull[2], to, (endT - startT) / (1.0f - startT), hull); result->cubic(hull[0], hull[3], hull[5]); diff --git a/viewer/README.md b/viewer/README.md new file mode 100644 index 00000000..931355d2 --- /dev/null +++ b/viewer/README.md @@ -0,0 +1,22 @@ +# Rive Viewer +This is a desktop utility for previewing .riv files, Rive experiments, and different renderers. It also serves as a reference implementation for how to interface the Rive C++ Runtime (rive-cpp) with different renderers and factories for fonts, images, etc. + +## Abstraction +Rive is built to be platform and subsystem agnostic so you can plug-in any renderer to draw all of or only portions of your Rive animations. For example a simple WebGL renderer could only draw Rive meshes and drop all the vector content if it wanted to. Similarly image and font loading can be deferred to platform decoders. We provide some example fully fledged implementations that support all of the Rive features. + +## Building +We currently provide build files for MacOS but we will be adding Windows and others soon too. +### MacOS +All the build scripts are in viewer/build/macosx. +``` +cd viewer/build/macosx +``` +You can tell the build script to build and run a Viewer that's backed by a Metal view with our Skia renderer: +``` +./build_viewer.sh metal skia run +``` +An OpenGL example using a tessellating renderer: +``` +./build_viewer.sh gl tess run +``` +Both the Skia and Tess renderers work with either gl or metal options. \ No newline at end of file diff --git a/viewer/build/macosx/build_viewer.sh b/viewer/build/macosx/build_viewer.sh new file mode 100755 index 00000000..f93119fe --- /dev/null +++ b/viewer/build/macosx/build_viewer.sh @@ -0,0 +1,73 @@ +#!/bin/sh +set -e + +source ../../../dependencies/macosx/config_directories.sh + +CONFIG=debug +GRAPHICS=gl +RENDERER=skia + +for var in "$@"; do + if [[ $var = "release" ]]; then + CONFIG=release + fi + if [[ $var = "gl" ]]; then + GRAPHICS=gl + fi + if [[ $var = "d3d" ]]; then + GRAPHICS=d3d + fi + if [[ $var = "metal" ]]; then + GRAPHICS=metal + fi + if [[ $var = "skia" ]]; then + RENDERER=skia + fi +done + +if [[ ! -f "$DEPENDENCIES/bin/premake5" ]]; then + pushd $DEPENDENCIES_SCRIPTS + ./get_premake5.sh + popd +fi + +if [[ ! -d "$DEPENDENCIES/imgui" ]]; then + pushd $DEPENDENCIES_SCRIPTS + ./get_imgui.sh + popd +fi + +pushd ../../../build/macosx +./build_rive.sh $CONFIG +popd + +if [ $RENDERER = "skia" ]; then + pushd ../../../skia/renderer/build/macosx + ./build_skia_renderer.sh text $@ + popd +fi + +export PREMAKE=$DEPENDENCIES/bin/premake5 +pushd .. + +$PREMAKE --file=./premake5_viewer.lua gmake2 --graphics=$GRAPHICS --renderer=$RENDERER + +for var in "$@"; do + if [[ $var = "clean" ]]; then + make clean + make config=release clean + fi +done + +make config=$CONFIG -j$(($(sysctl -n hw.physicalcpu) + 1)) + +popd + +for var in "$@"; do + if [[ $var = "run" ]]; then + bin/$CONFIG/$RENDERER/$GRAPHICS/rive_viewer + fi + if [[ $var = "lldb" ]]; then + lldb bin/$CONFIG/$RENDERER/$GRAPHICS/rive_viewer + fi +done diff --git a/viewer/build/premake5_libpng.lua b/viewer/build/premake5_libpng.lua new file mode 100644 index 00000000..e6074b69 --- /dev/null +++ b/viewer/build/premake5_libpng.lua @@ -0,0 +1,85 @@ +dependencies = os.getenv('DEPENDENCIES') + +libpng = dependencies .. '/libpng' + +project 'libpng' +do + kind 'StaticLib' + language 'C++' + cppdialect 'C++17' + toolset 'clang' + targetdir '%{cfg.system}/bin/%{cfg.buildcfg}/' + objdir '%{cfg.system}/obj/%{cfg.buildcfg}/' + buildoptions { + '-fno-exceptions', + '-fno-rtti' + } + includedirs { + '../include/viewer/tess', + dependencies, + libpng + } + files { + libpng .. '/png.c', + libpng .. '/pngerror.c', + libpng .. '/pngget.c', + libpng .. '/pngmem.c', + libpng .. '/pngpread.c', + libpng .. '/pngread.c', + libpng .. '/pngrio.c', + libpng .. '/pngrtran.c', + libpng .. '/pngrutil.c', + libpng .. '/pngset.c', + libpng .. '/pngtrans.c', + libpng .. '/pngwio.c', + libpng .. '/pngwrite.c', + libpng .. '/pngwtran.c', + libpng .. '/pngwutil.c' + } + + architecture('ARM64') + do + files { + libpng .. '/arm/arm_init.c', + libpng .. '/arm/filter_neon_intrinsics.c', + libpng .. '/arm/palette_neon_intrinsics.c' + } + end +end + +zlib = dependencies .. '/zlib' + +project 'zlib' +do + kind 'StaticLib' + language 'C++' + cppdialect 'C++17' + toolset 'clang' + targetdir '%{cfg.system}/bin/%{cfg.buildcfg}/' + objdir '%{cfg.system}/obj/%{cfg.buildcfg}/' + buildoptions { + '-fno-exceptions', + '-fno-rtti' + } + defines {'ZLIB_IMPLEMENTATION', 'HAVE_UNISTD_H'} + includedirs { + zlib + } + files { + zlib .. '/adler32.c', + zlib .. '/compress.c', + zlib .. '/crc32.c', + zlib .. '/deflate.c', + zlib .. '/gzclose.c', + zlib .. '/gzlib.c', + zlib .. '/gzread.c', + zlib .. '/gzwrite.c', + zlib .. '/infback.c', + zlib .. '/inffast.c', + zlib .. '/inftrees.c', + zlib .. '/trees.c', + zlib .. '/uncompr.c', + zlib .. '/zutil.c', + zlib .. '/inflate.c' + } +end diff --git a/viewer/build/premake5_viewer.lua b/viewer/build/premake5_viewer.lua new file mode 100644 index 00000000..10f78a92 --- /dev/null +++ b/viewer/build/premake5_viewer.lua @@ -0,0 +1,213 @@ +workspace 'rive' +configurations { + 'debug', + 'release' +} + +dependencies = os.getenv('DEPENDENCIES') + +rive = '../../' +rive_skia = '../../skia' +skia = dependencies .. '/skia' +libpng = dependencies .. '/libpng' + +project 'rive_viewer' +do + kind 'ConsoleApp' + language 'C++' + cppdialect 'C++17' + toolset 'clang' + targetdir('%{cfg.system}/bin/%{cfg.buildcfg}/' .. _OPTIONS.renderer .. '/' .. _OPTIONS.graphics) + objdir('%{cfg.system}/obj/%{cfg.buildcfg}/' .. _OPTIONS.renderer .. '/' .. _OPTIONS.graphics) + includedirs { + '../include', + rive .. '/include', + dependencies, + dependencies .. '/sokol', + dependencies .. '/imgui' + } + + links { + 'rive' + } + + libdirs { + rive .. '/build/%{cfg.system}/bin/%{cfg.buildcfg}' + } + + files { + '../src/**.cpp', + dependencies .. '/imgui/imgui.cpp', + dependencies .. '/imgui/imgui_widgets.cpp', + dependencies .. '/imgui/imgui_tables.cpp', + dependencies .. '/imgui/imgui_draw.cpp' + } + + buildoptions { + '-Wall', + '-fno-exceptions', + '-fno-rtti' + } + + filter { + 'system:macosx' + } + do + links { + 'Cocoa.framework', + 'IOKit.framework', + 'CoreVideo.framework', + 'OpenGL.framework' + } + files { + '../src/**.m', + '../src/**.mm' + } + end + + filter { + 'system:macosx', + 'options:graphics=gl' + } + do + links { + 'OpenGL.framework' + } + end + + filter { + 'system:macosx', + 'options:graphics=metal' + } + do + links { + 'Metal.framework', + 'MetalKit.framework', + 'QuartzCore.framework' + } + end + + filter { + 'options:renderer=skia', + 'options:graphics=gl' + } + do + defines { + 'SK_GL', + 'SOKOL_GLCORE33' + } + files { + '../src/skia/viewer_skia_gl.cpp' + } + libdirs { + skia .. '/out/gl/%{cfg.buildcfg}' + } + end + + filter { + 'options:renderer=skia', + 'options:graphics=metal' + } + do + defines { + 'SK_METAL', + 'SOKOL_METAL' + } + libdirs { + skia .. '/out/metal/%{cfg.buildcfg}' + } + end + + filter { + 'options:renderer=skia', + 'options:graphics=d3d' + } + do + defines { + 'SK_DIRECT3D' + } + libdirs { + skia .. '/out/d3d/%{cfg.buildcfg}' + } + end + + filter { + 'options:renderer=skia' + } + do + includedirs { + skia, + skia .. '/include/core', + skia .. '/include/effects', + skia .. '/include/gpu', + skia .. '/include/config', + rive_skia .. '/renderer/include' + } + defines { + 'RIVE_RENDERER_SKIA' + } + libdirs { + rive_skia .. '/renderer/build/%{cfg.system}/bin/%{cfg.buildcfg}', + '../../../../third_party/harfbuzz/build/%{cfg.buildcfg}/bin' + } + links { + 'skia', + 'rive_skia_renderer', + 'rive_harfbuzz' + } + end + + filter 'configurations:debug' + do + buildoptions { + '-g' + } + defines { + 'DEBUG' + } + symbols 'On' + end + + filter 'configurations:release' + do + buildoptions { + '-flto=full' + } + defines { + 'RELEASE' + } + defines { + 'NDEBUG' + } + optimize 'On' + end + + -- CLI config options + newoption { + trigger = 'graphics', + value = 'gl', + description = 'The graphics api to use.', + allowed = { + { + 'gl' + }, + { + 'metal' + }, + { + 'd3d' + } + } + } + + newoption { + trigger = 'renderer', + value = 'skia', + description = 'The renderer to use.', + allowed = { + { + 'skia' + } + } + } +end diff --git a/viewer/include/viewer/skia/viewer_skia_renderer.hpp b/viewer/include/viewer/skia/viewer_skia_renderer.hpp new file mode 100644 index 00000000..8d8a8693 --- /dev/null +++ b/viewer/include/viewer/skia/viewer_skia_renderer.hpp @@ -0,0 +1,13 @@ +#ifndef _RIVE_VIEWER_SKIA_RENDERER_HPP_ +#define _RIVE_VIEWER_SKIA_RENDERER_HPP_ + +#ifdef RIVE_RENDERER_SKIA +#include "skia_renderer.hpp" + +class ViewerSkiaRenderer : public rive::SkiaRenderer { +public: + ViewerSkiaRenderer(SkCanvas* canvas) : rive::SkiaRenderer(canvas) {} + SkCanvas* canvas() { return m_Canvas; } +}; +#endif +#endif \ No newline at end of file diff --git a/viewer/include/viewer/tess/bitmap_decoder.hpp b/viewer/include/viewer/tess/bitmap_decoder.hpp new file mode 100644 index 00000000..115c6da6 --- /dev/null +++ b/viewer/include/viewer/tess/bitmap_decoder.hpp @@ -0,0 +1,40 @@ +#ifndef _RIVE_BITMAP_DECODER_HPP_ +#define _RIVE_BITMAP_DECODER_HPP_ + +#include "rive/rive_types.hpp" +#include "rive/span.hpp" + +/// Bitmap will always take ownership of the bytes it is constructed with. +class Bitmap { +public: + enum class PixelFormat : uint8_t { R, RGB, RGBA }; + + Bitmap(uint32_t width, + uint32_t height, + PixelFormat pixelFormat, + std::unique_ptr bytes); + + Bitmap(uint32_t width, uint32_t height, PixelFormat pixelFormat, const uint8_t* bytes); + +private: + uint32_t m_Width; + uint32_t m_Height; + PixelFormat m_PixelFormat; + std::unique_ptr m_Bytes; + +public: + uint32_t width() const { return m_Width; } + uint32_t height() const { return m_Height; } + PixelFormat pixelFormat() const { return m_PixelFormat; } + const uint8_t* bytes() const { return m_Bytes.get(); } + size_t byteSize() const; + size_t byteSize(PixelFormat format) const; + size_t bytesPerPixel(PixelFormat format) const; + + static std::unique_ptr decode(rive::Span bytes); + + // Change the pixel format (note this will resize bytes). + void pixelFormat(PixelFormat format); +}; + +#endif \ No newline at end of file diff --git a/viewer/include/viewer/tess/pnglibconf.h b/viewer/include/viewer/tess/pnglibconf.h new file mode 100644 index 00000000..c9ae700c --- /dev/null +++ b/viewer/include/viewer/tess/pnglibconf.h @@ -0,0 +1 @@ +#include "libpng/scripts/pnglibconf.h.prebuilt" \ No newline at end of file diff --git a/viewer/include/viewer/tess/viewer_sokol_factory.hpp b/viewer/include/viewer/tess/viewer_sokol_factory.hpp new file mode 100644 index 00000000..cc4c811b --- /dev/null +++ b/viewer/include/viewer/tess/viewer_sokol_factory.hpp @@ -0,0 +1,10 @@ +#ifndef _RIVE_VIEWER_SOKOL_FACTORY_HPP_ +#define _RIVE_VIEWER_SOKOL_FACTORY_HPP_ + +#include "rive/tess/sokol/sokol_factory.hpp" + +class ViewerSokolFactory : public rive::SokolFactory { +public: + std::unique_ptr decodeImage(rive::Span) override; +}; +#endif \ No newline at end of file diff --git a/viewer/include/viewer/viewer.hpp b/viewer/include/viewer/viewer.hpp new file mode 100644 index 00000000..5142e173 --- /dev/null +++ b/viewer/include/viewer/viewer.hpp @@ -0,0 +1,20 @@ +#ifndef _RIVE_VIEWER_HPP_ +#define _RIVE_VIEWER_HPP_ + +#ifdef RIVE_RENDERER_SKIA +#include "GrBackendSurface.h" +#include "GrDirectContext.h" +#include "SkCanvas.h" +#include "SkColorSpace.h" +#include "SkSurface.h" +#include "SkTypes.h" + +sk_sp makeSkiaContext(); +sk_sp makeSkiaSurface(GrDirectContext* context, int width, int height); +void skiaPresentSurface(sk_sp surface); +#endif + +// Helper to ensure the gl context is currently bound. +void bindGraphicsContext(); + +#endif \ No newline at end of file diff --git a/skia/viewer/src/viewer_content.hpp b/viewer/include/viewer/viewer_content.hpp similarity index 51% rename from skia/viewer/src/viewer_content.hpp rename to viewer/include/viewer/viewer_content.hpp index 22dd9e9b..e9c8d0e3 100644 --- a/skia/viewer/src/viewer_content.hpp +++ b/viewer/include/viewer/viewer_content.hpp @@ -6,28 +6,42 @@ #define _RIVE_VIEWER_CONTENT_HPP_ #include "rive/span.hpp" +#include "imgui.h" -#include "include/core/SkCanvas.h" -#include "include/core/SkSize.h" -#include "imgui/imgui.h" +#ifdef RIVE_RENDERER_SKIA +#include "SkCanvas.h" +#include "viewer/skia/viewer_skia_renderer.hpp" +#endif + +namespace rive { + class Renderer; + class Factory; +} // namespace rive class ViewerContent { public: virtual ~ViewerContent(); virtual void handleResize(int width, int height) = 0; - virtual void handleDraw(SkCanvas* canvas, double elapsed) = 0; + virtual void handleDraw(rive::Renderer* renderer, double elapsed) = 0; virtual void handleImgui() = 0; virtual void handlePointerMove(float x, float y) {} - virtual void handlePointerDown() {} - virtual void handlePointerUp() {} + virtual void handlePointerDown(float x, float y) {} + virtual void handlePointerUp(float x, float y) {} using Factory = std::unique_ptr (*)(const char filename[]); // Searches all handlers and returns a content if it is found. - static std::unique_ptr FindHandler(const char filename[]) { - Factory factories[] = {Scene, Image, Text, TextPath}; + static std::unique_ptr findHandler(const char filename[]) { + Factory factories[] = { + Scene, +#ifdef RIVE_RENDERER_SKIA + Image, + Text, + TextPath, +#endif + }; for (auto f : factories) { if (auto content = f(filename)) { return content; @@ -38,12 +52,22 @@ public: // Private factories... static std::unique_ptr Scene(const char[]); +#ifdef RIVE_RENDERER_SKIA + // Helper to get the canvas from a rive::Renderer. We know that when we're + // using the skia renderer our viewer always creates a skia renderer. + SkCanvas* skiaCanvas(rive::Renderer* renderer) { + return static_cast(renderer)->canvas(); + } static std::unique_ptr Image(const char[]); static std::unique_ptr Text(const char[]); static std::unique_ptr TextPath(const char[]); +#endif static std::vector LoadFile(const char path[]); static void DumpCounters(const char label[]); + + // Abstracts which rive Factory is currently used. + static rive::Factory* RiveFactory(); }; #endif diff --git a/viewer/src/platform/imgui_sokol_impl.cpp b/viewer/src/platform/imgui_sokol_impl.cpp new file mode 100644 index 00000000..8888b324 --- /dev/null +++ b/viewer/src/platform/imgui_sokol_impl.cpp @@ -0,0 +1,5 @@ +#include "sokol_app.h" +#include "sokol_gfx.h" +#define SOKOL_IMPL +#include "imgui.h" +#include "util/sokol_imgui.h" \ No newline at end of file diff --git a/viewer/src/platform/viewer_gl.mm b/viewer/src/platform/viewer_gl.mm new file mode 100644 index 00000000..ae45be53 --- /dev/null +++ b/viewer/src/platform/viewer_gl.mm @@ -0,0 +1,17 @@ +#include "viewer/viewer.hpp" +#ifdef SOKOL_GLCORE33 +#include "sokol_app.h" +#ifndef GL_SILENCE_DEPRECATION +#define GL_SILENCE_DEPRECATION +#endif +#import "Cocoa/Cocoa.h" +#endif + +void bindGraphicsContext() { +#ifdef SOKOL_GLCORE33 + NSWindow* window = (NSWindow*)sapp_macos_get_window(); + NSOpenGLView* sokolView = (NSOpenGLView*)window.contentView; + NSOpenGLContext* ctx = [sokolView openGLContext]; + [ctx makeCurrentContext]; +#endif +} \ No newline at end of file diff --git a/viewer/src/platform/viewer_mac.m b/viewer/src/platform/viewer_mac.m new file mode 100644 index 00000000..7bbb1bf3 --- /dev/null +++ b/viewer/src/platform/viewer_mac.m @@ -0,0 +1,4 @@ +#define SOKOL_IMPL +#include "sokol_app.h" +#include "sokol_gfx.h" +#include "sokol_glue.h" \ No newline at end of file diff --git a/viewer/src/skia/viewer_skia_gl.cpp b/viewer/src/skia/viewer_skia_gl.cpp new file mode 100644 index 00000000..3258fc92 --- /dev/null +++ b/viewer/src/skia/viewer_skia_gl.cpp @@ -0,0 +1,30 @@ +#if defined(RIVE_RENDERER_SKIA) && defined(SK_GL) +#include "sokol_app.h" +#include "sokol_gfx.h" +#include "viewer/viewer.hpp" + +#include "gl/GrGLInterface.h" + +sk_sp makeSkiaContext() { return GrDirectContext::MakeGL(); } + +sk_sp makeSkiaSurface(GrDirectContext* context, int width, int height) { + GrGLFramebufferInfo framebufferInfo; + framebufferInfo.fFBOID = 0; + framebufferInfo.fFormat = 0x8058; // GL_RGBA8; + + GrBackendRenderTarget backendRenderTarget(width, + height, + 0, // sample count + 0, // stencil bits + framebufferInfo); + + return SkSurface::MakeFromBackendRenderTarget(context, + backendRenderTarget, + kBottomLeft_GrSurfaceOrigin, + kRGBA_8888_SkColorType, + nullptr, + nullptr); +} + +void skiaPresentSurface(sk_sp surface) {} +#endif \ No newline at end of file diff --git a/viewer/src/skia/viewer_skia_metal.mm b/viewer/src/skia/viewer_skia_metal.mm new file mode 100644 index 00000000..be715d3c --- /dev/null +++ b/viewer/src/skia/viewer_skia_metal.mm @@ -0,0 +1,87 @@ +#if defined(RIVE_RENDERER_SKIA) && defined(SK_METAL) +#include "viewer/viewer.hpp" +#include "sokol_app.h" +#include "sokol_gfx.h" + +#import +#import +#include "mtl/GrMtlBackendContext.h" +#include "mtl/GrMtlTypes.h" +#import +#import "Cocoa/Cocoa.h" + +id commandQueue; +id drawable; +GrMtlTextureInfo mtlTexture; +MTKView* skiaView; +NSView* contentView; + +typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) { + UIViewAutoresizingNone = 0, + UIViewAutoresizingFlexibleLeftMargin = 1 << 0, + UIViewAutoresizingFlexibleWidth = 1 << 1, + UIViewAutoresizingFlexibleRightMargin = 1 << 2, + UIViewAutoresizingFlexibleTopMargin = 1 << 3, + UIViewAutoresizingFlexibleHeight = 1 << 4, + UIViewAutoresizingFlexibleBottomMargin = 1 << 5 +}; + +sk_sp makeSkiaContext() { + // This is a little tricky...when using Metal we need to divorce the two + // views so we don't get contention between Sokol drawing (mostly for ImGui) + // with Metal and Skia drawing with Metal. I couldn't find a good way to let + // them share a command queue, so drawing to two separate Metal Layers is + // the next best thing. + id device = (id)sg_mtl_device(); + commandQueue = [device newCommandQueue]; + + NSWindow* window = (NSWindow*)sapp_macos_get_window(); + + // Add a new metal view to our window. + skiaView = [[MTKView alloc] init]; + skiaView.device = device; + skiaView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + [skiaView setWantsLayer:YES]; + + // Grab the current contentView which is the default view Sokol App creates. + NSView* sokolView = window.contentView; + sokolView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + + // Make a new contentView (root container). + contentView = [[NSView alloc] init]; + contentView.frame = sokolView.bounds; + skiaView.frame = sokolView.bounds; + window.contentView = contentView; + + // Add Sokol and Skia views to it. Make sure to layer Sokol over Skia. + [contentView addSubview:skiaView]; + [contentView addSubview:sokolView]; + // Make sure Sokol view is transparent so ImGui can draw over our Skia + // content. + sokolView.layer.opaque = false; + + return GrDirectContext::MakeMetal(device, commandQueue); +} + +sk_sp makeSkiaSurface(GrDirectContext* context, int width, int height) { + NSView* view = skiaView; + CAMetalLayer* layer = (CAMetalLayer*)view.layer; + + drawable = [layer nextDrawable]; + GrMtlTextureInfo fbInfo; + fbInfo.fTexture.retain((const void*)(drawable.texture)); + GrBackendRenderTarget renderTarget = + GrBackendRenderTarget(width, height, 1 /* sample count/MSAA */, fbInfo); + + return SkSurface::MakeFromBackendRenderTarget( + context, renderTarget, kTopLeft_GrSurfaceOrigin, kBGRA_8888_SkColorType, nullptr, nullptr); +} + +void skiaPresentSurface(sk_sp surface) { + id commandBuffer = [(id)commandQueue commandBuffer]; + commandBuffer.label = @"Present"; + [commandBuffer presentDrawable:(id)drawable]; + [commandBuffer commit]; +} + +#endif \ No newline at end of file diff --git a/viewer/src/stats.cpp b/viewer/src/stats.cpp new file mode 100644 index 00000000..4faf1382 --- /dev/null +++ b/viewer/src/stats.cpp @@ -0,0 +1,71 @@ +#include "sokol_app.h" +#include "imgui.h" + +void displayStats() { + bool isOpen = true; + ImGuiStyle& style = ImGui::GetStyle(); + style.WindowBorderSize = 0.0f; + ImGui::Begin("stats", + &isOpen, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); + if (ImGui::BeginTable("table2", 2)) { + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("fps"); + ImGui::TableNextColumn(); + ImGui::Text("%.1f", ImGui::GetIO().Framerate); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("ms/frame"); + ImGui::TableNextColumn(); + ImGui::Text("%.3f", 1000.0f / ImGui::GetIO().Framerate); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("window size"); + ImGui::TableNextColumn(); + ImGui::Text("%dx%d (%.1f)", sapp_width(), sapp_height(), sapp_dpi_scale()); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("graphics api"); + ImGui::TableNextColumn(); + ImGui::Text( +#if defined(SOKOL_GLCORE33) + "OpenGL 3.3" +#elif defined(SOKOL_GLES2) + "OpenGL ES 2" +#elif defined(SOKOL_GLES3) + "OpenGL ES 3" +#elif defined(SOKOL_D3D11) + "D3D11" +#elif defined(SOKOL_METAL) + "Metal" +#elif defined(SOKOL_WGPU) + "WebGPU" +#endif + ); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("renderer"); + ImGui::TableNextColumn(); + ImGui::Text( +#if defined(RIVE_RENDERER_TESS) + "Rive Tess" +#elif defined(RIVE_RENDERER_SKIA) + "Rive Skia" +#endif + ); + + ImGui::EndTable(); + } + + ImGui::SetWindowSize(ImVec2(230.0f, 102.0f)); + ImGui::SetWindowPos(ImVec2(sapp_width() / sapp_dpi_scale() - ImGui::GetWindowWidth(), + sapp_height() / sapp_dpi_scale() - ImGui::GetWindowHeight()), + true); + ImGui::End(); +} \ No newline at end of file diff --git a/viewer/src/tess/bitmap_decoder.cpp b/viewer/src/tess/bitmap_decoder.cpp new file mode 100644 index 00000000..53c50a15 --- /dev/null +++ b/viewer/src/tess/bitmap_decoder.cpp @@ -0,0 +1,105 @@ +#ifdef RIVE_RENDERER_TESS +#include "viewer/tess/bitmap_decoder.hpp" +#include + +Bitmap::Bitmap(uint32_t width, + uint32_t height, + PixelFormat pixelFormat, + std::unique_ptr bytes) : + m_Width(width), m_Height(height), m_PixelFormat(pixelFormat), m_Bytes(std::move(bytes)) {} + +Bitmap::Bitmap(uint32_t width, uint32_t height, PixelFormat pixelFormat, const uint8_t* bytes) : + Bitmap(width, height, pixelFormat, std::unique_ptr(bytes)) {} + +size_t Bitmap::bytesPerPixel(PixelFormat format) const { + switch (format) { + case PixelFormat::R: + return 1; + case PixelFormat::RGB: + return 3; + case PixelFormat::RGBA: + return 4; + } +} + +size_t Bitmap::byteSize(PixelFormat format) const { + return m_Width * m_Height * bytesPerPixel(format); +} + +size_t Bitmap::byteSize() const { return byteSize(m_PixelFormat); } + +std::unique_ptr DecodePng(rive::Span bytes); +std::unique_ptr DecodeJpeg(rive::Span bytes) { return nullptr; } +std::unique_ptr DecodeWebP(rive::Span bytes) { return nullptr; } + +using BitmapDecoder = std::unique_ptr (*)(rive::Span bytes); +struct ImageFormat { + const char* name; + std::vector fingerprint; + BitmapDecoder decodeImage; +}; + +std::unique_ptr Bitmap::decode(rive::Span bytes) { + static ImageFormat decoders[] = { + { + "png", + {0x89, 0x50, 0x4E, 0x47}, + DecodePng, + }, + { + "jpeg", + {0xFF, 0xD8, 0xFF}, + DecodeJpeg, + }, + { + "webp", + {0x52, 0x49, 0x46}, + DecodeWebP, + }, + }; + + for (auto recognizer : decoders) { + auto& fingerprint = recognizer.fingerprint; + + // Immediately discard decoders with fingerprints that are longer than + // the file buffer. + if (recognizer.fingerprint.size() > bytes.size()) { + continue; + } + + // If the fingerprint doesn't match, discrd this decoder. These are all bytes so .size() is + // fine here. + if (std::memcmp(fingerprint.data(), bytes.data(), fingerprint.size()) != 0) { + continue; + } + + auto bitmap = recognizer.decodeImage(bytes); + if (!bitmap) { + fprintf(stderr, "Bitmap::decode - failed to decode a %s.\n", recognizer.name); + } + return bitmap; + } + return nullptr; +} + +void Bitmap::pixelFormat(PixelFormat format) { + if (format == m_PixelFormat) { + return; + } + auto nextByteSize = byteSize(format); + auto nextBytes = std::make_unique(nextByteSize); + + auto fromBytesPerPixel = bytesPerPixel(m_PixelFormat); + auto toBytesPerPixel = bytesPerPixel(format); + int writeIndex = 0; + int readIndex = 0; + for (int i = 0; i < m_Width * m_Height; i++) { + for (int j = 0; j < toBytesPerPixel; j++) { + nextBytes[writeIndex++] = j < fromBytesPerPixel ? m_Bytes[readIndex++] : 255; + } + } + + m_Bytes = std::move(nextBytes); + m_PixelFormat = format; +} +#endif \ No newline at end of file diff --git a/viewer/src/tess/decode_png.cpp b/viewer/src/tess/decode_png.cpp new file mode 100644 index 00000000..56882172 --- /dev/null +++ b/viewer/src/tess/decode_png.cpp @@ -0,0 +1,118 @@ +#ifdef RIVE_RENDERER_TESS +#include "viewer/tess/bitmap_decoder.hpp" +#include "viewer/tess/pnglibconf.h" +#include "png.h" + +struct EncodedImageBuffer { + const uint8_t* bytes; + size_t position; + size_t size; +}; + +static void +ReadDataFromMemory(png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) { + png_voidp a = png_get_io_ptr(png_ptr); + if (a == nullptr) { + return; + } + EncodedImageBuffer& stream = *(EncodedImageBuffer*)a; + + size_t bytesRead = std::min(byteCountToRead, (stream.size - stream.position)); + memcpy(outBytes, stream.bytes + stream.position, bytesRead); + stream.position += bytesRead; + + if ((png_size_t)bytesRead != byteCountToRead) { + // Report image error? + } +} + +std::unique_ptr DecodePng(rive::Span bytes) { + png_structp png_ptr; + png_infop info_ptr; + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + + if (png_ptr == nullptr) { + printf("DecodePng - libpng failed (png_create_read_struct)."); + return nullptr; + } + + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); + printf("DecodePng - libpng failed (png_create_info_struct)."); + return nullptr; + } + + EncodedImageBuffer stream = { + .bytes = bytes.data(), + .size = bytes.size(), + .position = 0, + }; + + png_set_read_fn(png_ptr, &stream, ReadDataFromMemory); + + png_read_info(png_ptr, info_ptr); + + png_get_IHDR( + png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, NULL, NULL); + + png_set_strip_16(png_ptr); + + int bitDepth = 0; + if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_RGB) { + png_set_expand(png_ptr); + bitDepth = 24; + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_expand(png_ptr); + bitDepth += 8; + } + } else if (color_type == PNG_COLOR_TYPE_GRAY) { + png_set_expand(png_ptr); + bitDepth = 8; + } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_expand(png_ptr); + png_set_gray_to_rgb(png_ptr); + bitDepth = 32; + } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + png_set_expand(png_ptr); + bitDepth = 32; + } + + int pixelBytes = bitDepth / 8; + auto pixelBuffer = new uint8_t[width * height * pixelBytes]; + + png_bytep* row_pointers = new png_bytep[height]; + + for (unsigned row = 0; row < height; row++) { + unsigned int rIndex = row; + // if (flipY) { + // rIndex = height - row - 1; + // } + row_pointers[rIndex] = pixelBuffer + (row * (width * pixelBytes)); + } + png_read_image(png_ptr, row_pointers); + png_read_end(png_ptr, info_ptr); + + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) nullptr); + + delete[] row_pointers; + + Bitmap::PixelFormat pixelFormat; + assert(bitDepth == 32 || bitDepth == 24 || bitDepth == 8); + switch (bitDepth) { + case 32: + pixelFormat = Bitmap::PixelFormat::RGBA; + break; + case 24: + pixelFormat = Bitmap::PixelFormat::RGB; + break; + case 8: + pixelFormat = Bitmap::PixelFormat::R; + break; + } + return std::make_unique(width, height, pixelFormat, pixelBuffer); +} +#endif \ No newline at end of file diff --git a/viewer/src/tess/viewer_sokol_factory.cpp b/viewer/src/tess/viewer_sokol_factory.cpp new file mode 100644 index 00000000..1d664782 --- /dev/null +++ b/viewer/src/tess/viewer_sokol_factory.cpp @@ -0,0 +1,37 @@ +#ifdef RIVE_RENDERER_TESS +#include "viewer/tess/viewer_sokol_factory.hpp" +#include "viewer/tess/bitmap_decoder.hpp" +#include "rive/tess/sokol/sokol_tess_renderer.hpp" +#include "sokol_gfx.h" + +sg_pixel_format SgPixelFormat(Bitmap* bitmap) { + switch (bitmap->pixelFormat()) { + case Bitmap::PixelFormat::R: + return SG_PIXELFORMAT_R8; + case Bitmap::PixelFormat::RGBA: + return SG_PIXELFORMAT_RGBA8; + default: + return SG_PIXELFORMAT_NONE; + } +} + +std::unique_ptr +ViewerSokolFactory::decodeImage(rive::Span bytes) { + auto bitmap = Bitmap::decode(bytes); + if (bitmap) { + // We have a bitmap, let's make an image. + + // Sokol doesn't support an RGB pixel format, so we have to bump RGB to RGBA. + if (bitmap->pixelFormat() == Bitmap::PixelFormat::RGB) { + bitmap->pixelFormat(Bitmap::PixelFormat::RGBA); + } + return std::make_unique(sg_make_image((sg_image_desc){ + .width = (int)bitmap->width(), + .height = (int)bitmap->height(), + .data.subimage[0][0] = {bitmap->bytes(), bitmap->byteSize()}, + .pixel_format = SgPixelFormat(bitmap.get()), + })); + } + return nullptr; +} +#endif \ No newline at end of file diff --git a/viewer/src/viewer.cpp b/viewer/src/viewer.cpp new file mode 100644 index 00000000..8cf3df96 --- /dev/null +++ b/viewer/src/viewer.cpp @@ -0,0 +1,218 @@ +// Viewer & Rive +#include "viewer/viewer.hpp" +#include "viewer/viewer_content.hpp" +#include "rive/shapes/paint/color.hpp" + +// Graphics and UI abstraction +#include "sokol_app.h" +#include "sokol_gfx.h" +#include "sokol_glue.h" +#include "imgui.h" +#include "util/sokol_imgui.h" + +// Std lib +#include +#include + +#ifdef RIVE_RENDERER_SKIA +#include "viewer/skia/viewer_skia_renderer.hpp" +sk_sp g_SkiaContext; +sk_sp g_SkiaSurface; +#endif +#ifdef RIVE_RENDERER_TESS +#include "rive/tess/sokol/sokol_tess_renderer.hpp" +std::unique_ptr g_TessRenderer; +#endif +std::unique_ptr g_Content; +static struct { sg_pass_action pass_action; } state; + +void displayStats(); + +static const int backgroundColor = rive::colorARGB(255, 22, 22, 22); + +static void init(void) { + sg_desc descriptor = {.context = sapp_sgcontext()}; + sg_setup(&descriptor); + simgui_desc_t imguiDescriptor = { + .write_alpha_channel = true, + }; + simgui_setup(&imguiDescriptor); + +#if defined(SK_METAL) + // Skia is layered behind the Sokol view, so we need to make sure Sokol + // clears transparent. Skia will draw the background. + state.pass_action = + (sg_pass_action){.colors[0] = {.action = SG_ACTION_CLEAR, .value = {0.0f, 0.0, 0.0f, 0.0}}}; +#elif defined(SK_GL) + // Skia commands are issued to the same GL context before Sokol, so we need + // to make sure Sokol does not clear the buffer. + state.pass_action = (sg_pass_action){.colors[0] = {.action = SG_ACTION_DONTCARE}}; +#else + // In every other case, Sokol is in full control, so let's clear to our bg + // color. + state.pass_action = + (sg_pass_action){.colors[0] = {.action = SG_ACTION_CLEAR, + .value = {rive::colorRed(backgroundColor) / 255.0f, + rive::colorGreen(backgroundColor) / 255.0f, + rive::colorBlue(backgroundColor) / 255.0f, + rive::colorOpacity(backgroundColor)}}}; +#endif + +#ifdef RIVE_RENDERER_SKIA + g_SkiaContext = makeSkiaContext(); + if (!g_SkiaContext) { + fprintf(stderr, "failed to create skia context\n"); + sapp_quit(); + } +#endif +#ifdef RIVE_RENDERER_TESS + g_TessRenderer = std::make_unique(); + g_TessRenderer->orthographicProjection(0.0f, sapp_width(), sapp_height(), 0.0f, 0.0f, 1.0f); +#endif +} + +static void frame(void) { +#ifdef RIVE_RENDERER_SKIA + g_SkiaContext->resetContext(); + g_SkiaSurface = makeSkiaSurface(g_SkiaContext.get(), sapp_width(), sapp_height()); + SkCanvas* canvas = g_SkiaSurface->getCanvas(); + SkPaint paint; + paint.setColor(backgroundColor); + canvas->drawPaint(paint); + + ViewerSkiaRenderer skiaRenderer(canvas); + if (g_Content) { + g_Content->handleDraw(&skiaRenderer, sapp_frame_duration()); + } + + canvas->flush(); + skiaPresentSurface(g_SkiaSurface); + sg_reset_state_cache(); +#endif + + sg_begin_default_pass(&state.pass_action, sapp_width(), sapp_height()); + +#ifdef RIVE_RENDERER_TESS + if (g_Content) { + g_Content->handleDraw(g_TessRenderer.get(), sapp_frame_duration()); + } +#endif + + simgui_frame_desc_t imguiDesc = { + .width = sapp_width(), + .height = sapp_height(), + .delta_time = sapp_frame_duration(), + .dpi_scale = sapp_dpi_scale(), + }; + simgui_new_frame(&imguiDesc); + + displayStats(); + + if (g_Content) { + g_Content->handleImgui(); + } + simgui_render(); + + sg_end_pass(); + sg_commit(); +} + +static void cleanup(void) { + g_Content = nullptr; +#ifdef RIVE_RENDERER_SKIA + g_SkiaSurface = nullptr; + g_SkiaContext = nullptr; +#endif + simgui_shutdown(); + sg_shutdown(); +} + +static void event(const sapp_event* ev) { + simgui_handle_event(ev); + + switch (ev->type) { + case SAPP_EVENTTYPE_RESIZED: + if (g_Content) { + g_Content->handleResize(ev->framebuffer_width, ev->framebuffer_height); + } +#ifdef RIVE_RENDERER_TESS + if (g_TessRenderer) { + g_TessRenderer->orthographicProjection( + 0.0f, ev->framebuffer_width, ev->framebuffer_height, 0.0f, 0.0f, 1.0f); + } +#endif + break; + case SAPP_EVENTTYPE_FILES_DROPPED: { + // Do this to make sure the graphics is bound. + bindGraphicsContext(); + + // get the number of files and their paths like this: + const int numDroppedFiles = sapp_get_num_dropped_files(); + if (numDroppedFiles != 0) { + const char* filename = sapp_get_dropped_file_path(numDroppedFiles - 1); + auto newContent = ViewerContent::findHandler(filename); + if (newContent) { + g_Content = std::move(newContent); + g_Content->handleResize(ev->framebuffer_width, ev->framebuffer_height); + } else { + fprintf(stderr, "No handler found for %s\n", filename); + } + } + break; + } + case SAPP_EVENTTYPE_MOUSE_DOWN: + case SAPP_EVENTTYPE_TOUCHES_BEGAN: + if (g_Content) { + g_Content->handlePointerDown(ev->mouse_x, ev->mouse_y); + } + break; + case SAPP_EVENTTYPE_MOUSE_UP: + case SAPP_EVENTTYPE_TOUCHES_ENDED: + if (g_Content) { + g_Content->handlePointerUp(ev->mouse_x, ev->mouse_y); + break; + } + case SAPP_EVENTTYPE_MOUSE_MOVE: + case SAPP_EVENTTYPE_TOUCHES_MOVED: + if (g_Content) { + g_Content->handlePointerMove(ev->mouse_x, ev->mouse_y); + } + break; + case SAPP_EVENTTYPE_KEY_UP: + switch (ev->key_code) { + case SAPP_KEYCODE_ESCAPE: + sapp_quit(); + break; + default: + break; + } + break; + default: + break; + } +} + +sapp_desc sokol_main(int argc, char* argv[]) { + (void)argc; + (void)argv; + + return (sapp_desc) { + .init_cb = init, .frame_cb = frame, .cleanup_cb = cleanup, .event_cb = event, + .enable_dragndrop = true, .high_dpi = true, + .window_title = "Rive Viewer " +#if defined(SOKOL_GLCORE33) + "(OpenGL 3.3)", +#elif defined(SOKOL_GLES2) + "(OpenGL ES 2)", +#elif defined(SOKOL_GLES3) + "(OpenGL ES 3)", +#elif defined(SOKOL_D3D11) + "(D3D11)", +#elif defined(SOKOL_METAL) + "(Metal)", +#elif defined(SOKOL_WGPU) + "(WebGPU)", +#endif + .width = 800, .height = 600, .icon.sokol_default = true, .gl_force_gles2 = true, + }; +} \ No newline at end of file diff --git a/skia/viewer/src/image_content.cpp b/viewer/src/viewer_content/image_content.cpp similarity index 75% rename from skia/viewer/src/image_content.cpp rename to viewer/src/viewer_content/image_content.cpp index 8f3d39cb..c98cb231 100644 --- a/skia/viewer/src/image_content.cpp +++ b/viewer/src/viewer_content/image_content.cpp @@ -1,8 +1,8 @@ /* * Copyright 2022 Rive */ - -#include "viewer_content.hpp" +#ifdef RIVE_RENDERER_SKIA +#include "viewer/viewer_content.hpp" #include "include/core/SkData.h" #include "include/core/SkImage.h" @@ -13,7 +13,10 @@ class ImageContent : public ViewerContent { public: ImageContent(sk_sp image) : m_image(std::move(image)) {} - void handleDraw(SkCanvas* canvas, double) override { canvas->drawImage(m_image, 0, 0); } + void handleDraw(rive::Renderer* renderer, double) override { + auto canvas = skiaCanvas(renderer); + canvas->drawImage(m_image, 0, 0); + } void handleResize(int width, int height) override {} void handleImgui() override {} @@ -27,3 +30,4 @@ std::unique_ptr ViewerContent::Image(const char filename[]) { } return nullptr; } +#endif \ No newline at end of file diff --git a/viewer/src/viewer_content/scene_content.cpp b/viewer/src/viewer_content/scene_content.cpp new file mode 100644 index 00000000..efd96097 --- /dev/null +++ b/viewer/src/viewer_content/scene_content.cpp @@ -0,0 +1,249 @@ +/* + * Copyright 2022 Rive + */ + +#include "rive/animation/linear_animation_instance.hpp" +#include "rive/animation/state_machine_instance.hpp" +#include "rive/animation/state_machine_input_instance.hpp" +#include "rive/animation/state_machine_number.hpp" +#include "rive/animation/state_machine_bool.hpp" +#include "rive/animation/state_machine_trigger.hpp" +#include "rive/artboard.hpp" +#include "rive/file.hpp" +#include "rive/layout.hpp" +#include "rive/math/aabb.hpp" +#include "viewer/viewer_content.hpp" + +constexpr int REQUEST_DEFAULT_SCENE = -1; + +class SceneContent : public ViewerContent { + // ImGui wants raw pointers to names, but our public API returns + // names as strings (by value), so we cache these names each time we + // load a file + std::vector animationNames; + std::vector stateMachineNames; + + void loadNames(const rive::Artboard* ab) { + animationNames.clear(); + stateMachineNames.clear(); + if (ab) { + for (size_t i = 0; i < ab->animationCount(); ++i) { + animationNames.push_back(ab->animationNameAt(i)); + } + for (size_t i = 0; i < ab->stateMachineCount(); ++i) { + stateMachineNames.push_back(ab->stateMachineNameAt(i)); + } + } + } + + std::string m_Filename; + std::unique_ptr m_File; + + std::unique_ptr m_ArtboardInstance; + std::unique_ptr m_CurrentScene; + int m_AnimationIndex = 0; + int m_StateMachineIndex = -1; + + int m_width = 0, m_height = 0; + rive::Mat2D m_InverseViewTransform; + + void initStateMachine(int index) { + m_StateMachineIndex = -1; + m_AnimationIndex = -1; + m_CurrentScene = nullptr; + m_ArtboardInstance = nullptr; + + m_ArtboardInstance = m_File->artboardDefault(); + m_ArtboardInstance->advance(0.0f); + loadNames(m_ArtboardInstance.get()); + + if (index < 0) { + m_CurrentScene = m_ArtboardInstance->defaultStateMachine(); + index = m_ArtboardInstance->defaultStateMachineIndex(); + } + if (!m_CurrentScene) { + if (index >= m_ArtboardInstance->stateMachineCount()) { + index = 0; + } + m_CurrentScene = m_ArtboardInstance->stateMachineAt(index); + } + if (!m_CurrentScene) { + index = -1; + m_CurrentScene = m_ArtboardInstance->animationAt(0); + m_AnimationIndex = 0; + } + m_StateMachineIndex = index; + + if (m_CurrentScene) { + m_CurrentScene->inputCount(); + } + + DumpCounters("After loading file"); + } + + void initAnimation(int index) { + m_StateMachineIndex = -1; + m_AnimationIndex = -1; + m_CurrentScene = nullptr; + m_ArtboardInstance = nullptr; + + m_ArtboardInstance = m_File->artboardDefault(); + m_ArtboardInstance->advance(0.0f); + loadNames(m_ArtboardInstance.get()); + + if (index >= 0 && index < m_ArtboardInstance->animationCount()) { + m_AnimationIndex = index; + m_CurrentScene = m_ArtboardInstance->animationAt(index); + m_CurrentScene->inputCount(); + } + + DumpCounters("After loading file"); + } + +public: + SceneContent(const char filename[], std::unique_ptr file) : + m_Filename(filename), m_File(std::move(file)) { + initStateMachine(REQUEST_DEFAULT_SCENE); + } + + void handlePointerMove(float x, float y) override { + auto pointer = m_InverseViewTransform * rive::Vec2D(x, y); + if (m_CurrentScene) { + m_CurrentScene->pointerMove(pointer); + } + } + + void handlePointerDown(float x, float y) override { + auto pointer = m_InverseViewTransform * rive::Vec2D(x, y); + if (m_CurrentScene) { + m_CurrentScene->pointerDown(pointer); + } + } + + void handlePointerUp(float x, float y) override { + auto pointer = m_InverseViewTransform * rive::Vec2D(x, y); + if (m_CurrentScene) { + m_CurrentScene->pointerUp(pointer); + } + } + + void handleResize(int width, int height) override { + m_width = width; + m_height = height; + } + + void handleDraw(rive::Renderer* renderer, double elapsed) override { + if (m_CurrentScene) { + m_CurrentScene->advanceAndApply(elapsed); + + renderer->save(); + + auto viewTransform = rive::computeAlignment(rive::Fit::contain, + rive::Alignment::center, + rive::AABB(0, 0, m_width, m_height), + m_CurrentScene->bounds()); + renderer->transform(viewTransform); + // Store the inverse view so we can later go from screen to world. + m_InverseViewTransform = viewTransform.invertOrIdentity(); + // post_mouse_event(artboard.get(), canvas->getTotalMatrix()); + + m_CurrentScene->draw(renderer); + renderer->restore(); + } + } + + void handleImgui() override { + if (m_ArtboardInstance != nullptr) { + ImGui::Begin(m_Filename.c_str(), nullptr); + if (ImGui::ListBox( + "Animations", + &m_AnimationIndex, + [](void* data, int index, const char** name) { + auto& names = *static_cast*>(data); + *name = names[index].c_str(); + return true; + }, + &animationNames, + animationNames.size(), + 4)) + { + m_StateMachineIndex = -1; + initAnimation(m_AnimationIndex); + } + if (ImGui::ListBox( + "State Machines", + &m_StateMachineIndex, + [](void* data, int index, const char** name) { + auto& names = *static_cast*>(data); + *name = names[index].c_str(); + return true; + }, + &stateMachineNames, + stateMachineNames.size(), + 4)) + { + m_AnimationIndex = -1; + initStateMachine(m_StateMachineIndex); + } + if (m_CurrentScene != nullptr) { + + ImGui::Columns(2); + ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.6666); + + for (int i = 0; i < m_CurrentScene->inputCount(); i++) { + auto inputInstance = m_CurrentScene->input(i); + + if (inputInstance->input()->is()) { + // ImGui requires names as id's, use ## to hide the + // label but still give it an id. + char label[256]; + snprintf(label, 256, "##%u", i); + + auto number = static_cast(inputInstance); + float v = number->value(); + ImGui::InputFloat(label, &v, 1.0f, 2.0f, "%.3f"); + number->value(v); + ImGui::NextColumn(); + } else if (inputInstance->input()->is()) { + // ImGui requires names as id's, use ## to hide the + // label but still give it an id. + char label[256]; + snprintf(label, 256, "Fire##%u", i); + if (ImGui::Button(label)) { + auto trigger = static_cast(inputInstance); + trigger->fire(); + } + ImGui::NextColumn(); + } else if (inputInstance->input()->is()) { + // ImGui requires names as id's, use ## to hide the + // label but still give it an id. + char label[256]; + snprintf(label, 256, "##%u", i); + auto boolInput = static_cast(inputInstance); + bool value = boolInput->value(); + + ImGui::Checkbox(label, &value); + boolInput->value(value); + ImGui::NextColumn(); + } + ImGui::Text("%s", inputInstance->input()->name().c_str()); + ImGui::NextColumn(); + } + + ImGui::Columns(1); + } + ImGui::End(); + + } else { + ImGui::Text("Drop a .riv file to preview."); + } + } +}; + +std::unique_ptr ViewerContent::Scene(const char filename[]) { + auto bytes = LoadFile(filename); + if (auto file = rive::File::import(rive::toSpan(bytes), RiveFactory())) { + return std::make_unique(filename, std::move(file)); + } + return nullptr; +} \ No newline at end of file diff --git a/skia/viewer/src/text_content.cpp b/viewer/src/viewer_content/text_content.cpp similarity index 91% rename from skia/viewer/src/text_content.cpp rename to viewer/src/viewer_content/text_content.cpp index 81bf9ed8..e4acfaa3 100644 --- a/skia/viewer/src/text_content.cpp +++ b/viewer/src/viewer_content/text_content.cpp @@ -2,13 +2,14 @@ * Copyright 2022 Rive */ -#include "viewer_content.hpp" +// Currently only compatible with the skia renderer because line_breaker is from there. +#ifdef RIVE_RENDERER_SKIA +#include "viewer/viewer_content.hpp" +#include "rive/factory.hpp" #include "rive/refcnt.hpp" #include "rive/render_text.hpp" -#include "skia_factory.hpp" -#include "skia_renderer.hpp" #include "line_breaker.hpp" using RenderFontTextRuns = std::vector; @@ -100,8 +101,6 @@ static void draw_line(rive::Factory* factory, rive::Renderer* renderer, float x) renderer->drawPath(path.get(), paint.get()); } -static rive::SkiaFactory skiaFactory; - static rive::RenderTextRun append(std::vector* unichars, rive::rcp font, float size, @@ -133,8 +132,8 @@ class TextContent : public ViewerContent { }; const char* fontFiles[] = { - "../../test/assets/RobotoFlex.ttf", - "../../test/assets/LibreBodoni-Italic-VariableFont_wght.ttf", + "../../../test/assets/RobotoFlex.ttf", + "../../../test/assets/LibreBodoni-Italic-VariableFont_wght.ttf", }; auto font0 = loader(fontFiles[0]); @@ -179,18 +178,16 @@ public: auto lines = rive::RenderGlyphLine::BreakLines(rive::toSpan(gruns), rive::toSpan(m_breaks), width); - drawpara(&skiaFactory, renderer, rive::toSpan(lines), rive::toSpan(gruns), {0, 0}); - draw_line(&skiaFactory, renderer, width); + drawpara(RiveFactory(), renderer, rive::toSpan(lines), rive::toSpan(gruns), {0, 0}); + draw_line(RiveFactory(), renderer, width); renderer->restore(); } - void handleDraw(SkCanvas* canvas, double) override { - rive::SkiaRenderer renderer(canvas); - + void handleDraw(rive::Renderer* renderer, double) override { for (auto& grun : m_gruns) { - this->draw(&renderer, m_width, grun); - renderer.translate(1200, 0); + this->draw(renderer, m_width, grun); + renderer->translate(1200, 0); } } @@ -222,3 +219,4 @@ std::unique_ptr ViewerContent::Text(const char filename[]) { } return nullptr; } +#endif \ No newline at end of file diff --git a/skia/viewer/src/textpath_content.cpp b/viewer/src/viewer_content/textpath_content.cpp similarity index 86% rename from skia/viewer/src/textpath_content.cpp rename to viewer/src/viewer_content/textpath_content.cpp index 29ca0f84..a8c71cdb 100644 --- a/skia/viewer/src/textpath_content.cpp +++ b/viewer/src/viewer_content/textpath_content.cpp @@ -2,17 +2,16 @@ * Copyright 2022 Rive */ -#include "viewer_content.hpp" +// Currently only supports skia renderer. +#ifdef RIVE_RENDERER_SKIA +#include "viewer/viewer_content.hpp" -#include "rive/math/contour_measure.hpp" #include "rive/refcnt.hpp" #include "rive/render_text.hpp" -#include "skia_factory.hpp" -#include "skia_renderer.hpp" +#include "rive/math/contour_measure.hpp" #include "line_breaker.hpp" #include "to_skia.hpp" - using namespace rive; using RenderFontTextRuns = std::vector; @@ -65,14 +64,13 @@ static void warp_in_place(ContourMeasure* meas, RawPath* path) { #include "renderfont_hb.hpp" #include "renderfont_coretext.hpp" -static SkiaFactory skiaFactory; - static std::unique_ptr make_rpath(const RawPath& path) { - return skiaFactory.makeRenderPath(path.points(), path.verbsU8(), FillRule::nonZero); + return ViewerContent::RiveFactory()->makeRenderPath( + path.points(), path.verbsU8(), FillRule::nonZero); } static void stroke_path(Renderer* renderer, const RawPath& path, float size, ColorInt color) { - auto paint = skiaFactory.makeRenderPaint(); + auto paint = ViewerContent::RiveFactory()->makeRenderPaint(); paint->color(color); paint->thickness(size); paint->style(RenderPaintStyle::stroke); @@ -141,8 +139,8 @@ class TextPathContent : public ViewerContent { }; const char* fontFiles[] = { - "../../test/assets/RobotoFlex.ttf", - "../../test/assets/LibreBodoni-Italic-VariableFont_wght.ttf", + "../../../test/assets/RobotoFlex.ttf", + "../../../test/assets/LibreBodoni-Italic-VariableFont_wght.ttf", }; auto font0 = loader(fontFiles[0]); @@ -184,7 +182,7 @@ public: m_gbounds = compute_bounds(m_gruns); m_oneLineXform = Mat2D::fromScale(2.5, 2.5) * Mat2D::fromTranslate(20, 80); - m_paint = skiaFactory.makeRenderPaint(); + m_paint = ViewerContent::RiveFactory()->makeRenderPaint(); m_paint->color(0xFFFFFFFF); m_pathpts.push_back({20, 300}); @@ -199,7 +197,7 @@ public: void draw_warp(Renderer* renderer, const RawPath& warp) { stroke_path(renderer, warp, 0.5, 0xFF00FF00); - auto paint = skiaFactory.makeRenderPaint(); + auto paint = ViewerContent::RiveFactory()->makeRenderPaint(); paint->color(0xFF008800); const float r = 4; for (auto p : m_pathpts) { @@ -264,21 +262,21 @@ public: } void drawOneLine(Renderer* renderer) { - auto paint = skiaFactory.makeRenderPaint(); + auto paint = ViewerContent::RiveFactory()->makeRenderPaint(); paint->color(0xFF88FFFF); if (m_trackingOneLine) { float mx = m_oneLineX / m_gbounds.width(); const ColorInt colors[] = {0xFF88FFFF, 0xFF88FFFF, 0xFFFFFFFF, 0xFF88FFFF, 0xFF88FFFF}; const float stops[] = {0, mx / 2, mx, (1 + mx) / 2, 1}; - paint->shader(skiaFactory.makeLinearGradient(m_gbounds.left(), - 0, - m_gbounds.right(), - 0, - colors, - stops, - 5, - RenderTileMode::clamp)); + paint->shader(ViewerContent::RiveFactory()->makeLinearGradient(m_gbounds.left(), + 0, + m_gbounds.right(), + 0, + colors, + stops, + 5, + RenderTileMode::clamp)); } struct EaseWindow { @@ -315,17 +313,15 @@ public: }); } - void handleDraw(SkCanvas* canvas, double) override { - SkiaRenderer renderer(canvas); + void handleDraw(rive::Renderer* renderer, double) override { + renderer->save(); + this->draw(renderer, m_gruns); + renderer->restore(); - renderer.save(); - this->draw(&renderer, m_gruns); - renderer.restore(); - - renderer.save(); - renderer.transform(m_oneLineXform); - this->drawOneLine(&renderer); - renderer.restore(); + renderer->save(); + renderer->transform(m_oneLineXform); + this->drawOneLine(renderer); + renderer->restore(); } void handlePointerMove(float x, float y) override { @@ -350,7 +346,7 @@ public: m_pathpts[m_trackingIndex] = m_lastPt; } } - void handlePointerDown() override { + void handlePointerDown(float x, float y) override { auto close_to = [](Vec2D a, Vec2D b) { return Vec2D::distance(a, b) <= 10; }; for (size_t i = 0; i < m_pathpts.size(); ++i) { if (close_to(m_lastPt, m_pathpts[i])) { @@ -360,7 +356,7 @@ public: } } - void handlePointerUp() override { m_trackingIndex = -1; } + void handlePointerUp(float x, float y) override { m_trackingIndex = -1; } void handleResize(int width, int height) override {} @@ -379,3 +375,4 @@ public: std::unique_ptr ViewerContent::TextPath(const char filename[]) { return std::make_unique(); } +#endif \ No newline at end of file diff --git a/skia/viewer/src/viewer_content.cpp b/viewer/src/viewer_content/viewer_content.cpp similarity index 73% rename from skia/viewer/src/viewer_content.cpp rename to viewer/src/viewer_content/viewer_content.cpp index ed686ac5..c94715c9 100644 --- a/skia/viewer/src/viewer_content.cpp +++ b/viewer/src/viewer_content/viewer_content.cpp @@ -1,9 +1,13 @@ -/* - * Copyright 2022 Rive - */ +#ifdef RIVE_RENDERER_TESS +#include "viewer/tess/viewer_sokol_factory.hpp" +#endif +#ifdef RIVE_RENDERER_SKIA +#include "skia_factory.hpp" +#endif -#include "viewer_content.hpp" +#include "viewer/viewer_content.hpp" #include "rive/rive_counter.hpp" +#include ViewerContent::~ViewerContent() { DumpCounters("After deleting content"); } @@ -19,19 +23,6 @@ const char* gCounterNames[] = { "image", }; -void ViewerContent::DumpCounters(const char label[]) { - assert(sizeof(gCounterNames) / sizeof(gCounterNames[0]) == rive::Counter::kLastType + 1); - - if (label == nullptr) { - label = "Counters"; - } - printf("%s:", label); - for (int i = 0; i <= rive::Counter::kLastType; ++i) { - printf(" [%s]:%d", gCounterNames[i], rive::Counter::counts[i]); - } - printf("\n"); -} - std::vector ViewerContent::LoadFile(const char filename[]) { std::vector bytes; @@ -55,3 +46,27 @@ std::vector ViewerContent::LoadFile(const char filename[]) { } return bytes; } + +void ViewerContent::DumpCounters(const char label[]) { + assert(sizeof(gCounterNames) / sizeof(gCounterNames[0]) == rive::Counter::kLastType + 1); + + if (label == nullptr) { + label = "Counters"; + } + printf("%s:", label); + for (int i = 0; i <= rive::Counter::kLastType; ++i) { + printf(" [%s]:%d", gCounterNames[i], rive::Counter::counts[i]); + } + printf("\n"); +} + +rive::Factory* ViewerContent::RiveFactory() { +#ifdef RIVE_RENDERER_TESS + static ViewerSokolFactory sokolFactory; + return &sokolFactory; +#endif +#ifdef RIVE_RENDERER_SKIA + static rive::SkiaFactory skiaFactory; + return &skiaFactory; +#endif +} \ No newline at end of file