| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/command_line.h" |
| #include "base/process/process.h" |
| #include "base/threading/platform_thread.h" |
| #include "gpu/command_buffer/client/gles2_cmd_helper.h" |
| #include "gpu/command_buffer/client/gles2_implementation.h" |
| #include "gpu/command_buffer/client/gpu_control.h" |
| #include "gpu/command_buffer/client/shared_memory_limits.h" |
| #include "gpu/command_buffer/client/transfer_buffer.h" |
| #include "gpu/command_buffer/common/constants.h" |
| #include "gpu/command_buffer/common/context_creation_attribs.h" |
| #include "gpu/command_buffer/common/sync_token.h" |
| #include "gpu/command_buffer/service/command_buffer_direct.h" |
| #include "gpu/command_buffer/service/context_group.h" |
| #include "gpu/command_buffer/service/gles2_cmd_decoder.h" |
| #include "gpu/command_buffer/service/gpu_switches.h" |
| #include "gpu/command_buffer/service/gpu_tracer.h" |
| #include "gpu/command_buffer/service/image_manager.h" |
| #include "gpu/command_buffer/service/logger.h" |
| #include "gpu/command_buffer/service/mailbox_manager_impl.h" |
| #include "gpu/command_buffer/service/memory_tracking.h" |
| #include "gpu/command_buffer/service/passthrough_discardable_manager.h" |
| #include "gpu/command_buffer/service/service_discardable_manager.h" |
| #include "gpu/command_buffer/service/service_utils.h" |
| #include "gpu/command_buffer/service/shared_image_manager.h" |
| #include "gpu/command_buffer/service/sync_point_manager.h" |
| #include "gpu/command_buffer/service/transfer_buffer_manager.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/perf/perf_test.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gl/gl_context_stub.h" |
| #include "ui/gl/gl_share_group.h" |
| #include "ui/gl/gl_surface_stub.h" |
| #include "ui/gl/init/gl_factory.h" |
| |
| namespace gpu { |
| namespace { |
| |
| constexpr int kDefaultRuns = 8; |
| constexpr int kDefaultIterations = 50000; |
| |
| // A command buffer that can record and replay commands |
| // This goes through 3 states, allowing setting up of initial state before |
| // record/replay a tight loop: |
| // - kDirect directly sends commands to the service on Flush |
| // - kRecord doesn't send anything on Flush, but keeps track of the put pointer |
| // - kReplay allows replaying of commands recorded in the kRecord state |
| // |
| // The initial state is kDirect. AdvanceMode is used to transition from one |
| // state to the next. The transition from kDirect to kRecord requires the |
| // GetBuffer to have been freed, allowing a fresh start for record. |
| class RecordReplayCommandBuffer : public CommandBufferDirect { |
| public: |
| enum Mode { kDirect, kRecord, kReplay }; |
| |
| RecordReplayCommandBuffer() = default; |
| ~RecordReplayCommandBuffer() override = default; |
| |
| void AdvanceMode() { |
| switch (mode_) { |
| case kDirect: |
| mode_ = kRecord; |
| DCHECK_EQ(current_get_buffer_, -1); |
| DCHECK_EQ(service()->GetState().get_offset, 0); |
| break; |
| case kRecord: |
| mode_ = kReplay; |
| DCHECK_NE(saved_get_buffer_, -1); |
| CommandBufferDirect::SetGetBuffer(saved_get_buffer_); |
| break; |
| case kReplay: |
| mode_ = kDirect; |
| break; |
| } |
| } |
| |
| void Flush(int32_t put_offset) override { |
| DCHECK_NE(mode_, kReplay); |
| if (mode_ == kDirect) { |
| CommandBufferDirect::Flush(put_offset); |
| } else { |
| DCHECK_GE(put_offset, saved_put_offset_); |
| saved_put_offset_ = put_offset; |
| } |
| } |
| |
| CommandBuffer::State WaitForTokenInRange(int32_t start, |
| int32_t end) override { |
| DCHECK_EQ(mode_, kDirect); |
| return CommandBufferDirect::WaitForTokenInRange(start, end); |
| } |
| |
| CommandBuffer::State WaitForGetOffsetInRange(uint32_t set_get_buffer_count, |
| int32_t start, |
| int32_t end) override { |
| DCHECK_EQ(mode_, kDirect); |
| return CommandBufferDirect::WaitForGetOffsetInRange(set_get_buffer_count, |
| start, end); |
| } |
| |
| void SetGetBuffer(int32_t transfer_buffer_id) override { |
| switch (mode_) { |
| case kDirect: |
| current_get_buffer_ = transfer_buffer_id; |
| CommandBufferDirect::SetGetBuffer(transfer_buffer_id); |
| break; |
| case kRecord: |
| DCHECK_EQ(saved_get_buffer_, -1); |
| saved_get_buffer_ = transfer_buffer_id; |
| break; |
| case kReplay: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void OnParseError() override { |
| ASSERT_EQ(service()->GetState().error, error::kNoError); |
| } |
| |
| void Replay() { |
| DCHECK_EQ(mode_, kReplay); |
| SetGetOffsetForTest(0); |
| CommandBufferDirect::Flush(saved_put_offset_); |
| } |
| |
| int32_t saved_put_offset() const { return saved_put_offset_; } |
| Mode mode() const { return mode_; } |
| |
| private: |
| Mode mode_ = kDirect; |
| int32_t saved_put_offset_ = 0; |
| int32_t saved_get_buffer_ = -1; |
| int32_t current_get_buffer_ = -1; |
| }; |
| |
| GpuPreferences GetGpuPreferences() { |
| GpuPreferences preferences; |
| if (gles2::UsePassthroughCommandDecoder( |
| base::CommandLine::ForCurrentProcess())) |
| preferences.use_passthrough_cmd_decoder = true; |
| return preferences; |
| } |
| |
| // This wraps a RecordReplayCommandBuffer and gives it a back-end decoder, as |
| // well as a front-end GLES2Implementation. This allows recording commands at |
| // the GL level and replaying them to the driver (or a stub). |
| class RecordReplayContext : public GpuControl { |
| public: |
| RecordReplayContext() |
| : gpu_preferences_(GetGpuPreferences()), |
| share_group_(new gl::GLShareGroup), |
| translator_cache_(gpu_preferences_) { |
| bool bind_generates_resource = false; |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch("use-stub")) { |
| surface_ = new gl::GLSurfaceStub; |
| scoped_refptr<gl::GLContextStub> context_stub = |
| new gl::GLContextStub(share_group_.get()); |
| context_stub->SetGLVersionString("OpenGL ES 3.1"); |
| context_stub->SetUseStubApi(true); |
| context_ = context_stub; |
| } else { |
| gl::GLContextAttribs attribs; |
| if (gpu_preferences_.use_passthrough_cmd_decoder) |
| attribs.bind_generates_resource = bind_generates_resource; |
| surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size()); |
| context_ = gl::init::CreateGLContext(share_group_.get(), surface_.get(), |
| attribs); |
| } |
| context_->MakeCurrent(surface_.get()); |
| |
| scoped_refptr<gles2::FeatureInfo> feature_info = new gles2::FeatureInfo(); |
| scoped_refptr<gles2::ContextGroup> context_group = new gles2::ContextGroup( |
| gpu_preferences_, true, &mailbox_manager_, nullptr /* memory_tracker */, |
| &translator_cache_, &completeness_cache_, feature_info, |
| bind_generates_resource, &image_manager_, nullptr /* image_factory */, |
| nullptr /* progress_reporter */, GpuFeatureInfo(), |
| &discardable_manager_, &passthrough_discardable_manager_, |
| &shared_image_manager_); |
| command_buffer_.reset(new RecordReplayCommandBuffer()); |
| |
| decoder_.reset(gles2::GLES2Decoder::Create( |
| command_buffer_.get(), command_buffer_->service(), &outputter_, |
| context_group.get())); |
| command_buffer_->set_handler(decoder_.get()); |
| |
| decoder_->GetLogger()->set_log_synthesized_gl_errors(false); |
| |
| ContextCreationAttribs attrib_helper; |
| attrib_helper.offscreen_framebuffer_size = gfx::Size(16, 16); |
| attrib_helper.red_size = 8; |
| attrib_helper.green_size = 8; |
| attrib_helper.blue_size = 8; |
| attrib_helper.alpha_size = 8; |
| attrib_helper.depth_size = 0; |
| attrib_helper.stencil_size = 0; |
| attrib_helper.context_type = CONTEXT_TYPE_OPENGLES3; |
| |
| ContextResult result = |
| decoder_->Initialize(surface_.get(), context_.get(), true, |
| gles2::DisallowedFeatures(), attrib_helper); |
| DCHECK_EQ(result, ContextResult::kSuccess); |
| capabilities_ = decoder_->GetCapabilities(); |
| |
| const SharedMemoryLimits limits; |
| gles2_helper_.reset(new gles2::GLES2CmdHelper(command_buffer_.get())); |
| result = gles2_helper_->Initialize(limits.command_buffer_size); |
| DCHECK_EQ(result, ContextResult::kSuccess); |
| |
| // Create a transfer buffer. |
| transfer_buffer_.reset(new TransferBuffer(gles2_helper_.get())); |
| |
| // Create the object exposing the OpenGL API. |
| const bool lose_context_when_out_of_memory = false; |
| const bool support_client_side_arrays = false; |
| gles2_implementation_.reset(new gles2::GLES2Implementation( |
| gles2_helper_.get(), nullptr, transfer_buffer_.get(), |
| bind_generates_resource, lose_context_when_out_of_memory, |
| support_client_side_arrays, this)); |
| |
| result = gles2_implementation_->Initialize(limits); |
| DCHECK_EQ(result, ContextResult::kSuccess); |
| } |
| |
| ~RecordReplayContext() override { |
| while (command_buffer_->mode() != RecordReplayCommandBuffer::kDirect) |
| command_buffer_->AdvanceMode(); |
| gles2_implementation_.reset(); |
| transfer_buffer_.reset(); |
| gles2_helper_.reset(); |
| command_buffer_.reset(); |
| decoder_->Destroy(true); |
| decoder_.reset(); |
| } |
| |
| void StartRecord() { |
| DCHECK_EQ(command_buffer_->mode(), RecordReplayCommandBuffer::kDirect); |
| gles2_helper_->FreeRingBuffer(); |
| command_buffer_->AdvanceMode(); |
| } |
| |
| void StartReplay() { |
| DCHECK_EQ(command_buffer_->mode(), RecordReplayCommandBuffer::kRecord); |
| gles2_helper_->FlushLazy(); |
| command_buffer_->AdvanceMode(); |
| } |
| |
| void Replay() { command_buffer_->Replay(); } |
| |
| gles2::GLES2Implementation* gl() { return gles2_implementation_.get(); } |
| |
| private: |
| // GpuControl implementation; |
| void SetGpuControlClient(GpuControlClient*) override {} |
| |
| const Capabilities& GetCapabilities() const override { return capabilities_; } |
| |
| int32_t CreateImage(ClientBuffer buffer, |
| size_t width, |
| size_t height) override { |
| NOTIMPLEMENTED(); |
| return -1; |
| } |
| |
| void DestroyImage(int32_t id) override { NOTREACHED(); } |
| |
| void SignalQuery(uint32_t query, base::OnceClosure callback) override { |
| NOTREACHED(); |
| } |
| |
| void CreateGpuFence(uint32_t gpu_fence_id, ClientGpuFence source) override { |
| NOTREACHED(); |
| } |
| |
| void GetGpuFence(uint32_t gpu_fence_id, |
| base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> |
| callback) override { |
| NOTREACHED(); |
| } |
| |
| void SetLock(base::Lock*) override { NOTREACHED(); } |
| |
| void EnsureWorkVisible() override { NOTREACHED(); } |
| |
| gpu::CommandBufferNamespace GetNamespaceID() const override { |
| return gpu::CommandBufferNamespace::INVALID; |
| } |
| |
| CommandBufferId GetCommandBufferID() const override { |
| return gpu::CommandBufferId(); |
| } |
| |
| void FlushPendingWork() override { NOTREACHED(); } |
| |
| uint64_t GenerateFenceSyncRelease() override { |
| NOTREACHED(); |
| return 0; |
| } |
| |
| bool IsFenceSyncReleased(uint64_t release) override { |
| NOTREACHED(); |
| return true; |
| } |
| |
| void SignalSyncToken(const gpu::SyncToken& sync_token, |
| base::OnceClosure callback) override { |
| NOTREACHED(); |
| } |
| |
| void WaitSyncToken(const gpu::SyncToken& sync_token) override { |
| NOTREACHED(); |
| } |
| |
| bool CanWaitUnverifiedSyncToken(const gpu::SyncToken& sync_token) override { |
| NOTREACHED(); |
| return true; |
| } |
| |
| GpuPreferences gpu_preferences_; |
| |
| gles2::MailboxManagerImpl mailbox_manager_; |
| scoped_refptr<gl::GLShareGroup> share_group_; |
| gles2::ImageManager image_manager_; |
| ServiceDiscardableManager discardable_manager_; |
| PassthroughDiscardableManager passthrough_discardable_manager_; |
| SharedImageManager shared_image_manager_; |
| |
| scoped_refptr<gl::GLSurface> surface_; |
| scoped_refptr<gl::GLContext> context_; |
| |
| gles2::ShaderTranslatorCache translator_cache_; |
| gles2::FramebufferCompletenessCache completeness_cache_; |
| |
| std::unique_ptr<RecordReplayCommandBuffer> command_buffer_; |
| |
| gles2::TraceOutputter outputter_; |
| std::unique_ptr<gles2::GLES2Decoder> decoder_; |
| gpu::Capabilities capabilities_; |
| |
| std::unique_ptr<gles2::GLES2CmdHelper> gles2_helper_; |
| std::unique_ptr<TransferBuffer> transfer_buffer_; |
| std::unique_ptr<gles2::GLES2Implementation> gles2_implementation_; |
| }; |
| |
| // This abstracts the performance capture loop, iterating through a warmup run |
| // and then a number of performance capturing runs. |
| class PerfIterator { |
| public: |
| PerfIterator(std::string name, int runs, int iterations) |
| : name_(std::move(name)), runs_(runs), iterations_(iterations) { |
| // When running under linux-perf, we try to isolate the microbenchmark |
| // performance: |
| // 1- sleep 1 second after warmup so that one can skip perf for |
| // intitialization with 'perf record -D 1000' |
| // 2- exit immediately after the capture loop is finished to skip teardown. |
| // 3- avoid unneeded syscalls (time, print). |
| for_linux_perf_ = |
| base::CommandLine::ForCurrentProcess()->HasSwitch("for-linux-perf"); |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch("fast-run")) { |
| runs_ = 1; |
| iterations_ = 100; |
| } |
| } |
| |
| bool Iterate() { |
| if (--current_iterations_ > 0) |
| return true; |
| return NextOuter(); |
| } |
| |
| private: |
| bool NextOuter() { |
| base::TimeTicks time; |
| if (warmup_) { |
| warmup_ = false; |
| if (for_linux_perf_) |
| base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); |
| else |
| time = base::TimeTicks::Now(); |
| } else if (!for_linux_perf_) { |
| time = base::TimeTicks::Now(); |
| double ns = (time - run_start_time_).InNanoseconds() / iterations_; |
| perf_test::PrintResult(name_, "", "wall_time", ns, "ns", true); |
| } |
| if (runs_ == 0) { |
| if (for_linux_perf_) |
| base::Process::TerminateCurrentProcessImmediately(0); |
| return false; |
| } |
| --runs_; |
| current_iterations_ = iterations_; |
| run_start_time_ = time; |
| return true; |
| } |
| |
| static constexpr int kWarmupIterations = 2; |
| |
| std::string name_; |
| base::TimeTicks run_start_time_; |
| int runs_; |
| int iterations_; |
| int current_iterations_ = 1 + kWarmupIterations; |
| bool warmup_ = true; |
| bool for_linux_perf_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(PerfIterator); |
| }; |
| |
| class DecoderPerfTest : public testing::Test { |
| public: |
| ~DecoderPerfTest() override = default; |
| |
| void SetUp() override { |
| context_ = std::make_unique<RecordReplayContext>(); |
| gl_ = context_->gl(); |
| gl_->GenRenderbuffers(1, &renderbuffer_); |
| gl_->BindRenderbuffer(GL_RENDERBUFFER, renderbuffer_); |
| gl_->RenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 256, 256); |
| gl_->GenFramebuffers(1, &framebuffer_); |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, framebuffer_); |
| gl_->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| GL_RENDERBUFFER, renderbuffer_); |
| gl_->Viewport(0, 0, 256, 256); |
| } |
| |
| void TearDown() override { context_.reset(); } |
| |
| void StartRecord() { context_->StartRecord(); } |
| |
| void StartReplay() { context_->StartReplay(); } |
| |
| void Replay() { context_->Replay(); } |
| |
| GLuint CompileShader(GLenum type, const char* source) { |
| GLuint shader = gl_->CreateShader(type); |
| GLint length = base::checked_cast<GLint>(strlen(source)); |
| gl_->ShaderSource(shader, 1, &source, &length); |
| gl_->CompileShader(shader); |
| GLint compile_status = 0; |
| gl_->GetShaderiv(shader, GL_COMPILE_STATUS, &compile_status); |
| if (!compile_status) { |
| GLint log_length = 0; |
| gl_->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); |
| if (log_length) { |
| std::unique_ptr<GLchar[]> log(new GLchar[log_length]); |
| GLsizei returned_log_length = 0; |
| gl_->GetShaderInfoLog(shader, log_length, &returned_log_length, |
| log.get()); |
| LOG(ERROR) << std::string(log.get(), returned_log_length); |
| } |
| gl_->DeleteShader(shader); |
| return 0; |
| } |
| return shader; |
| } |
| |
| struct AttribBinding { |
| const char* name; |
| GLuint location; |
| }; |
| |
| GLuint CreateAndLinkProgram( |
| const char* vertex_shader, |
| const char* fragment_shader, |
| const std::initializer_list<AttribBinding>& attrib_bindings) { |
| GLuint program = gl_->CreateProgram(); |
| GLuint vshader = CompileShader(GL_VERTEX_SHADER, vertex_shader); |
| DCHECK_NE(0u, vshader); |
| gl_->AttachShader(program, vshader); |
| gl_->DeleteShader(vshader); |
| GLuint fshader = CompileShader(GL_FRAGMENT_SHADER, fragment_shader); |
| DCHECK_NE(0u, fshader); |
| gl_->AttachShader(program, fshader); |
| gl_->DeleteShader(fshader); |
| for (const auto& attrib : attrib_bindings) |
| gl_->BindAttribLocation(program, attrib.location, attrib.name); |
| gl_->LinkProgram(program); |
| GLint link_status = 0; |
| gl_->GetProgramiv(program, GL_LINK_STATUS, &link_status); |
| DCHECK_EQ(link_status, GL_TRUE); |
| return program; |
| } |
| |
| void CreateBasicTexture(GLuint texture) { |
| gl_->BindTexture(GL_TEXTURE_2D, texture); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| gl_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, |
| GL_UNSIGNED_BYTE, nullptr); |
| } |
| |
| protected: |
| std::unique_ptr<RecordReplayContext> context_; |
| gles2::GLES2Implementation* gl_; |
| GLuint renderbuffer_ = 0; |
| GLuint framebuffer_ = 0; |
| }; |
| |
| constexpr const char kVertexShader[] = |
| "attribute vec2 position;\n" |
| "uniform vec2 scale;\n" |
| "uniform vec2 offset;\n" |
| "varying vec2 texcoords;\n" |
| "void main () {\n" |
| " gl_Position = vec4(position * scale + offset, 0.0, 1.0);\n" |
| " texcoords = position;\n" |
| "}\n"; |
| |
| constexpr const char kFragmentShader[] = |
| "precision mediump float;\n" |
| "varying vec2 texcoords;\n" |
| "uniform sampler2D texture;\n" |
| "void main() {\n" |
| " gl_FragColor = texture2D(texture, texcoords);\n" |
| "}\n"; |
| |
| constexpr const float kVertices[] = { |
| 0.f, 0.f, 0.f, 1.f, 1.f, 0.f, 1.f, 1.f, |
| }; |
| |
| // Measures a loop with Uniform2f and DrawArrays. |
| TEST_F(DecoderPerfTest, BasicDraw) { |
| GLuint program = |
| CreateAndLinkProgram(kVertexShader, kFragmentShader, {{"postition", 0}}); |
| gl_->UseProgram(program); |
| |
| GLint scale_location = gl_->GetUniformLocation(program, "scale"); |
| GLint offset_location = gl_->GetUniformLocation(program, "offset"); |
| GLint texture_location = gl_->GetUniformLocation(program, "texture"); |
| |
| GLuint texture; |
| gl_->GenTextures(1, &texture); |
| CreateBasicTexture(texture); |
| |
| GLuint buffer; |
| gl_->GenBuffers(1, &buffer); |
| gl_->BindBuffer(GL_ARRAY_BUFFER, buffer); |
| gl_->BufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, |
| GL_STATIC_DRAW); |
| |
| gl_->VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), |
| nullptr); |
| gl_->EnableVertexAttribArray(0); |
| |
| gl_->Uniform1i(texture_location, 0); |
| |
| constexpr int N = 10; |
| gl_->Uniform2f(scale_location, 2.f / N, 2.f / N); |
| |
| StartRecord(); |
| for (int x = 0; x < N; ++x) { |
| float xpos = 2.f * x / N - 1.f; |
| for (int y = 0; y < N; ++y) { |
| float ypos = 2.f * y / N - 1.f; |
| gl_->Uniform2f(offset_location, xpos, ypos); |
| gl_->DrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| } |
| } |
| |
| StartReplay(); |
| PerfIterator iterator("decoder_basic_draw_100", kDefaultRuns, |
| kDefaultIterations); |
| while (iterator.Iterate()) |
| Replay(); |
| } |
| |
| // Measures a loop with changing the texture binding between draws. |
| TEST_F(DecoderPerfTest, TextureDraw) { |
| GLuint program = |
| CreateAndLinkProgram(kVertexShader, kFragmentShader, {{"position", 0}}); |
| gl_->UseProgram(program); |
| |
| GLint scale_location = gl_->GetUniformLocation(program, "scale"); |
| GLint offset_location = gl_->GetUniformLocation(program, "offset"); |
| GLint texture_location = gl_->GetUniformLocation(program, "texture"); |
| |
| constexpr size_t kTextures = 16; |
| GLuint textures[kTextures]; |
| gl_->GenTextures(kTextures, textures); |
| for (GLuint texture : textures) |
| CreateBasicTexture(texture); |
| |
| GLuint buffer; |
| gl_->GenBuffers(1, &buffer); |
| gl_->BindBuffer(GL_ARRAY_BUFFER, buffer); |
| gl_->BufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, |
| GL_STATIC_DRAW); |
| |
| gl_->VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), |
| nullptr); |
| gl_->EnableVertexAttribArray(0); |
| |
| gl_->Uniform1i(texture_location, 0); |
| |
| constexpr int N = 10; |
| gl_->Uniform2f(scale_location, 2.f / N, 2.f / N); |
| |
| StartRecord(); |
| size_t texture = 0; |
| for (int x = 0; x < N; ++x) { |
| float xpos = 2.f * x / N - 1.f; |
| for (int y = 0; y < N; ++y) { |
| float ypos = 2.f * y / N - 1.f; |
| gl_->BindTexture(GL_TEXTURE_2D, textures[texture]); |
| gl_->Uniform2f(offset_location, xpos, ypos); |
| gl_->DrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| texture = (texture + 1) % kTextures; |
| } |
| } |
| |
| StartReplay(); |
| PerfIterator iterator("decoder_texture_draw_100", kDefaultRuns, |
| kDefaultIterations); |
| while (iterator.Iterate()) |
| Replay(); |
| } |
| |
| // Measures a loop with changing the program between draws. |
| TEST_F(DecoderPerfTest, ProgramDraw) { |
| const char kVertexShader2[] = |
| "attribute vec2 position;\n" |
| "uniform vec2 scale;\n" |
| "uniform vec2 offset;\n" |
| "void main () {\n" |
| " gl_Position = vec4(position * scale + offset, 0.0, 1.0);\n" |
| "}\n"; |
| |
| const char kFragmentShader2[] = |
| "precision mediump float;\n" |
| "uniform vec4 color;\n" |
| "void main() {\n" |
| " gl_FragColor = color;\n" |
| "}\n"; |
| |
| GLuint programs[2]; |
| programs[0] = |
| CreateAndLinkProgram(kVertexShader, kFragmentShader, {{"position", 0}}); |
| |
| GLint scale_location1 = gl_->GetUniformLocation(programs[0], "scale"); |
| GLint texture_location1 = gl_->GetUniformLocation(programs[0], "texture"); |
| |
| programs[1] = |
| CreateAndLinkProgram(kVertexShader2, kFragmentShader2, {{"position", 0}}); |
| |
| GLint scale_location2 = gl_->GetUniformLocation(programs[1], "scale"); |
| GLint color_location2 = gl_->GetUniformLocation(programs[1], "color"); |
| |
| GLuint texture; |
| gl_->GenTextures(1, &texture); |
| CreateBasicTexture(texture); |
| |
| GLuint buffer; |
| gl_->GenBuffers(1, &buffer); |
| gl_->BindBuffer(GL_ARRAY_BUFFER, buffer); |
| gl_->BufferData(GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, |
| GL_STATIC_DRAW); |
| |
| gl_->VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), |
| nullptr); |
| gl_->EnableVertexAttribArray(0); |
| |
| constexpr int N = 10; |
| |
| gl_->UseProgram(programs[0]); |
| |
| gl_->Uniform1i(texture_location1, 0); |
| gl_->Uniform2f(scale_location1, 2.f / N, 2.f / N); |
| |
| gl_->UseProgram(programs[1]); |
| gl_->Uniform2f(scale_location2, 2.f / N, 2.f / N); |
| gl_->Uniform4f(color_location2, 1.f, 0.f, 0.f, 1.f); |
| |
| GLint offset_locations[2] = {gl_->GetUniformLocation(programs[0], "offset"), |
| gl_->GetUniformLocation(programs[1], "offset")}; |
| |
| StartRecord(); |
| size_t program = 0; |
| for (int x = 0; x < N; ++x) { |
| float xpos = 2.f * x / N - 1.f; |
| for (int y = 0; y < N; ++y) { |
| float ypos = 2.f * y / N - 1.f; |
| gl_->UseProgram(programs[program]); |
| gl_->Uniform2f(offset_locations[program], xpos, ypos); |
| gl_->DrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
| program = 1 - program; |
| } |
| } |
| |
| StartReplay(); |
| PerfIterator iterator("decoder_program_draw_100", kDefaultRuns, |
| kDefaultIterations); |
| while (iterator.Iterate()) |
| Replay(); |
| } |
| |
| } // anonymous namespace |
| } // namespace gpu |