blob: 7d589da407166f5fae745f7f8549b2fe64a63671 [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "rive/pls/pls.hpp"
#include "rive/pls/pls_render_target.hpp"
#include "shaders/constants.glsl"
#include "rive/pls/pls_image.hpp"
#include "pls_paint.hpp"
#include "generated/shaders/draw_path.exports.h"
namespace rive::pls
{
static_assert(kGradTextureWidth == GRAD_TEXTURE_WIDTH);
static_assert(kTessTextureWidth == TESS_TEXTURE_WIDTH);
static_assert(kTessTextureWidthLog2 == TESS_TEXTURE_WIDTH_LOG2);
uint32_t ShaderUniqueKey(DrawType drawType,
ShaderFeatures shaderFeatures,
InterlockMode interlockMode,
ShaderMiscFlags miscFlags)
{
if (miscFlags & ShaderMiscFlags::coalescedResolveAndTransfer)
{
assert(drawType == DrawType::plsAtomicResolve);
assert(shaderFeatures & ShaderFeatures::ENABLE_ADVANCED_BLEND);
assert(interlockMode == InterlockMode::atomics);
}
if (miscFlags & (ShaderMiscFlags::storeColorClear | ShaderMiscFlags::swizzleColorBGRAToRGBA))
{
assert(drawType == DrawType::plsAtomicInitialize);
}
uint32_t drawTypeKey;
switch (drawType)
{
case DrawType::midpointFanPatches:
case DrawType::outerCurvePatches:
drawTypeKey = 0;
break;
case DrawType::interiorTriangulation:
drawTypeKey = 1;
break;
case DrawType::imageRect:
drawTypeKey = 2;
break;
case DrawType::imageMesh:
drawTypeKey = 3;
break;
case DrawType::plsAtomicInitialize:
assert(interlockMode == pls::InterlockMode::atomics);
drawTypeKey = 4;
break;
case DrawType::plsAtomicResolve:
assert(interlockMode == pls::InterlockMode::atomics);
drawTypeKey = 5;
break;
case DrawType::stencilClipReset:
assert(interlockMode == pls::InterlockMode::depthStencil);
drawTypeKey = 6;
break;
}
uint32_t key = static_cast<uint32_t>(miscFlags);
assert(static_cast<uint32_t>(interlockMode) < 1 << 2);
key = (key << 2) | static_cast<uint32_t>(interlockMode);
key = (key << kShaderFeatureCount) |
(shaderFeatures & ShaderFeaturesMaskFor(drawType, interlockMode)).bits();
assert(drawTypeKey < 1 << 3);
key = (key << 3) | drawTypeKey;
return key;
}
const char* GetShaderFeatureGLSLName(ShaderFeatures feature)
{
switch (feature)
{
case ShaderFeatures::NONE:
RIVE_UNREACHABLE();
case ShaderFeatures::ENABLE_CLIPPING:
return GLSL_ENABLE_CLIPPING;
case ShaderFeatures::ENABLE_CLIP_RECT:
return GLSL_ENABLE_CLIP_RECT;
case ShaderFeatures::ENABLE_ADVANCED_BLEND:
return GLSL_ENABLE_ADVANCED_BLEND;
case ShaderFeatures::ENABLE_EVEN_ODD:
return GLSL_ENABLE_EVEN_ODD;
case ShaderFeatures::ENABLE_NESTED_CLIPPING:
return GLSL_ENABLE_NESTED_CLIPPING;
case ShaderFeatures::ENABLE_HSL_BLEND_MODES:
return GLSL_ENABLE_HSL_BLEND_MODES;
}
RIVE_UNREACHABLE();
}
constexpr static float pack_params(int32_t patchSegmentSpan, int32_t vertexType)
{
return static_cast<float>((patchSegmentSpan << 2) | vertexType);
}
static void generate_buffer_data_for_patch_type(PatchType patchType,
PatchVertex vertices[],
uint16_t indices[],
uint16_t baseVertex)
{
// AA border vertices. "Inner tessellation curves" have one more segment without a fan triangle
// whose purpose is to be a bowtie join.
size_t vertexCount = 0;
int32_t patchSegmentSpan = patchType == PatchType::midpointFan ? kMidpointFanPatchSegmentSpan
: kOuterCurvePatchSegmentSpan;
for (int32_t i = 0; i < patchSegmentSpan; ++i)
{
float params = pack_params(patchSegmentSpan, STROKE_VERTEX);
float l = static_cast<float>(i);
float r = l + 1;
if (patchType == PatchType::outerCurves)
{
vertices[vertexCount + 0].set(l, 0.f, .5f, params);
vertices[vertexCount + 1].set(l, 1.f, .0f, params);
vertices[vertexCount + 2].set(r, 0.f, .5f, params);
vertices[vertexCount + 3].set(r, 1.f, .0f, params);
// Give the vertex an alternate position when mirrored so the border has the same
// diagonals whether morrored or not.
vertices[vertexCount + 0].setMirroredPosition(r, 0.f, .5f);
vertices[vertexCount + 1].setMirroredPosition(l, 0.f, .5f);
vertices[vertexCount + 2].setMirroredPosition(r, 1.f, .0f);
vertices[vertexCount + 3].setMirroredPosition(l, 1.f, .0f);
}
else
{
assert(patchType == PatchType::midpointFan);
vertices[vertexCount + 0].set(l, -1.f, 1.f, params);
vertices[vertexCount + 1].set(l, +1.f, 0.f, params);
vertices[vertexCount + 2].set(r, -1.f, 1.f, params);
vertices[vertexCount + 3].set(r, +1.f, 0.f, params);
// Give the vertex an alternate position when mirrored so the border has the same
// diagonals whether morrored or not.
vertices[vertexCount + 0].setMirroredPosition(r - 1.f, -1.f, 1.f);
vertices[vertexCount + 1].setMirroredPosition(l - 1.f, -1.f, 1.f);
vertices[vertexCount + 2].setMirroredPosition(r - 1.f, +1.f, 0.f);
vertices[vertexCount + 3].setMirroredPosition(l - 1.f, +1.f, 0.f);
}
vertexCount += 4;
}
// Bottom (negative coverage) side of the AA border.
if (patchType == PatchType::outerCurves)
{
float params = pack_params(patchSegmentSpan, STROKE_VERTEX);
for (int i = 0; i < patchSegmentSpan; ++i)
{
float l = static_cast<float>(i);
float r = l + 1;
vertices[vertexCount + 0].set(l, -.0f, .5f, params);
vertices[vertexCount + 1].set(r, -.0f, .5f, params);
vertices[vertexCount + 2].set(l, -1.f, .0f, params);
vertices[vertexCount + 3].set(r, -1.f, .0f, params);
// Give the vertex an alternate position when mirrored so the border has the same
// diagonals whether morrored or not.
vertices[vertexCount + 0].setMirroredPosition(r, -0.f, .5f);
vertices[vertexCount + 1].setMirroredPosition(r, -1.f, .0f);
vertices[vertexCount + 2].setMirroredPosition(l, -0.f, .5f);
vertices[vertexCount + 3].setMirroredPosition(l, -1.f, .0f);
vertexCount += 4;
}
}
// Triangle fan vertices. (These only touch the first "fanSegmentSpan" segments on inner
// tessellation curves.
size_t fanVerticesIdx = vertexCount;
size_t fanSegmentSpan =
patchType == PatchType::midpointFan ? patchSegmentSpan : patchSegmentSpan - 1;
assert((fanSegmentSpan & (fanSegmentSpan - 1)) == 0); // The fan must be a power of two.
for (int i = 0; i <= fanSegmentSpan; ++i)
{
float params = pack_params(patchSegmentSpan, FAN_VERTEX);
if (patchType == PatchType::outerCurves)
{
vertices[vertexCount].set(static_cast<float>(i), 0.f, 1, params);
}
else
{
vertices[vertexCount].set(static_cast<float>(i), -1.f, 1, params);
vertices[vertexCount].setMirroredPosition(static_cast<float>(i) - 1, -1.f, 1);
}
++vertexCount;
}
// The midpoint vertex is only included on midpoint fan patches.
size_t midpointIdx = vertexCount;
if (patchType == PatchType::midpointFan)
{
vertices[vertexCount++].set(0, 0, 1, pack_params(patchSegmentSpan, FAN_MIDPOINT_VERTEX));
}
assert(vertexCount == (patchType == PatchType::outerCurves ? kOuterCurvePatchVertexCount
: kMidpointFanPatchVertexCount));
// AA border indices.
constexpr static size_t kBorderPatternVertexCount = 4;
constexpr static size_t kBorderPatternIndexCount = 6;
constexpr static uint16_t kBorderPattern[kBorderPatternIndexCount] = {0, 1, 2, 2, 1, 3};
constexpr static uint16_t kNegativeBorderPattern[kBorderPatternIndexCount] = {0, 2, 1, 1, 2, 3};
size_t indexCount = 0;
size_t borderEdgeVerticesIdx = 0;
for (size_t borderSegmentIdx = 0; borderSegmentIdx < patchSegmentSpan; ++borderSegmentIdx)
{
for (size_t i = 0; i < kBorderPatternIndexCount; ++i)
{
indices[indexCount++] = baseVertex + borderEdgeVerticesIdx + kBorderPattern[i];
}
borderEdgeVerticesIdx += kBorderPatternVertexCount;
}
// Bottom (negative coverage) side of the AA border.
if (patchType == PatchType::outerCurves)
{
for (size_t borderSegmentIdx = 0; borderSegmentIdx < patchSegmentSpan; ++borderSegmentIdx)
{
for (size_t i = 0; i < kBorderPatternIndexCount; ++i)
{
indices[indexCount++] =
baseVertex + borderEdgeVerticesIdx + kNegativeBorderPattern[i];
}
borderEdgeVerticesIdx += kBorderPatternVertexCount;
}
assert(indexCount == kOuterCurvePatchBorderIndexCount);
}
else
{
assert(indexCount == kMidpointFanPatchBorderIndexCount);
}
assert(borderEdgeVerticesIdx == fanVerticesIdx);
// Triangle fan indices, in a middle-out topology.
// Don't include the final bowtie join if this is an "outerStroke" patch. (i.e., use
// fanSegmentSpan and not "patchSegmentSpan".)
for (int step = 1; step < fanSegmentSpan; step <<= 1)
{
for (int i = 0; i < fanSegmentSpan; i += step * 2)
{
indices[indexCount++] = fanVerticesIdx + i + baseVertex;
indices[indexCount++] = fanVerticesIdx + i + step + baseVertex;
indices[indexCount++] = fanVerticesIdx + i + step * 2 + baseVertex;
}
}
if (patchType == PatchType::midpointFan)
{
// Triangle to the contour midpoint.
indices[indexCount++] = fanVerticesIdx + baseVertex;
indices[indexCount++] = fanVerticesIdx + fanSegmentSpan + baseVertex;
indices[indexCount++] = midpointIdx + baseVertex;
assert(indexCount == kMidpointFanPatchIndexCount);
}
else
{
assert(patchType == PatchType::outerCurves);
assert(indexCount == kOuterCurvePatchIndexCount);
}
}
void GeneratePatchBufferData(PatchVertex vertices[kPatchVertexBufferCount],
uint16_t indices[kPatchIndexBufferCount])
{
generate_buffer_data_for_patch_type(PatchType::midpointFan, vertices, indices, 0);
generate_buffer_data_for_patch_type(PatchType::outerCurves,
vertices + kMidpointFanPatchVertexCount,
indices + kMidpointFanPatchIndexCount,
kMidpointFanPatchVertexCount);
}
void ClipRectInverseMatrix::reset(const Mat2D& clipMatrix, const AABB& clipRect)
{
// Find the matrix that transforms from pixel space to "normalized clipRect space", where the
// clipRect is the normalized rectangle: [-1, -1, +1, +1].
Mat2D m = clipMatrix * Mat2D(clipRect.width() * .5f,
0,
0,
clipRect.height() * .5f,
clipRect.center().x,
clipRect.center().y);
if (clipRect.width() <= 0 || clipRect.height() <= 0 || !m.invert(&m_inverseMatrix))
{
// If the width or height went zero or negative, or if "m" is non-invertible, clip away
// everything.
*this = Empty();
}
}
static uint32_t paint_type_to_glsl_id(PaintType paintType)
{
return static_cast<uint32_t>(paintType);
static_assert((int)PaintType::solidColor == SOLID_COLOR_PAINT_TYPE);
static_assert((int)PaintType::linearGradient == LINEAR_GRADIENT_PAINT_TYPE);
static_assert((int)PaintType::radialGradient == RADIAL_GRADIENT_PAINT_TYPE);
static_assert((int)PaintType::image == IMAGE_PAINT_TYPE);
static_assert((int)PaintType::clipUpdate == CLIP_UPDATE_PAINT_TYPE);
}
uint32_t ConvertBlendModeToPLSBlendMode(BlendMode riveMode)
{
switch (riveMode)
{
case BlendMode::srcOver:
return BLEND_SRC_OVER;
case BlendMode::screen:
return BLEND_MODE_SCREEN;
case BlendMode::overlay:
return BLEND_MODE_OVERLAY;
case BlendMode::darken:
return BLEND_MODE_DARKEN;
case BlendMode::lighten:
return BLEND_MODE_LIGHTEN;
case BlendMode::colorDodge:
return BLEND_MODE_COLORDODGE;
case BlendMode::colorBurn:
return BLEND_MODE_COLORBURN;
case BlendMode::hardLight:
return BLEND_MODE_HARDLIGHT;
case BlendMode::softLight:
return BLEND_MODE_SOFTLIGHT;
case BlendMode::difference:
return BLEND_MODE_DIFFERENCE;
case BlendMode::exclusion:
return BLEND_MODE_EXCLUSION;
case BlendMode::multiply:
return BLEND_MODE_MULTIPLY;
case BlendMode::hue:
return BLEND_MODE_HUE;
case BlendMode::saturation:
return BLEND_MODE_SATURATION;
case BlendMode::color:
return BLEND_MODE_COLOR;
case BlendMode::luminosity:
return BLEND_MODE_LUMINOSITY;
}
RIVE_UNREACHABLE();
}
FlushUniforms::InverseViewports::InverseViewports(const FlushDescriptor& flushDesc,
const PlatformFeatures& platformFeatures)
{
float4 numerators = 2;
if (platformFeatures.invertOffscreenY)
{
numerators.xy = -numerators.xy;
}
if (platformFeatures.uninvertOnScreenY)
{
numerators.w = -numerators.w;
}
float4 vals = numerators / float4{static_cast<float>(flushDesc.complexGradRowsHeight),
static_cast<float>(flushDesc.tessDataHeight),
static_cast<float>(flushDesc.renderTarget->width()),
static_cast<float>(flushDesc.renderTarget->height())};
m_vals[0] = vals[0];
m_vals[1] = vals[1];
m_vals[2] = vals[2];
m_vals[3] = vals[3];
}
FlushUniforms::FlushUniforms(const FlushDescriptor& flushDesc,
const PlatformFeatures& platformFeatures) :
m_inverseViewports(flushDesc, platformFeatures),
m_renderTargetWidth(flushDesc.renderTarget->width()),
m_renderTargetHeight(flushDesc.renderTarget->height()),
m_colorClearValue(SwizzleRiveColorToRGBA(flushDesc.clearColor)),
m_coverageClearValue(flushDesc.coverageClearValue),
m_renderTargetUpdateBounds(flushDesc.renderTargetUpdateBounds),
m_pathIDGranularity(platformFeatures.pathIDGranularity)
{}
static void write_matrix(volatile float* dst, const Mat2D& matrix)
{
const float* vals = matrix.values();
for (size_t i = 0; i < 6; ++i)
{
dst[i] = vals[i];
}
}
void PathData::set(const Mat2D& m, float strokeRadius, uint32_t zIndex)
{
write_matrix(m_matrix, m);
m_strokeRadius = strokeRadius; // 0 if the path is filled.
m_zIndex = zIndex;
}
void PaintData::set(FillRule fillRule,
PaintType paintType,
SimplePaintValue simplePaintValue,
GradTextureLayout gradTextureLayout,
uint32_t clipID,
bool hasClipRect,
BlendMode blendMode)
{
uint32_t shiftedClipID = clipID << 16;
uint32_t shiftedBlendMode = ConvertBlendModeToPLSBlendMode(blendMode) << 4;
uint32_t localParams = paint_type_to_glsl_id(paintType);
switch (paintType)
{
case PaintType::solidColor:
{
// Swizzle the riveColor to little-endian RGBA (the order expected by GLSL).
m_color = SwizzleRiveColorToRGBA(simplePaintValue.color);
localParams |= shiftedClipID | shiftedBlendMode;
break;
}
case PaintType::linearGradient:
case PaintType::radialGradient:
{
uint32_t row = simplePaintValue.colorRampLocation.row;
if (simplePaintValue.colorRampLocation.isComplex())
{
// Complex gradients rows are offset after the simple gradients.
row += gradTextureLayout.complexOffsetY;
}
m_gradTextureY = (static_cast<float>(row) + .5f) * gradTextureLayout.inverseHeight;
localParams |= shiftedClipID | shiftedBlendMode;
break;
}
case PaintType::image:
{
m_opacity = simplePaintValue.imageOpacity;
localParams |= shiftedClipID | shiftedBlendMode;
break;
}
case PaintType::clipUpdate:
{
m_shiftedClipReplacementID = shiftedClipID;
localParams |= simplePaintValue.outerClipID << 16;
break;
}
}
if (fillRule == FillRule::evenOdd)
{
localParams |= PAINT_FLAG_EVEN_ODD;
}
if (hasClipRect)
{
localParams |= PAINT_FLAG_HAS_CLIP_RECT;
}
m_params = localParams;
}
void PaintAuxData::set(const Mat2D& viewMatrix,
PaintType paintType,
SimplePaintValue simplePaintValue,
const PLSGradient* gradient,
const PLSTexture* imageTexture,
const ClipRectInverseMatrix* clipRectInverseMatrix,
const PLSRenderTarget* renderTarget,
const pls::PlatformFeatures& platformFeatures)
{
switch (paintType)
{
case PaintType::solidColor:
{
break;
}
case PaintType::linearGradient:
case PaintType::radialGradient:
case PaintType::image:
{
Mat2D paintMatrix;
viewMatrix.invert(&paintMatrix);
if (platformFeatures.fragCoordBottomUp)
{
// Flip _fragCoord.y.
paintMatrix = paintMatrix * Mat2D(1, 0, 0, -1, 0, renderTarget->height());
}
if (paintType == PaintType::image)
{
uint64_t bindlessTextureHandle = imageTexture->bindlessTextureHandle();
m_bindlessTextureHandle[0] = static_cast<uint32_t>(bindlessTextureHandle);
m_bindlessTextureHandle[1] = bindlessTextureHandle >> 32;
}
else
{
assert(gradient != nullptr);
const float* gradCoeffs = gradient->coeffs();
if (paintType == PaintType::linearGradient)
{
paintMatrix =
Mat2D(gradCoeffs[0], 0, gradCoeffs[1], 0, gradCoeffs[2], 0) * paintMatrix;
}
else
{
assert(paintType == PaintType::radialGradient);
float w = 1 / gradCoeffs[2];
paintMatrix =
Mat2D(w, 0, 0, w, -gradCoeffs[0] * w, -gradCoeffs[1] * w) * paintMatrix;
}
float left, right;
if (simplePaintValue.colorRampLocation.isComplex())
{
left = 0;
right = kGradTextureWidth;
}
else
{
left = simplePaintValue.colorRampLocation.col;
right = left + 2;
}
m_gradTextureHorizontalSpan[0] = (right - left - 1) * GRAD_TEXTURE_INVERSE_WIDTH;
m_gradTextureHorizontalSpan[1] = (left + .5f) * GRAD_TEXTURE_INVERSE_WIDTH;
}
write_matrix(m_matrix, paintMatrix);
break;
}
case PaintType::clipUpdate:
{
break;
}
}
if (clipRectInverseMatrix != nullptr)
{
Mat2D m = clipRectInverseMatrix->inverseMatrix();
if (platformFeatures.fragCoordBottomUp)
{
// Flip _fragCoord.y.
m = m * Mat2D(1, 0, 0, -1, 0, renderTarget->height());
}
write_matrix(m_clipRectInverseMatrix, m);
m_inverseFwidth.x = -1.f / (fabsf(m.xx()) + fabsf(m.xy()));
m_inverseFwidth.y = -1.f / (fabsf(m.yx()) + fabsf(m.yy()));
}
else
{
write_matrix(m_clipRectInverseMatrix, ClipRectInverseMatrix::WideOpen().inverseMatrix());
m_inverseFwidth.x = 0;
m_inverseFwidth.y = 0;
}
}
ImageDrawUniforms::ImageDrawUniforms(const Mat2D& matrix,
float opacity,
const ClipRectInverseMatrix* clipRectInverseMatrix,
uint32_t clipID,
BlendMode blendMode,
uint32_t zIndex)
{
write_matrix(m_matrix, matrix);
m_opacity = opacity;
write_matrix(m_clipRectInverseMatrix,
clipRectInverseMatrix != nullptr
? clipRectInverseMatrix->inverseMatrix()
: ClipRectInverseMatrix::WideOpen().inverseMatrix());
m_clipID = clipID;
m_blendMode = ConvertBlendModeToPLSBlendMode(blendMode);
m_zIndex = zIndex;
}
std::tuple<uint32_t, uint32_t> StorageTextureSize(size_t bufferSizeInBytes,
StorageBufferStructure bufferStructure)
{
assert(bufferSizeInBytes % pls::StorageBufferElementSizeInBytes(bufferStructure) == 0);
uint32_t elementCount = math::lossless_numeric_cast<uint32_t>(bufferSizeInBytes) /
pls::StorageBufferElementSizeInBytes(bufferStructure);
uint32_t height = (elementCount + STORAGE_TEXTURE_WIDTH - 1) / STORAGE_TEXTURE_WIDTH;
// PLSRenderContext is responsible for breaking up a flush before any storage buffer grows
// larger than can be supported by a GL texture of width "STORAGE_TEXTURE_WIDTH".
// (2048 is the min required value for GL_MAX_TEXTURE_SIZE.)
constexpr int kMaxRequredTextureHeight RIVE_MAYBE_UNUSED = 2048;
assert(height <= kMaxRequredTextureHeight);
uint32_t width = std::min<uint32_t>(elementCount, STORAGE_TEXTURE_WIDTH);
return {width, height};
}
size_t StorageTextureBufferSize(size_t bufferSizeInBytes, StorageBufferStructure bufferStructure)
{
// The polyfill texture needs to be updated in entire rows at a time. Extend the buffer's length
// to be able to service a worst-case scenario.
return bufferSizeInBytes +
(STORAGE_TEXTURE_WIDTH - 1) * pls::StorageBufferElementSizeInBytes(bufferStructure);
}
float FindTransformedArea(const AABB& bounds, const Mat2D& matrix)
{
Vec2D pts[4] = {{bounds.left(), bounds.top()},
{bounds.right(), bounds.top()},
{bounds.right(), bounds.bottom()},
{bounds.left(), bounds.bottom()}};
Vec2D screenSpacePts[4];
matrix.mapPoints(screenSpacePts, pts, 4);
Vec2D v[3] = {screenSpacePts[1] - screenSpacePts[0],
screenSpacePts[2] - screenSpacePts[0],
screenSpacePts[3] - screenSpacePts[0]};
return (fabsf(Vec2D::cross(v[0], v[1])) + fabsf(Vec2D::cross(v[1], v[2]))) * .5f;
}
} // namespace rive::pls