/* * Copyright 2015 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkMatrix.h" #include "SkPoint.h" #include "SkString.h" #if SK_SUPPORT_GPU #include "GLBench.h" #include "gl/GrGLContext.h" #include "gl/GrGLInterface.h" #include "gl/GrGLUtil.h" #include "glsl/GrGLSL.h" #include "glsl/GrGLSLCaps.h" #include "glsl/GrGLSLShaderVar.h" #include /** * This is a GL benchmark for comparing the performance of using vec4 or float for coverage in GLSL. * The generated shader code from this bench will draw several overlapping circles, one in each * stage, to simulate coverage calculations. The number of circles (i.e. the number of stages) can * be set as a parameter. */ class GLVec4ScalarBench : public GLBench { public: /* * Use float or vec4 as GLSL data type for the output coverage */ enum CoverageSetup { kUseScalar_CoverageSetup, kUseVec4_CoverageSetup, }; /* * numStages determines the number of shader stages before the XP, * which consequently determines how many circles are drawn */ GLVec4ScalarBench(CoverageSetup coverageSetup, uint32_t numStages) : fCoverageSetup(coverageSetup) , fNumStages(numStages) , fVboId(0) , fProgram(0) { fName = NumStagesSetupToStr(coverageSetup, numStages); } protected: const char* onGetName() override { return fName.c_str(); } void setup(const GrGLContext*) override; void glDraw(int loops, const GrGLContext*) override; void teardown(const GrGLInterface*) override; private: void setupSingleVbo(const GrGLInterface*, const SkMatrix*); GrGLuint setupShader(const GrGLContext*); static SkString NumStagesSetupToStr(CoverageSetup coverageSetup, uint32_t numStages) { SkString name("GLVec4ScalarBench"); switch (coverageSetup) { default: case kUseScalar_CoverageSetup: name.appendf("_scalar_%u_stage", numStages); break; case kUseVec4_CoverageSetup: name.appendf("_vec4_%u_stage", numStages); break; } return name; } static const GrGLuint kScreenWidth = 800; static const GrGLuint kScreenHeight = 600; static const uint32_t kNumTriPerDraw = 512; static const uint32_t kVerticesPerTri = 3; SkString fName; CoverageSetup fCoverageSetup; uint32_t fNumStages; GrGLuint fVboId; GrGLuint fProgram; GrGLuint fFboTextureId; }; /////////////////////////////////////////////////////////////////////////////////////////////////// GrGLuint GLVec4ScalarBench::setupShader(const GrGLContext* ctx) { const GrGLSLCaps* glslCaps = ctx->caps()->glslCaps(); const char* version = glslCaps->versionDeclString(); // this shader draws fNumStages overlapping circles of increasing opacity (coverage) and // decreasing size, with the center of each subsequent circle closer to the bottom-right // corner of the screen than the previous circle. // set up vertex shader; this is a trivial vertex shader that passes through position and color GrGLSLShaderVar aPosition("a_position", kVec2f_GrSLType, GrShaderVar::kIn_TypeModifier); GrGLSLShaderVar oPosition("o_position", kVec2f_GrSLType, GrShaderVar::kOut_TypeModifier); GrGLSLShaderVar aColor("a_color", kVec3f_GrSLType, GrShaderVar::kIn_TypeModifier); GrGLSLShaderVar oColor("o_color", kVec3f_GrSLType, GrShaderVar::kOut_TypeModifier); SkString vshaderTxt(version); aPosition.appendDecl(glslCaps, &vshaderTxt); vshaderTxt.append(";\n"); aColor.appendDecl(glslCaps, &vshaderTxt); vshaderTxt.append(";\n"); oPosition.appendDecl(glslCaps, &vshaderTxt); vshaderTxt.append(";\n"); oColor.appendDecl(glslCaps, &vshaderTxt); vshaderTxt.append(";\n"); vshaderTxt.append( "void main()\n" "{\n" " gl_Position = vec4(a_position, 0.0, 1.0);\n" " o_position = a_position;\n" " o_color = a_color;\n" "}\n"); // set up fragment shader; this fragment shader will have fNumStages coverage stages plus an // XP stage at the end. Each coverage stage computes the pixel's distance from some hard- // coded center and compare that to some hard-coded circle radius to compute a coverage. // Then, this coverage is mixed with the coverage from the previous stage and passed to the // next stage. GrGLSLShaderVar oFragColor("o_FragColor", kVec4f_GrSLType, GrShaderVar::kOut_TypeModifier); SkString fshaderTxt(version); GrGLSLAppendDefaultFloatPrecisionDeclaration(kDefault_GrSLPrecision, *glslCaps, &fshaderTxt); oPosition.setTypeModifier(GrShaderVar::kIn_TypeModifier); oPosition.appendDecl(glslCaps, &fshaderTxt); fshaderTxt.append(";\n"); oColor.setTypeModifier(GrShaderVar::kIn_TypeModifier); oColor.appendDecl(glslCaps, &fshaderTxt); fshaderTxt.append(";\n"); const char* fsOutName; if (glslCaps->mustDeclareFragmentShaderOutput()) { oFragColor.appendDecl(glslCaps, &fshaderTxt); fshaderTxt.append(";\n"); fsOutName = oFragColor.c_str(); } else { fsOutName = "sk_FragColor"; } fshaderTxt.appendf( "void main()\n" "{\n" " vec4 outputColor;\n" " %s outputCoverage;\n" " outputColor = vec4(%s, 1.0);\n" " outputCoverage = %s;\n", fCoverageSetup == kUseVec4_CoverageSetup ? "vec4" : "float", oColor.getName().c_str(), fCoverageSetup == kUseVec4_CoverageSetup ? "vec4(1.0)" : "1.0" ); float radius = 1.0f; for (uint32_t i = 0; i < fNumStages; i++) { float centerX = 1.0f - radius; float centerY = 1.0f - radius; fshaderTxt.appendf( " {\n" " float d = length(%s - vec2(%f, %f));\n" " float edgeAlpha = clamp(100.0 * (%f - d), 0.0, 1.0);\n" " outputCoverage = 0.5 * outputCoverage + 0.5 * %s;\n" " }\n", oPosition.getName().c_str(), centerX, centerY, radius, fCoverageSetup == kUseVec4_CoverageSetup ? "vec4(edgeAlpha)" : "edgeAlpha" ); radius *= 0.8f; } fshaderTxt.appendf( " {\n" " %s = outputColor * outputCoverage;\n" " }\n" "}\n", fsOutName); return CreateProgram(ctx, vshaderTxt.c_str(), fshaderTxt.c_str()); } template static void setup_matrices(int numQuads, Func f) { // We draw a really small triangle so we are not fill rate limited for (int i = 0 ; i < numQuads; i++) { SkMatrix m = SkMatrix::I(); m.setScale(0.01f, 0.01f); f(m); } } /////////////////////////////////////////////////////////////////////////////////////////////////// struct Vertex { SkPoint fPositions; GrGLfloat fColors[3]; }; void GLVec4ScalarBench::setupSingleVbo(const GrGLInterface* gl, const SkMatrix* viewMatrices) { // triangles drawn will alternate between the top-right half of the screen and the bottom-left // half of the screen Vertex vertices[kVerticesPerTri * kNumTriPerDraw]; for (uint32_t i = 0; i < kNumTriPerDraw; i++) { Vertex* v = &vertices[i * kVerticesPerTri]; if (i % 2 == 0) { v[0].fPositions.set(-1.0f, -1.0f); v[1].fPositions.set( 1.0f, -1.0f); v[2].fPositions.set( 1.0f, 1.0f); } else { v[0].fPositions.set(-1.0f, -1.0f); v[1].fPositions.set( 1.0f, 1.0f); v[2].fPositions.set( -1.0f, 1.0f); } SkPoint* position = reinterpret_cast(v); viewMatrices[i].mapPointsWithStride(position, sizeof(Vertex), kVerticesPerTri); GrGLfloat color[3] = {1.0f, 0.0f, 1.0f}; for (uint32_t j = 0; j < kVerticesPerTri; j++) { v->fColors[0] = color[0]; v->fColors[1] = color[1]; v->fColors[2] = color[2]; v++; } } GR_GL_CALL(gl, GenBuffers(1, &fVboId)); GR_GL_CALL(gl, BindBuffer(GR_GL_ARRAY_BUFFER, fVboId)); GR_GL_CALL(gl, EnableVertexAttribArray(0)); GR_GL_CALL(gl, EnableVertexAttribArray(1)); GR_GL_CALL(gl, VertexAttribPointer(0, 2, GR_GL_FLOAT, GR_GL_FALSE, sizeof(Vertex), (GrGLvoid*)0)); GR_GL_CALL(gl, VertexAttribPointer(1, 3, GR_GL_FLOAT, GR_GL_FALSE, sizeof(Vertex), (GrGLvoid*)(sizeof(SkPoint)))); GR_GL_CALL(gl, BufferData(GR_GL_ARRAY_BUFFER, sizeof(vertices), vertices, GR_GL_STATIC_DRAW)); } void GLVec4ScalarBench::setup(const GrGLContext* ctx) { const GrGLInterface* gl = ctx->interface(); if (!gl) { SkFAIL("GL interface is nullptr in setup()!\n"); } fFboTextureId = SetupFramebuffer(gl, kScreenWidth, kScreenHeight); fProgram = this->setupShader(ctx); int index = 0; SkMatrix viewMatrices[kNumTriPerDraw]; setup_matrices(kNumTriPerDraw, [&index, &viewMatrices](const SkMatrix& m) { viewMatrices[index++] = m; }); this->setupSingleVbo(gl, viewMatrices); GR_GL_CALL(gl, UseProgram(fProgram)); } void GLVec4ScalarBench::glDraw(int loops, const GrGLContext* ctx) { const GrGLInterface* gl = ctx->interface(); for (int i = 0; i < loops; i++) { GR_GL_CALL(gl, DrawArrays(GR_GL_TRIANGLES, 0, kVerticesPerTri * kNumTriPerDraw)); } // using -w when running nanobench will not produce correct images; // changing this to #if 1 will write the correct images to the Skia folder. #if 0 SkString filename("out"); filename.appendf("_%s.png", this->getName()); DumpImage(gl, kScreenWidth, kScreenHeight, filename.c_str()); #endif } void GLVec4ScalarBench::teardown(const GrGLInterface* gl) { GR_GL_CALL(gl, BindBuffer(GR_GL_ARRAY_BUFFER, 0)); GR_GL_CALL(gl, BindTexture(GR_GL_TEXTURE_2D, 0)); GR_GL_CALL(gl, BindFramebuffer(GR_GL_FRAMEBUFFER, 0)); GR_GL_CALL(gl, DeleteTextures(1, &fFboTextureId)); GR_GL_CALL(gl, DeleteProgram(fProgram)); GR_GL_CALL(gl, DeleteBuffers(1, &fVboId)); } /////////////////////////////////////////////////////////////////////////////// DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseScalar_CoverageSetup, 1) ) DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseVec4_CoverageSetup, 1) ) DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseScalar_CoverageSetup, 2) ) DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseVec4_CoverageSetup, 2) ) DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseScalar_CoverageSetup, 4) ) DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseVec4_CoverageSetup, 4) ) DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseScalar_CoverageSetup, 6) ) DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseVec4_CoverageSetup, 6) ) DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseScalar_CoverageSetup, 8) ) DEF_BENCH( return new GLVec4ScalarBench(GLVec4ScalarBench::kUseVec4_CoverageSetup, 8) ) #endif