Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d0b0bf69 authored by Oleg Blinnikov's avatar Oleg Blinnikov
Browse files

Handle non-square pixels on External display

In case aspect ratio of the screen is different
from the aspect ratio of the resolution then the
pixels are non-square. There is usually a setting
on the display itself to force pixels to be square
but this setting is usually off by default.

Before this CL, the picture would be stretched
on the external display. With this CL, resolution
of the rendered picture is forced to be larger
by making logicaldisplay size larger. As the content
is now forced to render on a wider/higher surface then
when it is displayed on the screen it would appear
normal.

Bug: 304248677
Bug: 317363416
Test: atest LogicalDisplayTest DisplayDeviceTest
Test: try resolution aspect different from physical aspect-ratio
Change-Id: Ic437d62ffa4fa48972f6bdf3aaaba80e8b247b47
parent 5e3340b2
Loading
Loading
Loading
Loading
+29 −3
Original line number Diff line number Diff line
@@ -44,6 +44,15 @@ import java.io.PrintWriter;
 * </p>
 */
abstract class DisplayDevice {
    /**
     * Maximum acceptable anisotropy for the output image.
     *
     * Necessary to avoid unnecessary scaling when pixels are almost square, as they are non ideal
     * anyway. For external displays, we expect an anisotropy of about 2% even if the pixels
     * are, in fact, square due to the imprecision of the display's actual size (parsed from edid
     * and rounded to the nearest cm).
     */
    static final float MAX_ANISOTROPY = 1.025f;
    private static final String TAG = "DisplayDevice";
    private static final Display.Mode EMPTY_DISPLAY_MODE = new Display.Mode.Builder().build();

@@ -69,13 +78,21 @@ abstract class DisplayDevice {
    // Do not use for any other purpose.
    DisplayDeviceInfo mDebugLastLoggedDeviceInfo;

    public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
    private final boolean mIsAnisotropyCorrectionEnabled;

    DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
            Context context) {
        this(displayAdapter, displayToken, uniqueId, context, false);
    }

    DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken, String uniqueId,
            Context context, boolean isAnisotropyCorrectionEnabled) {
        mDisplayAdapter = displayAdapter;
        mDisplayToken = displayToken;
        mUniqueId = uniqueId;
        mDisplayDeviceConfig = null;
        mContext = context;
        mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
    }

    /**
@@ -143,8 +160,17 @@ abstract class DisplayDevice {
        DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
        final boolean isRotated = mCurrentOrientation == ROTATION_90
                || mCurrentOrientation == ROTATION_270;
        return isRotated ? new Point(displayDeviceInfo.height, displayDeviceInfo.width)
                : new Point(displayDeviceInfo.width, displayDeviceInfo.height);
        var width = displayDeviceInfo.width;
        var height = displayDeviceInfo.height;
        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
                    && displayDeviceInfo.xDpi > 0) {
            if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) {
                height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5);
            } else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) {
                width = (int) (width * displayDeviceInfo.yDpi / displayDeviceInfo.xDpi  + 0.5);
            }
        }
        return isRotated ? new Point(height, width) : new Point(width, height);
    }

    /**
+2 −1
Original line number Diff line number Diff line
@@ -257,7 +257,8 @@ final class LocalDisplayAdapter extends DisplayAdapter {
                SurfaceControl.DynamicDisplayInfo dynamicInfo,
                SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isFirstDisplay) {
            super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId,
                    getContext());
                    getContext(),
                    getFeatureFlags().isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
            mPhysicalDisplayId = physicalDisplayId;
            mIsFirstDisplay = isFirstDisplay;
            updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
+61 −7
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ import android.view.SurfaceControl;

import com.android.server.display.layout.Layout;
import com.android.server.display.mode.DisplayModeDirector;
import com.android.server.wm.utils.DisplayInfoOverrides;
import com.android.server.wm.utils.InsetUtils;

import java.io.PrintWriter;
@@ -204,7 +203,28 @@ final class LogicalDisplay {
    private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
            new SparseArray<>();

    /**
     * If the aspect ratio of the resolution of the display does not match the physical aspect
     * ratio of the display, then without this feature enabled, picture would appear stretched to
     * the user. This is because applications assume that they are rendered on square pixels
     * (meaning density of pixels in x and y directions are equal). This would result into circles
     * appearing as ellipses to the user.
     * To compensate for non-square (anisotropic) pixels, if this feature is enabled:
     * 1. LogicalDisplay will add more pixels for the applications to render on, as if the pixels
     * were square and occupied the full display.
     * 2. SurfaceFlinger will squeeze this taller/wider surface into the available number of
     * physical pixels in the current display resolution.
     * 3. If a setting on the display itself is set to "fill the entire display panel" then the
     * display will stretch the pixels to fill the display fully.
     */
    private final boolean mIsAnisotropyCorrectionEnabled;

    LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
        this(displayId, layerStack, primaryDisplayDevice, false);
    }

    LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice,
            boolean isAnisotropyCorrectionEnabled) {
        mDisplayId = displayId;
        mLayerStack = layerStack;
        mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -215,6 +235,7 @@ final class LogicalDisplay {
        mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
        mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID;
        mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId;
        mIsAnisotropyCorrectionEnabled = isAnisotropyCorrectionEnabled;
    }

    public void setDevicePositionLocked(int position) {
@@ -453,6 +474,14 @@ final class LogicalDisplay {
            int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
            int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;

            if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
                if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
                    maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5);
                } else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) {
                    maskedWidth = (int) (maskedWidth * deviceInfo.yDpi / deviceInfo.xDpi + 0.5);
                }
            }

            mBaseDisplayInfo.type = deviceInfo.type;
            mBaseDisplayInfo.address = deviceInfo.address;
            mBaseDisplayInfo.deviceProductInfo = deviceInfo.deviceProductInfo;
@@ -666,6 +695,31 @@ final class LogicalDisplay {
        physWidth -= maskingInsets.left + maskingInsets.right;
        physHeight -= maskingInsets.top + maskingInsets.bottom;

        var displayLogicalWidth = displayInfo.logicalWidth;
        var displayLogicalHeight = displayInfo.logicalHeight;

        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0
                    && displayDeviceInfo.yDpi > 0) {
            if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
                var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi;
                if (rotated) {
                    displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
                } else {
                    displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
                                                          + 0.5);
                }
            } else if (displayDeviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY
                               < displayDeviceInfo.yDpi) {
                var scalingFactor = displayDeviceInfo.xDpi / displayDeviceInfo.yDpi;
                if (rotated) {
                    displayLogicalHeight = (int) ((float) displayLogicalHeight * scalingFactor
                                                          + 0.5);
                } else {
                    displayLogicalWidth = (int) ((float) displayLogicalWidth * scalingFactor + 0.5);
                }
            }
        }

        // Determine whether the width or height is more constrained to be scaled.
        //    physWidth / displayInfo.logicalWidth    => letter box
        // or physHeight / displayInfo.logicalHeight  => pillar box
@@ -675,16 +729,16 @@ final class LogicalDisplay {
        // comparing them.
        int displayRectWidth, displayRectHeight;
        if ((displayInfo.flags & Display.FLAG_SCALING_DISABLED) != 0 || mDisplayScalingDisabled) {
            displayRectWidth = displayInfo.logicalWidth;
            displayRectHeight = displayInfo.logicalHeight;
        } else if (physWidth * displayInfo.logicalHeight
                < physHeight * displayInfo.logicalWidth) {
            displayRectWidth = displayLogicalWidth;
            displayRectHeight = displayLogicalHeight;
        } else if (physWidth * displayLogicalHeight
                < physHeight * displayLogicalWidth) {
            // Letter box.
            displayRectWidth = physWidth;
            displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
            displayRectHeight = displayLogicalHeight * physWidth / displayLogicalWidth;
        } else {
            // Pillar box.
            displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
            displayRectWidth = displayLogicalWidth * physHeight / displayLogicalHeight;
            displayRectHeight = physHeight;
        }
        int displayRectTop = (physHeight - displayRectHeight) / 2;
+2 −1
Original line number Diff line number Diff line
@@ -1149,7 +1149,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
     */
    private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
        final int layerStack = assignLayerStackLocked(displayId);
        final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
        final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device,
                mFlags.isPixelAnisotropyCorrectionInLogicalDisplayEnabled());
        display.updateLocked(mDisplayDeviceRepo);

        final DisplayInfo info = display.getDisplayInfoLocked();
+10 −0
Original line number Diff line number Diff line
@@ -121,6 +121,11 @@ public class DisplayManagerFlags {
            Flags::refreshRateVotingTelemetry
    );

    private final FlagState mPixelAnisotropyCorrectionEnabled = new FlagState(
            Flags.FLAG_ENABLE_PIXEL_ANISOTROPY_CORRECTION,
            Flags::enablePixelAnisotropyCorrection
    );

    private final FlagState mSensorBasedBrightnessThrottling = new FlagState(
            Flags.FLAG_SENSOR_BASED_BRIGHTNESS_THROTTLING,
            Flags::sensorBasedBrightnessThrottling
@@ -252,6 +257,10 @@ public class DisplayManagerFlags {
        return mRefreshRateVotingTelemetry.isEnabled();
    }

    public boolean isPixelAnisotropyCorrectionInLogicalDisplayEnabled() {
        return mPixelAnisotropyCorrectionEnabled.isEnabled();
    }

    public boolean isSensorBasedBrightnessThrottlingEnabled() {
        return mSensorBasedBrightnessThrottling.isEnabled();
    }
@@ -279,6 +288,7 @@ public class DisplayManagerFlags {
        pw.println(" " + mAutoBrightnessModesFlagState);
        pw.println(" " + mFastHdrTransitions);
        pw.println(" " + mRefreshRateVotingTelemetry);
        pw.println(" " + mPixelAnisotropyCorrectionEnabled);
        pw.println(" " + mSensorBasedBrightnessThrottling);
    }

Loading