| /* |
| * Copyright (c) 2014 - 2015, The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without modification, are permitted |
| * provided that the following conditions are met: |
| * * Redistributions of source code must retain the above copyright notice, this list of |
| * conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright notice, this list of |
| * conditions and the following disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * * Neither the name of The Linux Foundation nor the names of its contributors may be used to |
| * endorse or promote products derived from this software without specific prior written |
| * permission. |
| * |
| * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <math.h> |
| #include <utils/constants.h> |
| #include <utils/debug.h> |
| #include <dlfcn.h> |
| |
| #include "res_manager.h" |
| |
| #define __CLASS__ "ResManager" |
| |
| namespace sde { |
| |
| ResManager::ResManager() |
| : num_pipe_(0), vig_pipes_(NULL), rgb_pipes_(NULL), dma_pipes_(NULL), virtual_count_(0), |
| buffer_allocator_(NULL), buffer_sync_handler_(NULL), scalar_(NULL) { |
| } |
| |
| DisplayError ResManager::Init(const HWResourceInfo &hw_res_info) { |
| DisplayError error = kErrorNone; |
| uint32_t num_pipe = 0; |
| |
| num_pipe = hw_res_info.num_vig_pipe + hw_res_info.num_rgb_pipe + hw_res_info.num_dma_pipe; |
| |
| if (num_pipe > kPipeIdMax) { |
| DLOGE("Number of pipe is over the limit! %d", num_pipe); |
| return kErrorParameters; |
| } |
| |
| num_pipe_ = num_pipe; |
| hw_res_info_ = hw_res_info; |
| // Init pipe info |
| vig_pipes_ = &src_pipes_[0]; |
| rgb_pipes_ = &src_pipes_[hw_res_info_.num_vig_pipe]; |
| dma_pipes_ = &src_pipes_[hw_res_info_.num_vig_pipe + hw_res_info_.num_rgb_pipe]; |
| |
| for (uint32_t i = 0; i < hw_res_info_.num_vig_pipe; i++) { |
| vig_pipes_[i].type = kPipeTypeVIG; |
| vig_pipes_[i].index = i; |
| vig_pipes_[i].mdss_pipe_id = GetMdssPipeId(vig_pipes_[i].type, i); |
| } |
| |
| for (uint32_t i = 0; i < hw_res_info_.num_rgb_pipe; i++) { |
| rgb_pipes_[i].type = kPipeTypeRGB; |
| rgb_pipes_[i].index = i + hw_res_info_.num_vig_pipe; |
| rgb_pipes_[i].mdss_pipe_id = GetMdssPipeId(rgb_pipes_[i].type, i); |
| } |
| |
| for (uint32_t i = 0; i < hw_res_info_.num_dma_pipe; i++) { |
| dma_pipes_[i].type = kPipeTypeDMA; |
| dma_pipes_[i].index = i + hw_res_info_.num_vig_pipe + hw_res_info_.num_rgb_pipe; |
| dma_pipes_[i].mdss_pipe_id = GetMdssPipeId(dma_pipes_[i].type, i); |
| } |
| |
| for (uint32_t i = 0; i < num_pipe_; i++) { |
| src_pipes_[i].priority = i; |
| } |
| |
| DLOGI("hw_rev=%x, DMA=%d RGB=%d VIG=%d", hw_res_info_.hw_revision, hw_res_info_.num_dma_pipe, |
| hw_res_info_.num_rgb_pipe, hw_res_info_.num_vig_pipe); |
| |
| // TODO(user): Need to get it from HW capability |
| hw_res_info_.num_rotator = 2; |
| |
| if (hw_res_info_.num_rotator > kMaxNumRotator) { |
| DLOGE("Number of rotator is over the limit! %d", hw_res_info_.num_rotator); |
| hw_res_info_.num_rotator = kMaxNumRotator; |
| } |
| |
| if (hw_res_info_.max_scale_down < 1 || hw_res_info_.max_scale_up < 1) { |
| DLOGE("Max scaling setting is invalid! max_scale_down = %d, max_scale_up = %d", |
| hw_res_info_.max_scale_down, hw_res_info_.max_scale_up); |
| hw_res_info_.max_scale_down = 1; |
| hw_res_info_.max_scale_up = 1; |
| } |
| |
| if (hw_res_info_.num_rotator > 0) { |
| rotators_[0].pipe_index = dma_pipes_[0].index; |
| rotators_[0].writeback_id = kHWWriteback0; |
| } |
| if (hw_res_info_.num_rotator > 1) { |
| rotators_[1].pipe_index = dma_pipes_[1].index; |
| rotators_[1].writeback_id = kHWWriteback1; |
| } |
| |
| // Used by splash screen |
| rgb_pipes_[0].state = kPipeStateOwnedByKernel; |
| rgb_pipes_[1].state = kPipeStateOwnedByKernel; |
| |
| error = Scalar::CreateScalar(&scalar_); |
| if (error != kErrorNone) { |
| DLOGE("Failed to create Scalar object!"); |
| } |
| |
| max_system_bw_ = FLOAT(hw_res_info_.max_bandwidth_high); |
| return error; |
| } |
| |
| DisplayError ResManager::Deinit() { |
| Scalar::Destroy(scalar_); |
| return kErrorNone; |
| } |
| |
| DisplayError ResManager::RegisterDisplay(DisplayType type, const HWDisplayAttributes &attributes, |
| const HWPanelInfo &hw_panel_info, Handle *display_ctx) { |
| DisplayError error = kErrorNone; |
| |
| HWBlockType hw_block_id = kHWBlockMax; |
| switch (type) { |
| case kPrimary: |
| if (!hw_block_ctx_[kHWPrimary].is_in_use) { |
| hw_block_id = kHWPrimary; |
| } |
| break; |
| |
| case kHDMI: |
| if (!hw_block_ctx_[kHWHDMI].is_in_use) { |
| hw_block_id = kHWHDMI; |
| } |
| break; |
| |
| case kVirtual: |
| // TODO(user): read this block id from kernel |
| virtual_count_++; |
| hw_block_id = kHWWriteback2; |
| break; |
| |
| default: |
| DLOGW("RegisterDisplay, invalid type %d", type); |
| return kErrorParameters; |
| } |
| |
| if (hw_block_id == kHWBlockMax) { |
| return kErrorResources; |
| } |
| |
| DisplayResourceContext *display_resource_ctx = new DisplayResourceContext(); |
| if (!display_resource_ctx) { |
| return kErrorMemory; |
| } |
| |
| display_resource_ctx->hw_panel_info_ = hw_panel_info; |
| hw_block_ctx_[hw_block_id].is_in_use = true; |
| |
| display_resource_ctx->display_attributes = attributes; |
| display_resource_ctx->display_type = type; |
| display_resource_ctx->hw_block_id = hw_block_id; |
| if (!display_resource_ctx->display_attributes.is_device_split) |
| display_resource_ctx->display_attributes.split_left = |
| display_resource_ctx->display_attributes.x_pixels; |
| |
| display_resource_ctx->max_mixer_stages = hw_res_info_.num_blending_stages; |
| |
| *display_ctx = display_resource_ctx; |
| return error; |
| } |
| |
| DisplayError ResManager::UnregisterDisplay(Handle display_ctx) { |
| DisplayResourceContext *display_resource_ctx = |
| reinterpret_cast<DisplayResourceContext *>(display_ctx); |
| |
| if (display_resource_ctx->hw_block_id == kHWWriteback2) { |
| virtual_count_--; |
| if (!virtual_count_) |
| hw_block_ctx_[display_resource_ctx->hw_block_id].is_in_use = false; |
| } else { |
| hw_block_ctx_[display_resource_ctx->hw_block_id].is_in_use = false; |
| } |
| |
| if (!hw_block_ctx_[display_resource_ctx->hw_block_id].is_in_use) |
| Purge(display_ctx); |
| |
| delete display_resource_ctx; |
| |
| return kErrorNone; |
| } |
| |
| void ResManager::ReconfigureDisplay(Handle display_ctx, const HWDisplayAttributes &attributes, |
| const HWPanelInfo &hw_panel_info) { |
| SCOPE_LOCK(locker_); |
| |
| DisplayResourceContext *display_resource_ctx = |
| reinterpret_cast<DisplayResourceContext *>(display_ctx); |
| |
| display_resource_ctx->display_attributes = attributes; |
| display_resource_ctx->hw_panel_info_ = hw_panel_info; |
| } |
| |
| DisplayError ResManager::Start(Handle display_ctx) { |
| locker_.Lock(); |
| |
| DisplayResourceContext *display_resource_ctx = |
| reinterpret_cast<DisplayResourceContext *>(display_ctx); |
| |
| if (display_resource_ctx->frame_start) { |
| return kErrorNone; // keep context locked. |
| } |
| |
| // First call in the cycle |
| display_resource_ctx->frame_start = true; |
| display_resource_ctx->frame_count++; |
| |
| // Release the pipes not used in the previous cycle |
| HWBlockType hw_block_id = display_resource_ctx->hw_block_id; |
| for (uint32_t i = 0; i < num_pipe_; i++) { |
| if ((src_pipes_[i].hw_block_id == hw_block_id) && |
| (src_pipes_[i].state == kPipeStateToRelease)) { |
| src_pipes_[i].state = kPipeStateIdle; |
| } |
| } |
| |
| // Clear rotator usage |
| for (uint32_t i = 0; i < hw_res_info_.num_rotator; i++) { |
| uint32_t pipe_index; |
| pipe_index = rotators_[i].pipe_index; |
| rotators_[i].ClearState(display_resource_ctx->hw_block_id); |
| if (rotators_[i].client_bit_mask == 0 && |
| src_pipes_[pipe_index].state == kPipeStateToRelease && |
| src_pipes_[pipe_index].hw_block_id == rotators_[i].writeback_id) { |
| src_pipes_[pipe_index].dedicated_hw_block = kHWBlockMax; |
| src_pipes_[pipe_index].state = kPipeStateIdle; |
| } |
| } |
| |
| property_setting_.disable_rotator_downscaling = Debug::IsRotatorDownScaleDisabled(); |
| property_setting_.disable_decimation = Debug::IsDecimationDisabled(); |
| |
| return kErrorNone; |
| } |
| |
| DisplayError ResManager::Stop(Handle display_ctx) { |
| locker_.Unlock(); |
| |
| DisplayResourceContext *display_resource_ctx = |
| reinterpret_cast<DisplayResourceContext *>(display_ctx); |
| |
| return kErrorNone; |
| } |
| |
| DisplayError ResManager::Acquire(Handle display_ctx, HWLayers *hw_layers) { |
| DisplayResourceContext *display_resource_ctx = |
| reinterpret_cast<DisplayResourceContext *>(display_ctx); |
| |
| DisplayError error = kErrorNone; |
| const struct HWLayersInfo &layer_info = hw_layers->info; |
| HWBlockType hw_block_id = display_resource_ctx->hw_block_id; |
| |
| DLOGV_IF(kTagResources, "==== Resource reserving start: hw_block = %d ====", hw_block_id); |
| |
| if (layer_info.count > num_pipe_) { |
| DLOGV_IF(kTagResources, "layer count exceeds pipe limit of %d, layer count %d", num_pipe_, |
| layer_info.count); |
| return kErrorResources; |
| } |
| |
| if (layer_info.count > display_resource_ctx->max_mixer_stages) { |
| DLOGV_IF(kTagResources, "layer count exceeds max mixer stage limit of %d, layer count %d", |
| display_resource_ctx->max_mixer_stages, layer_info.count); |
| return kErrorResources; |
| } |
| |
| uint32_t rotate_count = 0; |
| error = Config(display_resource_ctx, hw_layers, &rotate_count); |
| if (error != kErrorNone) { |
| DLOGV_IF(kTagResources, "Resource config failed"); |
| return error; |
| } |
| |
| uint32_t left_index = kPipeIdMax; |
| bool need_scale = false; |
| HWBlockType rotator_block = kHWBlockMax; |
| |
| // Clear reserved marking |
| for (uint32_t i = 0; i < num_pipe_; i++) { |
| if (src_pipes_[i].reserved_hw_block == hw_block_id) |
| src_pipes_[i].reserved_hw_block = kHWBlockMax; |
| } |
| |
| // allocate rotator |
| error = AcquireRotator(display_resource_ctx, rotate_count); |
| if (error != kErrorNone) { |
| return error; |
| } |
| |
| rotate_count = 0; |
| for (uint32_t i = 0; i < layer_info.count; i++) { |
| Layer &layer = layer_info.stack->layers[layer_info.index[i]]; |
| struct HWLayerConfig &layer_config = hw_layers->config[i]; |
| HWRotatorSession &hw_rotator_session = layer_config.hw_rotator_session; |
| bool use_non_dma_pipe = layer_config.use_non_dma_pipe; |
| |
| // TODO(user): set this from comp_manager |
| if (hw_block_id == kHWPrimary) { |
| use_non_dma_pipe = true; |
| } |
| |
| for (uint32_t j = 0; j < hw_rotator_session.hw_block_count; j++) { |
| AssignRotator(&hw_rotator_session.hw_rotate_info[j], &rotate_count); |
| } |
| |
| HWPipeInfo *pipe_info = &layer_config.left_pipe; |
| |
| bool is_yuv = !IS_RGB_FORMAT(layer.input_buffer->format); |
| |
| // left pipe is needed |
| if (pipe_info->valid) { |
| need_scale = IsScalingNeeded(pipe_info); |
| left_index = GetPipe(hw_block_id, is_yuv, need_scale, false, use_non_dma_pipe); |
| if (left_index >= num_pipe_) { |
| DLOGV_IF(kTagResources, "Get left pipe failed: hw_block_id = %d, is_yuv = %d, " \ |
| "need_scale = %d, use_non_dma_pipe= %d", |
| hw_block_id, is_yuv, need_scale, use_non_dma_pipe); |
| ResourceStateLog(); |
| goto CleanupOnError; |
| } |
| src_pipes_[left_index].reserved_hw_block = hw_block_id; |
| } |
| |
| error = SetDecimationFactor(pipe_info); |
| if (error != kErrorNone) { |
| goto CleanupOnError; |
| } |
| |
| pipe_info = &layer_config.right_pipe; |
| if (!pipe_info->valid) { |
| // assign single pipe |
| if (left_index < num_pipe_) { |
| layer_config.left_pipe.pipe_id = src_pipes_[left_index].mdss_pipe_id; |
| src_pipes_[left_index].at_right = false; |
| } |
| DLOGV_IF(kTagResources, "1 pipe acquired, layer index = %d, left_pipe = %x", |
| i, layer_config.left_pipe.pipe_id); |
| continue; |
| } |
| |
| need_scale = IsScalingNeeded(pipe_info); |
| |
| uint32_t right_index; |
| right_index = GetPipe(hw_block_id, is_yuv, need_scale, true, use_non_dma_pipe); |
| if (right_index >= num_pipe_) { |
| DLOGV_IF(kTagResources, "Get right pipe failed: hw_block_id = %d, is_yuv = %d, " \ |
| "need_scale = %d, use_non_dma_pipe= %d", |
| hw_block_id, is_yuv, need_scale, use_non_dma_pipe); |
| ResourceStateLog(); |
| goto CleanupOnError; |
| } |
| |
| if (src_pipes_[right_index].priority < src_pipes_[left_index].priority) { |
| // Swap pipe based on priority |
| Swap(left_index, right_index); |
| } |
| |
| // assign dual pipes |
| pipe_info->pipe_id = src_pipes_[right_index].mdss_pipe_id; |
| src_pipes_[right_index].reserved_hw_block = hw_block_id; |
| src_pipes_[right_index].at_right = true; |
| src_pipes_[left_index].reserved_hw_block = hw_block_id; |
| src_pipes_[left_index].at_right = false; |
| layer_config.left_pipe.pipe_id = src_pipes_[left_index].mdss_pipe_id; |
| error = SetDecimationFactor(pipe_info); |
| if (error != kErrorNone) { |
| goto CleanupOnError; |
| } |
| |
| DLOGV_IF(kTagResources, "2 pipes acquired, layer index = %d, left_pipe = %x, right_pipe = %x", |
| i, layer_config.left_pipe.pipe_id, pipe_info->pipe_id); |
| } |
| |
| if (scalar_->ConfigureScale(hw_layers) != kErrorNone) { |
| DLOGV_IF(kTagResources, "Scale data configuration has failed!"); |
| goto CleanupOnError; |
| } |
| |
| if (!CheckBandwidth(display_resource_ctx, hw_layers)) { |
| DLOGV_IF(kTagResources, "Bandwidth check failed!"); |
| goto CleanupOnError; |
| } |
| |
| return kErrorNone; |
| |
| CleanupOnError: |
| DLOGV_IF(kTagResources, "Resource reserving failed! hw_block = %d", hw_block_id); |
| for (uint32_t i = 0; i < num_pipe_; i++) { |
| if (src_pipes_[i].reserved_hw_block == hw_block_id) |
| src_pipes_[i].reserved_hw_block = kHWBlockMax; |
| } |
| return kErrorResources; |
| } |
| |
| bool ResManager::CheckBandwidth(DisplayResourceContext *display_ctx, HWLayers *hw_layers) { |
| // No need to check bandwidth for virtual displays |
| if (display_ctx->display_type == kVirtual) { |
| return true; |
| } |
| |
| float max_pipe_bw = FLOAT(hw_res_info_.max_pipe_bw) / 1000000; // KBps to GBps |
| float max_sde_clk = FLOAT(hw_res_info_.max_sde_clk) / 1000000; // Hz to MHz |
| const struct HWLayersInfo &layer_info = hw_layers->info; |
| |
| float left_pipe_bw[kMaxSDELayers] = {0}; |
| float right_pipe_bw[kMaxSDELayers] = {0}; |
| float left_max_clk = 0; |
| float right_max_clk = 0; |
| |
| for (uint32_t i = 0; i < layer_info.count; i++) { |
| Layer &layer = layer_info.stack->layers[layer_info.index[i]]; |
| float bpp = GetBpp(layer.input_buffer->format); |
| HWPipeInfo *left_pipe = &hw_layers->config[i].left_pipe; |
| HWPipeInfo *right_pipe = &hw_layers->config[i].right_pipe; |
| |
| left_pipe_bw[i] = left_pipe->valid ? GetPipeBw(display_ctx, left_pipe, bpp) : 0; |
| right_pipe_bw[i] = right_pipe->valid ? GetPipeBw(display_ctx, right_pipe, bpp) : 0; |
| |
| if ((left_pipe_bw[i] > max_pipe_bw) || (right_pipe_bw[i] > max_pipe_bw)) { |
| DLOGV_IF(kTagResources, "Pipe bandwidth exceeds limit for layer index=%d !" \ |
| " left_pipe_bw=%f, right_pipe_bw=%f, max_pipe_bw=%f", |
| i, left_pipe_bw[i], right_pipe_bw[i], max_pipe_bw); |
| return false; |
| } |
| |
| float left_clk = left_pipe->valid ? GetClockForPipe(display_ctx, left_pipe) : 0; |
| float right_clk = right_pipe->valid ? GetClockForPipe(display_ctx, right_pipe) : 0; |
| |
| left_max_clk = MAX(left_clk, left_max_clk); |
| right_max_clk = MAX(right_clk, right_max_clk); |
| } |
| |
| float left_mixer_bw = GetOverlapBw(hw_layers, left_pipe_bw, true); |
| float right_mixer_bw = GetOverlapBw(hw_layers, right_pipe_bw, false); |
| float display_bw = left_mixer_bw + right_mixer_bw; |
| |
| // Check system bandwidth (nth External + max(nth, n-1th) Primary) |
| if (display_ctx->hw_block_id == kHWPrimary) { |
| display_bw = MAX(display_bw, last_primary_bw_); |
| last_primary_bw_ = left_mixer_bw + right_mixer_bw; |
| } |
| |
| // If system has Video mode panel, then bw limit is max_bandwidth_low |
| if (display_ctx->hw_panel_info_.mode == kModeVideo) { |
| max_system_bw_ = FLOAT(hw_res_info_.max_bandwidth_low); |
| } |
| |
| if ((display_bw + bw_claimed_) > (max_system_bw_ / 1000000)) { |
| DLOGV_IF(kTagResources, "Overlap bandwidth: %f exceeds system limit: %f (GBps)!", |
| (display_bw + bw_claimed_), (max_system_bw_ / 1000000)); |
| return false; |
| } |
| |
| // Max clock requirement of display |
| float display_clk = MAX(left_max_clk, right_max_clk); |
| |
| // Check max clock requirement of system |
| float system_clk = MAX(display_clk, clk_claimed_); |
| |
| // Apply fudge factor to consider in-efficieny |
| if ((system_clk * hw_res_info_.clk_fudge_factor) > max_sde_clk) { |
| DLOGV_IF(kTagResources, "Clock requirement: %f exceeds system limit: %f (MHz)!", |
| (system_clk * hw_res_info_.clk_fudge_factor), max_sde_clk); |
| return false; |
| } |
| |
| // If Primary display, reset claimed bw & clk for next cycle |
| if (display_ctx->hw_block_id == kHWPrimary) { |
| bw_claimed_ = 0.0f; |
| clk_claimed_ = 0.0f; |
| } else { |
| bw_claimed_ = display_bw; |
| clk_claimed_ = display_clk; |
| } |
| |
| return true; |
| } |
| |
| float ResManager::GetPipeBw(DisplayResourceContext *display_ctx, HWPipeInfo *pipe, float bpp) { |
| HWDisplayAttributes &display_attributes = display_ctx->display_attributes; |
| float src_w = pipe->src_roi.right - pipe->src_roi.left; |
| float src_h = pipe->src_roi.bottom - pipe->src_roi.top; |
| float dst_h = pipe->dst_roi.bottom - pipe->dst_roi.top; |
| |
| // Adjust src_h with pipe decimation |
| float decimation = powf(2.0f, pipe->vertical_decimation); |
| src_h /= decimation; |
| |
| float bw = src_w * src_h * bpp * FLOAT(display_attributes.fps); |
| |
| // Consider panel dimension |
| // (v_total / v_active) * (v_active / dst_h) |
| bw *= FLOAT(display_attributes.v_total) / dst_h; |
| |
| // Bandwidth is the rate at which data needs to be fetched from source to MDP (bytes/time). |
| // On Video mode panel, there is no transfer of data from MDP to panel in horizontal blanking |
| // time (hBP + hFP + hPW). So MDP gets this extra time to fetch data from source. But on the |
| // Command mode panel, data gets transferred from MDP to panel during blanking time as well, |
| // which means MDP needs to fetch the data faster. So pipe bandwidth needs to be adjusted. |
| if (display_ctx->hw_panel_info_.mode == kModeCommand) { |
| uint32_t h_total = display_attributes.h_total; |
| uint32_t h_active = display_attributes.x_pixels; |
| // On split-panel device, h_active is left split |
| if (display_attributes.is_device_split) { |
| uint32_t h_blanking = (h_total - h_active) / 2; |
| h_active = display_attributes.split_left; |
| h_total = h_active + h_blanking; |
| } |
| bw *= FLOAT(h_total) / FLOAT(h_active); |
| } |
| |
| // Bandwidth in GBps |
| return (bw / 1000000000.0f); |
| } |
| |
| float ResManager::GetClockForPipe(DisplayResourceContext *display_ctx, HWPipeInfo *pipe) { |
| HWDisplayAttributes &display_attributes = display_ctx->display_attributes; |
| float v_total = FLOAT(display_attributes.v_total); |
| float fps = FLOAT(display_attributes.fps); |
| |
| float src_h = pipe->src_roi.bottom - pipe->src_roi.top; |
| float dst_h = pipe->dst_roi.bottom - pipe->dst_roi.top; |
| float dst_w = pipe->dst_roi.right - pipe->dst_roi.left; |
| |
| // Adjust src_h with pipe decimation |
| float decimation = powf(2.0f, pipe->vertical_decimation); |
| src_h /= decimation; |
| |
| |
| // SDE Clock requirement in MHz |
| float clk = (dst_w * v_total * fps) / 1000000.0f; |
| |
| // Consider down-scaling |
| if (src_h > dst_h) |
| clk *= (src_h / dst_h); |
| |
| return clk; |
| } |
| |
| float ResManager::GetOverlapBw(HWLayers *hw_layers, float *pipe_bw, bool left_mixer) { |
| uint32_t count = hw_layers->info.count; |
| float overlap_bw[count][count]; |
| float overall_max = 0; |
| |
| memset(overlap_bw, 0, sizeof(overlap_bw)); |
| |
| // Algorithm: |
| // 1.Create an 'n' by 'n' sized 2D array, overlap_bw[n][n] (n = # of layers). |
| // 2.Get overlap_bw between two layers, i and j, and account for other overlaps (prev_max) if any. |
| // This will fill the bottom-left half of the array including diagonal (0 <= i < n, 0 <= j <= i) |
| // {1. pipe_bw[i], where i == j |
| // overlap_bw[i][j] = {2. 0, where i != j && !Overlap(i, j) |
| // {3. pipe_bw[i] + pipe_bw[j] + prev_max, where i != j && Overlap(i, j) |
| // |
| // Overlap(i, j) = !(bottom_i <= top_j || top_i >= bottom_j) |
| // prev_max = max(prev_max, overlap_bw[j, k]), where 0 <= k < j and prev_max initially 0 |
| // prev_max = prev_max ? (prev_max - pipe_bw[j]) : 0; (to account for "double counting") |
| // 3.Get the max value in 2D array, overlap_bw[n][n], for the final overall_max bandwidth. |
| // overall_max = max(overlap_bw[i, j]), where 0 <= i < n, 0 <= j <= i |
| |
| for (uint32_t i = 0; i < count; i++) { |
| HWPipeInfo &pipe1 = left_mixer ? hw_layers->config[i].left_pipe : |
| hw_layers->config[i].right_pipe; |
| |
| // Non existing pipe never overlaps |
| if (pipe_bw[i] == 0) |
| continue; |
| |
| float top1 = pipe1.dst_roi.top; |
| float bottom1 = pipe1.dst_roi.bottom; |
| float row_max = 0; |
| |
| for (uint32_t j = 0; j <= i; j++) { |
| HWPipeInfo &pipe2 = left_mixer ? hw_layers->config[j].left_pipe : |
| hw_layers->config[j].right_pipe; |
| |
| if ((pipe_bw[j] == 0) || (i == j)) { |
| overlap_bw[i][j] = pipe_bw[j]; |
| row_max = MAX(pipe_bw[j], row_max); |
| continue; |
| } |
| |
| float top2 = pipe2.dst_roi.top; |
| float bottom2 = pipe2.dst_roi.bottom; |
| |
| if ((bottom1 <= top2) || (top1 >= bottom2)) { |
| overlap_bw[i][j] = 0; |
| continue; |
| } |
| |
| overlap_bw[i][j] = pipe_bw[i] + pipe_bw[j]; |
| |
| float prev_max = 0; |
| for (uint32_t k = 0; k < j; k++) { |
| if (overlap_bw[j][k]) |
| prev_max = MAX(overlap_bw[j][k], prev_max); |
| } |
| overlap_bw[i][j] += (prev_max > 0) ? (prev_max - pipe_bw[j]) : 0; |
| row_max = MAX(overlap_bw[i][j], row_max); |
| } |
| |
| overall_max = MAX(row_max, overall_max); |
| } |
| |
| return overall_max; |
| } |
| |
| float ResManager::GetBpp(LayerBufferFormat format) { |
| switch (format) { |
| case kFormatARGB8888: |
| case kFormatRGBA8888: |
| case kFormatBGRA8888: |
| case kFormatXRGB8888: |
| case kFormatRGBX8888: |
| case kFormatBGRX8888: |
| case kFormatRGBA8888Ubwc: |
| case kFormatRGBX8888Ubwc: |
| return 4.0f; |
| case kFormatRGB888: |
| case kFormatBGR888: |
| return 3.0f; |
| case kFormatRGB565: |
| case kFormatRGBA5551: |
| case kFormatRGBA4444: |
| case kFormatRGB565Ubwc: |
| case kFormatYCbCr422H2V1Packed: |
| case kFormatYCrCb422H2V1SemiPlanar: |
| case kFormatYCrCb422H1V2SemiPlanar: |
| case kFormatYCbCr422H2V1SemiPlanar: |
| case kFormatYCbCr422H1V2SemiPlanar: |
| return 2.0f; |
| case kFormatYCbCr420Planar: |
| case kFormatYCrCb420Planar: |
| case kFormatYCbCr420SemiPlanar: |
| case kFormatYCrCb420SemiPlanar: |
| case kFormatYCbCr420SemiPlanarVenus: |
| case kFormatYCbCr420SPVenusUbwc: |
| return 1.5f; |
| default: |
| DLOGE("GetBpp: Invalid buffer format: %x", format); |
| return 0.0f; |
| } |
| } |
| |
| DisplayError ResManager::PostPrepare(Handle display_ctx, HWLayers *hw_layers) { |
| SCOPE_LOCK(locker_); |
| |
| return kErrorNone; |
| } |
| |
| DisplayError ResManager::PostCommit(Handle display_ctx, HWLayers *hw_layers) { |
| SCOPE_LOCK(locker_); |
| DisplayResourceContext *display_resource_ctx = |
| reinterpret_cast<DisplayResourceContext *>(display_ctx); |
| HWBlockType hw_block_id = display_resource_ctx->hw_block_id; |
| uint64_t frame_count = display_resource_ctx->frame_count; |
| DisplayError error = kErrorNone; |
| |
| DLOGV_IF(kTagResources, "Resource for hw_block = %d, frame_count = %d", hw_block_id, frame_count); |
| |
| for (uint32_t i = 0; i < num_pipe_; i++) { |
| if (src_pipes_[i].reserved_hw_block == hw_block_id) { |
| src_pipes_[i].hw_block_id = hw_block_id; |
| src_pipes_[i].state = kPipeStateAcquired; |
| src_pipes_[i].state_frame_count = frame_count; |
| DLOGV_IF(kTagResources, "Pipe acquired index = %d, type = %d, pipe_id = %x", i, |
| src_pipes_[i].type, src_pipes_[i].mdss_pipe_id); |
| } else if ((src_pipes_[i].hw_block_id == hw_block_id) && |
| (src_pipes_[i].state == kPipeStateAcquired)) { |
| src_pipes_[i].state = kPipeStateToRelease; |
| src_pipes_[i].state_frame_count = frame_count; |
| DLOGV_IF(kTagResources, "Pipe to release index = %d, type = %d, pipe_id = %x", i, |
| src_pipes_[i].type, src_pipes_[i].mdss_pipe_id); |
| } |
| } |
| |
| // handoff pipes which are used by splash screen |
| if ((frame_count == 1) && (hw_block_id == kHWPrimary)) { |
| for (uint32_t i = 0; i < num_pipe_; i++) { |
| if ((src_pipes_[i].state == kPipeStateOwnedByKernel)) { |
| src_pipes_[i].state = kPipeStateToRelease; |
| src_pipes_[i].hw_block_id = kHWPrimary; |
| } |
| } |
| } |
| // set rotator pipes |
| for (uint32_t i = 0; i < hw_res_info_.num_rotator; i++) { |
| uint32_t pipe_index = rotators_[i].pipe_index; |
| |
| if (IS_BIT_SET(rotators_[i].client_bit_mask, hw_block_id)) { |
| src_pipes_[pipe_index].hw_block_id = rotators_[i].writeback_id; |
| src_pipes_[pipe_index].state = kPipeStateAcquired; |
| } else if (!rotators_[i].client_bit_mask && |
| src_pipes_[pipe_index].hw_block_id == rotators_[i].writeback_id && |
| src_pipes_[pipe_index].state == kPipeStateAcquired) { |
| src_pipes_[pipe_index].state = kPipeStateToRelease; |
| src_pipes_[pipe_index].state_frame_count = frame_count; |
| } |
| // If no request on the rotation, release the pipe. |
| if (!rotators_[i].request_bit_mask) { |
| src_pipes_[pipe_index].dedicated_hw_block = kHWBlockMax; |
| } |
| } |
| display_resource_ctx->frame_start = false; |
| |
| return kErrorNone; |
| } |
| |
| void ResManager::Purge(Handle display_ctx) { |
| SCOPE_LOCK(locker_); |
| |
| DisplayResourceContext *display_resource_ctx = |
| reinterpret_cast<DisplayResourceContext *>(display_ctx); |
| HWBlockType hw_block_id = display_resource_ctx->hw_block_id; |
| |
| for (uint32_t i = 0; i < num_pipe_; i++) { |
| if (src_pipes_[i].hw_block_id == hw_block_id) |
| src_pipes_[i].ResetState(); |
| } |
| ClearRotator(display_resource_ctx); |
| DLOGV_IF(kTagResources, "display id = %d", display_resource_ctx->hw_block_id); |
| } |
| |
| DisplayError ResManager::SetMaxMixerStages(Handle display_ctx, uint32_t max_mixer_stages) { |
| SCOPE_LOCK(locker_); |
| |
| if (max_mixer_stages < 1 || max_mixer_stages > hw_res_info_.num_blending_stages) { |
| DLOGE("Max mixer stages = %d and that should be in between 1 to %d", max_mixer_stages, |
| hw_res_info_.num_blending_stages); |
| |
| return kErrorParameters; |
| } |
| |
| DisplayResourceContext *display_resource_ctx = |
| reinterpret_cast<DisplayResourceContext *>(display_ctx); |
| |
| if (display_resource_ctx) { |
| display_resource_ctx->max_mixer_stages = max_mixer_stages; |
| } |
| |
| return kErrorNone; |
| } |
| |
| uint32_t ResManager::GetMdssPipeId(PipeType type, uint32_t index) { |
| uint32_t mdss_id = kPipeIdMax; |
| switch (type) { |
| case kPipeTypeVIG: |
| if (index < 3) { |
| mdss_id = kPipeIdVIG0 + index; |
| } else if (index == 3) { |
| mdss_id = kPipeIdVIG3; |
| } else { |
| DLOGE("vig pipe index is over the limit! %d", index); |
| } |
| break; |
| case kPipeTypeRGB: |
| if (index < 3) { |
| mdss_id = kPipeIdRGB0 + index; |
| } else if (index == 3) { |
| mdss_id = kPipeIdRGB3; |
| } else { |
| DLOGE("rgb pipe index is over the limit! %d", index); |
| } |
| break; |
| case kPipeTypeDMA: |
| if (index < 2) { |
| mdss_id = kPipeIdDMA0 + index; |
| } else { |
| DLOGE("dma pipe index is over the limit! %d", index); |
| } |
| break; |
| default: |
| DLOGE("wrong pipe type! %d", type); |
| break; |
| } |
| |
| return (1 << mdss_id); |
| } |
| |
| uint32_t ResManager::SearchPipe(HWBlockType hw_block_id, SourcePipe *src_pipes, |
| uint32_t num_pipe, bool at_right) { |
| uint32_t index = kPipeIdMax; |
| SourcePipe *src_pipe; |
| HWBlockType dedicated_block; |
| |
| // search dedicated idle pipes |
| for (uint32_t i = 0; i < num_pipe; i++) { |
| src_pipe = &src_pipes[i]; |
| if (src_pipe->reserved_hw_block == kHWBlockMax && |
| src_pipe->state == kPipeStateIdle && |
| src_pipe->dedicated_hw_block == hw_block_id) { |
| index = src_pipe->index; |
| break; |
| } |
| } |
| |
| // found |
| if (index < num_pipe_) { |
| return index; |
| } |
| |
| // search the pipe being used |
| for (uint32_t i = 0; i < num_pipe; i++) { |
| src_pipe = &src_pipes[i]; |
| dedicated_block = src_pipe->dedicated_hw_block; |
| if (src_pipe->reserved_hw_block == kHWBlockMax && |
| (src_pipe->state == kPipeStateAcquired) && |
| (src_pipe->hw_block_id == hw_block_id) && |
| (src_pipe->at_right == at_right) && |
| (dedicated_block == hw_block_id || dedicated_block == kHWBlockMax)) { |
| index = src_pipe->index; |
| break; |
| } |
| } |
| |
| // found |
| if (index < num_pipe_) { |
| return index; |
| } |
| |
| // search the pipes idle or being used but not at the same side |
| for (uint32_t i = 0; i < num_pipe; i++) { |
| src_pipe = &src_pipes[i]; |
| dedicated_block = src_pipe->dedicated_hw_block; |
| if (src_pipe->reserved_hw_block == kHWBlockMax && |
| ((src_pipe->state == kPipeStateIdle) || |
| (src_pipe->state == kPipeStateAcquired && src_pipe->hw_block_id == hw_block_id)) && |
| (dedicated_block == hw_block_id || dedicated_block == kHWBlockMax)) { |
| index = src_pipe->index; |
| break; |
| } |
| } |
| return index; |
| } |
| |
| uint32_t ResManager::NextPipe(PipeType type, HWBlockType hw_block_id, bool at_right) { |
| uint32_t num_pipe = 0; |
| SourcePipe *src_pipes = NULL; |
| |
| switch (type) { |
| case kPipeTypeVIG: |
| src_pipes = vig_pipes_; |
| num_pipe = hw_res_info_.num_vig_pipe; |
| break; |
| case kPipeTypeRGB: |
| src_pipes = rgb_pipes_; |
| num_pipe = hw_res_info_.num_rgb_pipe; |
| break; |
| case kPipeTypeDMA: |
| default: |
| src_pipes = dma_pipes_; |
| num_pipe = hw_res_info_.num_dma_pipe; |
| break; |
| } |
| |
| return SearchPipe(hw_block_id, src_pipes, num_pipe, at_right); |
| } |
| |
| uint32_t ResManager::GetPipe(HWBlockType hw_block_id, bool is_yuv, bool need_scale, bool at_right, |
| bool use_non_dma_pipe) { |
| uint32_t index = kPipeIdMax; |
| |
| // The default behavior is to assume RGB and VG pipes have scalars |
| if (is_yuv) { |
| return NextPipe(kPipeTypeVIG, hw_block_id, at_right); |
| } else { |
| if (!need_scale && !use_non_dma_pipe) { |
| index = NextPipe(kPipeTypeDMA, hw_block_id, at_right); |
| } |
| |
| if ((index >= num_pipe_) && (!need_scale || !hw_res_info_.has_non_scalar_rgb)) { |
| index = NextPipe(kPipeTypeRGB, hw_block_id, at_right); |
| } |
| |
| if (index >= num_pipe_) { |
| index = NextPipe(kPipeTypeVIG, hw_block_id, at_right); |
| } |
| } |
| |
| return index; |
| } |
| |
| bool ResManager::IsScalingNeeded(const HWPipeInfo *pipe_info) { |
| const LayerRect &src_roi = pipe_info->src_roi; |
| const LayerRect &dst_roi = pipe_info->dst_roi; |
| |
| return ((dst_roi.right - dst_roi.left) != (src_roi.right - src_roi.left)) || |
| ((dst_roi.bottom - dst_roi.top) != (src_roi.bottom - src_roi.top)); |
| } |
| |
| void ResManager::AppendDump(char *buffer, uint32_t length) { |
| SCOPE_LOCK(locker_); |
| AppendString(buffer, length, "\nresource manager pipe state"); |
| uint32_t i; |
| for (i = 0; i < num_pipe_; i++) { |
| SourcePipe *src_pipe = &src_pipes_[i]; |
| AppendString(buffer, length, |
| "\nindex = %d, id = %x, reserved = %d, state = %d, hw_block = %d, dedicated = %d", |
| src_pipe->index, src_pipe->mdss_pipe_id, src_pipe->reserved_hw_block, |
| src_pipe->state, src_pipe->hw_block_id, src_pipe->dedicated_hw_block); |
| } |
| |
| for (i = 0; i < hw_res_info_.num_rotator; i++) { |
| if (rotators_[i].client_bit_mask || rotators_[i].request_bit_mask) { |
| AppendString(buffer, length, |
| "\nrotator = %d, pipe index = %x, client_bit_mask = %x, request_bit_mask = %x", |
| i, rotators_[i].pipe_index, rotators_[i].client_bit_mask, |
| rotators_[i].request_bit_mask); |
| } |
| } |
| } |
| |
| void ResManager::ResourceStateLog() { |
| DLOGV_IF(kTagResources, "==== resource manager pipe state ===="); |
| uint32_t i; |
| for (i = 0; i < num_pipe_; i++) { |
| SourcePipe *src_pipe = &src_pipes_[i]; |
| DLOGV_IF(kTagResources, |
| "index = %d, id = %x, reserved = %d, state = %d, hw_block = %d, dedicated = %d", |
| src_pipe->index, src_pipe->mdss_pipe_id, src_pipe->reserved_hw_block, |
| src_pipe->state, src_pipe->hw_block_id, src_pipe->dedicated_hw_block); |
| } |
| |
| for (i = 0; i < hw_res_info_.num_rotator; i++) { |
| if (rotators_[i].client_bit_mask || rotators_[i].request_bit_mask) { |
| DLOGV_IF(kTagResources, |
| "rotator = %d, pipe index = %x, client_bit_mask = %x, request_bit_mask = %x", |
| i, rotators_[i].pipe_index, rotators_[i].client_bit_mask, |
| rotators_[i].request_bit_mask); |
| } |
| } |
| } |
| |
| DisplayError ResManager::AcquireRotator(DisplayResourceContext *display_resource_ctx, |
| const uint32_t rotate_count) { |
| if (rotate_count == 0) |
| return kErrorNone; |
| if (hw_res_info_.num_rotator == 0) { |
| DLOGV_IF(kTagResources, "Rotator hardware is not available"); |
| return kErrorResources; |
| } |
| |
| uint32_t i, j, pipe_index, num_rotator; |
| if (rotate_count > hw_res_info_.num_rotator) |
| num_rotator = hw_res_info_.num_rotator; |
| else |
| num_rotator = rotate_count; |
| |
| for (i = 0; i < num_rotator; i++) { |
| uint32_t rotate_pipe_index = rotators_[i].pipe_index; |
| SourcePipe *src_pipe = &src_pipes_[rotate_pipe_index]; |
| |
| if (src_pipe->dedicated_hw_block != kHWBlockMax) |
| DLOGV_IF(kTagResources, "Overwrite dedicated block %d", src_pipe->dedicated_hw_block); |
| src_pipe->dedicated_hw_block = rotators_[i].writeback_id; |
| SET_BIT(rotators_[i].request_bit_mask, display_resource_ctx->hw_block_id); |
| } |
| |
| for (i = 0; i < num_rotator; i++) { |
| uint32_t rotate_pipe_index = rotators_[i].pipe_index; |
| if (src_pipes_[rotate_pipe_index].reserved_hw_block != kHWBlockMax) { |
| DLOGV_IF(kTagResources, "pipe %x is reserved by block:%d", |
| src_pipes_[rotate_pipe_index].mdss_pipe_id, |
| src_pipes_[rotate_pipe_index].reserved_hw_block); |
| return kErrorResources; |
| } |
| pipe_index = SearchPipe(rotators_[i].writeback_id, &src_pipes_[rotate_pipe_index], 1, false); |
| if (pipe_index >= num_pipe_) { |
| DLOGV_IF(kTagResources, "pipe %x is not ready for rotator", |
| src_pipes_[rotate_pipe_index].mdss_pipe_id); |
| return kErrorResources; |
| } |
| } |
| |
| for (i = 0; i < num_rotator; i++) |
| SET_BIT(rotators_[i].client_bit_mask, display_resource_ctx->hw_block_id); |
| |
| return kErrorNone; |
| } |
| |
| void ResManager::AssignRotator(HWRotateInfo *rotate, uint32_t *rotate_count) { |
| if (!rotate->valid) |
| return; |
| // Interleave rotator assignment |
| if ((*rotate_count & 0x1) && (hw_res_info_.num_rotator > 1)) { |
| rotate->pipe_id = src_pipes_[rotators_[1].pipe_index].mdss_pipe_id; |
| rotate->writeback_id = rotators_[1].writeback_id; |
| } else { |
| rotate->pipe_id = src_pipes_[rotators_[0].pipe_index].mdss_pipe_id; |
| rotate->writeback_id = rotators_[0].writeback_id; |
| } |
| (*rotate_count)++; |
| } |
| |
| void ResManager::ClearRotator(DisplayResourceContext *display_resource_ctx) { |
| for (uint32_t i = 0; i < hw_res_info_.num_rotator; i++) { |
| uint32_t pipe_index; |
| pipe_index = rotators_[i].pipe_index; |
| rotators_[i].ClearState(display_resource_ctx->hw_block_id); |
| if (rotators_[i].client_bit_mask) |
| continue; |
| |
| if (src_pipes_[pipe_index].hw_block_id == rotators_[i].writeback_id) { |
| src_pipes_[pipe_index].ResetState(); |
| } else if (src_pipes_[pipe_index].dedicated_hw_block == rotators_[i].writeback_id) { |
| src_pipes_[pipe_index].dedicated_hw_block = kHWBlockMax; |
| } |
| } |
| } |
| |
| void ResManager::SetRotatorOutputFormat(const LayerBufferFormat &input_format, |
| const bool &is_opaque, const bool &rot90, |
| const bool &downscale, LayerBufferFormat *output_format) { |
| // Initialize the output format with input format by default. |
| *output_format = input_format; |
| |
| switch (input_format) { |
| case kFormatARGB8888: |
| case kFormatRGBA8888: |
| if (is_opaque) { |
| *output_format = kFormatRGB888; |
| } |
| break; |
| case kFormatBGRA8888: |
| if (is_opaque) { |
| *output_format = kFormatBGR888; |
| } |
| break; |
| case kFormatBGRX8888: |
| *output_format = kFormatBGR888; |
| break; |
| case kFormatXRGB8888: |
| case kFormatRGBX8888: |
| *output_format = kFormatRGB888; |
| break; |
| case kFormatYCrCb420SemiPlanar: |
| case kFormatYCbCr420SemiPlanar: |
| case kFormatYCbCr420SemiPlanarVenus: |
| *output_format = kFormatYCbCr420SemiPlanar; |
| break; |
| default: |
| break; |
| } |
| |
| if (downscale) { |
| switch (input_format) { |
| case kFormatRGBA8888Ubwc: |
| *output_format = is_opaque ? kFormatRGB888 : kFormatRGBA8888; |
| break; |
| case kFormatRGBX8888Ubwc: |
| *output_format = kFormatRGB888; |
| break; |
| case kFormatRGB565Ubwc: |
| *output_format = kFormatRGB565; |
| break; |
| case kFormatYCbCr420SPVenusUbwc: |
| *output_format = kFormatYCbCr420SemiPlanar; |
| break; |
| default: |
| break; |
| } |
| } else { |
| if (hw_res_info_.has_ubwc) { |
| switch (input_format) { |
| case kFormatRGBA8888: |
| *output_format = kFormatRGBA8888Ubwc; |
| break; |
| case kFormatRGBX8888: |
| *output_format = kFormatRGBX8888Ubwc; |
| break; |
| case kFormatRGB565: |
| *output_format = kFormatRGB565Ubwc; |
| break; |
| case kFormatYCrCb420SemiPlanar: |
| case kFormatYCbCr420SemiPlanar: |
| case kFormatYCbCr420SemiPlanarVenus: |
| *output_format = kFormatYCbCr420SPVenusUbwc; |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| if (rot90) { |
| if (input_format == kFormatYCbCr422H2V1SemiPlanar) { |
| *output_format = kFormatYCbCr422H1V2SemiPlanar; |
| } else if (input_format == kFormatYCbCr422H1V2SemiPlanar) { |
| *output_format = kFormatYCbCr422H2V1SemiPlanar; |
| } else if (input_format == kFormatYCrCb422H2V1SemiPlanar) { |
| *output_format = kFormatYCrCb422H1V2SemiPlanar; |
| } else if (input_format == kFormatYCrCb422H1V2SemiPlanar) { |
| *output_format = kFormatYCrCb422H2V1SemiPlanar; |
| } |
| } |
| |
| DLOGV_IF(kTagResources, "Input format = %x, Output format = %x, rot90 = %d, ubwc = %d," |
| "downscale = %d", input_format, *output_format, rot90, hw_res_info_.has_ubwc, |
| downscale); |
| |
| return; |
| } |
| |
| } // namespace sde |
| |