blob: 2e8d00e0d84d350debd2bca71419d767cd3d05ff [file]
/*
* 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);
}
}
}