mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 13:11:19 +01:00
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:
32
.lua-format
Normal file
32
.lua-format
Normal 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
|
||||
@@ -1 +1 @@
|
||||
ac811aaa4b35e861838711db9b92e08649fb1c8a
|
||||
86d5b0924f0696e86940dc907954861436ffbc19
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -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
34
build/macosx/build_rive.sh
Executable 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
1
dependencies/macosx/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
cache
|
||||
4
dependencies/macosx/config_directories.sh
vendored
Executable file
4
dependencies/macosx/config_directories.sh
vendored
Executable 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
18
dependencies/macosx/get_imgui.sh
vendored
Executable 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
35
dependencies/macosx/get_libpng.sh
vendored
Executable 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
15
dependencies/macosx/get_premake5.sh
vendored
Executable 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
28
dependencies/macosx/get_sokol.sh
vendored
Executable 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
123
dependencies/macosx/make_viewer_skia.sh
vendored
Executable 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
|
||||
@@ -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
|
||||
|
||||
52
include/rive/math/cubic_utilities.hpp
Normal file
52
include/rive/math/cubic_utilities.hpp
Normal 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
|
||||
60
skia/renderer/build/macosx/build_skia_renderer.sh
Executable file
60
skia/renderer/build/macosx/build_skia_renderer.sh
Executable 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
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
22
viewer/README.md
Normal 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.
|
||||
73
viewer/build/macosx/build_viewer.sh
Executable file
73
viewer/build/macosx/build_viewer.sh
Executable 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
|
||||
85
viewer/build/premake5_libpng.lua
Normal file
85
viewer/build/premake5_libpng.lua
Normal 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
|
||||
213
viewer/build/premake5_viewer.lua
Normal file
213
viewer/build/premake5_viewer.lua
Normal 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
|
||||
13
viewer/include/viewer/skia/viewer_skia_renderer.hpp
Normal file
13
viewer/include/viewer/skia/viewer_skia_renderer.hpp
Normal 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
|
||||
40
viewer/include/viewer/tess/bitmap_decoder.hpp
Normal file
40
viewer/include/viewer/tess/bitmap_decoder.hpp
Normal 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
|
||||
1
viewer/include/viewer/tess/pnglibconf.h
Normal file
1
viewer/include/viewer/tess/pnglibconf.h
Normal file
@@ -0,0 +1 @@
|
||||
#include "libpng/scripts/pnglibconf.h.prebuilt"
|
||||
10
viewer/include/viewer/tess/viewer_sokol_factory.hpp
Normal file
10
viewer/include/viewer/tess/viewer_sokol_factory.hpp
Normal 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
|
||||
20
viewer/include/viewer/viewer.hpp
Normal file
20
viewer/include/viewer/viewer.hpp
Normal 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
|
||||
@@ -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
|
||||
5
viewer/src/platform/imgui_sokol_impl.cpp
Normal file
5
viewer/src/platform/imgui_sokol_impl.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "sokol_app.h"
|
||||
#include "sokol_gfx.h"
|
||||
#define SOKOL_IMPL
|
||||
#include "imgui.h"
|
||||
#include "util/sokol_imgui.h"
|
||||
17
viewer/src/platform/viewer_gl.mm
Normal file
17
viewer/src/platform/viewer_gl.mm
Normal 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
|
||||
}
|
||||
4
viewer/src/platform/viewer_mac.m
Normal file
4
viewer/src/platform/viewer_mac.m
Normal file
@@ -0,0 +1,4 @@
|
||||
#define SOKOL_IMPL
|
||||
#include "sokol_app.h"
|
||||
#include "sokol_gfx.h"
|
||||
#include "sokol_glue.h"
|
||||
30
viewer/src/skia/viewer_skia_gl.cpp
Normal file
30
viewer/src/skia/viewer_skia_gl.cpp
Normal 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
|
||||
87
viewer/src/skia/viewer_skia_metal.mm
Normal file
87
viewer/src/skia/viewer_skia_metal.mm
Normal 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
71
viewer/src/stats.cpp
Normal 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();
|
||||
}
|
||||
105
viewer/src/tess/bitmap_decoder.cpp
Normal file
105
viewer/src/tess/bitmap_decoder.cpp
Normal 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
|
||||
118
viewer/src/tess/decode_png.cpp
Normal file
118
viewer/src/tess/decode_png.cpp
Normal 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
|
||||
37
viewer/src/tess/viewer_sokol_factory.cpp
Normal file
37
viewer/src/tess/viewer_sokol_factory.cpp
Normal 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
218
viewer/src/viewer.cpp
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
249
viewer/src/viewer_content/scene_content.cpp
Normal file
249
viewer/src/viewer_content/scene_content.cpp
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,14 +262,14 @@ 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(),
|
||||
paint->shader(ViewerContent::RiveFactory()->makeLinearGradient(m_gbounds.left(),
|
||||
0,
|
||||
m_gbounds.right(),
|
||||
0,
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user