/*
* Copyright 2014 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gl/GrGLPathRendering.h"
#include "gl/GrGLUtil.h"
#include "gl/GrGLGpu.h"
#include "GrGLPath.h"
#include "GrGLPathRange.h"
#include "GrGLPathRendering.h"
#include "SkStream.h"
#include "SkTypeface.h"
#define GL_CALL(X) GR_GL_CALL(this->gpu()->glInterface(), X)
#define GL_CALL_RET(RET, X) GR_GL_CALL_RET(this->gpu()->glInterface(), RET, X)
// Number of paths to allocate per glGenPaths call. The call can be overly slow on command buffer GL
// implementation. The call has a result value, and thus waiting for the call completion is needed.
static const GrGLsizei kPathIDPreallocationAmount = 65536;
static const GrGLenum gIndexType2GLType[] = {
GR_GL_UNSIGNED_BYTE,
GR_GL_UNSIGNED_SHORT,
GR_GL_UNSIGNED_INT
};
GR_STATIC_ASSERT(0 == GrPathRange::kU8_PathIndexType);
GR_STATIC_ASSERT(1 == GrPathRange::kU16_PathIndexType);
GR_STATIC_ASSERT(2 == GrPathRange::kU32_PathIndexType);
GR_STATIC_ASSERT(GrPathRange::kU32_PathIndexType == GrPathRange::kLast_PathIndexType);
static const GrGLenum gXformType2GLType[] = {
GR_GL_NONE,
GR_GL_TRANSLATE_X,
GR_GL_TRANSLATE_Y,
GR_GL_TRANSLATE_2D,
GR_GL_TRANSPOSE_AFFINE_2D
};
GR_STATIC_ASSERT(0 == GrPathRendering::kNone_PathTransformType);
GR_STATIC_ASSERT(1 == GrPathRendering::kTranslateX_PathTransformType);
GR_STATIC_ASSERT(2 == GrPathRendering::kTranslateY_PathTransformType);
GR_STATIC_ASSERT(3 == GrPathRendering::kTranslate_PathTransformType);
GR_STATIC_ASSERT(4 == GrPathRendering::kAffine_PathTransformType);
GR_STATIC_ASSERT(GrPathRendering::kAffine_PathTransformType == GrPathRendering::kLast_PathTransformType);
#ifdef SK_DEBUG
static const GrGLenum gXformType2ComponentCount[] = {
0,
1,
1,
2,
6
};
static void verify_floats(const float* floats, int count) {
for (int i = 0; i < count; ++i) {
SkASSERT(!SkScalarIsNaN(SkFloatToScalar(floats[i])));
}
}
#endif
static GrGLenum gr_stencil_op_to_gl_path_rendering_fill_mode(GrStencilOp op) {
switch (op) {
default:
SK_ABORT("Unexpected path fill.");
/* fallthrough */;
case GrStencilOp::kIncWrap:
return GR_GL_COUNT_UP;
case GrStencilOp::kInvert:
return GR_GL_INVERT;
}
}
GrGLPathRendering::GrGLPathRendering(GrGLGpu* gpu)
: GrPathRendering(gpu)
, fPreallocatedPathCount(0) {
const GrGLInterface* glInterface = gpu->glInterface();
fCaps.bindFragmentInputSupport = (bool)glInterface->fFunctions.fBindFragmentInputLocation;
}
GrGLPathRendering::~GrGLPathRendering() {
if (fPreallocatedPathCount > 0) {
this->deletePaths(fFirstPreallocatedPathID, fPreallocatedPathCount);
}
}
void GrGLPathRendering::disconnect(GrGpu::DisconnectType type) {
if (GrGpu::DisconnectType::kCleanup == type) {
this->deletePaths(fFirstPreallocatedPathID, fPreallocatedPathCount);
};
fPreallocatedPathCount = 0;
}
void GrGLPathRendering::resetContext() {
fHWProjectionMatrixState.invalidate();
// we don't use the model view matrix.
GL_CALL(MatrixLoadIdentity(GR_GL_PATH_MODELVIEW));
fHWPathStencilSettings.invalidate();
}
sk_sp<GrPath> GrGLPathRendering::createPath(const SkPath& inPath, const GrStyle& style) {
return sk_make_sp<GrGLPath>(this->gpu(), inPath, style);
}
sk_sp<GrPathRange> GrGLPathRendering::createPathRange(GrPathRange::PathGenerator* pathGenerator,
const GrStyle& style) {
return sk_make_sp<GrGLPathRange>(this->gpu(), pathGenerator, style);
}
void GrGLPathRendering::onStencilPath(const StencilPathArgs& args, const GrPath* path) {
GrGLGpu* gpu = this->gpu();
SkASSERT(gpu->caps()->shaderCaps()->pathRenderingSupport());
gpu->flushColorWrite(false);
GrGLRenderTarget* rt = static_cast<GrGLRenderTarget*>(args.fProxy->priv().peekRenderTarget());
SkISize size = SkISize::Make(rt->width(), rt->height());
this->setProjectionMatrix(*args.fViewMatrix, size, args.fProxy->origin());
gpu->flushScissor(*args.fScissor, rt->getViewport(), args.fProxy->origin());
gpu->flushHWAAState(rt, args.fUseHWAA, true);
gpu->flushRenderTarget(rt);
const GrGLPath* glPath = static_cast<const GrGLPath*>(path);
this->flushPathStencilSettings(*args.fStencil);
SkASSERT(!fHWPathStencilSettings.isTwoSided());
GrGLenum fillMode =
gr_stencil_op_to_gl_path_rendering_fill_mode(fHWPathStencilSettings.front().fPassOp);
GrGLint writeMask = fHWPathStencilSettings.front().fWriteMask;
if (glPath->shouldFill()) {
GL_CALL(StencilFillPath(glPath->pathID(), fillMode, writeMask));
}
if (glPath->shouldStroke()) {
GL_CALL(StencilStrokePath(glPath->pathID(), 0xffff, writeMask));
}
}
void GrGLPathRendering::onDrawPath(const GrPipeline& pipeline,
const GrPrimitiveProcessor& primProc,
const GrStencilSettings& stencilPassSettings,
const GrPath* path) {
if (!this->gpu()->flushGLState(pipeline, primProc, false)) {
return;
}
const GrGLPath* glPath = static_cast<const GrGLPath*>(path);
this->flushPathStencilSettings(stencilPassSettings);
SkASSERT(!fHWPathStencilSettings.isTwoSided());
GrGLenum fillMode =
gr_stencil_op_to_gl_path_rendering_fill_mode(fHWPathStencilSettings.front().fPassOp);
GrGLint writeMask = fHWPathStencilSettings.front().fWriteMask;
if (glPath->shouldStroke()) {
if (glPath->shouldFill()) {
GL_CALL(StencilFillPath(glPath->pathID(), fillMode, writeMask));
}
GL_CALL(StencilThenCoverStrokePath(glPath->pathID(), 0xffff, writeMask,
GR_GL_BOUNDING_BOX));
} else {
GL_CALL(StencilThenCoverFillPath(glPath->pathID(), fillMode, writeMask,
GR_GL_BOUNDING_BOX));
}
}
void GrGLPathRendering::onDrawPaths(const GrPipeline& pipeline,
const GrPrimitiveProcessor& primProc,
const GrStencilSettings& stencilPassSettings,
const GrPathRange* pathRange, const void* indices,
PathIndexType indexType, const float transformValues[],
PathTransformType transformType, int count) {
SkDEBUGCODE(verify_floats(transformValues, gXformType2ComponentCount[transformType] * count));
if (!this->gpu()->flushGLState(pipeline, primProc, false)) {
return;
}
this->flushPathStencilSettings(stencilPassSettings);
SkASSERT(!fHWPathStencilSettings.isTwoSided());
const GrGLPathRange* glPathRange = static_cast<const GrGLPathRange*>(pathRange);
GrGLenum fillMode =
gr_stencil_op_to_gl_path_rendering_fill_mode(fHWPathStencilSettings.front().fPassOp);
GrGLint writeMask = fHWPathStencilSettings.front().fWriteMask;
if (glPathRange->shouldStroke()) {
if (glPathRange->shouldFill()) {
GL_CALL(StencilFillPathInstanced(
count, gIndexType2GLType[indexType], indices, glPathRange->basePathID(),
fillMode, writeMask, gXformType2GLType[transformType],
transformValues));
}
GL_CALL(StencilThenCoverStrokePathInstanced(
count, gIndexType2GLType[indexType], indices, glPathRange->basePathID(),
0xffff, writeMask, GR_GL_BOUNDING_BOX_OF_BOUNDING_BOXES,
gXformType2GLType[transformType], transformValues));
} else {
GL_CALL(StencilThenCoverFillPathInstanced(
count, gIndexType2GLType[indexType], indices, glPathRange->basePathID(),
fillMode, writeMask, GR_GL_BOUNDING_BOX_OF_BOUNDING_BOXES,
gXformType2GLType[transformType], transformValues));
}
}
void GrGLPathRendering::setProgramPathFragmentInputTransform(GrGLuint program, GrGLint location,
GrGLenum genMode, GrGLint components,
const SkMatrix& matrix) {
float coefficients[3 * 3];
SkASSERT(components >= 1 && components <= 3);
coefficients[0] = SkScalarToFloat(matrix[SkMatrix::kMScaleX]);
coefficients[1] = SkScalarToFloat(matrix[SkMatrix::kMSkewX]);
coefficients[2] = SkScalarToFloat(matrix[SkMatrix::kMTransX]);
if (components >= 2) {
coefficients[3] = SkScalarToFloat(matrix[SkMatrix::kMSkewY]);
coefficients[4] = SkScalarToFloat(matrix[SkMatrix::kMScaleY]);
coefficients[5] = SkScalarToFloat(matrix[SkMatrix::kMTransY]);
}
if (components >= 3) {
coefficients[6] = SkScalarToFloat(matrix[SkMatrix::kMPersp0]);
coefficients[7] = SkScalarToFloat(matrix[SkMatrix::kMPersp1]);
coefficients[8] = SkScalarToFloat(matrix[SkMatrix::kMPersp2]);
}
SkDEBUGCODE(verify_floats(coefficients, components * 3));
GL_CALL(ProgramPathFragmentInputGen(program, location, genMode, components, coefficients));
}
void GrGLPathRendering::setProjectionMatrix(const SkMatrix& matrix,
const SkISize& renderTargetSize,
GrSurfaceOrigin renderTargetOrigin) {
SkASSERT(this->gpu()->glCaps().shaderCaps()->pathRenderingSupport());
if (renderTargetOrigin == fHWProjectionMatrixState.fRenderTargetOrigin &&
renderTargetSize == fHWProjectionMatrixState.fRenderTargetSize &&
matrix.cheapEqualTo(fHWProjectionMatrixState.fViewMatrix)) {
return;
}
fHWProjectionMatrixState.fViewMatrix = matrix;
fHWProjectionMatrixState.fRenderTargetSize = renderTargetSize;
fHWProjectionMatrixState.fRenderTargetOrigin = renderTargetOrigin;
float glMatrix[4 * 4];
fHWProjectionMatrixState.getRTAdjustedGLMatrix<4>(glMatrix);
SkDEBUGCODE(verify_floats(glMatrix, SK_ARRAY_COUNT(glMatrix)));
GL_CALL(MatrixLoadf(GR_GL_PATH_PROJECTION, glMatrix));
}
GrGLuint GrGLPathRendering::genPaths(GrGLsizei range) {
SkASSERT(range > 0);
GrGLuint firstID;
if (fPreallocatedPathCount >= range) {
firstID = fFirstPreallocatedPathID;
fPreallocatedPathCount -= range;
fFirstPreallocatedPathID += range;
return firstID;
}
// Allocate range + the amount to fill up preallocation amount. If succeed, either join with
// the existing preallocation range or delete the existing and use the new (potentially partial)
// preallocation range.
GrGLsizei allocAmount = range + (kPathIDPreallocationAmount - fPreallocatedPathCount);
if (allocAmount >= range) {
GL_CALL_RET(firstID, GenPaths(allocAmount));
if (firstID != 0) {
if (fPreallocatedPathCount > 0 &&
firstID == fFirstPreallocatedPathID + fPreallocatedPathCount) {
firstID = fFirstPreallocatedPathID;
fPreallocatedPathCount += allocAmount - range;
fFirstPreallocatedPathID += range;
return firstID;
}
if (allocAmount > range) {
if (fPreallocatedPathCount > 0) {
this->deletePaths(fFirstPreallocatedPathID, fPreallocatedPathCount);
}
fFirstPreallocatedPathID = firstID + range;
fPreallocatedPathCount = allocAmount - range;
}
// Special case: if allocAmount == range, we have full preallocated range.
return firstID;
}
}
// Failed to allocate with preallocation. Remove existing preallocation and try to allocate just
// the range.
if (fPreallocatedPathCount > 0) {
this->deletePaths(fFirstPreallocatedPathID, fPreallocatedPathCount);
fPreallocatedPathCount = 0;
}
GL_CALL_RET(firstID, GenPaths(range));
if (firstID == 0) {
SkDebugf("Warning: Failed to allocate path\n");
}
return firstID;
}
void GrGLPathRendering::deletePaths(GrGLuint path, GrGLsizei range) {
GL_CALL(DeletePaths(path, range));
}
void GrGLPathRendering::flushPathStencilSettings(const GrStencilSettings& stencilSettings) {
if (fHWPathStencilSettings != stencilSettings) {
SkASSERT(stencilSettings.isValid());
// Just the func, ref, and mask is set here. The op and write mask are params to the call
// that draws the path to the SB (glStencilFillPath)
uint16_t ref = stencilSettings.front().fRef;
GrStencilTest test = stencilSettings.front().fTest;
uint16_t testMask = stencilSettings.front().fTestMask;
if (!fHWPathStencilSettings.isValid() ||
ref != fHWPathStencilSettings.front().fRef ||
test != fHWPathStencilSettings.front().fTest ||
testMask != fHWPathStencilSettings.front().fTestMask) {
GL_CALL(PathStencilFunc(GrToGLStencilFunc(test), ref, testMask));
}
fHWPathStencilSettings = stencilSettings;
}
}
inline GrGLGpu* GrGLPathRendering::gpu() {
return static_cast<GrGLGpu*>(fGpu);
}