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.
This commit is contained in:
luigi-rosso
2022-07-16 04:21:44 +00:00
parent 63b887f94e
commit f17eb06604
50 changed files with 2011 additions and 760 deletions

32
.lua-format Normal file
View File

@@ -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

View File

@@ -1 +1 @@
ac811aaa4b35e861838711db9b92e08649fb1c8a
86d5b0924f0696e86940dc907954861436ffbc19

View File

@@ -101,7 +101,8 @@
"concepts": "cpp",
"scoped_allocator": "cpp",
"span": "cpp",
"typeindex": "cpp"
"typeindex": "cpp",
"filesystem": "cpp"
},
"git.ignoreLimitWarning": true
}

34
build/macosx/build_rive.sh Executable file
View File

@@ -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

1
dependencies/macosx/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
cache

4
dependencies/macosx/config_directories.sh vendored Executable file
View File

@@ -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

18
dependencies/macosx/get_imgui.sh vendored Executable file
View File

@@ -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

35
dependencies/macosx/get_libpng.sh vendored Executable file
View File

@@ -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

15
dependencies/macosx/get_premake5.sh vendored Executable file
View File

@@ -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

28
dependencies/macosx/get_sokol.sh vendored Executable file
View File

@@ -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

123
dependencies/macosx/make_viewer_skia.sh vendored Executable file
View File

@@ -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

View File

@@ -5,6 +5,7 @@
#include "rive/math/mat2d.hpp"
#include "rive/math/vec2d.hpp"
#include <cstddef>
#include <limits>
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<float>::max(),
std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::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

View File

@@ -0,0 +1,52 @@
#ifndef _RIVE_CUBIC_UTILITIES_HPP_
#define _RIVE_CUBIC_UTILITIES_HPP_
#include "rive/math/vec2d.hpp"
#include <algorithm>
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

View File

@@ -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

View File

@@ -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",

View File

@@ -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 <ApplicationServices/ApplicationServices.h>
#elif defined(RIVE_BUILD_FOR_IOS)

View File

@@ -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<const rive::Unichar> text,
return gruns;
}
#endif

View File

@@ -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<const rive::Unichar> text,
return gruns;
}
#endif

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 <cmath>
#include <stdio.h>
#include "viewer_content.hpp"
int lastScreenWidth = 0, lastScreenHeight = 0;
std::unique_ptr<ViewerContent> 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<GrDirectContext> context = GrDirectContext::MakeGL(nullptr, options);
GrGLFramebufferInfo framebufferInfo;
framebufferInfo.fFBOID = 0;
framebufferInfo.fFormat = GL_RGBA8;
sk_sp<SkSurface> 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;
}

View File

@@ -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<std::string> animationNames;
std::vector<std::string> stateMachineNames;
constexpr int REQUEST_DEFAULT_SCENE = -1;
#include <time.h>
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<uint8_t> 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<rive::File> m_file;
std::unique_ptr<rive::ArtboardInstance> m_artboardInstance;
std::unique_ptr<rive::Scene> 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<rive::File> 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<rive::StateMachineNumber>()) {
// 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<rive::SMINumber*>(inputInstance);
float v = number->value();
ImGui::InputFloat(label, &v, 1.0f, 2.0f, "%.3f");
number->value(v);
ImGui::NextColumn();
} else if (inputInstance->input()->is<rive::StateMachineTrigger>()) {
// 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<rive::SMITrigger*>(inputInstance);
trigger->fire();
}
ImGui::NextColumn();
} else if (inputInstance->input()->is<rive::StateMachineBool>()) {
// 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<rive::SMIBool*>(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> ViewerContent::Scene(const char filename[]) {
auto bytes = LoadFile(filename);
if (auto file = rive::File::import(rive::toSpan(bytes), &skiaFactory)) {
return std::make_unique<SceneContent>(filename, std::move(file));
}
return nullptr;
}

View File

@@ -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;
}
}

View File

@@ -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<CubicSegment>& 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]);

22
viewer/README.md Normal file
View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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<const uint8_t[]> 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<const uint8_t[]> 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<Bitmap> decode(rive::Span<const uint8_t> bytes);
// Change the pixel format (note this will resize bytes).
void pixelFormat(PixelFormat format);
};
#endif

View File

@@ -0,0 +1 @@
#include "libpng/scripts/pnglibconf.h.prebuilt"

View File

@@ -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<rive::RenderImage> decodeImage(rive::Span<const uint8_t>) override;
};
#endif

View File

@@ -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<GrDirectContext> makeSkiaContext();
sk_sp<SkSurface> makeSkiaSurface(GrDirectContext* context, int width, int height);
void skiaPresentSurface(sk_sp<SkSurface> surface);
#endif
// Helper to ensure the gl context is currently bound.
void bindGraphicsContext();
#endif

View File

@@ -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<ViewerContent> (*)(const char filename[]);
// Searches all handlers and returns a content if it is found.
static std::unique_ptr<ViewerContent> FindHandler(const char filename[]) {
Factory factories[] = {Scene, Image, Text, TextPath};
static std::unique_ptr<ViewerContent> 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<ViewerContent> 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<ViewerSkiaRenderer*>(renderer)->canvas();
}
static std::unique_ptr<ViewerContent> Image(const char[]);
static std::unique_ptr<ViewerContent> Text(const char[]);
static std::unique_ptr<ViewerContent> TextPath(const char[]);
#endif
static std::vector<uint8_t> LoadFile(const char path[]);
static void DumpCounters(const char label[]);
// Abstracts which rive Factory is currently used.
static rive::Factory* RiveFactory();
};
#endif

View File

@@ -0,0 +1,5 @@
#include "sokol_app.h"
#include "sokol_gfx.h"
#define SOKOL_IMPL
#include "imgui.h"
#include "util/sokol_imgui.h"

View File

@@ -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
}

View File

@@ -0,0 +1,4 @@
#define SOKOL_IMPL
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"

View File

@@ -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<GrDirectContext> makeSkiaContext() { return GrDirectContext::MakeGL(); }
sk_sp<SkSurface> 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<SkSurface> surface) {}
#endif

View File

@@ -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 <Metal/Metal.h>
#import <MetalKit/MetalKit.h>
#include "mtl/GrMtlBackendContext.h"
#include "mtl/GrMtlTypes.h"
#import <QuartzCore/CAMetalLayer.h>
#import "Cocoa/Cocoa.h"
id<MTLCommandQueue> commandQueue;
id<CAMetalDrawable> 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<GrDirectContext> 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<MTLDevice> device = (id<MTLDevice>)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<SkSurface> 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<SkSurface> surface) {
id<MTLCommandBuffer> commandBuffer = [(id<MTLCommandQueue>)commandQueue commandBuffer];
commandBuffer.label = @"Present";
[commandBuffer presentDrawable:(id<CAMetalDrawable>)drawable];
[commandBuffer commit];
}
#endif

71
viewer/src/stats.cpp Normal file
View File

@@ -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();
}

View File

@@ -0,0 +1,105 @@
#ifdef RIVE_RENDERER_TESS
#include "viewer/tess/bitmap_decoder.hpp"
#include <vector>
Bitmap::Bitmap(uint32_t width,
uint32_t height,
PixelFormat pixelFormat,
std::unique_ptr<const uint8_t[]> 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<const uint8_t[]>(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<Bitmap> DecodePng(rive::Span<const uint8_t> bytes);
std::unique_ptr<Bitmap> DecodeJpeg(rive::Span<const uint8_t> bytes) { return nullptr; }
std::unique_ptr<Bitmap> DecodeWebP(rive::Span<const uint8_t> bytes) { return nullptr; }
using BitmapDecoder = std::unique_ptr<Bitmap> (*)(rive::Span<const uint8_t> bytes);
struct ImageFormat {
const char* name;
std::vector<const uint8_t> fingerprint;
BitmapDecoder decodeImage;
};
std::unique_ptr<Bitmap> Bitmap::decode(rive::Span<const uint8_t> 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<uint8_t[]>(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

View File

@@ -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<Bitmap> DecodePng(rive::Span<const uint8_t> 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<Bitmap>(width, height, pixelFormat, pixelBuffer);
}
#endif

View File

@@ -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<rive::RenderImage>
ViewerSokolFactory::decodeImage(rive::Span<const uint8_t> 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<rive::SokolRenderImage>(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

218
viewer/src/viewer.cpp Normal file
View File

@@ -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 <stdio.h>
#include <memory>
#ifdef RIVE_RENDERER_SKIA
#include "viewer/skia/viewer_skia_renderer.hpp"
sk_sp<GrDirectContext> g_SkiaContext;
sk_sp<SkSurface> g_SkiaSurface;
#endif
#ifdef RIVE_RENDERER_TESS
#include "rive/tess/sokol/sokol_tess_renderer.hpp"
std::unique_ptr<rive::SokolTessRenderer> g_TessRenderer;
#endif
std::unique_ptr<ViewerContent> 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<rive::SokolTessRenderer>();
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,
};
}

View File

@@ -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<SkImage> 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> ViewerContent::Image(const char filename[]) {
}
return nullptr;
}
#endif

View File

@@ -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<std::string> animationNames;
std::vector<std::string> 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<rive::File> m_File;
std::unique_ptr<rive::ArtboardInstance> m_ArtboardInstance;
std::unique_ptr<rive::Scene> 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<rive::File> 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<std::vector<std::string>*>(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<std::vector<std::string>*>(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<rive::StateMachineNumber>()) {
// 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<rive::SMINumber*>(inputInstance);
float v = number->value();
ImGui::InputFloat(label, &v, 1.0f, 2.0f, "%.3f");
number->value(v);
ImGui::NextColumn();
} else if (inputInstance->input()->is<rive::StateMachineTrigger>()) {
// 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<rive::SMITrigger*>(inputInstance);
trigger->fire();
}
ImGui::NextColumn();
} else if (inputInstance->input()->is<rive::StateMachineBool>()) {
// 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<rive::SMIBool*>(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> ViewerContent::Scene(const char filename[]) {
auto bytes = LoadFile(filename);
if (auto file = rive::File::import(rive::toSpan(bytes), RiveFactory())) {
return std::make_unique<SceneContent>(filename, std::move(file));
}
return nullptr;
}

View File

@@ -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<rive::RenderTextRun>;
@@ -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<rive::Unichar>* unichars,
rive::rcp<rive::RenderFont> 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> ViewerContent::Text(const char filename[]) {
}
return nullptr;
}
#endif

View File

@@ -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<RenderTextRun>;
@@ -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<RenderPath> 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> ViewerContent::TextPath(const char filename[]) {
return std::make_unique<TextPathContent>();
}
#endif

View File

@@ -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 <vector>
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<uint8_t> ViewerContent::LoadFile(const char filename[]) {
std::vector<uint8_t> bytes;
@@ -55,3 +46,27 @@ std::vector<uint8_t> 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
}