/* SPDX-License-Identifier: MIT */ /* * Copyright 2023 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Authors: AMD *
*/
/* full_mblk_width_ub_l/c = malldml2_mall_phantom.c_alloc_width_blk_aligned_l/c; * full_mblk_height_ub_l/c = mall_alloc_height_blk_aligned_l/c; * num_mblk_l/c = (full_mblk_width_ub_l/c / mblk_width_l/c) * (full_mblk_height_ub_l/c / mblk_height_l/c); * (Should be divisible, but round up if not)
*/
num_mblks = ((mall_alloc_width_blk_aligned + mblk_width - 1) / mblk_width) *
((mall_alloc_height_blk_aligned + mblk_height - 1) / mblk_height);
bytes_in_mall = num_mblks * ctx->config.mall_cfg.mblk_size_bytes; // cache lines used is total bytes / cache_line size. Add +2 for worst case alignment // (MALL is 64-byte aligned)
cache_lines_per_plane = bytes_in_mall / ctx->config.mall_cfg.cache_line_size_bytes + 2;
// For DCC we must cache the meat surface, so double cache lines required if (pipe->plane_state->dcc.enable)
cache_lines_per_plane *= 2;
cache_lines_used += cache_lines_per_plane;
}
}
staticvoid merge_pipes_for_subvp(struct dml2_context *ctx, struct dc_state *context)
{ int i;
/* merge pipes if necessary */ for (i = 0; i < ctx->config.dcn_pipe_count; i++) { struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i];
// For now merge all pipes for SubVP since pipe split case isn't supported yet
/* if ODM merge we ignore mpc tree, mpo pipes will have their own flags */ if (pipe->prev_odm_pipe) { /*split off odm pipe*/
pipe->prev_odm_pipe->next_odm_pipe = pipe->next_odm_pipe; if (pipe->next_odm_pipe)
pipe->next_odm_pipe->prev_odm_pipe = pipe->prev_odm_pipe;
staticbool all_pipes_have_stream_and_plane(struct dml2_context *ctx, conststruct dc_state *context)
{ int i;
for (i = 0; i < ctx->config.dcn_pipe_count; i++) { conststruct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i];
if (!pipe->stream) continue;
if (!pipe->plane_state) returnfalse;
} returntrue;
}
staticbool mpo_in_use(conststruct dc_state *context)
{ int i;
for (i = 0; i < context->stream_count; i++) { if (context->stream_status[i].plane_count > 1) returntrue;
} returnfalse;
}
/* * dcn32_get_num_free_pipes: Calculate number of free pipes * * This function assumes that a "used" pipe is a pipe that has * both a stream and a plane assigned to it. * * @dc: current dc state * @context: new dc state * * Return: * Number of free pipes available in the context
*/ staticunsignedint get_num_free_pipes(struct dml2_context *ctx, struct dc_state *state)
{ unsignedint i; unsignedint free_pipes = 0; unsignedint num_pipes = 0;
for (i = 0; i < ctx->config.dcn_pipe_count; i++) { struct pipe_ctx *pipe = &state->res_ctx.pipe_ctx[i];
if (pipe->stream && !pipe->top_pipe) { while (pipe) {
num_pipes++;
pipe = pipe->bottom_pipe;
}
}
}
/* * dcn32_assign_subvp_pipe: Function to decide which pipe will use Sub-VP. * * We enter this function if we are Sub-VP capable (i.e. enough pipes available) * and regular P-State switching (i.e. VACTIVE/VBLANK) is not supported, or if * we are forcing SubVP P-State switching on the current config. * * The number of pipes used for the chosen surface must be less than or equal to the * number of free pipes available. * * In general we choose surfaces with the longest frame time first (better for SubVP + VBLANK). * For multi-display cases the ActiveDRAMClockChangeMargin doesn't provide enough info on its own * for determining which should be the SubVP pipe (need a way to determine if a pipe / plane doesn't * support MCLK switching naturally [i.e. ACTIVE or VBLANK]). * * @param dc: current dc state * @param context: new dc state * @param index: [out] dc pipe index for the pipe chosen to have phantom pipes assigned * * Return: * True if a valid pipe assignment was found for Sub-VP. Otherwise false.
*/ staticbool assign_subvp_pipe(struct dml2_context *ctx, struct dc_state *context, unsignedint *index)
{ unsignedint i, pipe_idx; unsignedint max_frame_time = 0; bool valid_assignment_found = false; unsignedint free_pipes = 2; //dcn32_get_num_free_pipes(dc, context); bool current_assignment_freesync = false; struct vba_vars_st *vba = &context->bw_ctx.dml.vba;
for (i = 0, pipe_idx = 0; i < ctx->config.dcn_pipe_count; i++) { struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i]; unsignedint num_pipes = 0; unsignedint refresh_rate = 0;
if (!pipe->stream) continue;
// Round up
refresh_rate = (pipe->stream->timing.pix_clk_100hz * 100 +
pipe->stream->timing.v_total * pipe->stream->timing.h_total - 1)
/ (double)(pipe->stream->timing.v_total * pipe->stream->timing.h_total); /* SubVP pipe candidate requirements: * - Refresh rate < 120hz * - Not able to switch in vactive naturally (switching in active means the * DET provides enough buffer to hide the P-State switch latency -- trying * to combine this with SubVP can cause issues with the scheduling).
*/ if (pipe->plane_state && !pipe->top_pipe &&
ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe) == SUBVP_NONE && refresh_rate < 120 &&
vba->ActiveDRAMClockChangeLatencyMarginPerState[vba->VoltageLevel][vba->maxMpcComb][vba->pipe_plane[pipe_idx]] <= 0) { while (pipe) {
num_pipes++;
pipe = pipe->bottom_pipe;
}
pipe = &context->res_ctx.pipe_ctx[i]; if (num_pipes <= free_pipes) { struct dc_stream_state *stream = pipe->stream; unsignedint frame_us = (stream->timing.v_total * stream->timing.h_total /
(double)(stream->timing.pix_clk_100hz * 100)) * 1000000; if (frame_us > max_frame_time && !stream->ignore_msa_timing_param) {
*index = i;
max_frame_time = frame_us;
valid_assignment_found = true;
current_assignment_freesync = false; /* For the 2-Freesync display case, still choose the one with the * longest frame time
*/
} elseif (stream->ignore_msa_timing_param && (!valid_assignment_found ||
(current_assignment_freesync && frame_us > max_frame_time))) {
*index = i;
valid_assignment_found = true;
current_assignment_freesync = true;
}
}
}
pipe_idx++;
} return valid_assignment_found;
}
/* * enough_pipes_for_subvp: Function to check if there are "enough" pipes for SubVP. * * This function returns true if there are enough free pipes * to create the required phantom pipes for any given stream * (that does not already have phantom pipe assigned). * * e.g. For a 2 stream config where the first stream uses one * pipe and the second stream uses 2 pipes (i.e. pipe split), * this function will return true because there is 1 remaining * pipe which can be used as the phantom pipe for the non pipe * split pipe. * * @dc: current dc state * @context: new dc state * * Return: * True if there are enough free pipes to assign phantom pipes to at least one * stream that does not already have phantom pipes assigned. Otherwise false.
*/ staticbool enough_pipes_for_subvp(struct dml2_context *ctx, struct dc_state *state)
{ unsignedint i, split_cnt, free_pipes; unsignedint min_pipe_split = ctx->config.dcn_pipe_count + 1; // init as max number of pipes + 1 bool subvp_possible = false;
for (i = 0; i < ctx->config.dcn_pipe_count; i++) { struct pipe_ctx *pipe = &state->res_ctx.pipe_ctx[i];
// Find the minimum pipe split count for non SubVP pipes if (pipe->stream && !pipe->top_pipe &&
ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(state, pipe) == SUBVP_NONE) {
split_cnt = 0; while (pipe) {
split_cnt++;
pipe = pipe->bottom_pipe;
}
if (split_cnt < min_pipe_split)
min_pipe_split = split_cnt;
}
}
free_pipes = get_num_free_pipes(ctx, state);
// SubVP only possible if at least one pipe is being used (i.e. free_pipes // should not equal to the pipe_count) if (free_pipes >= min_pipe_split && free_pipes < ctx->config.dcn_pipe_count)
subvp_possible = true;
return subvp_possible;
}
/* * subvp_subvp_schedulable: Determine if SubVP + SubVP config is schedulable * * High level algorithm: * 1. Find longest microschedule length (in us) between the two SubVP pipes * 2. Check if the worst case overlap (VBLANK in middle of ACTIVE) for both * pipes still allows for the maximum microschedule to fit in the active * region for both pipes. * * @dc: current dc state * @context: new dc state * * Return: * bool - True if the SubVP + SubVP config is schedulable, false otherwise
*/ staticbool subvp_subvp_schedulable(struct dml2_context *ctx, struct dc_state *context)
{ struct pipe_ctx *subvp_pipes[2]; struct dc_stream_state *phantom = NULL;
uint32_t microschedule_lines = 0;
uint32_t index = 0;
uint32_t i;
uint32_t max_microschedule_us = 0;
int32_t vactive1_us, vactive2_us, vblank1_us, vblank2_us;
for (i = 0; i < ctx->config.dcn_pipe_count; i++) { struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i];
uint32_t time_us = 0;
/* Loop to calculate the maximum microschedule time between the two SubVP pipes, * and also to store the two main SubVP pipe pointers in subvp_pipes[2].
*/ if (pipe->stream && pipe->plane_state && !pipe->top_pipe &&
ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe) == SUBVP_MAIN) {
phantom = ctx->config.svp_pstate.callbacks.get_paired_subvp_stream(context, pipe->stream);
microschedule_lines = (phantom->timing.v_total - phantom->timing.v_front_porch) +
phantom->timing.v_addressable;
// Round up when calculating microschedule time (+ 1 at the end)
time_us = (microschedule_lines * phantom->timing.h_total) /
(double)(phantom->timing.pix_clk_100hz * 100) * 1000000 +
ctx->config.svp_pstate.subvp_prefetch_end_to_mall_start_us +
ctx->config.svp_pstate.subvp_fw_processing_delay_us + 1; if (time_us > max_microschedule_us)
max_microschedule_us = time_us;
/* * dml2_svp_drr_schedulable: Determine if SubVP + DRR config is schedulable * * High level algorithm: * 1. Get timing for SubVP pipe, phantom pipe, and DRR pipe * 2. Determine the frame time for the DRR display when adding required margin for MCLK switching * (the margin is equal to the MALL region + DRR margin (500us)) * 3.If (SubVP Active - Prefetch > Stretched DRR frame + max(MALL region, Stretched DRR frame)) * then report the configuration as supported * * @dc: current dc state * @context: new dc state * @drr_pipe: DRR pipe_ctx for the SubVP + DRR config * * Return: * bool - True if the SubVP + DRR config is schedulable, false otherwise
*/ bool dml2_svp_drr_schedulable(struct dml2_context *ctx, struct dc_state *context, struct dc_crtc_timing *drr_timing)
{ bool schedulable = false;
uint32_t i; struct pipe_ctx *pipe = NULL; struct dc_crtc_timing *main_timing = NULL; struct dc_crtc_timing *phantom_timing = NULL; struct dc_stream_state *phantom_stream;
int16_t prefetch_us = 0;
int16_t mall_region_us = 0;
int16_t drr_frame_us = 0; // nominal frame time
int16_t subvp_active_us = 0;
int16_t stretched_drr_us = 0;
int16_t drr_stretched_vblank_us = 0;
int16_t max_vblank_mallregion = 0;
// Find SubVP pipe for (i = 0; i < ctx->config.dcn_pipe_count; i++) {
pipe = &context->res_ctx.pipe_ctx[i];
// We check for master pipe, but it shouldn't matter since we only need // the pipe for timing info (stream should be same for any pipe splits) if (!pipe->stream || !pipe->plane_state || pipe->top_pipe || pipe->prev_odm_pipe) continue;
// Find the SubVP pipe if (ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe) == SUBVP_MAIN) break;
}
/* We consider SubVP + DRR schedulable if the stretched frame duration of the DRR display (i.e. the * highest refresh rate + margin that can support UCLK P-State switch) passes the static analysis * for VBLANK: (VACTIVE region of the SubVP pipe can fit the MALL prefetch, VBLANK frame time, * and the max of (VBLANK blanking time, MALL region)).
*/ if (stretched_drr_us < (1 / (double)drr_timing->min_refresh_in_uhz) * 1000000 * 1000000 &&
subvp_active_us - prefetch_us - stretched_drr_us - max_vblank_mallregion > 0)
schedulable = true;
return schedulable;
}
/* * subvp_vblank_schedulable: Determine if SubVP + VBLANK config is schedulable * * High level algorithm: * 1. Get timing for SubVP pipe, phantom pipe, and VBLANK pipe * 2. If (SubVP Active - Prefetch > Vblank Frame Time + max(MALL region, Vblank blanking time)) * then report the configuration as supported * 3. If the VBLANK display is DRR, then take the DRR static schedulability path * * @dc: current dc state * @context: new dc state * * Return: * bool - True if the SubVP + VBLANK/DRR config is schedulable, false otherwise
*/ staticbool subvp_vblank_schedulable(struct dml2_context *ctx, struct dc_state *context)
{ struct pipe_ctx *pipe = NULL; struct pipe_ctx *subvp_pipe = NULL; bool found = false; bool schedulable = false;
uint32_t i = 0;
uint8_t vblank_index = 0;
uint16_t prefetch_us = 0;
uint16_t mall_region_us = 0;
uint16_t vblank_frame_us = 0;
uint16_t subvp_active_us = 0;
uint16_t vblank_blank_us = 0;
uint16_t max_vblank_mallregion = 0; struct dc_crtc_timing *main_timing = NULL; struct dc_crtc_timing *phantom_timing = NULL; struct dc_crtc_timing *vblank_timing = NULL; struct dc_stream_state *phantom_stream; enum mall_stream_type pipe_mall_type;
/* For SubVP + VBLANK/DRR cases, we assume there can only be * a single VBLANK/DRR display. If DML outputs SubVP + VBLANK * is supported, it is either a single VBLANK case or two VBLANK * displays which are synchronized (in which case they have identical * timings).
*/ for (i = 0; i < ctx->config.dcn_pipe_count; i++) {
pipe = &context->res_ctx.pipe_ctx[i];
pipe_mall_type = ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe);
// We check for master pipe, but it shouldn't matter since we only need // the pipe for timing info (stream should be same for any pipe splits) if (!pipe->stream || !pipe->plane_state || pipe->top_pipe || pipe->prev_odm_pipe) continue;
if (!found && pipe_mall_type == SUBVP_NONE) { // Found pipe which is not SubVP or Phantom (i.e. the VBLANK pipe).
vblank_index = i;
found = true;
}
if (!subvp_pipe && pipe_mall_type == SUBVP_MAIN)
subvp_pipe = pipe;
} // Use ignore_msa_timing_param flag to identify as DRR if (found && context->res_ctx.pipe_ctx[vblank_index].stream->ignore_msa_timing_param) { // SUBVP + DRR case
schedulable = dml2_svp_drr_schedulable(ctx, context, &context->res_ctx.pipe_ctx[vblank_index].stream->timing);
} elseif (found) {
phantom_stream = ctx->config.svp_pstate.callbacks.get_paired_subvp_stream(context, subvp_pipe->stream);
main_timing = &subvp_pipe->stream->timing;
phantom_timing = &phantom_stream->timing;
vblank_timing = &context->res_ctx.pipe_ctx[vblank_index].stream->timing; // Prefetch time is equal to VACTIVE + BP + VSYNC of the phantom pipe // Also include the prefetch end to mallstart delay time
prefetch_us = (phantom_timing->v_total - phantom_timing->v_front_porch) * phantom_timing->h_total /
(double)(phantom_timing->pix_clk_100hz * 100) * 1000000 +
ctx->config.svp_pstate.subvp_prefetch_end_to_mall_start_us; // P-State allow width and FW delays already included phantom_timing->v_addressable
mall_region_us = phantom_timing->v_addressable * phantom_timing->h_total /
(double)(phantom_timing->pix_clk_100hz * 100) * 1000000;
vblank_frame_us = vblank_timing->v_total * vblank_timing->h_total /
(double)(vblank_timing->pix_clk_100hz * 100) * 1000000;
vblank_blank_us = (vblank_timing->v_total - vblank_timing->v_addressable) * vblank_timing->h_total /
(double)(vblank_timing->pix_clk_100hz * 100) * 1000000;
subvp_active_us = main_timing->v_addressable * main_timing->h_total /
(double)(main_timing->pix_clk_100hz * 100) * 1000000;
max_vblank_mallregion = vblank_blank_us > mall_region_us ? vblank_blank_us : mall_region_us;
// Schedulable if VACTIVE region of the SubVP pipe can fit the MALL prefetch, VBLANK frame time, // and the max of (VBLANK blanking time, MALL region) // TODO: Possibly add some margin (i.e. the below conditions should be [...] > X instead of [...] > 0) if (subvp_active_us - prefetch_us - vblank_frame_us - max_vblank_mallregion > 0)
schedulable = true;
} return schedulable;
}
/* * subvp_validate_static_schedulability: Check which SubVP case is calculated and handle * static analysis based on the case. * * Three cases: * 1. SubVP + SubVP * 2. SubVP + VBLANK (DRR checked internally) * 3. SubVP + VACTIVE (currently unsupported) * * @dc: current dc state * @context: new dc state * @vlevel: Voltage level calculated by DML * * Return: * bool - True if statically schedulable, false otherwise
*/ bool dml2_svp_validate_static_schedulability(struct dml2_context *ctx, struct dc_state *context, enum dml_dram_clock_change_support pstate_change_type)
{ bool schedulable = true; // true by default for single display case struct vba_vars_st *vba = &context->bw_ctx.dml.vba;
uint32_t i, pipe_idx;
uint8_t subvp_count = 0;
uint8_t vactive_count = 0;
for (i = 0, pipe_idx = 0; i < ctx->config.dcn_pipe_count; i++) { struct pipe_ctx *pipe = &context->res_ctx.pipe_ctx[i]; enum mall_stream_type pipe_mall_type = ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(context, pipe);
if (!pipe->stream) continue;
if (pipe->plane_state && !pipe->top_pipe &&
pipe_mall_type == SUBVP_MAIN)
subvp_count++;
// Count how many planes that aren't SubVP/phantom are capable of VACTIVE // switching (SubVP + VACTIVE unsupported). In situations where we force // SubVP for a VACTIVE plane, we don't want to increment the vactive_count. if (vba->ActiveDRAMClockChangeLatencyMargin[vba->pipe_plane[pipe_idx]] > 0 &&
pipe_mall_type == SUBVP_NONE) {
vactive_count++;
}
pipe_idx++;
}
if (subvp_count == 2) { // Static schedulability check for SubVP + SubVP case
schedulable = subvp_subvp_schedulable(ctx, context);
} elseif (pstate_change_type == dml_dram_clock_change_vblank_w_mall_sub_vp) { // Static schedulability check for SubVP + VBLANK case. Also handle the case where // DML outputs SubVP + VBLANK + VACTIVE (DML will report as SubVP + VBLANK) if (vactive_count > 0)
schedulable = false; else
schedulable = subvp_vblank_schedulable(ctx, context);
} elseif (pstate_change_type == dml_dram_clock_change_vactive_w_mall_sub_vp &&
vactive_count > 0) { // For single display SubVP cases, DML will output dm_dram_clock_change_vactive_w_mall_sub_vp by default. // We tell the difference between SubVP vs. SubVP + VACTIVE by checking the vactive_count. // SubVP + VACTIVE currently unsupported
schedulable = false;
} return schedulable;
}
// Find DML pipe index (pipe_idx) using dc_pipe_idx for (i = 0, pipe_idx = 0; i < ctx->config.dcn_pipe_count; i++) {
pipe = &state->res_ctx.pipe_ctx[i];
// DML calculation for MALL region doesn't take into account FW delay // and required pstate allow width for multi-display cases /* Add 16 lines margin to the MALL REGION because SUB_VP_START_LINE must be aligned * to 2 swaths (i.e. 16 lines)
*/
phantom_vactive = svp_height + pstate_width_fw_delay_lines + ctx->config.svp_pstate.subvp_swath_height_margin_lines;
// The index of the DC pipe passed into this function is guarenteed to // be a valid candidate for SubVP (i.e. has a plane, stream, doesn't // already have phantom pipe assigned, etc.) by previous checks.
phantom_stream = enable_phantom_stream(ctx, state, main_pipe_idx, svp_height, vstartup);
enable_phantom_plane(ctx, state, phantom_stream, main_pipe_idx);
for (i = 0; i < ctx->config.dcn_pipe_count; i++) { struct pipe_ctx *pipe = &state->res_ctx.pipe_ctx[i];
// Build scaling params for phantom pipes which were newly added. // We determine which phantom pipes were added by comparing with // the phantom stream. if (pipe->plane_state && pipe->stream && pipe->stream == phantom_stream &&
ctx->config.svp_pstate.callbacks.get_pipe_subvp_type(state, pipe) == SUBVP_PHANTOM) {
pipe->stream->use_dynamic_meta = false;
pipe->plane_state->flip_immediate = false; if (!ctx->config.svp_pstate.callbacks.build_scaling_params(pipe)) { // Log / remove phantom pipes since failed to build scaling params
}
}
}
}
for (i = 0; i < context->stream_count; i++) if (context->streams[i] == stream) {
stream_status = &context->stream_status[i]; break;
}
if (stream_status == NULL) { returnfalse;
}
old_plane_count = stream_status->plane_count;
for (i = 0; i < old_plane_count; i++)
del_planes[i] = stream_status->plane_states[i];
for (i = 0; i < old_plane_count; i++) { if (!ctx->config.svp_pstate.callbacks.remove_phantom_plane(ctx->config.svp_pstate.callbacks.dc, stream, del_planes[i], context)) returnfalse;
ctx->config.svp_pstate.callbacks.release_phantom_plane(ctx->config.svp_pstate.callbacks.dc, context, del_planes[i]);
}
/* Conditions for setting up phantom pipes for SubVP: * 1. Not force disable SubVP * 2. Full update (i.e. DC_VALIDATE_MODE_AND_PROGRAMMING) * 3. Enough pipes are available to support SubVP (TODO: Which pipes will use VACTIVE / VBLANK / SUBVP?) * 4. Display configuration passes validation * 5. (Config doesn't support MCLK in VACTIVE/VBLANK || dc->debug.force_subvp_mclk_switch)
*/ bool dml2_svp_add_phantom_pipe_to_dc_state(struct dml2_context *ctx, struct dc_state *state, struct dml_mode_support_info_st *mode_support_info)
{ unsignedint dc_pipe_idx, dml_pipe_idx; unsignedint svp_height, vstartup;
if (ctx->config.svp_pstate.force_disable_subvp) returnfalse;
if (!all_pipes_have_stream_and_plane(ctx, state)) returnfalse;
if (mpo_in_use(state)) returnfalse;
merge_pipes_for_subvp(ctx, state); // to re-initialize viewport after the pipe merge for (int i = 0; i < ctx->config.dcn_pipe_count; i++) { struct pipe_ctx *pipe_ctx = &state->res_ctx.pipe_ctx[i];
if (!pipe_ctx->plane_state || !pipe_ctx->stream) continue;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.