Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2010 The Chromium Authors. All rights reserved. | 1 // Copyright 2010 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "cc/output/gl_renderer.h" | 5 #include "cc/output/gl_renderer.h" |
| 6 | 6 |
| 7 #include <stddef.h> | 7 #include <stddef.h> |
| 8 #include <stdint.h> | 8 #include <stdint.h> |
| 9 | 9 |
| 10 #include <algorithm> | 10 #include <algorithm> |
| 11 #include <limits> | 11 #include <limits> |
| 12 #include <memory> | 12 #include <memory> |
| 13 #include <set> | 13 #include <set> |
| 14 #include <string> | 14 #include <string> |
| 15 #include <vector> | 15 #include <vector> |
| 16 | 16 |
| 17 #include "base/barrier_closure.h" | |
| 17 #include "base/feature_list.h" | 18 #include "base/feature_list.h" |
| 18 #include "base/logging.h" | 19 #include "base/logging.h" |
| 19 #include "base/macros.h" | 20 #include "base/macros.h" |
| 20 #include "base/memory/ptr_util.h" | 21 #include "base/memory/ptr_util.h" |
| 21 #include "base/strings/string_split.h" | 22 #include "base/strings/string_split.h" |
| 22 #include "base/strings/string_util.h" | 23 #include "base/strings/string_util.h" |
| 23 #include "base/strings/stringprintf.h" | 24 #include "base/strings/stringprintf.h" |
| 24 #include "base/threading/thread_task_runner_handle.h" | 25 #include "base/threading/thread_task_runner_handle.h" |
| 25 #include "base/trace_event/trace_event.h" | 26 #include "base/trace_event/trace_event.h" |
| 26 #include "build/build_config.h" | 27 #include "build/build_config.h" |
| (...skipping 454 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 481 } | 482 } |
| 482 | 483 |
| 483 void GLRenderer::ClearFramebuffer(DrawingFrame* frame) { | 484 void GLRenderer::ClearFramebuffer(DrawingFrame* frame) { |
| 484 // On DEBUG builds, opaque render passes are cleared to blue to easily see | 485 // On DEBUG builds, opaque render passes are cleared to blue to easily see |
| 485 // regions that were not drawn on the screen. | 486 // regions that were not drawn on the screen. |
| 486 if (frame->current_render_pass->has_transparent_background) | 487 if (frame->current_render_pass->has_transparent_background) |
| 487 gl_->ClearColor(0, 0, 0, 0); | 488 gl_->ClearColor(0, 0, 0, 0); |
| 488 else | 489 else |
| 489 gl_->ClearColor(0, 0, 1, 1); | 490 gl_->ClearColor(0, 0, 1, 1); |
| 490 | 491 |
| 491 bool always_clear = false; | 492 gl_->ClearStencil(0); |
| 493 | |
| 494 bool always_clear = overdraw_feedback_; | |
| 492 #ifndef NDEBUG | 495 #ifndef NDEBUG |
| 493 always_clear = true; | 496 always_clear = true; |
| 494 #endif | 497 #endif |
| 495 if (always_clear || frame->current_render_pass->has_transparent_background) { | 498 if (always_clear || frame->current_render_pass->has_transparent_background) { |
| 496 GLbitfield clear_bits = GL_COLOR_BUFFER_BIT; | 499 GLbitfield clear_bits = GL_COLOR_BUFFER_BIT; |
| 497 if (always_clear) | 500 if (always_clear) |
| 498 clear_bits |= GL_STENCIL_BUFFER_BIT; | 501 clear_bits |= GL_STENCIL_BUFFER_BIT; |
| 499 gl_->Clear(clear_bits); | 502 gl_->Clear(clear_bits); |
| 500 } | 503 } |
| 501 } | 504 } |
| (...skipping 2077 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2579 } | 2582 } |
| 2580 } | 2583 } |
| 2581 | 2584 |
| 2582 void GLRenderer::FinishDrawingFrame(DrawingFrame* frame) { | 2585 void GLRenderer::FinishDrawingFrame(DrawingFrame* frame) { |
| 2583 if (use_sync_query_) { | 2586 if (use_sync_query_) { |
| 2584 DCHECK(current_sync_query_); | 2587 DCHECK(current_sync_query_); |
| 2585 current_sync_query_->End(); | 2588 current_sync_query_->End(); |
| 2586 pending_sync_queries_.push_back(std::move(current_sync_query_)); | 2589 pending_sync_queries_.push_back(std::move(current_sync_query_)); |
| 2587 } | 2590 } |
| 2588 | 2591 |
| 2592 swap_buffer_rect_.Union(frame->root_damage_rect); | |
| 2593 if (overdraw_feedback_) | |
| 2594 FlushOverdrawFeedback(frame, swap_buffer_rect_); | |
| 2595 | |
| 2589 current_framebuffer_lock_ = nullptr; | 2596 current_framebuffer_lock_ = nullptr; |
| 2590 swap_buffer_rect_.Union(frame->root_damage_rect); | |
| 2591 | 2597 |
| 2592 gl_->Disable(GL_BLEND); | 2598 gl_->Disable(GL_BLEND); |
| 2593 blend_shadow_ = false; | 2599 blend_shadow_ = false; |
| 2594 | 2600 |
| 2595 ScheduleCALayers(frame); | 2601 ScheduleCALayers(frame); |
| 2596 ScheduleOverlays(frame); | 2602 ScheduleOverlays(frame); |
| 2597 } | 2603 } |
| 2598 | 2604 |
| 2599 void GLRenderer::FinishDrawingQuadList() { | 2605 void GLRenderer::FinishDrawingQuadList() { |
| 2600 FlushTextureQuadCache(SHARED_BINDING); | 2606 FlushTextureQuadCache(SHARED_BINDING); |
| (...skipping 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2818 void GLRenderer::GetFramebufferPixelsAsync( | 2824 void GLRenderer::GetFramebufferPixelsAsync( |
| 2819 const DrawingFrame* frame, | 2825 const DrawingFrame* frame, |
| 2820 const gfx::Rect& rect, | 2826 const gfx::Rect& rect, |
| 2821 std::unique_ptr<CopyOutputRequest> request) { | 2827 std::unique_ptr<CopyOutputRequest> request) { |
| 2822 DCHECK(!request->IsEmpty()); | 2828 DCHECK(!request->IsEmpty()); |
| 2823 if (request->IsEmpty()) | 2829 if (request->IsEmpty()) |
| 2824 return; | 2830 return; |
| 2825 if (rect.IsEmpty()) | 2831 if (rect.IsEmpty()) |
| 2826 return; | 2832 return; |
| 2827 | 2833 |
| 2834 if (overdraw_feedback_) | |
| 2835 FlushOverdrawFeedback(frame, rect); | |
| 2836 | |
| 2828 gfx::Rect window_rect = MoveFromDrawToWindowSpace(frame, rect); | 2837 gfx::Rect window_rect = MoveFromDrawToWindowSpace(frame, rect); |
| 2829 DCHECK_GE(window_rect.x(), 0); | 2838 DCHECK_GE(window_rect.x(), 0); |
| 2830 DCHECK_GE(window_rect.y(), 0); | 2839 DCHECK_GE(window_rect.y(), 0); |
| 2831 DCHECK_LE(window_rect.right(), current_surface_size_.width()); | 2840 DCHECK_LE(window_rect.right(), current_surface_size_.width()); |
| 2832 DCHECK_LE(window_rect.bottom(), current_surface_size_.height()); | 2841 DCHECK_LE(window_rect.bottom(), current_surface_size_.height()); |
| 2833 | 2842 |
| 2834 if (!request->force_bitmap_result()) { | 2843 if (!request->force_bitmap_result()) { |
| 2835 bool own_mailbox = !request->has_texture_mailbox(); | 2844 bool own_mailbox = !request->has_texture_mailbox(); |
| 2836 | 2845 |
| 2837 GLuint texture_id = 0; | 2846 GLuint texture_id = 0; |
| (...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3009 gl_->CopyTexImage2D(GL_TEXTURE_2D, 0, format, window_rect.x(), | 3018 gl_->CopyTexImage2D(GL_TEXTURE_2D, 0, format, window_rect.x(), |
| 3010 window_rect.y(), window_rect.width(), | 3019 window_rect.y(), window_rect.width(), |
| 3011 window_rect.height(), 0); | 3020 window_rect.height(), 0); |
| 3012 gl_->BindTexture(GL_TEXTURE_2D, 0); | 3021 gl_->BindTexture(GL_TEXTURE_2D, 0); |
| 3013 } | 3022 } |
| 3014 | 3023 |
| 3015 void GLRenderer::BindFramebufferToOutputSurface(DrawingFrame* frame) { | 3024 void GLRenderer::BindFramebufferToOutputSurface(DrawingFrame* frame) { |
| 3016 current_framebuffer_lock_ = nullptr; | 3025 current_framebuffer_lock_ = nullptr; |
| 3017 output_surface_->BindFramebuffer(); | 3026 output_surface_->BindFramebuffer(); |
| 3018 | 3027 |
| 3019 if (output_surface_->HasExternalStencilTest()) { | 3028 if (overdraw_feedback_) { |
| 3029 DCHECK(!output_surface_->HasExternalStencilTest()); | |
|
danakj
2017/01/16 16:02:57
Can you put in a comment that the output surface s
reveman
2017/01/16 18:02:27
Done.
| |
| 3030 SetupOverdrawFeedback(); | |
| 3031 SetStencilEnabled(true); | |
| 3032 } else if (output_surface_->HasExternalStencilTest()) { | |
| 3020 output_surface_->ApplyExternalStencil(); | 3033 output_surface_->ApplyExternalStencil(); |
| 3021 SetStencilEnabled(true); | 3034 SetStencilEnabled(true); |
| 3022 } else { | 3035 } else { |
| 3023 SetStencilEnabled(false); | 3036 SetStencilEnabled(false); |
| 3024 } | 3037 } |
| 3025 } | 3038 } |
| 3026 | 3039 |
| 3027 bool GLRenderer::BindFramebufferToTexture(DrawingFrame* frame, | 3040 bool GLRenderer::BindFramebufferToTexture(DrawingFrame* frame, |
| 3028 const ScopedResource* texture) { | 3041 const ScopedResource* texture) { |
| 3029 DCHECK(texture->id()); | 3042 DCHECK(texture->id()); |
| 3030 | 3043 |
| 3031 // Explicitly release lock, otherwise we can crash when try to lock | 3044 // Explicitly release lock, otherwise we can crash when try to lock |
| 3032 // same texture again. | 3045 // same texture again. |
| 3033 current_framebuffer_lock_ = nullptr; | 3046 current_framebuffer_lock_ = nullptr; |
| 3034 | 3047 |
| 3035 SetStencilEnabled(false); | |
| 3036 gl_->BindFramebuffer(GL_FRAMEBUFFER, offscreen_framebuffer_id_); | 3048 gl_->BindFramebuffer(GL_FRAMEBUFFER, offscreen_framebuffer_id_); |
| 3037 current_framebuffer_lock_ = | 3049 current_framebuffer_lock_ = |
| 3038 base::MakeUnique<ResourceProvider::ScopedWriteLockGL>( | 3050 base::MakeUnique<ResourceProvider::ScopedWriteLockGL>( |
| 3039 resource_provider_, texture->id(), false); | 3051 resource_provider_, texture->id(), false); |
| 3040 current_framebuffer_format_ = texture->format(); | 3052 current_framebuffer_format_ = texture->format(); |
| 3041 unsigned texture_id = current_framebuffer_lock_->texture_id(); | 3053 unsigned texture_id = current_framebuffer_lock_->texture_id(); |
| 3042 gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | 3054 gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, |
| 3043 texture_id, 0); | 3055 texture_id, 0); |
| 3056 if (overdraw_feedback_) { | |
| 3057 if (!offscreen_stencil_renderbuffer_id_) | |
| 3058 gl_->GenRenderbuffers(1, &offscreen_stencil_renderbuffer_id_); | |
| 3059 if (texture->size() != offscreen_stencil_renderbuffer_size_) { | |
| 3060 gl_->BindRenderbuffer(GL_RENDERBUFFER, | |
| 3061 offscreen_stencil_renderbuffer_id_); | |
| 3062 gl_->RenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, | |
| 3063 texture->size().width(), | |
| 3064 texture->size().height()); | |
| 3065 gl_->BindRenderbuffer(GL_RENDERBUFFER, 0); | |
| 3066 offscreen_stencil_renderbuffer_size_ = texture->size(); | |
| 3067 } | |
| 3068 gl_->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, | |
| 3069 GL_RENDERBUFFER, | |
| 3070 offscreen_stencil_renderbuffer_id_); | |
| 3071 } | |
| 3044 | 3072 |
| 3045 DCHECK(gl_->CheckFramebufferStatus(GL_FRAMEBUFFER) == | 3073 DCHECK(gl_->CheckFramebufferStatus(GL_FRAMEBUFFER) == |
| 3046 GL_FRAMEBUFFER_COMPLETE || | 3074 GL_FRAMEBUFFER_COMPLETE || |
| 3047 IsContextLost()); | 3075 IsContextLost()); |
| 3076 | |
| 3077 if (overdraw_feedback_) { | |
| 3078 SetupOverdrawFeedback(); | |
| 3079 SetStencilEnabled(true); | |
| 3080 } else { | |
| 3081 SetStencilEnabled(false); | |
| 3082 } | |
| 3048 return true; | 3083 return true; |
| 3049 } | 3084 } |
| 3050 | 3085 |
| 3051 void GLRenderer::SetScissorTestRect(const gfx::Rect& scissor_rect) { | 3086 void GLRenderer::SetScissorTestRect(const gfx::Rect& scissor_rect) { |
| 3052 EnsureScissorTestEnabled(); | 3087 EnsureScissorTestEnabled(); |
| 3053 | 3088 |
| 3054 // Don't unnecessarily ask the context to change the scissor, because it | 3089 // Don't unnecessarily ask the context to change the scissor, because it |
| 3055 // may cause undesired GPU pipeline flushes. | 3090 // may cause undesired GPU pipeline flushes. |
| 3056 if (scissor_rect == scissor_rect_) | 3091 if (scissor_rect == scissor_rect_) |
| 3057 return; | 3092 return; |
| (...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3151 } | 3186 } |
| 3152 } | 3187 } |
| 3153 } | 3188 } |
| 3154 for (auto& iter : program_cache_) | 3189 for (auto& iter : program_cache_) |
| 3155 iter.second->Cleanup(gl_); | 3190 iter.second->Cleanup(gl_); |
| 3156 program_cache_.clear(); | 3191 program_cache_.clear(); |
| 3157 | 3192 |
| 3158 if (offscreen_framebuffer_id_) | 3193 if (offscreen_framebuffer_id_) |
| 3159 gl_->DeleteFramebuffers(1, &offscreen_framebuffer_id_); | 3194 gl_->DeleteFramebuffers(1, &offscreen_framebuffer_id_); |
| 3160 | 3195 |
| 3196 if (offscreen_stencil_renderbuffer_id_) | |
| 3197 gl_->DeleteRenderbuffers(1, &offscreen_stencil_renderbuffer_id_); | |
| 3198 | |
| 3161 ReleaseRenderPassTextures(); | 3199 ReleaseRenderPassTextures(); |
| 3162 } | 3200 } |
| 3163 | 3201 |
| 3164 void GLRenderer::ReinitializeGLState() { | 3202 void GLRenderer::ReinitializeGLState() { |
| 3165 is_scissor_enabled_ = false; | 3203 is_scissor_enabled_ = false; |
| 3166 scissor_rect_ = gfx::Rect(); | 3204 scissor_rect_ = gfx::Rect(); |
| 3167 stencil_shadow_ = false; | 3205 stencil_shadow_ = false; |
| 3168 blend_shadow_ = true; | 3206 blend_shadow_ = true; |
| 3169 program_shadow_ = 0; | 3207 program_shadow_ = 0; |
| 3170 | 3208 |
| (...skipping 312 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 3483 | 3521 |
| 3484 // The alpha has already been applied when copying the RPDQ to an IOSurface. | 3522 // The alpha has already been applied when copying the RPDQ to an IOSurface. |
| 3485 GLfloat alpha = 1; | 3523 GLfloat alpha = 1; |
| 3486 gl_->ScheduleCALayerSharedStateCHROMIUM(alpha, is_clipped, clip_rect, | 3524 gl_->ScheduleCALayerSharedStateCHROMIUM(alpha, is_clipped, clip_rect, |
| 3487 sorting_context_id, gl_transform); | 3525 sorting_context_id, gl_transform); |
| 3488 gl_->ScheduleCALayerCHROMIUM( | 3526 gl_->ScheduleCALayerCHROMIUM( |
| 3489 texture_id, contents_rect, ca_layer_overlay->background_color, | 3527 texture_id, contents_rect, ca_layer_overlay->background_color, |
| 3490 ca_layer_overlay->edge_aa_mask, bounds_rect, filter); | 3528 ca_layer_overlay->edge_aa_mask, bounds_rect, filter); |
| 3491 } | 3529 } |
| 3492 | 3530 |
| 3531 void GLRenderer::SetupOverdrawFeedback() { | |
| 3532 gl_->StencilFunc(GL_ALWAYS, 1, 0xffffffff); | |
| 3533 // First two values are ignored as test always passes. | |
| 3534 gl_->StencilOp(GL_KEEP, GL_KEEP, GL_INCR); | |
| 3535 gl_->StencilMask(0xff); | |
| 3536 } | |
| 3537 | |
| 3538 void GLRenderer::FlushOverdrawFeedback(const DrawingFrame* frame, | |
| 3539 const gfx::Rect& output_rect) { | |
| 3540 DCHECK(stencil_shadow_); | |
| 3541 | |
| 3542 // Test only, keep everything. | |
| 3543 gl_->StencilOp(GL_KEEP, GL_KEEP, GL_KEEP); | |
| 3544 gl_->StencilMask(0); | |
| 3545 | |
| 3546 EnsureScissorTestDisabled(); | |
| 3547 SetBlendEnabled(true); | |
| 3548 | |
| 3549 PrepareGeometry(SHARED_BINDING); | |
| 3550 | |
| 3551 const Program* program = GetProgram(ProgramKey::DebugBorder()); | |
| 3552 DCHECK(program && (program->initialized() || IsContextLost())); | |
| 3553 SetUseProgram(program->program()); | |
| 3554 | |
| 3555 gfx::Transform render_matrix; | |
| 3556 render_matrix.Translate(0.5 * output_rect.width() + output_rect.x(), | |
| 3557 0.5 * output_rect.height() + output_rect.y()); | |
| 3558 render_matrix.Scale(output_rect.width(), output_rect.height()); | |
| 3559 static float gl_matrix[16]; | |
| 3560 GLRenderer::ToGLMatrix(&gl_matrix[0], | |
| 3561 frame->projection_matrix * render_matrix); | |
| 3562 gl_->UniformMatrix4fv(program->matrix_location(), 1, false, &gl_matrix[0]); | |
| 3563 | |
| 3564 // Produce hinting for the amount of overdraw on screen for each pixel by | |
| 3565 // drawing hint colors to the framebuffer based on the current stencil value. | |
| 3566 struct { | |
| 3567 int category; | |
| 3568 GLenum func; | |
| 3569 GLint ref; | |
| 3570 SkColor color; | |
| 3571 } stencil_tests[] = { | |
| 3572 {1, GL_EQUAL, 2, 0x2f0000ff}, // Blue: Overdrawn once. | |
| 3573 {2, GL_EQUAL, 3, 0x2f00ff00}, // Green: Overdrawn twice. | |
| 3574 {3, GL_EQUAL, 4, 0x3fff0000}, // Pink: Overdrawn three times. | |
| 3575 {4, GL_LESS, 4, 0x7fff0000}, // Red: Overdrawn four or more times. | |
| 3576 }; | |
| 3577 | |
| 3578 // Occlusion queries can be expensive, so only collect trace data if we select | |
| 3579 // cc.debug.overdraw. | |
| 3580 bool tracing_enabled; | |
| 3581 TRACE_EVENT_CATEGORY_GROUP_ENABLED( | |
| 3582 TRACE_DISABLED_BY_DEFAULT("cc.debug.overdraw"), &tracing_enabled); | |
| 3583 | |
| 3584 // Trace only the root render pass. | |
| 3585 if (frame->current_render_pass != frame->root_render_pass) | |
| 3586 tracing_enabled = false; | |
| 3587 | |
| 3588 auto overdraw = new OverdrawResult; | |
| 3589 base::Closure barrier = base::BarrierClosure( | |
| 3590 arraysize(stencil_tests), | |
| 3591 base::Bind(&GLRenderer::UpdateOverdrawCounter, base::Owned(overdraw))); | |
| 3592 | |
| 3593 for (const auto& test : stencil_tests) { | |
| 3594 GLuint query = 0; | |
| 3595 if (tracing_enabled) { | |
| 3596 gl_->GenQueriesEXT(1, &query); | |
| 3597 // TODO(reveman): Use SAMPLES_PASSED_ARB when available for exact amount | |
| 3598 // of overdraw. | |
| 3599 gl_->BeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query); | |
| 3600 } | |
| 3601 | |
| 3602 gl_->StencilFunc(test.func, test.ref, 0xffffffff); | |
| 3603 // Transparent color unless color-coding of overdraw is enabled. | |
| 3604 Float4 color = | |
| 3605 PremultipliedColor(settings_->show_overdraw_feedback ? test.color : 0); | |
| 3606 gl_->Uniform4fv(program->color_location(), 1, color.data); | |
| 3607 gl_->DrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); | |
| 3608 | |
| 3609 if (query) { | |
| 3610 gl_->EndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT); | |
| 3611 context_support_->SignalQuery( | |
| 3612 query, base::Bind(&GLRenderer::ProcessOverdrawFeedback, | |
| 3613 weak_ptr_factory_.GetWeakPtr(), query, | |
| 3614 test.category, overdraw, barrier)); | |
| 3615 } | |
| 3616 } | |
| 3617 } | |
| 3618 | |
| 3619 void GLRenderer::ProcessOverdrawFeedback(unsigned query, | |
| 3620 int category, | |
| 3621 OverdrawResult* overdraw, | |
| 3622 const base::Closure& callback) { | |
| 3623 if (query) { | |
| 3624 unsigned result = 0; | |
| 3625 gl_->GetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result); | |
| 3626 gl_->DeleteQueriesEXT(1, &query); | |
| 3627 if (result) | |
| 3628 overdraw->insert(category); | |
| 3629 } | |
| 3630 | |
| 3631 callback.Run(); | |
| 3632 } | |
| 3633 | |
| 3634 // static | |
| 3635 void GLRenderer::UpdateOverdrawCounter(const OverdrawResult* overdraw) { | |
| 3636 // Report overdraw as multiple of the screen size. ie. 1x for the whole | |
| 3637 // screen is 1.0. Note: this will always report the worst overdraw on screen | |
| 3638 // as the overdraw for the whole screen until SAMPLES_PASSED_ARB is supported. | |
| 3639 TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("cc.debug.overdraw"), "GPU Overdraw", | |
| 3640 overdraw->empty() ? 0 : *overdraw->rbegin()); | |
| 3641 } | |
| 3642 | |
| 3493 } // namespace cc | 3643 } // namespace cc |
| OLD | NEW |