Add a "lite_rtti" utility and use it with Render objects We need to be more robust if a user tries to use mismatched factories and renderers. This PR creates a simple "lite_rtti" utility and applies it to every Render object. The renderers now abort early instead of crashing if they are given a Render object for the wrong renderer. Diffs= c357e7aa7 Add a "lite_rtti" utility and use it with Render objects (#6311) Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index 467da65..a54f657 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -53972cc17b532e83ffee7cae7d3420e95f822e8f +c357e7aa757f23486271df284a5c3aa5643f2ff5
diff --git a/cg_renderer/src/cg_factory.cpp b/cg_renderer/src/cg_factory.cpp index fc22574..8d4244a 100644 --- a/cg_renderer/src/cg_factory.cpp +++ b/cg_renderer/src/cg_factory.cpp
@@ -81,7 +81,7 @@ rgba[3] = colorAlpha(c) * kByteToUnit; } -class CGRenderPath : public RenderPath +class CGRenderPath : public lite_rtti_override<RenderPath, CGRenderPath> { private: AutoCF<CGMutablePathRef> m_path = CGPathCreateMutable(); @@ -156,7 +156,7 @@ void close() override { CGPathCloseSubpath(m_path); } }; -class CGRenderShader : public RenderShader +class CGRenderShader : public lite_rtti_override<RenderShader, CGRenderShader> { public: CGRenderShader() {} @@ -167,7 +167,7 @@ virtual void draw(CGContextRef) {} }; -class CGRenderPaint : public RenderPaint +class CGRenderPaint : public lite_rtti_override<RenderPaint, CGRenderPaint> { private: bool m_isStroke = false; @@ -176,7 +176,7 @@ CGLineJoin m_join = kCGLineJoinMiter; CGLineCap m_cap = kCGLineCapButt; CGBlendMode m_blend = kCGBlendModeNormal; - rcp<RenderShader> m_shader; + rcp<CGRenderShader> m_shader; public: CGRenderPaint() {} @@ -184,7 +184,7 @@ bool isStroke() const { return m_isStroke; } float opacity() const { return m_rgba[3]; } - CGRenderShader* shader() const { return static_cast<CGRenderShader*>(m_shader.get()); } + CGRenderShader* shader() const { return m_shader.get(); } void apply(CGContextRef ctx) { @@ -211,7 +211,10 @@ void join(StrokeJoin value) override { m_join = convert(value); } void cap(StrokeCap value) override { m_cap = convert(value); } void blendMode(BlendMode value) override { m_blend = convert(value); } - void shader(rcp<RenderShader> sh) override { m_shader = std::move(sh); } + void shader(rcp<RenderShader> sh) override + { + m_shader = lite_rtti_rcp_cast<CGRenderShader>(std::move(sh)); + } void invalidateStroke() override {} }; @@ -299,7 +302,7 @@ } }; -class CGRenderImage : public RenderImage +class CGRenderImage : public lite_rtti_override<RenderImage, CGRenderImage> { public: AutoCF<CGImageRef> m_image; @@ -319,11 +322,6 @@ { CGContextConcatCTM(ctx, CGAffineTransformMake(1, 0, 0, -1, 0, (float)m_Height)); } - - static const CGRenderImage* Cast(const RenderImage* image) - { - return static_cast<const CGRenderImage*>(image); - } }; ////////////////////////////////////////////////////////////////////////// @@ -348,8 +346,8 @@ void CGRenderer::drawPath(RenderPath* path, RenderPaint* paint) { - auto cgpaint = static_cast<CGRenderPaint*>(paint); - auto cgpath = static_cast<CGRenderPath*>(path); + LITE_RTTI_CAST_OR_RETURN(cgpaint, CGRenderPaint*, paint); + LITE_RTTI_CAST_OR_RETURN(cgpath, CGRenderPath*, path); cgpaint->apply(m_ctx); @@ -381,7 +379,7 @@ void CGRenderer::clipPath(RenderPath* path) { - auto cgpath = static_cast<CGRenderPath*>(path); + LITE_RTTI_CAST_OR_RETURN(cgpath, CGRenderPath*, path); CGContextBeginPath(m_ctx); CGContextAddPath(m_ctx, cgpath->path()); @@ -390,12 +388,13 @@ void CGRenderer::drawImage(const RenderImage* image, BlendMode blendMode, float opacity) { + LITE_RTTI_CAST_OR_RETURN(cgimg, const CGRenderImage*, image); + auto bounds = CGRectMake(0, 0, image->width(), image->height()); CGContextSaveGState(m_ctx); CGContextSetAlpha(m_ctx, opacity); CGContextSetBlendMode(m_ctx, convert(blendMode)); - auto cgimg = CGRenderImage::Cast(image); cgimg->applyLocalMatrix(m_ctx); CGContextDrawImage(m_ctx, bounds, cgimg->m_image); CGContextRestoreGState(m_ctx); @@ -417,7 +416,11 @@ BlendMode blendMode, float opacity) { - auto cgimage = CGRenderImage::Cast(image); + LITE_RTTI_CAST_OR_RETURN(cgimage, const CGRenderImage*, image); + LITE_RTTI_CAST_OR_RETURN(cgindices, DataRenderBuffer*, indices.get()); + LITE_RTTI_CAST_OR_RETURN(cgvertices, DataRenderBuffer*, vertices.get()); + LITE_RTTI_CAST_OR_RETURN(cguvcoords, DataRenderBuffer*, uvCoords.get()); + auto const localMatrix = cgimage->localM2D(); const float sx = image->width(); @@ -427,9 +430,9 @@ auto scale = [sx, sy](Vec2D v) { return Vec2D{v.x * sx, v.y * sy}; }; auto triangles = indexCount / 3; - auto ndx = DataRenderBuffer::Cast(indices.get())->u16s(); - auto pts = DataRenderBuffer::Cast(vertices.get())->vecs(); - auto uvs = DataRenderBuffer::Cast(uvCoords.get())->vecs(); + auto ndx = cgindices->u16s(); + auto pts = cgvertices->vecs(); + auto uvs = cguvcoords->vecs(); // We use the path to set the clip for each triangle. Since calling // CGContextClip() resets the path, we only need to this once at
diff --git a/include/rive/renderer.hpp b/include/rive/renderer.hpp index e595435..4b31825 100644 --- a/include/rive/renderer.hpp +++ b/include/rive/renderer.hpp
@@ -16,6 +16,7 @@ #include "rive/shapes/paint/blend_mode.hpp" #include "rive/shapes/paint/stroke_cap.hpp" #include "rive/shapes/paint/stroke_join.hpp" +#include "utils/lite_rtti.hpp" #include <cmath> #include <stdio.h> @@ -42,7 +43,7 @@ }; RIVE_MAKE_ENUM_BITSET(RenderBufferFlags) -class RenderBuffer : public RefCnt<RenderBuffer> +class RenderBuffer : public RefCnt<RenderBuffer>, public enable_lite_rtti<RenderBuffer> { public: RenderBuffer(RenderBufferType, RenderBufferFlags, size_t sizeInBytes); @@ -81,14 +82,14 @@ * It is common that a shader may be created with a 'localMatrix'. If this is * not null, then it is applied to the shader's domain before the Renderer's CTM. */ -class RenderShader : public RefCnt<RenderShader> +class RenderShader : public RefCnt<RenderShader>, public enable_lite_rtti<RenderShader> { public: RenderShader(); virtual ~RenderShader(); }; -class RenderPaint +class RenderPaint : public enable_lite_rtti<RenderPaint> { public: RenderPaint(); @@ -104,7 +105,7 @@ virtual void invalidateStroke() = 0; }; -class RenderImage : public RefCnt<RenderImage> +class RenderImage : public RefCnt<RenderImage>, public enable_lite_rtti<RenderImage> { protected: int m_Width = 0; @@ -121,7 +122,7 @@ const Mat2D& uvTransform() const { return m_uvTransform; } }; -class RenderPath : public CommandPath +class RenderPath : public CommandPath, public enable_lite_rtti<RenderPath> { public: RenderPath();
diff --git a/include/utils/factory_utils.hpp b/include/utils/factory_utils.hpp index 5ad69c8..27ee8b0 100644 --- a/include/utils/factory_utils.hpp +++ b/include/utils/factory_utils.hpp
@@ -12,11 +12,11 @@ // Generic subclass of RenderBuffer that just stores the data on the cpu. // -class DataRenderBuffer : public RenderBuffer +class DataRenderBuffer : public lite_rtti_override<RenderBuffer, DataRenderBuffer> { public: DataRenderBuffer(RenderBufferType type, RenderBufferFlags flags, size_t sizeInBytes) : - RenderBuffer(type, flags, sizeInBytes) + lite_rtti_override(type, flags, sizeInBytes) { m_storage = malloc(sizeInBytes); } @@ -29,11 +29,6 @@ const Vec2D* vecs() const { return reinterpret_cast<const Vec2D*>(f32s()); } - static const DataRenderBuffer* Cast(const RenderBuffer* buffer) - { - return static_cast<const DataRenderBuffer*>(buffer); - } - protected: void* onMap() override { return m_storage; } void onUnmap() override {}
diff --git a/include/utils/lite_rtti.hpp b/include/utils/lite_rtti.hpp new file mode 100644 index 0000000..2c1c934 --- /dev/null +++ b/include/utils/lite_rtti.hpp
@@ -0,0 +1,97 @@ +/* + * Copyright 2023 Rive + */ + +// "lite_rtti_cast<T*>()" is a very basic polyfill for "dynamic_cast<T*>()" that can only cast a +// pointer to its most-derived type. To use it, the base class must derive from enable_lite_rtti, +// and the subclass must inherit from lite_rtti_override: +// +// class Root : public enable_lite_rtti<Root> {}; +// class Derived : public lite_rtti_override<Root, Derived> {}; +// Root* derived = new Derived(); +// lite_rtti_cast<Derived*>(derived); +// + +#pragma once + +#include "rive/refcnt.hpp" +#include <stdint.h> +#include <type_traits> + +namespace rive +{ +// Derive type IDs based on the unique address of a static placeholder value. +template <typename T> +typename std::enable_if<!std::is_const<T>::value, uintptr_t>::type lite_type_id() +{ + static int placeholderForUniqueAddress; + return reinterpret_cast<uintptr_t>(&placeholderForUniqueAddress); +} + +// Type IDs for const-qualified types should match their non-const counterparts. +template <typename T> +typename std::enable_if<std::is_const<T>::value, uintptr_t>::type lite_type_id() +{ + return lite_type_id<typename std::remove_const<T>::type>(); +} + +// Enable lite rtti on the root of a class hierarchy. +template <class Root> class enable_lite_rtti +{ +public: + uintptr_t liteTypeID() const { return m_liteTypeID; } + +protected: + uintptr_t m_liteTypeID = lite_type_id<Root>(); +}; + +// Override the lite rtti type ID on subsequent classes of a class hierarchy. +template <class Base, class Derived> class lite_rtti_override : public Base +{ +public: + lite_rtti_override() { Base::m_liteTypeID = lite_type_id<Derived>(); } + + template <typename... Args> + lite_rtti_override(Args&&... args) : Base(std::forward<Args>(args)...) + { + Base::m_liteTypeID = lite_type_id<Derived>(); + } +}; + +// Like dynamic_cast<>, but can only cast a pointer to its most-derived type. +template <class U, class T> U lite_rtti_cast(T* t) +{ + if (t != nullptr && t->liteTypeID() == lite_type_id<typename std::remove_pointer<U>::type>()) + { + return static_cast<U>(t); + } + return nullptr; +} + +template <class U, class T> rcp<U> lite_rtti_rcp_cast(rcp<T> t) +{ + if (t != nullptr && t->liteTypeID() == lite_type_id<U>()) + { + return static_rcp_cast<U>(t); + } + return nullptr; +} + +// Different versions of clang-format disagree on how to formate these. +// clang-format off +#define LITE_RTTI_CAST_OR_RETURN(NAME, TYPE, POINTER) \ + auto NAME = rive::lite_rtti_cast<TYPE>(POINTER); \ + if (NAME == nullptr) \ + return + +#define LITE_RTTI_CAST_OR_BREAK(NAME, TYPE, POINTER) \ + auto NAME = rive::lite_rtti_cast<TYPE>(POINTER); \ + if (NAME == nullptr) \ + break + +#define LITE_RTTI_CAST_OR_CONTINUE(NAME, TYPE, POINTER) \ + auto NAME = rive::lite_rtti_cast<TYPE>(POINTER); \ + if (NAME == nullptr) \ + continue +// clang-format on +} // namespace rive
diff --git a/skia/renderer/src/skia_factory.cpp b/skia/renderer/src/skia_factory.cpp index d3ac0d3..f0bc87d 100644 --- a/skia/renderer/src/skia_factory.cpp +++ b/skia/renderer/src/skia_factory.cpp
@@ -24,7 +24,7 @@ // skia's has/had bugs in trilerp, so backing down to nearest mip const SkSamplingOptions gSampling(SkFilterMode::kLinear, SkMipmapMode::kNearest); -class SkiaRenderPath : public RenderPath +class SkiaRenderPath : public lite_rtti_override<RenderPath, SkiaRenderPath> { private: SkPath m_Path; @@ -44,7 +44,7 @@ virtual void close() override; }; -class SkiaRenderPaint : public RenderPaint +class SkiaRenderPaint : public lite_rtti_override<RenderPaint, SkiaRenderPaint> { private: SkPaint m_Paint; @@ -64,7 +64,7 @@ void invalidateStroke() override {} }; -class SkiaRenderImage : public RenderImage +class SkiaRenderImage : public lite_rtti_override<RenderImage, SkiaRenderImage> { private: sk_sp<SkImage> m_SkImage; @@ -75,7 +75,7 @@ sk_sp<SkImage> skImage() const { return m_SkImage; } }; -class SkiaRenderShader : public RenderShader +class SkiaRenderShader : public lite_rtti_override<RenderShader, SkiaRenderShader> { public: SkiaRenderShader(sk_sp<SkShader> sh) : shader(std::move(sh)) {} @@ -88,7 +88,8 @@ void SkiaRenderPath::rewind() { m_Path.rewind(); } void SkiaRenderPath::addRenderPath(RenderPath* path, const Mat2D& transform) { - m_Path.addPath(static_cast<SkiaRenderPath*>(path)->m_Path, ToSkia::convert(transform)); + LITE_RTTI_CAST_OR_RETURN(skPath, SkiaRenderPath*, path); + m_Path.addPath(skPath->m_Path, ToSkia::convert(transform)); } void SkiaRenderPath::moveTo(float x, float y) { m_Path.moveTo(x, y); } @@ -122,7 +123,7 @@ void SkiaRenderPaint::shader(rcp<RenderShader> rsh) { - SkiaRenderShader* sksh = (SkiaRenderShader*)rsh.get(); + SkiaRenderShader* sksh = lite_rtti_cast<SkiaRenderShader*>(rsh.get()); m_Paint.setShader(sksh ? sksh->shader : nullptr); } @@ -134,21 +135,23 @@ } void SkiaRenderer::drawPath(RenderPath* path, RenderPaint* paint) { - m_Canvas->drawPath(static_cast<SkiaRenderPath*>(path)->path(), - static_cast<SkiaRenderPaint*>(paint)->paint()); + LITE_RTTI_CAST_OR_RETURN(skPath, SkiaRenderPath*, path); + LITE_RTTI_CAST_OR_RETURN(skPaint, SkiaRenderPaint*, paint); + m_Canvas->drawPath(skPath->path(), skPaint->paint()); } void SkiaRenderer::clipPath(RenderPath* path) { - m_Canvas->clipPath(static_cast<SkiaRenderPath*>(path)->path(), true); + LITE_RTTI_CAST_OR_RETURN(skPath, SkiaRenderPath*, path); + m_Canvas->clipPath(skPath->path(), true); } void SkiaRenderer::drawImage(const RenderImage* image, BlendMode blendMode, float opacity) { + LITE_RTTI_CAST_OR_RETURN(skiaImage, const SkiaRenderImage*, image); SkPaint paint; paint.setAlphaf(opacity); paint.setBlendMode(ToSkia::convert(blendMode)); - auto skiaImage = static_cast<const SkiaRenderImage*>(image); m_Canvas->drawImage(skiaImage->skImage(), 0.0f, 0.0f, gSampling, &paint); } @@ -163,6 +166,11 @@ BlendMode blendMode, float opacity) { + LITE_RTTI_CAST_OR_RETURN(skImage, const SkiaRenderImage*, image); + LITE_RTTI_CAST_OR_RETURN(skVertices, DataRenderBuffer*, vertices.get()); + LITE_RTTI_CAST_OR_RETURN(skUVCoords, DataRenderBuffer*, uvCoords.get()); + LITE_RTTI_CAST_OR_RETURN(skIndices, DataRenderBuffer*, indices.get()); + // need our buffers and counts to agree assert(vertices->sizeInBytes() == vertexCount * sizeof(Vec2D)); assert(uvCoords->sizeInBytes() == vertexCount * sizeof(Vec2D)); @@ -170,7 +178,7 @@ SkMatrix scaleM; - auto uvs = (const SkPoint*)DataRenderBuffer::Cast(uvCoords.get())->vecs(); + auto uvs = (const SkPoint*)skUVCoords->vecs(); #ifdef SKIA_BUG_13047 // The local matrix is ignored for drawVertices, so we have to manually scale @@ -189,7 +197,7 @@ scaleM = SkMatrix::Scale(2.0f / image->width(), 2.0f / image->height()); #endif - auto skiaImage = static_cast<const SkiaRenderImage*>(image)->skImage(); + auto skiaImage = skImage->skImage(); auto shader = skiaImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, gSampling, &scaleM); SkPaint paint; @@ -201,11 +209,11 @@ auto vertexMode = SkVertices::kTriangles_VertexMode; auto vt = SkVertices::MakeCopy(vertexMode, vertexCount, - (const SkPoint*)DataRenderBuffer::Cast(vertices.get())->vecs(), + (const SkPoint*)skVertices->vecs(), uvs, no_colors, indexCount, - DataRenderBuffer::Cast(indices.get())->u16s()); + skIndices->u16s()); // The blend mode is ignored if we don't have colors && uvs m_Canvas->drawVertices(vt, SkBlendMode::kModulate, paint);
diff --git a/tess/include/rive/tess/sokol/sokol_tess_renderer.hpp b/tess/include/rive/tess/sokol/sokol_tess_renderer.hpp index 463bcc1..67454fa 100644 --- a/tess/include/rive/tess/sokol/sokol_tess_renderer.hpp +++ b/tess/include/rive/tess/sokol/sokol_tess_renderer.hpp
@@ -27,7 +27,7 @@ // The unique render image associated with a given source Rive asset. Can be stored in sub-region of // an actual graphics device image (SokolRenderImageResource). -class SokolRenderImage : public RenderImage +class SokolRenderImage : public lite_rtti_override<RenderImage, SokolRenderImage> { private: rcp<SokolRenderImageResource> m_gpuImage;
diff --git a/tess/include/rive/tess/tess_render_path.hpp b/tess/include/rive/tess/tess_render_path.hpp index 3e97bad..b374c75 100644 --- a/tess/include/rive/tess/tess_render_path.hpp +++ b/tess/include/rive/tess/tess_render_path.hpp
@@ -12,7 +12,7 @@ { class ContourStroke; -class TessRenderPath : public RenderPath +class TessRenderPath : public lite_rtti_override<RenderPath, TessRenderPath> { private: // TessRenderPath stores a RawPath so that it can use utility classes
diff --git a/tess/src/sokol/sokol_factory.cpp b/tess/src/sokol/sokol_factory.cpp index 51b0192..00904ab 100644 --- a/tess/src/sokol/sokol_factory.cpp +++ b/tess/src/sokol/sokol_factory.cpp
@@ -2,7 +2,7 @@ using namespace rive; -class NoOpRenderPaint : public RenderPaint +class NoOpRenderPaint : public lite_rtti_override<RenderPaint, NoOpRenderPaint> { public: void color(unsigned int value) override {} @@ -15,7 +15,7 @@ void invalidateStroke() override {} }; -class NoOpRenderPath : public RenderPath +class NoOpRenderPath : public lite_rtti_override<RenderPath, NoOpRenderPath> { public: void rewind() override {}
diff --git a/tess/src/sokol/sokol_tess_renderer.cpp b/tess/src/sokol/sokol_tess_renderer.cpp index b7fb081..6dc792e 100644 --- a/tess/src/sokol/sokol_tess_renderer.cpp +++ b/tess/src/sokol/sokol_tess_renderer.cpp
@@ -15,11 +15,11 @@ buffer[3] = colorOpacity(value); } -class SokolRenderPath : public TessRenderPath +class SokolRenderPath : public lite_rtti_override<TessRenderPath, SokolRenderPath> { public: SokolRenderPath() {} - SokolRenderPath(RawPath& rawPath, FillRule fillRule) : TessRenderPath(rawPath, fillRule) {} + SokolRenderPath(RawPath& rawPath, FillRule fillRule) : lite_rtti_override(rawPath, fillRule) {} ~SokolRenderPath() { @@ -66,7 +66,8 @@ { for (auto& subPath : m_subPaths) { - static_cast<SokolRenderPath*>(subPath.path())->drawStroke(stroke); + LITE_RTTI_CAST_OR_CONTINUE(sokolPath, SokolRenderPath*, subPath.path()); + sokolPath->drawStroke(stroke); } return; } @@ -149,11 +150,12 @@ return rivestd::make_unique<SokolRenderPath>(); } -class SokolBuffer : public RenderBuffer +class SokolBuffer : public lite_rtti_override<RenderBuffer, SokolBuffer> { public: SokolBuffer(RenderBufferType type, RenderBufferFlags renderBufferFlags, size_t sizeInBytes) : - RenderBuffer(type, renderBufferFlags, sizeInBytes), m_mappedMemory(new char[sizeInBytes]) + lite_rtti_override(type, renderBufferFlags, sizeInBytes), + m_mappedMemory(new char[sizeInBytes]) { // If the buffer will be immutable, defer creation until the client unmaps for the only time // and we have our initial data. @@ -581,12 +583,13 @@ void SokolTessRenderer::drawImage(const RenderImage* image, BlendMode, float opacity) { + LITE_RTTI_CAST_OR_RETURN(sokolImage, const SokolRenderImage*, image); + vs_params_t vs_params; const Mat2D& world = transform(); vs_params.mvp = m_Projection * world; - auto sokolImage = static_cast<const SokolRenderImage*>(image); setPipeline(m_meshPipeline); sg_bindings bind = { .vertex_buffers[0] = sokolImage->vertexBuffer(), @@ -609,6 +612,11 @@ BlendMode blendMode, float opacity) { + LITE_RTTI_CAST_OR_RETURN(sokolVertices, SokolBuffer*, vertices_f32.get()); + LITE_RTTI_CAST_OR_RETURN(sokolUVCoords, SokolBuffer*, uvCoords_f32.get()); + LITE_RTTI_CAST_OR_RETURN(sokolIndices, SokolBuffer*, indices_u16.get()); + LITE_RTTI_CAST_OR_RETURN(sokolRenderImage, const SokolRenderImage*, renderImage); + vs_params_t vs_params; const Mat2D& world = transform(); @@ -616,10 +624,10 @@ setPipeline(m_meshPipeline); sg_bindings bind = { - .vertex_buffers[0] = static_cast<SokolBuffer*>(vertices_f32.get())->buffer(), - .vertex_buffers[1] = static_cast<SokolBuffer*>(uvCoords_f32.get())->buffer(), - .index_buffer = static_cast<SokolBuffer*>(indices_u16.get())->buffer(), - .fs_images[SLOT_tex] = static_cast<const SokolRenderImage*>(renderImage)->image(), + .vertex_buffers[0] = sokolVertices->buffer(), + .vertex_buffers[1] = sokolUVCoords->buffer(), + .index_buffer = sokolIndices->buffer(), + .fs_images[SLOT_tex] = sokolRenderImage->image(), }; sg_apply_bindings(&bind); @@ -627,7 +635,7 @@ sg_draw(0, indexCount, 1); } -class SokolGradient : public RenderShader +class SokolGradient : public lite_rtti_override<RenderShader, SokolGradient> { private: Vec2D m_start; @@ -722,11 +730,11 @@ return rcp<RenderShader>(new SokolGradient(cx, cy, radius, colors, stops, count)); } -class SokolRenderPaint : public RenderPaint +class SokolRenderPaint : public lite_rtti_override<RenderPaint, SokolRenderPaint> { private: fs_path_uniforms_t m_uniforms = {0}; - rcp<RenderShader> m_shader; + rcp<SokolGradient> m_shader; RenderPaintStyle m_style; std::unique_ptr<ContourStroke> m_stroke; bool m_strokeDirty = false; @@ -801,13 +809,16 @@ void blendMode(BlendMode value) override { m_blendMode = value; } BlendMode blendMode() const { return m_blendMode; } - void shader(rcp<RenderShader> shader) override { m_shader = shader; } + void shader(rcp<RenderShader> shader) override + { + m_shader = lite_rtti_rcp_cast<SokolGradient>(std::move(shader)); + } void draw(vs_path_params_t& vertexUniforms, SokolRenderPath* path) { if (m_shader) { - static_cast<SokolGradient*>(m_shader.get())->bind(vertexUniforms, m_uniforms); + m_shader->bind(vertexUniforms, m_uniforms); } sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_path_params, SG_RANGE_REF(vertexUniforms)); @@ -984,11 +995,11 @@ if (decr) { // Draw appliedPath.path() with decr pipeline + LITE_RTTI_CAST_OR_CONTINUE(sokolPath, SokolRenderPath*, appliedPath.path()); setPipeline(m_decClipPipeline); vs_params.mvp = m_Projection * appliedPath.transform(); sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_path_params, SG_RANGE_REF(vs_params)); sg_apply_uniforms(SG_SHADERSTAGE_FS, SLOT_fs_path_uniforms, SG_RANGE_REF(uniforms)); - auto sokolPath = static_cast<SokolRenderPath*>(appliedPath.path()); sokolPath->drawFill(); } } @@ -1002,11 +1013,11 @@ continue; } // Draw nextClipPath.path() with incr pipeline + LITE_RTTI_CAST_OR_CONTINUE(sokolPath, SokolRenderPath*, nextClipPath.path()); setPipeline(m_incClipPipeline); vs_params.mvp = m_Projection * nextClipPath.transform(); sg_apply_uniforms(SG_SHADERSTAGE_VS, SLOT_vs_path_params, SG_RANGE_REF(vs_params)); sg_apply_uniforms(SG_SHADERSTAGE_FS, SLOT_fs_path_uniforms, SG_RANGE_REF(uniforms)); - auto sokolPath = static_cast<SokolRenderPath*>(nextClipPath.path()); sokolPath->drawFill(); } @@ -1030,7 +1041,8 @@ void SokolTessRenderer::drawPath(RenderPath* path, RenderPaint* paint) { - auto sokolPaint = static_cast<SokolRenderPaint*>(paint); + LITE_RTTI_CAST_OR_RETURN(sokolPath, SokolRenderPath*, path); + LITE_RTTI_CAST_OR_RETURN(sokolPaint, SokolRenderPaint*, paint); applyClipping(); vs_path_params_t vs_params = {.fillType = 0}; @@ -1056,7 +1068,7 @@ break; } - static_cast<SokolRenderPaint*>(paint)->draw(vs_params, static_cast<SokolRenderPath*>(path)); + sokolPaint->draw(vs_params, sokolPath); } SokolRenderImageResource::SokolRenderImageResource(const uint8_t* bytes, @@ -1076,7 +1088,7 @@ uint32_t height, const Mat2D& uvTransform) : - RenderImage(uvTransform), m_gpuImage(image) + lite_rtti_override(uvTransform), m_gpuImage(image) { float halfWidth = width / 2.0f;
diff --git a/test/lite_rtti_test.cpp b/test/lite_rtti_test.cpp new file mode 100644 index 0000000..d29d39f --- /dev/null +++ b/test/lite_rtti_test.cpp
@@ -0,0 +1,86 @@ +#include "utils/lite_rtti.hpp" +#include <catch.hpp> + +using namespace rive; + +TEST_CASE("lite rtti behaves correctly", "[lite_rtti]") +{ + class A : public enable_lite_rtti<A> + {}; + A a; + CHECK(lite_type_id<A>() == lite_type_id<A>()); + CHECK(lite_type_id<const A>() == lite_type_id<A>()); + CHECK(lite_type_id<const A>() == a.liteTypeID()); + + class B : public lite_rtti_override<A, B> + {}; + B b; + CHECK(lite_type_id<B>() != lite_type_id<A>()); + CHECK(b.liteTypeID() != a.liteTypeID()); + CHECK(b.liteTypeID() == lite_type_id<const B>()); + + class C : public lite_rtti_override<B, C> + {}; + const C c; + CHECK(lite_type_id<C>() != lite_type_id<A>()); + CHECK(lite_type_id<C>() != lite_type_id<B>()); + CHECK(c.liteTypeID() != a.liteTypeID()); + CHECK(c.liteTypeID() != b.liteTypeID()); + CHECK(c.liteTypeID() == lite_type_id<C>()); + + A* pA = &a; + A* pA_b = &b; + const A* pA_c = &c; + + CHECK(lite_rtti_cast<B*>(pA) == nullptr); + CHECK(lite_rtti_cast<const C*>(pA) == nullptr); + + CHECK(lite_rtti_cast<const B*>(pA_b) == &b); + CHECK(lite_rtti_cast<C*>(pA_b) == nullptr); + + CHECK(lite_rtti_cast<const B*>(pA_c) == nullptr); + CHECK(lite_rtti_cast<C*>(const_cast<A*>(pA_c)) == &c); + + const B* pB_c = &c; + CHECK(lite_rtti_cast<B*>(const_cast<B*>(pB_c)) == nullptr); + CHECK(lite_rtti_cast<const C*>(pB_c) == &c); + + A* nil = nullptr; + CHECK(lite_rtti_cast<B*>(nil) == nullptr); + CHECK(lite_rtti_cast<const C*>(nil) == nullptr); + + // Check constructor arguments. + struct D : public enable_lite_rtti<D> + { + D() = delete; + D(float x_, int y_) : x(x_), y(y_) {} + float x; + int y; + }; + + struct E : public lite_rtti_override<D, E> + { + E(float x_, int y_) : lite_rtti_override(x_, y_) {} + }; + + D* pD_e = new E(4.5f, 6); + E* pE = lite_rtti_cast<E*>(pD_e); + CHECK(pD_e->liteTypeID() == lite_type_id<const E>()); + REQUIRE(pE != nullptr); + CHECK(pE->x == 4.5f); + CHECK(pE->y == 6); + delete pD_e; + + // Check rcp + class F : public RefCnt<F>, public enable_lite_rtti<F> + {}; + class G : public lite_rtti_override<F, G> + {}; + class H : public lite_rtti_override<F, H> + {}; + rcp<F> pF_g = make_rcp<G>(); + rcp<G> pG = lite_rtti_rcp_cast<G>(pF_g); + rcp<H> pH = lite_rtti_rcp_cast<H>(pF_g); + CHECK(pG != nullptr); + CHECK(pH == nullptr); +}
diff --git a/viewer/src/sample_tools/sample_atlas_packer.cpp b/viewer/src/sample_tools/sample_atlas_packer.cpp index b250b34..8453615 100644 --- a/viewer/src/sample_tools/sample_atlas_packer.cpp +++ b/viewer/src/sample_tools/sample_atlas_packer.cpp
@@ -9,7 +9,7 @@ using namespace rive; -class AtlasRenderImage : public RenderImage +class AtlasRenderImage : public lite_rtti_override<RenderImage, AtlasRenderImage> { private: std::vector<uint8_t> m_Pixels; @@ -62,7 +62,9 @@ Mat2D uvTransform; auto imageAsset = asset->as<ImageAsset>(); - auto renderImage = static_cast<AtlasRenderImage*>(imageAsset->renderImage()); + LITE_RTTI_CAST_OR_CONTINUE(renderImage, + AtlasRenderImage*, + imageAsset->renderImage()); if (m_atlases.empty()) {