| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #include "rive/renderer/gpu.hpp" |
| |
| #include "rive/renderer/render_target.hpp" |
| #include "shaders/constants.glsl" |
| #include "rive/renderer/texture.hpp" |
| #include "rive_render_paint.hpp" |
| #include "gradient.hpp" |
| |
| #include "generated/shaders/draw_path.exports.h" |
| |
| namespace rive::gpu |
| { |
| 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::atomicResolve); |
| assert(shaderFeatures & ShaderFeatures::ENABLE_ADVANCED_BLEND); |
| assert(interlockMode == InterlockMode::atomics); |
| } |
| if (miscFlags & (ShaderMiscFlags::storeColorClear | |
| ShaderMiscFlags::swizzleColorBGRAToRGBA)) |
| { |
| assert(drawType == DrawType::atomicInitialize); |
| } |
| 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::atomicInitialize: |
| assert(interlockMode == gpu::InterlockMode::atomics); |
| drawTypeKey = 4; |
| break; |
| case DrawType::atomicResolve: |
| assert(interlockMode == gpu::InterlockMode::atomics); |
| drawTypeKey = 5; |
| break; |
| case DrawType::stencilClipReset: |
| assert(interlockMode == gpu::InterlockMode::msaa); |
| 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::clipUpdate == CLIP_UPDATE_PAINT_TYPE); |
| 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); |
| } |
| |
| 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(); |
| } |
| |
| uint32_t SwizzleRiveColorToRGBAPremul(ColorInt riveColor) |
| { |
| uint4 rgba = (rive::uint4(riveColor) >> uint4{16, 8, 0, 24}) & 0xffu; |
| uint32_t alpha = rgba.w; |
| rgba.w = 255; |
| uint4 premul = rgba * alpha / 255; |
| return simd::reduce_or(premul << uint4{0, 8, 16, 24}); |
| } |
| |
| 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.gradDataHeight), |
| 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(SwizzleRiveColorToRGBAPremul(flushDesc.clearColor)), |
| m_coverageClearValue(flushDesc.coverageClearValue), |
| m_renderTargetUpdateBounds(flushDesc.renderTargetUpdateBounds), |
| m_coverageBufferPrefix(flushDesc.coverageBufferPrefix), |
| 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 PathData::set(const Mat2D& m, |
| float strokeRadius, |
| uint32_t zIndex, |
| const CoverageBufferRange& coverageBufferRange) |
| { |
| set(m, strokeRadius, zIndex); |
| m_coverageBufferRange.offset = coverageBufferRange.offset; |
| m_coverageBufferRange.pitch = coverageBufferRange.pitch; |
| m_coverageBufferRange.offsetX = coverageBufferRange.offsetX; |
| m_coverageBufferRange.offsetY = coverageBufferRange.offsetY; |
| } |
| |
| void PaintData::set(DrawContents singleDrawContents, |
| 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 (singleDrawContents & gpu::DrawContents::nonZeroFill) |
| { |
| localParams |= PAINT_FLAG_NON_ZERO_FILL; |
| } |
| else if (singleDrawContents & gpu::DrawContents::evenOddFill) |
| { |
| localParams |= PAINT_FLAG_EVEN_ODD_FILL; |
| } |
| if (hasClipRect) |
| { |
| localParams |= PAINT_FLAG_HAS_CLIP_RECT; |
| } |
| m_params = localParams; |
| } |
| |
| void PaintAuxData::set(const Mat2D& viewMatrix, |
| PaintType paintType, |
| SimplePaintValue simplePaintValue, |
| const Gradient* gradient, |
| const Texture* imageTexture, |
| const ClipRectInverseMatrix* clipRectInverseMatrix, |
| const RenderTarget* renderTarget, |
| const gpu::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) |
| { |
| // Since we don't use perspective transformations, the image |
| // mipmap level-of-detail is constant throughout the entire |
| // path. Compute it ahead of time here. |
| float dudx = paintMatrix.xx() * imageTexture->width(); |
| float dudy = paintMatrix.yx() * imageTexture->height(); |
| float dvdx = paintMatrix.xy() * imageTexture->width(); |
| float dvdy = paintMatrix.yy() * imageTexture->height(); |
| float maxScaleFactorPow2 = std::max(dudx * dudx + dvdx * dvdx, |
| dudy * dudy + dvdy * dvdy); |
| // Instead of finding sqrt(maxScaleFactorPow2), just multiply |
| // the log by .5. |
| m_imageTextureLOD = |
| log2f(std::max(maxScaleFactorPow2, 1.f)) * .5f; |
| } |
| 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 % |
| gpu::StorageBufferElementSizeInBytes(bufferStructure) == |
| 0); |
| uint32_t elementCount = |
| math::lossless_numeric_cast<uint32_t>(bufferSizeInBytes) / |
| gpu::StorageBufferElementSizeInBytes(bufferStructure); |
| uint32_t height = |
| (elementCount + STORAGE_TEXTURE_WIDTH - 1) / STORAGE_TEXTURE_WIDTH; |
| // RenderContext 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) * |
| gpu::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::gpu |