Chris Dalton | 5a2f962 | 2019-12-27 14:56:38 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2019 Google Inc. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
| 6 | */ |
| 7 | |
| 8 | #include "gm/gm.h" |
| 9 | |
| 10 | #include "src/gpu/GrCaps.h" |
| 11 | #include "src/gpu/GrContextPriv.h" |
| 12 | #include "src/gpu/GrMemoryPool.h" |
| 13 | #include "src/gpu/GrMesh.h" |
| 14 | #include "src/gpu/GrOpFlushState.h" |
| 15 | #include "src/gpu/GrOpsRenderPass.h" |
| 16 | #include "src/gpu/GrPipeline.h" |
| 17 | #include "src/gpu/GrPrimitiveProcessor.h" |
| 18 | #include "src/gpu/GrProgramInfo.h" |
| 19 | #include "src/gpu/GrRecordingContextPriv.h" |
| 20 | #include "src/gpu/GrRenderTargetContext.h" |
| 21 | #include "src/gpu/GrRenderTargetContextPriv.h" |
| 22 | #include "src/gpu/GrShaderCaps.h" |
| 23 | #include "src/gpu/GrShaderVar.h" |
| 24 | #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" |
| 25 | #include "src/gpu/glsl/GrGLSLGeometryProcessor.h" |
| 26 | #include "src/gpu/glsl/GrGLSLPrimitiveProcessor.h" |
| 27 | #include "src/gpu/glsl/GrGLSLVarying.h" |
| 28 | #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" |
| 29 | #include "src/gpu/ops/GrDrawOp.h" |
| 30 | |
| 31 | namespace skiagm { |
| 32 | |
| 33 | constexpr static GrGeometryProcessor::Attribute kPositionAttrib = |
| 34 | {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; |
| 35 | |
| 36 | constexpr static std::array<float, 3> kTri1[3] = { |
| 37 | {20.5f,20.5f,1}, {170.5f,280.5f,4}, {320.5f,20.5f,1}}; |
| 38 | constexpr static std::array<float, 3> kTri2[3] = { |
| 39 | {640.5f,280.5f,3}, {490.5f,20.5f,1}, {340.5f,280.5f,6}}; |
| 40 | constexpr static SkRect kRect = {20.5f, 340.5f, 640.5f, 480.5f}; |
| 41 | |
| 42 | constexpr static int kWidth = (int)kRect.fRight + 21; |
| 43 | constexpr static int kHeight = (int)kRect.fBottom + 21; |
| 44 | |
| 45 | /** |
| 46 | * This is a GPU-backend specific test. It ensures that tessellation works as expected by drawing |
| 47 | * several triangles. The test passes as long as the triangle tessellations match the reference |
| 48 | * images on gold. |
| 49 | */ |
| 50 | class TessellationGM : public GpuGM { |
| 51 | SkString onShortName() override { return SkString("tessellation"); } |
| 52 | SkISize onISize() override { return {kWidth, kHeight}; } |
| 53 | DrawResult onDraw(GrContext*, GrRenderTargetContext*, SkCanvas*, SkString*) override; |
| 54 | }; |
| 55 | |
| 56 | |
| 57 | class TessellationTestTriShader : public GrGeometryProcessor { |
| 58 | public: |
| 59 | TessellationTestTriShader(const SkMatrix& viewMatrix) |
| 60 | : GrGeometryProcessor(kTessellationTestTriShader_ClassID), fViewMatrix(viewMatrix) { |
| 61 | this->setVertexAttributes(&kPositionAttrib, 1); |
| 62 | this->setWillUseTessellationShaders(); |
| 63 | } |
| 64 | |
| 65 | private: |
| 66 | const char* name() const final { return "TessellationTestTriShader"; } |
| 67 | void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {} |
| 68 | |
| 69 | class Impl : public GrGLSLGeometryProcessor { |
| 70 | void onEmitCode(EmitArgs& args, GrGPArgs*) override { |
| 71 | args.fVaryingHandler->emitAttributes(args.fGP.cast<TessellationTestTriShader>()); |
| 72 | const char* viewMatrix; |
| 73 | fViewMatrixUniform = args.fUniformHandler->addUniform( |
| 74 | kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix); |
| 75 | args.fVertBuilder->declareGlobal( |
| 76 | GrShaderVar("P_", kFloat3_GrSLType, GrShaderVar::kOut_TypeModifier)); |
| 77 | args.fVertBuilder->codeAppendf(R"( |
| 78 | P_.xy = (%s * float3(position.xy, 1)).xy; |
| 79 | P_.z = position.z;)", viewMatrix); |
| 80 | // GrGLProgramBuilder will call writeTess*ShaderGLSL when it is compiling. |
| 81 | this->writeFragmentShader(args.fFragBuilder, args.fOutputColor, args.fOutputCoverage); |
| 82 | } |
| 83 | void writeFragmentShader(GrGLSLFPFragmentBuilder*, const char* color, const char* coverage); |
| 84 | void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc, |
| 85 | const CoordTransformRange&) override { |
| 86 | pdman.setSkMatrix(fViewMatrixUniform, |
| 87 | proc.cast<TessellationTestTriShader>().fViewMatrix); |
| 88 | } |
| 89 | GrGLSLUniformHandler::UniformHandle fViewMatrixUniform; |
| 90 | }; |
| 91 | |
| 92 | GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { |
| 93 | return new Impl; |
| 94 | } |
| 95 | |
| 96 | SkString getTessControlShaderGLSL(const char* versionAndExtensionDecls, |
| 97 | const GrShaderCaps&) const override; |
| 98 | SkString getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls, |
| 99 | const GrShaderCaps&) const override; |
| 100 | |
| 101 | const SkMatrix fViewMatrix; |
| 102 | }; |
| 103 | |
| 104 | SkString TessellationTestTriShader::getTessControlShaderGLSL( |
| 105 | const char* versionAndExtensionDecls, const GrShaderCaps&) const { |
| 106 | SkString code(versionAndExtensionDecls); |
| 107 | code.append(R"( |
| 108 | layout(vertices = 3) out; |
| 109 | |
| 110 | in vec3 P_[]; |
| 111 | out vec3 P[]; |
| 112 | |
| 113 | void main() { |
| 114 | P[gl_InvocationID] = P_[gl_InvocationID]; |
| 115 | gl_TessLevelOuter[gl_InvocationID] = P_[gl_InvocationID].z; |
| 116 | gl_TessLevelInner[0] = 2.0; |
| 117 | })"); |
| 118 | |
| 119 | return code; |
| 120 | } |
| 121 | |
| 122 | SkString TessellationTestTriShader::getTessEvaluationShaderGLSL( |
| 123 | const char* versionAndExtensionDecls, const GrShaderCaps&) const { |
| 124 | SkString code(versionAndExtensionDecls); |
| 125 | code.append(R"( |
| 126 | layout(triangles, equal_spacing, cw) in; |
| 127 | |
| 128 | uniform vec4 sk_RTAdjust; |
| 129 | |
| 130 | in vec3 P[]; |
| 131 | out vec3 barycentric_coord; |
| 132 | |
| 133 | void main() { |
| 134 | vec2 devcoord = mat3x2(P[0].xy, P[1].xy, P[2].xy) * gl_TessCoord.xyz; |
| 135 | devcoord = round(devcoord - .5) + .5; // Make horz and vert lines on px bounds. |
| 136 | gl_Position = vec4(devcoord.xy * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0); |
| 137 | |
| 138 | float i = 0.0; |
| 139 | if (gl_TessCoord.y == 0.0) { |
| 140 | i += gl_TessCoord.z * P[1].z; |
| 141 | } else { |
| 142 | i += P[1].z; |
| 143 | if (gl_TessCoord.x == 0.0) { |
| 144 | i += gl_TessCoord.y * P[0].z; |
| 145 | } else { |
| 146 | i += P[0].z; |
Chris Dalton | 8dae7eb | 2019-12-27 22:20:55 -0700 | [diff] [blame] | 147 | if (gl_TessCoord.z == 0.0) { |
| 148 | i += gl_TessCoord.x * P[2].z; |
| 149 | } else { |
| 150 | barycentric_coord = vec3(0, 1, 0); |
| 151 | return; |
| 152 | } |
Chris Dalton | 5a2f962 | 2019-12-27 14:56:38 -0700 | [diff] [blame] | 153 | } |
| 154 | } |
Chris Dalton | 8dae7eb | 2019-12-27 22:20:55 -0700 | [diff] [blame] | 155 | i = abs(mod(i, 2.0) - 1.0); |
| 156 | barycentric_coord = vec3(i, 0, 1.0 - i); |
| 157 | })"); |
Chris Dalton | 5a2f962 | 2019-12-27 14:56:38 -0700 | [diff] [blame] | 158 | |
| 159 | return code; |
| 160 | } |
| 161 | |
| 162 | void TessellationTestTriShader::Impl::writeFragmentShader( |
| 163 | GrGLSLFPFragmentBuilder* f, const char* color, const char* coverage) { |
| 164 | f->declareGlobal( |
| 165 | GrShaderVar("barycentric_coord", kFloat3_GrSLType, GrShaderVar::kIn_TypeModifier)); |
| 166 | f->codeAppendf(R"( |
| 167 | half3 d = half3(1 - barycentric_coord/fwidth(barycentric_coord)); |
| 168 | half coverage = max(max(d.x, d.y), d.z); |
| 169 | %s = half4(0, coverage, coverage, 1); |
| 170 | %s = half4(1);)", color, coverage); |
| 171 | } |
| 172 | |
| 173 | class TessellationTestRectShader : public GrGeometryProcessor { |
| 174 | public: |
| 175 | TessellationTestRectShader(const SkMatrix& viewMatrix) |
| 176 | : GrGeometryProcessor(kTessellationTestTriShader_ClassID), fViewMatrix(viewMatrix) { |
| 177 | this->setWillUseTessellationShaders(); |
| 178 | } |
| 179 | |
| 180 | private: |
| 181 | const char* name() const final { return "TessellationTestRectShader"; } |
| 182 | void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const final {} |
| 183 | |
| 184 | class Impl : public GrGLSLGeometryProcessor { |
| 185 | void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { |
| 186 | const char* viewMatrix; |
| 187 | fViewMatrixUniform = args.fUniformHandler->addUniform( |
| 188 | kVertex_GrShaderFlag, kFloat3x3_GrSLType, "view_matrix", &viewMatrix); |
| 189 | args.fVertBuilder->declareGlobal( |
| 190 | GrShaderVar("M_", kFloat3x3_GrSLType, GrShaderVar::kOut_TypeModifier)); |
| 191 | args.fVertBuilder->codeAppendf("M_ = %s;", viewMatrix); |
| 192 | // GrGLProgramBuilder will call writeTess*ShaderGLSL when it is compiling. |
| 193 | this->writeFragmentShader(args.fFragBuilder, args.fOutputColor, args.fOutputCoverage); |
| 194 | } |
| 195 | void writeFragmentShader(GrGLSLFPFragmentBuilder*, const char* color, const char* coverage); |
| 196 | void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc, |
| 197 | const CoordTransformRange&) override { |
| 198 | pdman.setSkMatrix(fViewMatrixUniform, |
| 199 | proc.cast<TessellationTestRectShader>().fViewMatrix); |
| 200 | } |
| 201 | GrGLSLUniformHandler::UniformHandle fViewMatrixUniform; |
| 202 | }; |
| 203 | |
| 204 | GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { |
| 205 | return new Impl; |
| 206 | } |
| 207 | |
| 208 | SkString getTessControlShaderGLSL(const char* versionAndExtensionDecls, |
| 209 | const GrShaderCaps&) const override; |
| 210 | SkString getTessEvaluationShaderGLSL(const char* versionAndExtensionDecls, |
| 211 | const GrShaderCaps&) const override; |
| 212 | |
| 213 | const SkMatrix fViewMatrix; |
| 214 | }; |
| 215 | |
| 216 | SkString TessellationTestRectShader::getTessControlShaderGLSL( |
| 217 | const char* versionAndExtensionDecls, const GrShaderCaps& caps) const { |
| 218 | SkString code(versionAndExtensionDecls); |
| 219 | code.append(R"( |
| 220 | layout(vertices = 1) out; |
| 221 | |
| 222 | in mat3 M_[]; |
| 223 | out mat3 M[]; |
| 224 | |
| 225 | void main() { |
| 226 | M[gl_InvocationID] = M_[gl_InvocationID]; |
| 227 | gl_TessLevelInner[0] = 8.0; |
| 228 | gl_TessLevelInner[1] = 2.0; |
| 229 | gl_TessLevelOuter[0] = 2.0; |
| 230 | gl_TessLevelOuter[1] = 8.0; |
| 231 | gl_TessLevelOuter[2] = 2.0; |
| 232 | gl_TessLevelOuter[3] = 8.0; |
| 233 | })"); |
| 234 | |
| 235 | return code; |
| 236 | } |
| 237 | |
| 238 | SkString TessellationTestRectShader::getTessEvaluationShaderGLSL( |
| 239 | const char* versionAndExtensionDecls, const GrShaderCaps& caps) const { |
| 240 | SkString code(versionAndExtensionDecls); |
| 241 | code.appendf(R"( |
| 242 | layout(quads, equal_spacing, cw) in; |
| 243 | |
| 244 | uniform vec4 sk_RTAdjust; |
| 245 | |
| 246 | in mat3 M[]; |
| 247 | out vec4 barycentric_coord; |
| 248 | |
| 249 | void main() { |
| 250 | vec4 R = vec4(%f, %f, %f, %f); |
| 251 | vec2 localcoord = mix(R.xy, R.zw, gl_TessCoord.xy); |
| 252 | vec2 devcoord = (M[0] * vec3(localcoord, 1)).xy; |
| 253 | devcoord = round(devcoord - .5) + .5; // Make horz and vert lines on px bounds. |
| 254 | gl_Position = vec4(devcoord.xy * sk_RTAdjust.xz + sk_RTAdjust.yw, 0.0, 1.0); |
| 255 | |
| 256 | float i = gl_TessCoord.x * 8.0; |
| 257 | i = abs(mod(i, 2.0) - 1.0); |
| 258 | if (gl_TessCoord.y == 0.0 || gl_TessCoord.y == 1.0) { |
| 259 | barycentric_coord = vec4(i, 1.0 - i, 0, 0); |
| 260 | } else { |
| 261 | barycentric_coord = vec4(0, 0, i, 1.0 - i); |
| 262 | } |
| 263 | })", kRect.left(), kRect.top(), kRect.right(), kRect.bottom()); |
| 264 | |
| 265 | return code; |
| 266 | } |
| 267 | |
| 268 | void TessellationTestRectShader::Impl::writeFragmentShader( |
| 269 | GrGLSLFPFragmentBuilder* f, const char* color, const char* coverage) { |
| 270 | f->declareGlobal(GrShaderVar("barycentric_coord", kFloat4_GrSLType, |
| 271 | GrShaderVar::kIn_TypeModifier)); |
| 272 | f->codeAppendf(R"( |
| 273 | float4 fwidths = fwidth(barycentric_coord); |
| 274 | half coverage = 0; |
| 275 | for (int i = 0; i < 4; ++i) { |
| 276 | if (fwidths[i] != 0) { |
| 277 | coverage = half(max(coverage, 1 - barycentric_coord[i]/fwidths[i])); |
| 278 | } |
| 279 | } |
| 280 | %s = half4(coverage, 0, coverage, 1); |
| 281 | %s = half4(1);)", color, coverage); |
| 282 | } |
| 283 | |
| 284 | |
| 285 | class TessellationTestOp : public GrDrawOp { |
| 286 | DEFINE_OP_CLASS_ID |
| 287 | |
| 288 | public: |
| 289 | TessellationTestOp(const SkMatrix& viewMatrix, const std::array<float, 3>* triPositions) |
| 290 | : GrDrawOp(ClassID()), fViewMatrix(viewMatrix), fTriPositions(triPositions) { |
| 291 | this->setBounds(SkRect::MakeIWH(kWidth, kHeight), HasAABloat::kNo, IsHairline::kNo); |
| 292 | } |
| 293 | |
| 294 | private: |
| 295 | const char* name() const override { return "TessellationTestOp"; } |
| 296 | FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } |
| 297 | GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, |
| 298 | bool hasMixedSampledCoverage, GrClampType) override { |
| 299 | return GrProcessorSet::EmptySetAnalysis(); |
| 300 | } |
| 301 | |
| 302 | void onPrepare(GrOpFlushState* flushState) override { |
| 303 | if (fTriPositions) { |
| 304 | if (void* vertexData = flushState->makeVertexSpace(sizeof(float) * 3, 3, &fVertexBuffer, |
| 305 | &fBaseVertex)) { |
| 306 | memcpy(vertexData, fTriPositions, sizeof(float) * 3 * 3); |
| 307 | } |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | void onExecute(GrOpFlushState* state, const SkRect& chainBounds) override { |
| 312 | GrPipeline pipeline(GrScissorTest::kDisabled, SkBlendMode::kSrc, |
| 313 | state->drawOpArgs().outputSwizzle()); |
| 314 | GrPipeline::FixedDynamicState fixedDynamicState; |
| 315 | |
| 316 | GrMesh mesh(GrPrimitiveType::kPatches); |
| 317 | std::unique_ptr<GrGeometryProcessor> shader; |
| 318 | if (fTriPositions) { |
| 319 | if (!fVertexBuffer) { |
| 320 | return; |
| 321 | } |
| 322 | mesh.setTessellationPatchVertexCount(3); |
| 323 | mesh.setNonIndexedNonInstanced(3); |
| 324 | mesh.setVertexData(fVertexBuffer, fBaseVertex); |
| 325 | shader = std::make_unique<TessellationTestTriShader>(fViewMatrix); |
| 326 | } else { |
| 327 | // Use a mismatched number of vertices in the input patch vs output. |
| 328 | // (The tessellation control shader will output one vertex per patch.) |
| 329 | mesh.setTessellationPatchVertexCount(5); |
| 330 | mesh.setNonIndexedNonInstanced(5); |
| 331 | shader = std::make_unique<TessellationTestRectShader>(fViewMatrix); |
| 332 | } |
| 333 | |
| 334 | GrProgramInfo programInfo(state->proxy()->numSamples(), state->proxy()->numStencilSamples(), |
| 335 | state->proxy()->backendFormat(), state->view()->origin(), |
| 336 | &pipeline, shader.get(), &fixedDynamicState, nullptr, 0, |
| 337 | GrPrimitiveType::kPatches, mesh.tessellationPatchVertexCount()); |
| 338 | |
| 339 | state->opsRenderPass()->draw(programInfo, &mesh, 1, SkRect::MakeIWH(kWidth, kHeight)); |
| 340 | } |
| 341 | |
| 342 | const SkMatrix fViewMatrix; |
| 343 | const std::array<float, 3>* const fTriPositions; |
| 344 | sk_sp<const GrBuffer> fVertexBuffer; |
| 345 | int fBaseVertex = 0; |
| 346 | }; |
| 347 | |
| 348 | |
| 349 | static SkPath build_outset_triangle(const std::array<float, 3>* tri) { |
| 350 | SkPath outset; |
| 351 | for (int i = 0; i < 3; ++i) { |
| 352 | SkPoint p = {tri[i][0], tri[i][1]}; |
| 353 | SkPoint left = {tri[(i + 2) % 3][0], tri[(i + 2) % 3][1]}; |
| 354 | SkPoint right = {tri[(i + 1) % 3][0], tri[(i + 1) % 3][1]}; |
| 355 | SkPoint n0, n1; |
| 356 | n0.setNormalize(left.y() - p.y(), p.x() - left.x()); |
| 357 | n1.setNormalize(p.y() - right.y(), right.x() - p.x()); |
| 358 | p += (n0 + n1) * 3; |
| 359 | if (0 == i) { |
| 360 | outset.moveTo(p); |
| 361 | } else { |
| 362 | outset.lineTo(p); |
| 363 | } |
| 364 | } |
| 365 | return outset; |
| 366 | } |
| 367 | |
| 368 | DrawResult TessellationGM::onDraw(GrContext* ctx, GrRenderTargetContext* rtc, SkCanvas* canvas, |
| 369 | SkString* errorMsg) { |
| 370 | if (!ctx->priv().caps()->shaderCaps()->tessellationSupport()) { |
| 371 | *errorMsg = "Requires GPU tessellation support."; |
| 372 | return DrawResult::kSkip; |
| 373 | } |
| 374 | if (!ctx->priv().caps()->shaderCaps()->shaderDerivativeSupport()) { |
| 375 | *errorMsg = "Requires shader derivatives." |
| 376 | "(These are expected to always be present when there is tessellation!!)"; |
| 377 | return DrawResult::kFail; |
| 378 | } |
| 379 | |
| 380 | canvas->clear(SK_ColorBLACK); |
| 381 | SkPaint borderPaint; |
| 382 | borderPaint.setColor4f({0,1,1,1}); |
| 383 | borderPaint.setAntiAlias(true); |
| 384 | canvas->drawPath(build_outset_triangle(kTri1), borderPaint); |
| 385 | canvas->drawPath(build_outset_triangle(kTri2), borderPaint); |
| 386 | |
| 387 | borderPaint.setColor4f({1,0,1,1}); |
| 388 | canvas->drawRect(kRect.makeOutset(1.5f, 1.5f), borderPaint); |
| 389 | |
| 390 | GrOpMemoryPool* pool = ctx->priv().opMemoryPool(); |
| 391 | rtc->priv().testingOnly_addDrawOp( |
| 392 | pool->allocate<TessellationTestOp>(canvas->getTotalMatrix(), kTri1)); |
| 393 | rtc->priv().testingOnly_addDrawOp( |
| 394 | pool->allocate<TessellationTestOp>(canvas->getTotalMatrix(), kTri2)); |
| 395 | rtc->priv().testingOnly_addDrawOp( |
| 396 | pool->allocate<TessellationTestOp>(canvas->getTotalMatrix(), nullptr)); |
| 397 | |
| 398 | return skiagm::DrawResult::kOk; |
| 399 | } |
| 400 | |
| 401 | DEF_GM( return new TessellationGM(); ) |
| 402 | |
| 403 | } |