// // Copyright 2024 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // MultisampleResolvePerf: // Performance tests for glBlitFramebuffer and glInvalidateFramebuffer where the framebuffer is // multisampled. #include "ANGLEPerfTest.h" #include "util/shader_utils.h" namespace { constexpr unsigned int kIterationsPerStep = 1; enum class Multisample { Yes, No, }; enum class WithDepthStencil { Yes, No, }; struct MultisampleResolveParams final : public RenderTestParams { MultisampleResolveParams(Multisample multisample, WithDepthStencil depthStencil) { iterationsPerStep = kIterationsPerStep; majorVersion = 3; minorVersion = 0; windowWidth = 256; windowHeight = 256; if (multisample == Multisample::No) { samples = 0; } withDepthStencil = depthStencil == WithDepthStencil::Yes; } std::string story() const override { std::stringstream storyStr; storyStr << RenderTestParams::story(); if (withDepthStencil) { storyStr << "_ds"; } if (samples == 0) { storyStr << "_singlesampled_reference"; } return storyStr.str(); } unsigned int framebufferSize = 1024; unsigned int samples = 4; bool withDepthStencil = false; }; std::ostream &operator<<(std::ostream &os, const MultisampleResolveParams ¶ms) { os << params.backendAndStory().substr(1); return os; } class MultisampleResolvePerf : public ANGLERenderTest, public ::testing::WithParamInterface { public: MultisampleResolvePerf() : ANGLERenderTest("MultisampleResolvePerf", GetParam()) {} void initializeBenchmark() override; void destroyBenchmark() override; void drawBenchmark() override; private: GLuint mMSAAFramebuffer = 0; GLuint mMSAAColor[2] = {}; GLuint mMSAADepthStencil = 0; GLuint mResolveFramebuffer[2] = {}; GLuint mResolveColor[2] = {}; GLuint mResolveDepthStencil = 0; GLuint mReferenceFramebuffer = 0; GLuint mProgram = 0; }; void MultisampleResolvePerf::initializeBenchmark() { const MultisampleResolveParams ¶m = GetParam(); glGenFramebuffers(1, &mMSAAFramebuffer); glGenFramebuffers(2, mResolveFramebuffer); glGenFramebuffers(1, &mReferenceFramebuffer); // Create source and destination Renderbuffers. glGenRenderbuffers(2, mMSAAColor); glGenRenderbuffers(1, &mMSAADepthStencil); glGenRenderbuffers(2, mResolveColor); glGenRenderbuffers(1, &mResolveDepthStencil); ASSERT_GL_NO_ERROR(); const GLuint size = param.framebufferSize; for (uint32_t i = 0; i < 2; ++i) { glBindRenderbuffer(GL_RENDERBUFFER, mResolveColor[i]); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, size, size); glBindFramebuffer(GL_FRAMEBUFFER, mResolveFramebuffer[i]); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mResolveColor[i]); ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER)); } if (param.withDepthStencil) { glBindRenderbuffer(GL_RENDERBUFFER, mResolveDepthStencil); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, size, size); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mResolveDepthStencil); ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER)); } if (param.samples > 0) { glBindRenderbuffer(GL_RENDERBUFFER, mMSAAColor[0]); glRenderbufferStorageMultisample(GL_RENDERBUFFER, param.samples, GL_RGBA8, size, size); glBindRenderbuffer(GL_RENDERBUFFER, mMSAAColor[1]); glRenderbufferStorageMultisample(GL_RENDERBUFFER, param.samples, GL_RGBA8, size, size); glBindFramebuffer(GL_FRAMEBUFFER, mMSAAFramebuffer); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mMSAAColor[0]); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_RENDERBUFFER, mMSAAColor[1]); if (param.withDepthStencil) { glBindRenderbuffer(GL_RENDERBUFFER, mMSAADepthStencil); glRenderbufferStorageMultisample(GL_RENDERBUFFER, param.samples, GL_DEPTH24_STENCIL8, size, size); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mMSAADepthStencil); } ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER)); } else { glBindFramebuffer(GL_FRAMEBUFFER, mReferenceFramebuffer); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, mResolveColor[0]); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_RENDERBUFFER, mResolveColor[1]); if (param.withDepthStencil) { glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, mResolveDepthStencil); } ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER)); } GLenum bufs[3] = {GL_COLOR_ATTACHMENT0, GL_NONE, GL_COLOR_ATTACHMENT2}; glDrawBuffers(3, bufs); ASSERT_GL_NO_ERROR(); constexpr char kVS[] = R"(#version 300 es precision highp float; void main() { // gl_VertexID x y // 0 -1 -1 // 1 1 -1 // 2 -1 1 // 3 1 1 int bit0 = gl_VertexID & 1; int bit1 = gl_VertexID >> 1; gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, gl_VertexID % 2 == 0 ? -1 : 1, 1); })"; constexpr char kFS[] = R"(#version 300 es precision highp float; uniform vec4 value0; uniform vec4 value2; layout(location = 0) out vec4 color0; layout(location = 2) out vec4 color2; void main() { color0 = value0; color2 = value2; })"; mProgram = CompileProgram(kVS, kFS); ASSERT_NE(0u, mProgram); glUseProgram(mProgram); const GLint color0Loc = glGetUniformLocation(mProgram, "value0"); const GLint color1Loc = glGetUniformLocation(mProgram, "value2"); glUniform4f(color0Loc, 1, 0, 0, 1); glUniform4f(color1Loc, 0, 1, 0, 1); } void MultisampleResolvePerf::destroyBenchmark() { glDeleteFramebuffers(1, &mMSAAFramebuffer); glDeleteFramebuffers(2, mResolveFramebuffer); glDeleteFramebuffers(1, &mReferenceFramebuffer); glDeleteRenderbuffers(2, mMSAAColor); glDeleteRenderbuffers(1, &mMSAADepthStencil); glDeleteRenderbuffers(2, mResolveColor); glDeleteRenderbuffers(1, &mResolveDepthStencil); glDeleteProgram(mProgram); } void MultisampleResolvePerf::drawBenchmark() { const MultisampleResolveParams ¶m = GetParam(); const int size = param.framebufferSize; const bool singleSampled = param.samples == 0; glViewport(0, 0, size, size); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_ALWAYS); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_ALWAYS, 0x55, 0xFF); glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); glStencilMask(0xFF); for (unsigned int iteration = 0; iteration < param.iterationsPerStep; ++iteration) { const GLenum discards[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT2, GL_DEPTH_STENCIL_ATTACHMENT}; glBindFramebuffer(GL_FRAMEBUFFER, singleSampled ? mReferenceFramebuffer : mMSAAFramebuffer); glInvalidateFramebuffer(GL_FRAMEBUFFER, param.withDepthStencil ? 3 : 2, discards); // Start a render pass, then resolve each attachment + invalidate them. Every render pass // should thus start with LOAD_OP_DONT_CARE and end in STORE_OP_DONT_CARE (for the // attachments) and STORE_OP_STORE (for the resolve attachments). // // In single-sampled mode, just draw. This is used to compare the performance of // multisampled with single sampled rendering. glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); if (!singleSampled) { for (uint32_t i = 0; i < 2; ++i) { glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mResolveFramebuffer[i]); glReadBuffer(discards[i]); glBlitFramebuffer(0, 0, size, size, 0, 0, size, size, GL_COLOR_BUFFER_BIT, GL_NEAREST); ASSERT_GL_NO_ERROR(); glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, &discards[i]); } if (param.withDepthStencil) { glBlitFramebuffer(0, 0, size, size, 0, 0, size, size, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST); glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, &discards[2]); } ASSERT_GL_NO_ERROR(); } } ASSERT_GL_NO_ERROR(); } // Test that multisample resolve + invalidate is as efficient as single sampling on tilers. TEST_P(MultisampleResolvePerf, Run) { run(); } MultisampleResolveParams Vulkan(Multisample multisample, WithDepthStencil depthStencil) { MultisampleResolveParams params(multisample, depthStencil); params.eglParameters = angle::egl_platform::VULKAN(); return params; } } // anonymous namespace ANGLE_INSTANTIATE_TEST(MultisampleResolvePerf, Vulkan(Multisample::No, WithDepthStencil::No), Vulkan(Multisample::Yes, WithDepthStencil::No), Vulkan(Multisample::No, WithDepthStencil::Yes), Vulkan(Multisample::Yes, WithDepthStencil::Yes)); // This test suite is not instantiated on some OSes. GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MultisampleResolvePerf);