| /* |
| * Copyright 2026 Rive |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "gm.hpp" |
| #include "gmutils.hpp" |
| #include "rive/renderer.hpp" |
| |
| #include "assets/nomoon.png.hpp" |
| |
| using namespace rivegm; |
| using namespace rive; |
| |
| // Interleaves additive draws (the "additiveness" paint value) with advanced |
| // blend modes so both features appear adjacent in draw order within one scene. |
| // |
| // In normal (synchronous shader) mode this exercises the batcher's rule that |
| // additive and advanced-blend draws never share a batch: the advanced draws |
| // force batch breaks around the additive sequences and everything renders |
| // correctly. |
| // |
| // Under --only_ubershaders every batch runs a fully-featured pipeline that |
| // compiles ENABLE_ADVANCED_BLEND, whose unmultiplied paint path does not |
| // decode the additive encodings. Comparing the right column (additive) |
| // against the left column (srcOver references with identical geometry) makes |
| // any ubershader divergence obvious. |
| // |
| // Layout: 2 columns x 4 rows of 300x300 cells on a gray background (gray so |
| // multiply visibly darkens and screen visibly brightens). The LEFT column is |
| // always pure srcOver and must stay bit-identical to the pre-additiveness |
| // master baseline forever; the RIGHT column is the additive variant. |
| // Row 0: solid RGB venn circles interleaved with multiply + screen squares. |
| // Left circles srcOver, right circles additive. |
| // Row 1: gradient alpha-ramp overlays interleaved with a difference square, |
| // plus a translucent solid on top of the advanced draw. |
| // Row 2: drawImage interleaved with multiply + hardLight squares. |
| // Row 3, left: additiveness must be ignored on advanced blend modes -- two |
| // multiply squares with additiveness 0 and 1 must render identically |
| // (and never change). |
| // Row 3, right: the additiveness sweep (0, .25, .5, .75, 1) with a multiply |
| // square drawn mid-sequence. |
| constexpr float CELL = 300.f; |
| |
| static void draw_rect(Renderer* renderer, |
| const AABB& rect, |
| ColorInt color, |
| BlendMode blend, |
| float additiveness) |
| { |
| Paint paint; |
| paint->color(color); |
| paint->blendMode(blend); |
| paint->additiveness(additiveness); |
| renderer->drawPath(PathBuilder::Rect(rect), paint); |
| } |
| |
| static void draw_circle(Renderer* renderer, |
| float cx, |
| float cy, |
| ColorInt color, |
| float additiveness) |
| { |
| Paint paint; |
| paint->color(color); |
| paint->additiveness(additiveness); |
| renderer->drawPath(PathBuilder::Circle(cx, cy, 80.f), paint); |
| } |
| |
| DEF_SIMPLE_GM_WITH_CLEAR_COLOR(additive_advanced_blend, |
| 0xff404040, |
| 600, |
| 1200, |
| renderer) |
| { |
| Factory* factory = TestingWindow::Get()->factory(); |
| constexpr float stops[2] = {0.f, 1.f}; |
| |
| // Row 0: solid venn circles with advanced-blend squares drawn between |
| // them. 'additiveness' selects srcOver (left cell) vs additive (right). |
| auto vennCell = [&](float ox, float additiveness) { |
| constexpr float oy = 0; |
| draw_circle(renderer, ox + 150.f, oy + 110.f, 0xffff0000, additiveness); |
| draw_rect(renderer, |
| {ox + 40.f, oy + 40.f, ox + 190.f, oy + 190.f}, |
| 0xff8080ff, |
| BlendMode::multiply, |
| 0.f); |
| draw_circle(renderer, ox + 110.f, oy + 185.f, 0xff00ff00, additiveness); |
| draw_rect(renderer, |
| {ox + 110.f, oy + 110.f, ox + 260.f, oy + 260.f}, |
| 0xff404080, |
| BlendMode::screen, |
| 0.f); |
| draw_circle(renderer, ox + 190.f, oy + 185.f, 0xff0000ff, additiveness); |
| }; |
| vennCell(0, 0.f); |
| vennCell(CELL, 1.f); |
| |
| // Row 1: gradient overlays sandwiching a difference-blended gradient |
| // square, with a translucent additive solid drawn last. |
| auto gradientCell = [&](float ox, float additiveness) { |
| constexpr float oy = CELL; |
| AABB base = {ox + 40.f, oy + 40.f, ox + 260.f, oy + 260.f}; |
| ColorInt baseColors[2] = {0xff00c0ff, 0xffff8000}; |
| Paint basePaint; |
| basePaint->shader(factory->makeLinearGradient(base.left(), |
| 0, |
| base.right(), |
| 0, |
| baseColors, |
| stops, |
| 2)); |
| renderer->drawPath(PathBuilder::Rect(base), basePaint); |
| |
| AABB overlay = {ox + 70.f, oy + 70.f, ox + 170.f, oy + 230.f}; |
| ColorInt overlayColors[2] = {0xffff0000, 0x00ff0000}; |
| Paint overlayPaint; |
| overlayPaint->shader(factory->makeLinearGradient(0, |
| overlay.top(), |
| 0, |
| overlay.bottom(), |
| overlayColors, |
| stops, |
| 2)); |
| overlayPaint->additiveness(additiveness); |
| renderer->drawPath(PathBuilder::Rect(overlay), overlayPaint); |
| |
| AABB diff = {ox + 130.f, oy + 100.f, ox + 230.f, oy + 200.f}; |
| ColorInt diffColors[2] = {0xffffffff, 0xff000000}; |
| Paint diffPaint; |
| diffPaint->shader(factory->makeLinearGradient(diff.left(), |
| 0, |
| diff.right(), |
| 0, |
| diffColors, |
| stops, |
| 2)); |
| diffPaint->blendMode(BlendMode::difference); |
| renderer->drawPath(PathBuilder::Rect(diff), diffPaint); |
| |
| draw_rect(renderer, |
| {ox + 100.f, oy + 160.f, ox + 260.f, oy + 240.f}, |
| 0x8000ff00, |
| BlendMode::srcOver, |
| additiveness); |
| }; |
| gradientCell(0, 0.f); |
| gradientCell(CELL, 1.f); |
| |
| // Row 2: drawImage interleaved with multiply + hardLight squares. |
| auto img = LoadImage(assets::nomoon_png()); |
| if (img != nullptr) |
| { |
| auto imageCell = [&](float ox, float additiveness) { |
| constexpr float oy = CELL * 2; |
| auto drawImageRect = |
| [&](const AABB& rect, float opacity, float imageAdditiveness) { |
| renderer->save(); |
| renderer->translate(rect.left(), rect.top()); |
| renderer->scale(rect.width() / img->width(), |
| rect.height() / img->height()); |
| renderer->drawImage(img.get(), |
| ImageSampler::LinearClamp(), |
| BlendMode::srcOver, |
| opacity, |
| imageAdditiveness); |
| renderer->restore(); |
| }; |
| drawImageRect({ox + 40.f, oy + 40.f, ox + 190.f, oy + 190.f}, |
| 1.f, |
| 0.f); |
| draw_rect(renderer, |
| {ox + 90.f, oy + 60.f, ox + 210.f, oy + 180.f}, |
| 0xffa0a0a0, |
| BlendMode::multiply, |
| 0.f); |
| drawImageRect({ox + 110.f, oy + 110.f, ox + 260.f, oy + 260.f}, |
| .7f, |
| additiveness); |
| draw_rect(renderer, |
| {ox + 60.f, oy + 170.f, ox + 180.f, oy + 270.f}, |
| 0xff808040, |
| BlendMode::hardLight, |
| 0.f); |
| }; |
| imageCell(0, 0.f); |
| imageCell(CELL, 1.f); |
| } |
| |
| // Row 3, left: these two multiply squares differ only in additiveness, |
| // which advanced blend modes ignore -- they must render identically, and |
| // this cell must never change. |
| { |
| constexpr float oy = CELL * 3; |
| draw_rect(renderer, |
| {40.f, oy + 40.f, 140.f, oy + 260.f}, |
| 0xff80c0ff, |
| BlendMode::multiply, |
| 0.f); |
| draw_rect(renderer, |
| {160.f, oy + 40.f, 260.f, oy + 260.f}, |
| 0xff80c0ff, |
| BlendMode::multiply, |
| 1.f); |
| |
| // Row 3, right: the additiveness sweep with a multiply square drawn |
| // in the middle of the strip sequence, forcing a batch break within |
| // the sweep. |
| draw_rect(renderer, |
| {CELL + 40.f, oy + 40.f, CELL + 190.f, oy + 190.f}, |
| 0xff00ff00, |
| BlendMode::srcOver, |
| 0.f); |
| constexpr int strips = 5; |
| AABB overlay = {CELL + 110.f, oy + 110.f, CELL + 260.f, oy + 260.f}; |
| for (int i = 0; i < strips; ++i) |
| { |
| if (i == 3) |
| { |
| draw_rect( |
| renderer, |
| {overlay.left(), oy + 180.f, overlay.right(), oy + 220.f}, |
| 0xff8080ff, |
| BlendMode::multiply, |
| 0.f); |
| } |
| AABB strip = {overlay.left() + overlay.width() * i / strips, |
| overlay.top(), |
| overlay.left() + overlay.width() * (i + 1) / strips, |
| overlay.bottom()}; |
| Paint paint; |
| paint->color(0xc0ff0000); |
| paint->additiveness(float(i) / (strips - 1)); |
| renderer->drawPath(PathBuilder::Rect(strip), paint); |
| } |
| } |
| } |