SF: Move state out of DisplayDevice to a new Output class

CompositionEngine::Output holds the composition state of an output. A
CompositionEngine::Display is an output, so it derives from it.

The state is removed from DisplayDevice, with some (temporary) accessors
left behind as there are more changes coming.

The composition related code in SurfaceFlinger is adjusted to however
use the output state.

Test: atest libsurfaceflinger_unittest libcompositionengine_test
Bug: 121291683
Change-Id: Idae2d3d96315538d006b27b56e0a8b566ce0e3b8
diff --git a/libs/ui/Transform.cpp b/libs/ui/Transform.cpp
index 25128ef..d13942d 100644
--- a/libs/ui/Transform.cpp
+++ b/libs/ui/Transform.cpp
@@ -16,6 +16,7 @@
 
 #include <math.h>
 
+#include <android-base/stringprintf.h>
 #include <cutils/compiler.h>
 #include <ui/Region.h>
 #include <ui/Transform.h>
@@ -380,44 +381,47 @@
     return (getOrientation() & ROT_INVALID) ? false : true;
 }
 
-void Transform::dump(const char* name) const
-{
-    type(); // updates the type
+void Transform::dump(std::string& out, const char* name) const {
+    using android::base::StringAppendF;
 
-    String8 flags, type;
-    const mat33& m(mMatrix);
-    uint32_t orient = mType >> 8;
+    type(); // Ensure the information in mType is up to date
 
-    if (orient&ROT_INVALID) {
-        flags.append("ROT_INVALID ");
+    const uint32_t type = mType;
+    const uint32_t orient = type >> 8;
+
+    StringAppendF(&out, "%s 0x%08x (", name, orient);
+
+    if (orient & ROT_INVALID) {
+        out.append("ROT_INVALID ");
     } else {
-        if (orient&ROT_90) {
-            flags.append("ROT_90 ");
+        if (orient & ROT_90) {
+            out.append("ROT_90 ");
         } else {
-            flags.append("ROT_0 ");
+            out.append("ROT_0 ");
         }
-        if (orient&FLIP_V)
-            flags.append("FLIP_V ");
-        if (orient&FLIP_H)
-            flags.append("FLIP_H ");
+        if (orient & FLIP_V) out.append("FLIP_V ");
+        if (orient & FLIP_H) out.append("FLIP_H ");
     }
 
-    if (!(mType&(SCALE|ROTATE|TRANSLATE)))
-        type.append("IDENTITY ");
-    if (mType&SCALE)
-        type.append("SCALE ");
-    if (mType&ROTATE)
-        type.append("ROTATE ");
-    if (mType&TRANSLATE)
-        type.append("TRANSLATE ");
+    StringAppendF(&out, ") 0x%02x (", type);
 
-    ALOGD("%s 0x%08x (%s, %s)", name, mType, flags.string(), type.string());
-    ALOGD("%.4f  %.4f  %.4f", static_cast<double>(m[0][0]), static_cast<double>(m[1][0]),
-          static_cast<double>(m[2][0]));
-    ALOGD("%.4f  %.4f  %.4f", static_cast<double>(m[0][1]), static_cast<double>(m[1][1]),
-          static_cast<double>(m[2][1]));
-    ALOGD("%.4f  %.4f  %.4f", static_cast<double>(m[0][2]), static_cast<double>(m[1][2]),
-          static_cast<double>(m[2][2]));
+    if (!(type & (SCALE | ROTATE | TRANSLATE))) out.append("IDENTITY ");
+    if (type & SCALE) out.append("SCALE ");
+    if (type & ROTATE) out.append("ROTATE ");
+    if (type & TRANSLATE) out.append("TRANSLATE ");
+
+    out.append(")\n");
+
+    for (size_t i = 0; i < 3; i++) {
+        StringAppendF(&out, "    %.4f  %.4f  %.4f\n", static_cast<double>(mMatrix[0][i]),
+                      static_cast<double>(mMatrix[1][i]), static_cast<double>(mMatrix[2][i]));
+    }
+}
+
+void Transform::dump(const char* name) const {
+    std::string out;
+    dump(out, name);
+    ALOGD("%s", out.c_str());
 }
 
 }  // namespace ui
diff --git a/libs/ui/include/ui/Transform.h b/libs/ui/include/ui/Transform.h
index 900a5c4..dcb26cf 100644
--- a/libs/ui/include/ui/Transform.h
+++ b/libs/ui/include/ui/Transform.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+#include <string>
 
 #include <hardware/hardware.h>
 #include <math/vec2.h>
@@ -90,6 +91,7 @@
     Transform inverse() const;
 
     // for debugging
+    void dump(std::string& result, const char* name) const;
     void dump(const char* name) const;
 
 private:
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 5209204..3862d8b 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -36,6 +36,9 @@
     srcs: [
         "src/CompositionEngine.cpp",
         "src/Display.cpp",
+        "src/DumpHelpers.cpp",
+        "src/Output.cpp",
+        "src/OutputCompositionState.cpp",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
@@ -47,6 +50,7 @@
     srcs: [
         "mock/CompositionEngine.cpp",
         "mock/Display.cpp",
+        "mock/Output.cpp",
     ],
     static_libs: [
         "libgtest",
@@ -64,6 +68,7 @@
     srcs: [
         "tests/CompositionEngineTest.cpp",
         "tests/DisplayTest.cpp",
+        "tests/OutputTest.cpp",
         "tests/MockHWComposer.cpp",
     ],
     static_libs: [
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
index 9965d89..979fdad 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Display.h
@@ -21,13 +21,15 @@
 
 #include "DisplayHardware/DisplayIdentification.h"
 
+#include <compositionengine/Output.h>
+
 namespace android::compositionengine {
 
 /**
  * A display is a composition target which may be backed by a hardware composer
  * display device
  */
-class Display {
+class Display : public virtual Output {
 public:
     // Gets the HWC DisplayId for the display if there is one
     virtual const std::optional<DisplayId>& getId() const = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
new file mode 100644
index 0000000..731189f
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+#include <math/mat4.h>
+#include <ui/GraphicTypes.h>
+#include <ui/Region.h>
+#include <ui/Transform.h>
+
+namespace android::compositionengine {
+
+namespace impl {
+struct OutputCompositionState;
+} // namespace impl
+
+/**
+ * Encapsulates all the state involved with composing layers for an output
+ */
+class Output {
+public:
+    // Returns true if the output is valid. This is meant to be checked post-
+    // construction and prior to use, as not everything is set up by the
+    // constructor.
+    virtual bool isValid() const = 0;
+
+    // Enables (or disables) composition on this output
+    virtual void setCompositionEnabled(bool) = 0;
+
+    // Sets the projection state to use
+    virtual void setProjection(const ui::Transform&, int32_t orientation, const Rect& frame,
+                               const Rect& viewport, const Rect& scissor, bool needsFiltering) = 0;
+    // Sets the bounds to use
+    virtual void setBounds(const Rect&) = 0;
+
+    // Sets the layer stack filter for this output. If singleLayerStack is true,
+    // this output displays just the single layer stack specified by
+    // singleLayerStackId. Otherwise all layer stacks will be visible on this
+    // output.
+    virtual void setLayerStackFilter(bool singleLayerStack, uint32_t singleLayerStackId) = 0;
+
+    // Sets the color transform matrix to use
+    virtual void setColorTransform(const mat4&) = 0;
+
+    // Sets the output color mode
+    virtual void setColorMode(ui::ColorMode, ui::Dataspace, ui::RenderIntent) = 0;
+
+    // Outputs a string with a state dump
+    virtual void dump(std::string&) const = 0;
+
+    // Gets the debug name for the output
+    virtual const std::string& getName() const = 0;
+
+    // Sets a debug name for the output
+    virtual void setName(const std::string&) = 0;
+
+    using OutputCompositionState = compositionengine::impl::OutputCompositionState;
+
+    // Gets the raw composition state data for the output
+    // TODO(lpique): Make this protected once it is only internally called.
+    virtual const OutputCompositionState& getState() const = 0;
+
+    // Allows mutable access to the raw composition state data for the output.
+    // This is meant to be used by the various functions that are part of the
+    // composition process.
+    // TODO(lpique): Make this protected once it is only internally called.
+    virtual OutputCompositionState& editState() = 0;
+
+    // Gets the physical space dirty region. If repaintEverything is true, this
+    // will be the full display bounds. Internally the dirty region is stored in
+    // logical (aka layer stack) space.
+    virtual Region getPhysicalSpaceDirtyRegion(bool repaintEverything) const = 0;
+
+    // Tests whether a given layerStackId belongs in this output
+    virtual bool belongsInOutput(uint32_t layerStackId) const = 0;
+
+protected:
+    ~Output() = default;
+};
+
+} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index ad03054..7c38c49 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -19,6 +19,7 @@
 #include <memory>
 
 #include <compositionengine/Display.h>
+#include <compositionengine/impl/Output.h>
 
 #include "DisplayHardware/DisplayIdentification.h"
 
@@ -30,19 +31,23 @@
 
 namespace impl {
 
-class Display : public compositionengine::Display {
+class Display : public compositionengine::impl::Output, public compositionengine::Display {
 public:
     Display(const CompositionEngine&, compositionengine::DisplayCreationArgs&&);
     virtual ~Display();
 
+    // compositionengine::Output overrides
+    void dump(std::string&) const override;
+    void setColorTransform(const mat4&) override;
+    void setColorMode(ui::ColorMode, ui::Dataspace, ui::RenderIntent) override;
+
+    // compositionengine::Display overrides
     const std::optional<DisplayId>& getId() const override;
     bool isSecure() const override;
     bool isVirtual() const override;
     void disconnect() override;
 
 private:
-    const CompositionEngine& mCompositionEngine;
-    const bool mIsSecure;
     const bool mIsVirtual;
     std::optional<DisplayId> mId;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h
new file mode 100644
index 0000000..663010f
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DumpHelpers.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <type_traits>
+
+#include <math/mat4.h>
+#include <ui/FloatRect.h>
+#include <ui/Rect.h>
+#include <ui/Region.h>
+#include <ui/Transform.h>
+
+namespace android::compositionengine::impl {
+
+void dumpVal(std::string& out, const char* name, bool);
+void dumpVal(std::string& out, const char* name, const void*);
+void dumpVal(std::string& out, const char* name, int);
+void dumpVal(std::string& out, const char* name, float);
+void dumpVal(std::string& out, const char* name, uint32_t);
+void dumpHex(std::string& out, const char* name, uint64_t);
+void dumpVal(std::string& out, const char* name, const char* value);
+void dumpVal(std::string& out, const char* name, const std::string& value);
+
+// For enums with named values
+void dumpVal(std::string& out, const char* name, const char*, int);
+void dumpVal(std::string& out, const char* name, const std::string&, int);
+
+template <typename EnumType>
+void dumpVal(std::string& out, const char* name, const char* valueName, EnumType value) {
+    dumpVal(out, name, valueName, static_cast<std::underlying_type_t<EnumType>>(value));
+}
+
+template <typename EnumType>
+void dumpVal(std::string& out, const char* name, const std::string& valueName, EnumType value) {
+    dumpVal(out, name, valueName, static_cast<std::underlying_type_t<EnumType>>(value));
+}
+
+void dumpVal(std::string& out, const char* name, const FloatRect& rect);
+void dumpVal(std::string& out, const char* name, const Rect& rect);
+void dumpVal(std::string& out, const char* name, const Region& region);
+void dumpVal(std::string& out, const char* name, const ui::Transform&);
+
+void dumpVal(std::string& out, const char* name, const mat4& tr);
+
+} // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
new file mode 100644
index 0000000..60b94a4
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+
+#include <compositionengine/Output.h>
+#include <compositionengine/impl/OutputCompositionState.h>
+
+namespace android::compositionengine {
+
+class CompositionEngine;
+
+namespace impl {
+
+class Output : public virtual compositionengine::Output {
+public:
+    Output(const CompositionEngine&);
+    virtual ~Output();
+
+    bool isValid() const override;
+
+    void setCompositionEnabled(bool) override;
+    void setProjection(const ui::Transform&, int32_t orientation, const Rect& frame,
+                       const Rect& viewport, const Rect& scissor, bool needsFiltering) override;
+    void setBounds(const Rect&) override;
+    void setLayerStackFilter(bool singleLayerStack, uint32_t singleLayerStackId) override;
+
+    void setColorTransform(const mat4&) override;
+    void setColorMode(ui::ColorMode, ui::Dataspace, ui::RenderIntent) override;
+
+    void dump(std::string&) const override;
+
+    const std::string& getName() const override;
+    void setName(const std::string&) override;
+
+    const OutputCompositionState& getState() const override;
+    OutputCompositionState& editState() override;
+
+    Region getPhysicalSpaceDirtyRegion(bool repaintEverything) const override;
+    bool belongsInOutput(uint32_t) const override;
+
+protected:
+    const CompositionEngine& getCompositionEngine() const;
+    void dumpBase(std::string&) const;
+
+private:
+    void dirtyEntireOutput();
+
+    const CompositionEngine& mCompositionEngine;
+
+    std::string mName;
+
+    OutputCompositionState mState;
+};
+
+} // namespace impl
+} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
new file mode 100644
index 0000000..96a0e0f
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+
+#include <ui/GraphicTypes.h>
+#include <ui/Rect.h>
+#include <ui/Region.h>
+#include <ui/Transform.h>
+
+namespace android {
+
+namespace compositionengine::impl {
+
+struct OutputCompositionState {
+    // If false, composition will not per performed for this display
+    bool isEnabled{false};
+
+    // If false, this output is not considered secure
+    bool isSecure{false};
+
+    // If true, only layer stacks with a matching layerStack value will be
+    // displayed. If false, all layer stacks will be displayed.
+    bool singleLayerStack{true};
+
+    // The layer stack to display on this display
+    uint32_t singleLayerStackId{~0u};
+
+    // The physical space screen bounds
+    Rect bounds;
+
+    // The logical to physical transformation to use
+    ui::Transform transform;
+
+    // The physical orientation of the display, expressed as ui::Transform
+    // orientation flags.
+    uint32_t orientation{0};
+
+    // The logical space user visible bounds
+    Rect frame;
+
+    // The logical space user viewport rectangle
+    Rect viewport;
+
+    // The physical space scissor rectangle
+    Rect scissor;
+
+    // If true, RenderEngine filtering should be enabled
+    bool needsFiltering{false};
+
+    // The logical coordinates for the dirty region for the display.
+    // dirtyRegion is semi-persistent state. Dirty rectangles are added to it
+    // by the FE until composition happens, at which point it is cleared.
+    Region dirtyRegion;
+
+    // The logical coordinates for the undefined region for the display.
+    // The undefined region is internal to the composition engine. It is
+    // updated every time the geometry changes.
+    Region undefinedRegion;
+
+    // True if the last composition frame had visible layers
+    bool lastCompositionHadVisibleLayers{false};
+
+    // The color transform to apply
+    android_color_transform_t colorTransform{HAL_COLOR_TRANSFORM_IDENTITY};
+
+    // Current active color mode
+    ui::ColorMode colorMode{ui::ColorMode::NATIVE};
+
+    // Current active render intent
+    ui::RenderIntent renderIntent{ui::RenderIntent::COLORIMETRIC};
+
+    // Current active dstaspace
+    ui::Dataspace dataspace{ui::Dataspace::UNKNOWN};
+
+    // Debugging
+    void dump(std::string& result) const;
+};
+
+} // namespace compositionengine::impl
+} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
index 2371bec..22eb8f5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Display.h
@@ -16,15 +16,15 @@
 
 #pragma once
 
-#include <gmock/gmock.h>
-
 #include <compositionengine/Display.h>
+#include <compositionengine/mock/Output.h>
+#include <gmock/gmock.h>
 
 #include "DisplayHardware/DisplayIdentification.h"
 
 namespace android::compositionengine::mock {
 
-class Display : public compositionengine::Display {
+class Display : public compositionengine::mock::Output, public compositionengine::Display {
 public:
     Display();
     virtual ~Display();
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
new file mode 100644
index 0000000..ccdf804
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <compositionengine/Output.h>
+#include <compositionengine/impl/OutputCompositionState.h>
+#include <gmock/gmock.h>
+
+namespace android::compositionengine::mock {
+
+class Output : public virtual compositionengine::Output {
+public:
+    Output();
+    virtual ~Output();
+
+    MOCK_CONST_METHOD0(isValid, bool());
+
+    MOCK_METHOD1(setCompositionEnabled, void(bool));
+    MOCK_METHOD6(setProjection,
+                 void(const ui::Transform&, int32_t, const Rect&, const Rect&, const Rect&, bool));
+    MOCK_METHOD1(setBounds, void(const Rect&));
+    MOCK_METHOD2(setLayerStackFilter, void(bool, uint32_t));
+
+    MOCK_METHOD1(setColorTransform, void(const mat4&));
+    MOCK_METHOD3(setColorMode, void(ui::ColorMode, ui::Dataspace, ui::RenderIntent));
+
+    MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_CONST_METHOD0(getName, const std::string&());
+    MOCK_METHOD1(setName, void(const std::string&));
+
+    MOCK_CONST_METHOD0(getState, const OutputCompositionState&());
+    MOCK_METHOD0(editState, OutputCompositionState&());
+
+    MOCK_CONST_METHOD1(getPhysicalSpaceDirtyRegion, Region(bool));
+    MOCK_CONST_METHOD1(belongsInOutput, bool(uint32_t));
+};
+
+} // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/mock/Output.cpp b/services/surfaceflinger/CompositionEngine/mock/Output.cpp
new file mode 100644
index 0000000..44df4c3
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/mock/Output.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <compositionengine/mock/Output.h>
+
+namespace android::compositionengine::mock {
+
+// The Google Mock documentation recommends explicit non-header instantiations
+// for better compile time performance.
+Output::Output() = default;
+Output::~Output() = default;
+
+} // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index a0b277c..fb783e7 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#include <cinttypes>
-
+#include <android-base/stringprintf.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/DisplayCreationArgs.h>
 #include <compositionengine/impl/Display.h>
+#include <compositionengine/impl/DumpHelpers.h>
 
 #include "DisplayHardware/HWComposer.h"
 
@@ -31,10 +31,11 @@
 }
 
 Display::Display(const CompositionEngine& compositionEngine, DisplayCreationArgs&& args)
-      : mCompositionEngine(compositionEngine),
-        mIsSecure(args.isSecure),
+      : compositionengine::impl::Output(compositionEngine),
         mIsVirtual(args.isVirtual),
-        mId(args.displayId) {}
+        mId(args.displayId) {
+    editState().isSecure = args.isSecure;
+}
 
 Display::~Display() = default;
 
@@ -43,7 +44,7 @@
 }
 
 bool Display::isSecure() const {
-    return mIsSecure;
+    return getState().isSecure;
 }
 
 bool Display::isVirtual() const {
@@ -55,9 +56,55 @@
         return;
     }
 
-    auto& hwc = mCompositionEngine.getHwComposer();
+    auto& hwc = getCompositionEngine().getHwComposer();
     hwc.disconnectDisplay(*mId);
     mId.reset();
 }
 
+void Display::setColorTransform(const mat4& transform) {
+    Output::setColorTransform(transform);
+
+    auto& hwc = getCompositionEngine().getHwComposer();
+    status_t result = hwc.setColorTransform(*mId, transform);
+    ALOGE_IF(result != NO_ERROR, "Failed to set color transform on display \"%s\": %d",
+             mId ? to_string(*mId).c_str() : "", result);
+}
+
+void Display::setColorMode(ui::ColorMode mode, ui::Dataspace dataspace,
+                           ui::RenderIntent renderIntent) {
+    if (mode == getState().colorMode && dataspace == getState().dataspace &&
+        renderIntent == getState().renderIntent) {
+        return;
+    }
+
+    if (mIsVirtual) {
+        ALOGW("%s: Invalid operation on virtual display", __FUNCTION__);
+        return;
+    }
+
+    Output::setColorMode(mode, dataspace, renderIntent);
+
+    auto& hwc = getCompositionEngine().getHwComposer();
+    hwc.setActiveColorMode(*mId, mode, renderIntent);
+}
+
+void Display::dump(std::string& out) const {
+    using android::base::StringAppendF;
+
+    StringAppendF(&out, "   Composition Display State: [\"%s\"]", getName().c_str());
+
+    out.append("\n   ");
+
+    dumpVal(out, "isVirtual", mIsVirtual);
+    if (mId) {
+        dumpVal(out, "hwcId", to_string(*mId));
+    } else {
+        StringAppendF(&out, "no hwcId, ");
+    }
+
+    out.append("\n");
+
+    Output::dumpBase(out);
+}
+
 } // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
new file mode 100644
index 0000000..ba86be7
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/DumpHelpers.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cinttypes>
+
+#include <android-base/stringprintf.h>
+#include <compositionengine/impl/DumpHelpers.h>
+
+namespace android::compositionengine::impl {
+
+using android::base::StringAppendF;
+
+void dumpVal(std::string& out, const char* name, bool value) {
+    StringAppendF(&out, "%s=%c ", name, value ? 'T' : 'F');
+}
+
+void dumpVal(std::string& out, const char* name, const void* value) {
+    StringAppendF(&out, "%s=%p ", name, value);
+}
+
+void dumpVal(std::string& out, const char* name, int value) {
+    StringAppendF(&out, "%s=%d ", name, value);
+}
+
+void dumpVal(std::string& out, const char* name, float value) {
+    StringAppendF(&out, "%s=%f ", name, value);
+}
+
+void dumpVal(std::string& out, const char* name, uint32_t value) {
+    StringAppendF(&out, "%s=%u ", name, value);
+}
+
+void dumpHex(std::string& out, const char* name, uint64_t value) {
+    StringAppendF(&out, "%s=0x08%" PRIx64 " ", name, value);
+}
+
+void dumpVal(std::string& out, const char* name, const char* value) {
+    StringAppendF(&out, "%s=%s ", name, value);
+}
+
+void dumpVal(std::string& out, const char* name, const std::string& value) {
+    dumpVal(out, name, value.c_str());
+}
+
+void dumpVal(std::string& out, const char* name, const char* valueName, int value) {
+    StringAppendF(&out, "%s=%s (%d)", name, valueName, value);
+}
+
+void dumpVal(std::string& out, const char* name, const std::string& valueName, int value) {
+    dumpVal(out, name, valueName.c_str(), value);
+}
+
+void dumpVal(std::string& out, const char* name, const FloatRect& rect) {
+    StringAppendF(&out, "%s=[%f %f %f %f] ", name, rect.left, rect.top, rect.right, rect.bottom);
+}
+
+void dumpVal(std::string& out, const char* name, const Rect& rect) {
+    StringAppendF(&out, "%s=[%d %d %d %d] ", name, rect.left, rect.top, rect.right, rect.bottom);
+}
+
+void dumpVal(std::string& out, const char* name, const Region& region) {
+    region.dump(out, name, 0);
+}
+
+void dumpVal(std::string& out, const char* name, const ui::Transform& transform) {
+    transform.dump(out, name);
+}
+
+void dumpVal(std::string& out, const char* name, const mat4& tr) {
+    StringAppendF(&out,
+                  "%s=["
+                  /* clang-format off */
+                  "[%0.3f,%0.3f,%0.3f,%0.3f]"
+                  "[%0.3f,%0.3f,%0.3f,%0.3f]"
+                  "[%0.3f,%0.3f,%0.3f,%0.3f]"
+                  "[%0.3f,%0.3f,%0.3f,%0.3f]]",
+                  name,
+                  tr[0][0], tr[1][0], tr[2][0], tr[3][0],
+                  tr[0][1], tr[1][1], tr[2][1], tr[3][1],
+                  tr[0][2], tr[1][2], tr[2][2], tr[3][2],
+                  tr[0][3], tr[1][3], tr[2][3], tr[3][3]
+                  ); /* clang-format on */
+}
+
+} // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
new file mode 100644
index 0000000..8b2441f
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/stringprintf.h>
+#include <compositionengine/CompositionEngine.h>
+#include <compositionengine/impl/Output.h>
+#include <ui/DebugUtils.h>
+
+namespace android::compositionengine::impl {
+
+Output::Output(const CompositionEngine& compositionEngine)
+      : mCompositionEngine(compositionEngine) {}
+
+Output::~Output() = default;
+
+const CompositionEngine& Output::getCompositionEngine() const {
+    return mCompositionEngine;
+}
+
+bool Output::isValid() const {
+    return true;
+}
+
+const std::string& Output::getName() const {
+    return mName;
+}
+
+void Output::setName(const std::string& name) {
+    mName = name;
+}
+
+void Output::setCompositionEnabled(bool enabled) {
+    if (mState.isEnabled == enabled) {
+        return;
+    }
+
+    mState.isEnabled = enabled;
+    dirtyEntireOutput();
+}
+
+void Output::setProjection(const ui::Transform& transform, int32_t orientation, const Rect& frame,
+                           const Rect& viewport, const Rect& scissor, bool needsFiltering) {
+    mState.transform = transform;
+    mState.orientation = orientation;
+    mState.scissor = scissor;
+    mState.frame = frame;
+    mState.viewport = viewport;
+    mState.needsFiltering = needsFiltering;
+
+    dirtyEntireOutput();
+}
+
+void Output::setBounds(const Rect& bounds) {
+    mState.bounds = bounds;
+
+    dirtyEntireOutput();
+}
+
+void Output::setLayerStackFilter(bool singleLayerStack, uint32_t singleLayerStackId) {
+    mState.singleLayerStack = singleLayerStack;
+    mState.singleLayerStackId = singleLayerStackId;
+
+    dirtyEntireOutput();
+}
+
+void Output::setColorTransform(const mat4& transform) {
+    const bool isIdentity = (transform == mat4());
+
+    mState.colorTransform =
+            isIdentity ? HAL_COLOR_TRANSFORM_IDENTITY : HAL_COLOR_TRANSFORM_ARBITRARY_MATRIX;
+}
+
+void Output::setColorMode(ui::ColorMode mode, ui::Dataspace dataspace,
+                          ui::RenderIntent renderIntent) {
+    mState.colorMode = mode;
+    mState.dataspace = dataspace;
+    mState.renderIntent = renderIntent;
+
+    ALOGV("Set active color mode: %s (%d), active render intent: %s (%d)",
+          decodeColorMode(mode).c_str(), mode, decodeRenderIntent(renderIntent).c_str(),
+          renderIntent);
+}
+
+void Output::dump(std::string& out) const {
+    using android::base::StringAppendF;
+
+    StringAppendF(&out, "   Composition Output State: [\"%s\"]", mName.c_str());
+
+    out.append("\n   ");
+
+    dumpBase(out);
+}
+
+void Output::dumpBase(std::string& out) const {
+    mState.dump(out);
+}
+
+const OutputCompositionState& Output::getState() const {
+    return mState;
+}
+
+OutputCompositionState& Output::editState() {
+    return mState;
+}
+
+Region Output::getPhysicalSpaceDirtyRegion(bool repaintEverything) const {
+    Region dirty;
+    if (repaintEverything) {
+        dirty.set(mState.bounds);
+    } else {
+        dirty = mState.transform.transform(mState.dirtyRegion);
+        dirty.andSelf(mState.bounds);
+    }
+    return dirty;
+}
+
+bool Output::belongsInOutput(uint32_t layerStackId) const {
+    return !mState.singleLayerStack || (layerStackId == mState.singleLayerStackId);
+}
+
+void Output::dirtyEntireOutput() {
+    mState.dirtyRegion.set(mState.bounds);
+}
+
+} // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
new file mode 100644
index 0000000..7d8765f
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <compositionengine/impl/DumpHelpers.h>
+#include <compositionengine/impl/OutputCompositionState.h>
+
+namespace android::compositionengine::impl {
+
+void OutputCompositionState::dump(std::string& out) const {
+    dumpVal(out, "isEnabled", isEnabled);
+    dumpVal(out, "isSecure", isSecure);
+    if (singleLayerStack) {
+        out.append("layerStack=<any>");
+    } else {
+        dumpVal(out, "layerStack", singleLayerStackId);
+    }
+
+    out.append("\n   ");
+
+    dumpVal(out, "transform", transform);
+
+    out.append("\n   ");
+
+    dumpVal(out, "frame", frame);
+    dumpVal(out, "viewport", viewport);
+    dumpVal(out, "scissor", scissor);
+    dumpVal(out, "needsFiltering", needsFiltering);
+
+    out.append("\n");
+
+    dumpVal(out, "colorMode", toString(colorMode), colorMode);
+    dumpVal(out, "renderIntent", toString(renderIntent), renderIntent);
+    dumpVal(out, "dataspace", toString(dataspace), dataspace);
+    dumpVal(out, "colorTransform", colorTransform);
+
+    out.append("\n");
+}
+
+} // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 11f9910..a04c2aa 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-#include <gtest/gtest.h>
-
 #include <cmath>
 
 #include <compositionengine/DisplayCreationArgs.h>
 #include <compositionengine/impl/Display.h>
 #include <compositionengine/mock/CompositionEngine.h>
+#include <gtest/gtest.h>
 
 #include "MockHWComposer.h"
 
@@ -101,5 +100,77 @@
     EXPECT_FALSE(mDisplay.getId());
 }
 
+/* ------------------------------------------------------------------------
+ * Display::setColorTransform()
+ */
+
+TEST_F(DisplayTest, setColorTransformSetsTransform) {
+    // Identity matrix sets an identity state value
+    const mat4 identity;
+
+    EXPECT_CALL(mCompositionEngine, getHwComposer()).WillRepeatedly(ReturnRef(mHwComposer));
+
+    EXPECT_CALL(mHwComposer, setColorTransform(DEFAULT_DISPLAY_ID, identity)).Times(1);
+
+    mDisplay.setColorTransform(identity);
+
+    EXPECT_EQ(HAL_COLOR_TRANSFORM_IDENTITY, mDisplay.getState().colorTransform);
+
+    // Non-identity matrix sets a non-identity state value
+    const mat4 nonIdentity = mat4() * 2;
+
+    EXPECT_CALL(mHwComposer, setColorTransform(DEFAULT_DISPLAY_ID, nonIdentity)).Times(1);
+
+    mDisplay.setColorTransform(nonIdentity);
+
+    EXPECT_EQ(HAL_COLOR_TRANSFORM_ARBITRARY_MATRIX, mDisplay.getState().colorTransform);
+}
+
+/* ------------------------------------------------------------------------
+ * Display::setColorMode()
+ */
+
+TEST_F(DisplayTest, setColorModeSetsModeUnlessNoChange) {
+    EXPECT_CALL(mCompositionEngine, getHwComposer()).WillRepeatedly(ReturnRef(mHwComposer));
+
+    // These values are expected to be the initial state.
+    ASSERT_EQ(ui::ColorMode::NATIVE, mDisplay.getState().colorMode);
+    ASSERT_EQ(ui::Dataspace::UNKNOWN, mDisplay.getState().dataspace);
+    ASSERT_EQ(ui::RenderIntent::COLORIMETRIC, mDisplay.getState().renderIntent);
+
+    // Otherwise if the values are unchanged, nothing happens
+    mDisplay.setColorMode(ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
+                          ui::RenderIntent::COLORIMETRIC);
+
+    EXPECT_EQ(ui::ColorMode::NATIVE, mDisplay.getState().colorMode);
+    EXPECT_EQ(ui::Dataspace::UNKNOWN, mDisplay.getState().dataspace);
+    EXPECT_EQ(ui::RenderIntent::COLORIMETRIC, mDisplay.getState().renderIntent);
+
+    // Otherwise if the values are different, updates happen
+    EXPECT_CALL(mHwComposer,
+                setActiveColorMode(DEFAULT_DISPLAY_ID, ui::ColorMode::BT2100_PQ,
+                                   ui::RenderIntent::TONE_MAP_COLORIMETRIC))
+            .Times(1);
+
+    mDisplay.setColorMode(ui::ColorMode::BT2100_PQ, ui::Dataspace::SRGB,
+                          ui::RenderIntent::TONE_MAP_COLORIMETRIC);
+
+    EXPECT_EQ(ui::ColorMode::BT2100_PQ, mDisplay.getState().colorMode);
+    EXPECT_EQ(ui::Dataspace::SRGB, mDisplay.getState().dataspace);
+    EXPECT_EQ(ui::RenderIntent::TONE_MAP_COLORIMETRIC, mDisplay.getState().renderIntent);
+}
+
+TEST_F(DisplayTest, setColorModeDoesNothingForVirtualDisplay) {
+    impl::Display virtualDisplay{mCompositionEngine,
+                                 DisplayCreationArgs{false, true, DEFAULT_DISPLAY_ID}};
+
+    virtualDisplay.setColorMode(ui::ColorMode::BT2100_PQ, ui::Dataspace::SRGB,
+                                ui::RenderIntent::TONE_MAP_COLORIMETRIC);
+
+    EXPECT_EQ(ui::ColorMode::NATIVE, virtualDisplay.getState().colorMode);
+    EXPECT_EQ(ui::Dataspace::UNKNOWN, virtualDisplay.getState().dataspace);
+    EXPECT_EQ(ui::RenderIntent::COLORIMETRIC, virtualDisplay.getState().renderIntent);
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
new file mode 100644
index 0000000..0cd0d23
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cmath>
+
+#include <compositionengine/impl/Output.h>
+#include <compositionengine/mock/CompositionEngine.h>
+#include <gtest/gtest.h>
+#include <ui/Rect.h>
+#include <ui/Region.h>
+
+#include "RegionMatcher.h"
+#include "TransformMatcher.h"
+
+namespace android::compositionengine {
+namespace {
+
+using testing::ReturnRef;
+using testing::StrictMock;
+
+class OutputTest : public testing::Test {
+public:
+    ~OutputTest() override = default;
+
+    StrictMock<mock::CompositionEngine> mCompositionEngine;
+    impl::Output mOutput{mCompositionEngine};
+};
+
+/* ------------------------------------------------------------------------
+ * Basic construction
+ */
+
+TEST_F(OutputTest, canInstantiateOutput) {}
+
+/* ------------------------------------------------------------------------
+ * Output::setCompositionEnabled()
+ */
+
+TEST_F(OutputTest, setCompositionEnabledDoesNothingIfAlreadyEnabled) {
+    const Rect displaySize{100, 200};
+    mOutput.editState().bounds = displaySize;
+    mOutput.editState().isEnabled = true;
+
+    mOutput.setCompositionEnabled(true);
+
+    EXPECT_TRUE(mOutput.getState().isEnabled);
+    EXPECT_THAT(mOutput.getState().dirtyRegion, RegionEq(Region()));
+}
+
+TEST_F(OutputTest, setCompositionEnabledSetsEnabledAndDirtiesEntireOutput) {
+    const Rect displaySize{100, 200};
+    mOutput.editState().bounds = displaySize;
+    mOutput.editState().isEnabled = false;
+
+    mOutput.setCompositionEnabled(true);
+
+    EXPECT_TRUE(mOutput.getState().isEnabled);
+    EXPECT_THAT(mOutput.getState().dirtyRegion, RegionEq(Region(displaySize)));
+}
+
+TEST_F(OutputTest, setCompositionEnabledSetsDisabledAndDirtiesEntireOutput) {
+    const Rect displaySize{100, 200};
+    mOutput.editState().bounds = displaySize;
+    mOutput.editState().isEnabled = true;
+
+    mOutput.setCompositionEnabled(false);
+
+    EXPECT_FALSE(mOutput.getState().isEnabled);
+    EXPECT_THAT(mOutput.getState().dirtyRegion, RegionEq(Region(displaySize)));
+}
+
+/* ------------------------------------------------------------------------
+ * Output::setProjection()
+ */
+
+TEST_F(OutputTest, setProjectionTriviallyWorks) {
+    const ui::Transform transform{ui::Transform::ROT_180};
+    const int32_t orientation = 123;
+    const Rect frame{1, 2, 3, 4};
+    const Rect viewport{5, 6, 7, 8};
+    const Rect scissor{9, 10, 11, 12};
+    const bool needsFiltering = true;
+
+    mOutput.setProjection(transform, orientation, frame, viewport, scissor, needsFiltering);
+
+    EXPECT_THAT(mOutput.getState().transform, TransformEq(transform));
+    EXPECT_EQ(orientation, mOutput.getState().orientation);
+    EXPECT_EQ(frame, mOutput.getState().frame);
+    EXPECT_EQ(viewport, mOutput.getState().viewport);
+    EXPECT_EQ(scissor, mOutput.getState().scissor);
+    EXPECT_EQ(needsFiltering, mOutput.getState().needsFiltering);
+}
+
+/* ------------------------------------------------------------------------
+ * Output::setBounds()
+ */
+
+TEST_F(OutputTest, setBoundsSetsSizeAndDirtiesEntireOutput) {
+    const Rect displaySize{100, 200};
+    mOutput.setBounds(displaySize);
+
+    EXPECT_EQ(displaySize, mOutput.getState().bounds);
+
+    EXPECT_THAT(mOutput.getState().dirtyRegion, RegionEq(Region(displaySize)));
+}
+
+/* ------------------------------------------------------------------------
+ * Output::setLayerStackFilter()
+ */
+
+TEST_F(OutputTest, setLayerStackFilterSetsFilterAndDirtiesEntireOutput) {
+    const Rect displaySize{100, 200};
+    mOutput.editState().bounds = displaySize;
+
+    const uint32_t layerStack = 123u;
+    mOutput.setLayerStackFilter(true, layerStack);
+
+    EXPECT_TRUE(mOutput.getState().singleLayerStack);
+    EXPECT_EQ(layerStack, mOutput.getState().singleLayerStackId);
+
+    EXPECT_THAT(mOutput.getState().dirtyRegion, RegionEq(Region(displaySize)));
+}
+
+/* ------------------------------------------------------------------------
+ * Output::setColorTransform
+ */
+
+TEST_F(OutputTest, setColorTransformSetsTransform) {
+    // Identity matrix sets an identity state value
+    const mat4 identity;
+
+    mOutput.setColorTransform(identity);
+
+    EXPECT_EQ(HAL_COLOR_TRANSFORM_IDENTITY, mOutput.getState().colorTransform);
+
+    // Non-identity matrix sets a non-identity state value
+    const mat4 nonIdentity = mat4() * 2;
+
+    mOutput.setColorTransform(nonIdentity);
+
+    EXPECT_EQ(HAL_COLOR_TRANSFORM_ARBITRARY_MATRIX, mOutput.getState().colorTransform);
+}
+
+/* ------------------------------------------------------------------------
+ * Output::setColorMode
+ */
+
+TEST_F(OutputTest, setColorModeSetsModeUnlessNoChange) {
+    mOutput.setColorMode(ui::ColorMode::BT2100_PQ, ui::Dataspace::SRGB,
+                         ui::RenderIntent::TONE_MAP_COLORIMETRIC);
+
+    EXPECT_EQ(ui::ColorMode::BT2100_PQ, mOutput.getState().colorMode);
+    EXPECT_EQ(ui::Dataspace::SRGB, mOutput.getState().dataspace);
+    EXPECT_EQ(ui::RenderIntent::TONE_MAP_COLORIMETRIC, mOutput.getState().renderIntent);
+}
+
+/* ------------------------------------------------------------------------
+ * Output::getPhysicalSpaceDirtyRegion()
+ */
+
+TEST_F(OutputTest, getPhysicalSpaceDirtyRegionWithRepaintEverythingTrue) {
+    const Rect displaySize{100, 200};
+    mOutput.editState().bounds = displaySize;
+    mOutput.editState().dirtyRegion.set(50, 300);
+
+    {
+        Region result = mOutput.getPhysicalSpaceDirtyRegion(true);
+
+        EXPECT_THAT(result, RegionEq(Region(displaySize)));
+    }
+
+    // For repaint everything == true, the returned value does not depend on the display
+    // rotation.
+    mOutput.editState().transform.set(ui::Transform::ROT_90, 0, 0);
+
+    {
+        Region result = mOutput.getPhysicalSpaceDirtyRegion(true);
+
+        EXPECT_THAT(result, RegionEq(Region(displaySize)));
+    }
+}
+
+TEST_F(OutputTest, getPhysicalSpaceDirtyRegionWithRepaintEverythingFalse) {
+    const Rect displaySize{100, 200};
+    mOutput.editState().bounds = displaySize;
+    mOutput.editState().dirtyRegion.set(50, 300);
+
+    {
+        Region result = mOutput.getPhysicalSpaceDirtyRegion(false);
+
+        // The dirtyRegion should be clipped to the display bounds.
+        EXPECT_THAT(result, RegionEq(Region(Rect(50, 200))));
+    }
+
+    mOutput.editState().transform.set(ui::Transform::ROT_90, displaySize.getWidth(),
+                                      displaySize.getHeight());
+
+    {
+        Region result = mOutput.getPhysicalSpaceDirtyRegion(false);
+
+        // The dirtyRegion should be rotated and clipped to the display bounds.
+        EXPECT_THAT(result, RegionEq(Region(Rect(100, 50))));
+    }
+}
+
+} // namespace
+} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/RegionMatcher.h b/services/surfaceflinger/CompositionEngine/tests/RegionMatcher.h
new file mode 100644
index 0000000..5a4efa9
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/RegionMatcher.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gmock/gmock.h>
+
+namespace {
+
+// Checks for a region match
+MATCHER_P(RegionEq, expected, "") {
+    std::string buf;
+    buf.append("Regions are not equal\n");
+    expected.dump(buf, "expected region");
+    arg.dump(buf, "actual region");
+    *result_listener << buf;
+
+    size_t expectedRectCount = 0;
+    android::Rect const* expectedRects = expected.getArray(&expectedRectCount);
+    size_t actualRectCount = 0;
+    android::Rect const* actualRects = arg.getArray(&actualRectCount);
+
+    if (expectedRectCount != actualRectCount) return false;
+    for (size_t i = 0; i < expectedRectCount; i++) {
+        if (expectedRects[i] != actualRects[i]) return false;
+    }
+    return true;
+}
+
+} // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/TransformMatcher.h b/services/surfaceflinger/CompositionEngine/tests/TransformMatcher.h
new file mode 100644
index 0000000..ea07bed
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/TransformMatcher.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+namespace {
+
+// Check for a transform match
+MATCHER_P(TransformEq, expected, "") {
+    std::string buf;
+    buf.append("Transforms are not equal\n");
+    expected.dump(buf, "expected transform");
+    arg.dump(buf, "actual transform");
+    *result_listener << buf;
+
+    const float TOLERANCE = 1e-3f;
+
+    for (int i = 0; i < 3; i++) {
+        for (int j = 0; j < 3; j++) {
+            if (std::fabs(expected[i][j] - arg[i][j]) > TOLERANCE) {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+} // namespace
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 70e8f62..c3e34d4 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -33,6 +33,7 @@
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayCreationArgs.h>
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <configstore/Utils.h>
 #include <cutils/properties.h>
 #include <gui/Surface.h>
@@ -222,8 +223,7 @@
       : flinger(flinger), displayToken(displayToken), displayId(displayId) {}
 
 DisplayDevice::DisplayDevice(DisplayDeviceCreationArgs&& args)
-      : lastCompositionHadVisibleLayers(false),
-        mFlinger(args.flinger),
+      : mFlinger(args.flinger),
         mDisplayToken(args.displayToken),
         mSequenceId(args.sequenceId),
         mDisplayInstallOrientation(args.displayInstallOrientation),
@@ -235,13 +235,8 @@
         mDisplaySurface(args.displaySurface),
         mPageFlipCount(0),
         mIsVirtual(args.isVirtual),
-        mLayerStack(NO_LAYER_STACK),
         mOrientation(),
-        mViewport(Rect::INVALID_RECT),
-        mFrame(Rect::INVALID_RECT),
-        mPowerMode(args.initialPowerMode),
         mActiveConfig(0),
-        mColorTransform(HAL_COLOR_TRANSFORM_IDENTITY),
         mHasWideColorGamut(args.hasWideColorGamut),
         mHasHdr10Plus(false),
         mHasHdr10(false),
@@ -249,6 +244,10 @@
         mHasDolbyVision(false),
         mSupportedPerFrameMetadata(args.supportedPerFrameMetadata),
         mIsPrimary(args.isPrimary) {
+    if (!mCompositionDisplay->isValid()) {
+        ALOGE("Composition Display did not validate!");
+    }
+
     populateColorModes(args.hwcColorModes);
 
     ALOGE_IF(!mNativeWindow, "No native window was set for display");
@@ -303,11 +302,13 @@
     status = native_window_set_usage(window, GRALLOC_USAGE_HW_RENDER);
     ALOGE_IF(status != NO_ERROR, "Unable to set BQ usage bits for GPU rendering: %d", status);
 
-    mDisplayWidth = ANativeWindow_getWidth(window);
-    mDisplayHeight = ANativeWindow_getHeight(window);
+    mCompositionDisplay->setBounds(
+            Rect(ANativeWindow_getWidth(window), ANativeWindow_getHeight(window)));
+
+    setPowerMode(args.initialPowerMode);
 
     // initialize the display orientation transform.
-    setProjection(DisplayState::eOrientationDefault, mViewport, mFrame);
+    setProjection(DisplayState::eOrientationDefault, Rect::INVALID_RECT, Rect::INVALID_RECT);
 }
 
 DisplayDevice::~DisplayDevice() = default;
@@ -317,17 +318,18 @@
 }
 
 int DisplayDevice::getWidth() const {
-    return mDisplayWidth;
+    return mCompositionDisplay->getState().bounds.getWidth();
 }
 
 int DisplayDevice::getHeight() const {
-    return mDisplayHeight;
+    return mCompositionDisplay->getState().bounds.getHeight();
 }
 
 void DisplayDevice::setDisplayName(const std::string& displayName) {
     if (!displayName.empty()) {
         // never override the name with an empty name
         mDisplayName = displayName;
+        mCompositionDisplay->setName(displayName);
     }
 }
 
@@ -462,8 +464,8 @@
 }
 
 void DisplayDevice::setViewportAndProjection() const {
-    size_t w = mDisplayWidth;
-    size_t h = mDisplayHeight;
+    size_t w = getWidth();
+    size_t h = getHeight();
     Rect sourceCrop(0, 0, w, h);
     mFlinger->getRenderEngine().setViewportAndProjection(w, h, sourceCrop, ui::Transform::ROT_0);
 }
@@ -497,21 +499,10 @@
     return mLayersNeedingFences;
 }
 
-Region DisplayDevice::getDirtyRegion(bool repaintEverything) const {
-    Region dirty;
-    if (repaintEverything) {
-        dirty.set(getBounds());
-    } else {
-        const ui::Transform& planeTransform(mGlobalTransform);
-        dirty = planeTransform.transform(this->dirtyRegion);
-        dirty.andSelf(getBounds());
-    }
-    return dirty;
-}
-
 // ----------------------------------------------------------------------------
 void DisplayDevice::setPowerMode(int mode) {
     mPowerMode = mode;
+    getCompositionDisplay()->setCompositionEnabled(mPowerMode != HWC_POWER_MODE_OFF);
 }
 
 int DisplayDevice::getPowerMode()  const {
@@ -532,88 +523,42 @@
 }
 
 // ----------------------------------------------------------------------------
-void DisplayDevice::setActiveColorMode(ColorMode mode) {
-    mActiveColorMode = mode;
-}
-
-ColorMode DisplayDevice::getActiveColorMode() const {
-    return mActiveColorMode;
-}
-
-RenderIntent DisplayDevice::getActiveRenderIntent() const {
-    return mActiveRenderIntent;
-}
-
-void DisplayDevice::setActiveRenderIntent(RenderIntent renderIntent) {
-    mActiveRenderIntent = renderIntent;
-}
-
-void DisplayDevice::setColorTransform(const mat4& transform) {
-    const bool isIdentity = (transform == mat4());
-    mColorTransform =
-            isIdentity ? HAL_COLOR_TRANSFORM_IDENTITY : HAL_COLOR_TRANSFORM_ARBITRARY_MATRIX;
-}
-
-android_color_transform_t DisplayDevice::getColorTransform() const {
-    return mColorTransform;
-}
 
 void DisplayDevice::setCompositionDataSpace(ui::Dataspace dataspace) {
-    mCompositionDataSpace = dataspace;
     ANativeWindow* const window = mNativeWindow.get();
     native_window_set_buffers_data_space(window, static_cast<android_dataspace>(dataspace));
 }
 
 ui::Dataspace DisplayDevice::getCompositionDataSpace() const {
-    return mCompositionDataSpace;
+    return mCompositionDisplay->getState().dataspace;
 }
 
 // ----------------------------------------------------------------------------
 
 void DisplayDevice::setLayerStack(uint32_t stack) {
-    mLayerStack = stack;
-    dirtyRegion.set(bounds());
+    mCompositionDisplay->setLayerStackFilter(!isPrimary(), stack);
 }
 
 // ----------------------------------------------------------------------------
 
-uint32_t DisplayDevice::getOrientationTransform() const {
-    uint32_t transform = 0;
-    switch (mOrientation) {
-        case DisplayState::eOrientationDefault:
-            transform = ui::Transform::ROT_0;
-            break;
-        case DisplayState::eOrientation90:
-            transform = ui::Transform::ROT_90;
-            break;
-        case DisplayState::eOrientation180:
-            transform = ui::Transform::ROT_180;
-            break;
-        case DisplayState::eOrientation270:
-            transform = ui::Transform::ROT_270;
-            break;
-    }
-    return transform;
-}
-
-status_t DisplayDevice::orientationToTransfrom(
-        int orientation, int w, int h, ui::Transform* tr)
-{
-    uint32_t flags = 0;
+uint32_t DisplayDevice::displayStateOrientationToTransformOrientation(int orientation) {
     switch (orientation) {
     case DisplayState::eOrientationDefault:
-        flags = ui::Transform::ROT_0;
-        break;
+        return ui::Transform::ROT_0;
     case DisplayState::eOrientation90:
-        flags = ui::Transform::ROT_90;
-        break;
+        return ui::Transform::ROT_90;
     case DisplayState::eOrientation180:
-        flags = ui::Transform::ROT_180;
-        break;
+        return ui::Transform::ROT_180;
     case DisplayState::eOrientation270:
-        flags = ui::Transform::ROT_270;
-        break;
+        return ui::Transform::ROT_270;
     default:
+        return ui::Transform::ROT_INVALID;
+    }
+}
+
+status_t DisplayDevice::orientationToTransfrom(int orientation, int w, int h, ui::Transform* tr) {
+    uint32_t flags = displayStateOrientationToTransformOrientation(orientation);
+    if (flags == ui::Transform::ROT_INVALID) {
         return BAD_VALUE;
     }
     tr->set(flags, w, h);
@@ -621,12 +566,8 @@
 }
 
 void DisplayDevice::setDisplaySize(const int newWidth, const int newHeight) {
-    dirtyRegion.set(getBounds());
-
     mDisplaySurface->resizeBuffers(newWidth, newHeight);
-
-    mDisplayWidth = newWidth;
-    mDisplayHeight = newHeight;
+    mCompositionDisplay->setBounds(Rect(newWidth, newHeight));
 }
 
 void DisplayDevice::setProjection(int orientation,
@@ -634,8 +575,11 @@
     Rect viewport(newViewport);
     Rect frame(newFrame);
 
-    const int w = mDisplayWidth;
-    const int h = mDisplayHeight;
+    mOrientation = orientation;
+
+    const Rect& displayBounds = getCompositionDisplay()->getState().bounds;
+    const int w = displayBounds.width();
+    const int h = displayBounds.height();
 
     ui::Transform R;
     DisplayDevice::orientationToTransfrom(orientation, w, h, &R);
@@ -659,8 +603,6 @@
         }
     }
 
-    dirtyRegion.set(getBounds());
-
     ui::Transform TL, TP, S;
     float src_width  = viewport.width();
     float src_height = viewport.height();
@@ -679,7 +621,7 @@
     TL.set(-src_x, -src_y);
     TP.set(dst_x, dst_y);
 
-    // need to take care of primary display rotation for mGlobalTransform
+    // need to take care of primary display rotation for globalTransform
     // for case if the panel is not installed aligned with device orientation
     if (isPrimary()) {
         DisplayDevice::orientationToTransfrom(
@@ -690,38 +632,25 @@
     // The viewport and frame are both in the logical orientation.
     // Apply the logical translation, scale to physical size, apply the
     // physical translation and finally rotate to the physical orientation.
-    mGlobalTransform = R * TP * S * TL;
+    ui::Transform globalTransform = R * TP * S * TL;
 
-    const uint8_t type = mGlobalTransform.getType();
-    mNeedsFiltering = (!mGlobalTransform.preserveRects() ||
-            (type >= ui::Transform::SCALE));
+    const uint8_t type = globalTransform.getType();
+    const bool needsFiltering =
+            (!globalTransform.preserveRects() || (type >= ui::Transform::SCALE));
 
-    mScissor = mGlobalTransform.transform(viewport);
-    if (mScissor.isEmpty()) {
-        mScissor = getBounds();
+    Rect scissor = globalTransform.transform(viewport);
+    if (scissor.isEmpty()) {
+        scissor = displayBounds;
     }
 
-    mOrientation = orientation;
     if (isPrimary()) {
-        uint32_t transform = 0;
-        switch (mOrientation) {
-            case DisplayState::eOrientationDefault:
-                transform = ui::Transform::ROT_0;
-                break;
-            case DisplayState::eOrientation90:
-                transform = ui::Transform::ROT_90;
-                break;
-            case DisplayState::eOrientation180:
-                transform = ui::Transform::ROT_180;
-                break;
-            case DisplayState::eOrientation270:
-                transform = ui::Transform::ROT_270;
-                break;
-        }
-        sPrimaryDisplayOrientation = transform;
+        sPrimaryDisplayOrientation = displayStateOrientationToTransformOrientation(orientation);
     }
-    mViewport = viewport;
-    mFrame = frame;
+
+    getCompositionDisplay()->setProjection(globalTransform,
+                                           displayStateOrientationToTransformOrientation(
+                                                   orientation),
+                                           frame, viewport, scissor, needsFiltering);
 }
 
 uint32_t DisplayDevice::getPrimaryDisplayOrientationTransform() {
@@ -736,30 +665,21 @@
 }
 
 void DisplayDevice::dump(std::string& result) const {
-    const ui::Transform& tr(mGlobalTransform);
     ANativeWindow* const window = mNativeWindow.get();
     StringAppendF(&result, "+ %s\n", getDebugName().c_str());
-    StringAppendF(&result,
-                  "  layerStack=%u, (%4dx%4d), ANativeWindow=%p "
-                  "format=%d, orient=%2d (type=%08x), flips=%u, isSecure=%d, "
-                  "powerMode=%d, activeConfig=%d, numLayers=%zu\n",
-                  mLayerStack, mDisplayWidth, mDisplayHeight, window,
-                  ANativeWindow_getFormat(window), mOrientation, tr.getType(), getPageFlipCount(),
-                  isSecure(), mPowerMode, mActiveConfig, mVisibleLayersSortedByZ.size());
-    StringAppendF(&result,
-                  "   v:[%d,%d,%d,%d], f:[%d,%d,%d,%d], s:[%d,%d,%d,%d],"
-                  "transform:[[%0.3f,%0.3f,%0.3f][%0.3f,%0.3f,%0.3f][%0.3f,%0.3f,%0.3f]]\n",
-                  mViewport.left, mViewport.top, mViewport.right, mViewport.bottom, mFrame.left,
-                  mFrame.top, mFrame.right, mFrame.bottom, mScissor.left, mScissor.top,
-                  mScissor.right, mScissor.bottom, tr[0][0], tr[1][0], tr[2][0], tr[0][1], tr[1][1],
-                  tr[2][1], tr[0][2], tr[1][2], tr[2][2]);
+
+    result.append("   ");
+    StringAppendF(&result, "ANativeWindow=%p (format %d), ", window,
+                  ANativeWindow_getFormat(window));
+    StringAppendF(&result, "flips=%u, ", getPageFlipCount());
+    StringAppendF(&result, "powerMode=%d, ", mPowerMode);
+    StringAppendF(&result, "activeConfig=%d, ", mActiveConfig);
+    StringAppendF(&result, "numLayers=%zu\n", mVisibleLayersSortedByZ.size());
+    getCompositionDisplay()->dump(result);
     auto const surface = static_cast<Surface*>(window);
     ui::Dataspace dataspace = surface->getBuffersDataSpace();
-    StringAppendF(&result,
-                  "   wideColorGamut=%d, hdr10plus =%d, hdr10=%d, colorMode=%s, dataspace: %s "
-                  "(%d)\n",
+    StringAppendF(&result, "   wideColorGamut=%d, hdr10plus =%d, hdr10=%d, dataspace: %s (%d)\n",
                   mHasWideColorGamut, mHasHdr10Plus, mHasHdr10,
-                  decodeColorMode(mActiveColorMode).c_str(),
                   dataspaceDetails(static_cast<android_dataspace>(dataspace)).c_str(), dataspace);
 
     String8 surfaceDump;
@@ -884,6 +804,38 @@
     return mCompositionDisplay->isSecure();
 }
 
+const Rect& DisplayDevice::getBounds() const {
+    return mCompositionDisplay->getState().bounds;
+}
+
+const Region& DisplayDevice::getUndefinedRegion() const {
+    return mCompositionDisplay->getState().undefinedRegion;
+}
+
+bool DisplayDevice::needsFiltering() const {
+    return mCompositionDisplay->getState().needsFiltering;
+}
+
+uint32_t DisplayDevice::getLayerStack() const {
+    return mCompositionDisplay->getState().singleLayerStackId;
+}
+
+const ui::Transform& DisplayDevice::getTransform() const {
+    return mCompositionDisplay->getState().transform;
+}
+
+const Rect& DisplayDevice::getViewport() const {
+    return mCompositionDisplay->getState().viewport;
+}
+
+const Rect& DisplayDevice::getFrame() const {
+    return mCompositionDisplay->getState().frame;
+}
+
+const Rect& DisplayDevice::getScissor() const {
+    return mCompositionDisplay->getState().scissor;
+}
+
 std::atomic<int32_t> DisplayDeviceState::sNextSequenceId(1);
 
 }  // namespace android
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 0b92e53..55b6106 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -64,12 +64,6 @@
     constexpr static float sDefaultMinLumiance = 0.0;
     constexpr static float sDefaultMaxLumiance = 500.0;
 
-    // region in layer-stack space
-    mutable Region dirtyRegion;
-    // region in screen space
-    Region undefinedRegion;
-    bool lastCompositionHadVisibleLayers;
-
     enum {
         NO_LAYER_STACK = 0xFFFFFFFF,
     };
@@ -100,27 +94,26 @@
     const Vector< sp<Layer> >& getVisibleLayersSortedByZ() const;
     void                    setLayersNeedingFences(const Vector< sp<Layer> >& layers);
     const Vector< sp<Layer> >& getLayersNeedingFences() const;
-    Region                  getDirtyRegion(bool repaintEverything) const;
 
     void                    setLayerStack(uint32_t stack);
     void                    setDisplaySize(const int newWidth, const int newHeight);
     void                    setProjection(int orientation, const Rect& viewport, const Rect& frame);
 
     int                     getOrientation() const { return mOrientation; }
-    uint32_t                getOrientationTransform() const;
     static uint32_t         getPrimaryDisplayOrientationTransform();
-    const ui::Transform&   getTransform() const { return mGlobalTransform; }
-    const Rect              getViewport() const { return mViewport; }
-    const Rect              getFrame() const { return mFrame; }
-    const Rect&             getScissor() const { return mScissor; }
-    bool                    needsFiltering() const { return mNeedsFiltering; }
-
-    uint32_t                getLayerStack() const { return mLayerStack; }
+    const ui::Transform& getTransform() const;
+    const Rect& getViewport() const;
+    const Rect& getFrame() const;
+    const Rect& getScissor() const;
+    bool needsFiltering() const;
+    uint32_t getLayerStack() const;
 
     const std::optional<DisplayId>& getId() const;
     const wp<IBinder>& getDisplayToken() const { return mDisplayToken; }
     int32_t getSequenceId() const { return mSequenceId; }
 
+    const Region& getUndefinedRegion() const;
+
     int32_t getSupportedPerFrameMetadata() const { return mSupportedPerFrameMetadata; }
 
     // We pass in mustRecompose so we can keep VirtualDisplaySurface's state
@@ -163,10 +156,8 @@
     // called after h/w composer has completed its set() call
     void onPresentDisplayCompleted();
 
-    Rect getBounds() const {
-        return Rect(mDisplayWidth, mDisplayHeight);
-    }
-    inline Rect bounds() const { return getBounds(); }
+    const Rect& getBounds() const;
+    const Rect& bounds() const { return getBounds(); }
 
     void setDisplayName(const std::string& displayName);
     const std::string& getDisplayName() const { return mDisplayName; }
@@ -187,12 +178,6 @@
     void setPowerMode(int mode);
     bool isPoweredOn() const;
 
-    ui::ColorMode getActiveColorMode() const;
-    void setActiveColorMode(ui::ColorMode mode);
-    ui::RenderIntent getActiveRenderIntent() const;
-    void setActiveRenderIntent(ui::RenderIntent renderIntent);
-    android_color_transform_t getColorTransform() const;
-    void setColorTransform(const mat4& transform);
     void setCompositionDataSpace(ui::Dataspace dataspace);
     ui::Dataspace getCompositionDataSpace() const;
 
@@ -232,8 +217,6 @@
     // that drawing to the buffer is now complete.
     base::unique_fd mBufferReady;
 
-    int             mDisplayWidth;
-    int             mDisplayHeight;
     mutable uint32_t mPageFlipCount;
     std::string     mDisplayName;
 
@@ -252,35 +235,17 @@
     /*
      * Transaction state
      */
+    static uint32_t displayStateOrientationToTransformOrientation(int orientation);
     static status_t orientationToTransfrom(int orientation,
                                            int w, int h, ui::Transform* tr);
 
-    // The identifier of the active layer stack for this display. Several displays
-    // can use the same layer stack: A z-ordered group of layers (sometimes called
-    // "surfaces"). Any given layer can only be on a single layer stack.
-    uint32_t mLayerStack;
-
     int mOrientation;
     static uint32_t sPrimaryDisplayOrientation;
-    // user-provided visible area of the layer stack
-    Rect mViewport;
-    // user-provided rectangle where mViewport gets mapped to
-    Rect mFrame;
-    // pre-computed scissor to apply to the display
-    Rect mScissor;
-    ui::Transform mGlobalTransform;
-    bool mNeedsFiltering;
+
     // Current power mode
     int mPowerMode;
     // Current active config
     int mActiveConfig;
-    // current active color mode
-    ui::ColorMode mActiveColorMode = ui::ColorMode::NATIVE;
-    // Current active render intent.
-    ui::RenderIntent mActiveRenderIntent = ui::RenderIntent::COLORIMETRIC;
-    ui::Dataspace mCompositionDataSpace = ui::Dataspace::UNKNOWN;
-    // Current color transform
-    android_color_transform_t mColorTransform;
 
     // Need to know if display is wide-color capable or not.
     // Initialized by SurfaceFlinger when the DisplayDevice is created.
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index ac6172f..8d7ed53 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -36,6 +36,8 @@
 #include <binder/PermissionCache.h>
 
 #include <compositionengine/CompositionEngine.h>
+#include <compositionengine/Display.h>
+#include <compositionengine/impl/OutputCompositionState.h>
 #include <dvr/vr_flinger.h>
 #include <gui/BufferQueue.h>
 #include <gui/GuiConfig.h>
@@ -1017,40 +1019,11 @@
 
 ColorMode SurfaceFlinger::getActiveColorMode(const sp<IBinder>& displayToken) {
     if (const auto display = getDisplayDevice(displayToken)) {
-        return display->getActiveColorMode();
+        return display->getCompositionDisplay()->getState().colorMode;
     }
     return static_cast<ColorMode>(BAD_VALUE);
 }
 
-void SurfaceFlinger::setActiveColorModeInternal(const sp<DisplayDevice>& display, ColorMode mode,
-                                                Dataspace dataSpace, RenderIntent renderIntent) {
-    if (display->isVirtual()) {
-        ALOGE("%s: Invalid operation on virtual display", __FUNCTION__);
-        return;
-    }
-
-    ColorMode currentMode = display->getActiveColorMode();
-    Dataspace currentDataSpace = display->getCompositionDataSpace();
-    RenderIntent currentRenderIntent = display->getActiveRenderIntent();
-
-    if (mode == currentMode && dataSpace == currentDataSpace &&
-        renderIntent == currentRenderIntent) {
-        return;
-    }
-
-    display->setActiveColorMode(mode);
-    display->setCompositionDataSpace(dataSpace);
-    display->setActiveRenderIntent(renderIntent);
-
-    const auto displayId = display->getId();
-    LOG_ALWAYS_FATAL_IF(!displayId);
-    getHwComposer().setActiveColorMode(*displayId, mode, renderIntent);
-
-    ALOGV("Set active color mode: %s (%d), active render intent: %s (%d), display=%s",
-          decodeColorMode(mode).c_str(), mode, decodeRenderIntent(renderIntent).c_str(),
-          renderIntent, to_string(*displayId).c_str());
-}
-
 status_t SurfaceFlinger::setActiveColorMode(const sp<IBinder>& displayToken, ColorMode mode) {
     postMessageSync(new LambdaMessage([&] {
         Vector<ColorMode> modes;
@@ -1069,8 +1042,9 @@
             ALOGW("Attempt to set active color mode %s (%d) for virtual display",
                   decodeColorMode(mode).c_str(), mode);
         } else {
-            setActiveColorModeInternal(display, mode, Dataspace::UNKNOWN,
-                                       RenderIntent::COLORIMETRIC);
+            display->setCompositionDataSpace(Dataspace::UNKNOWN);
+            display->getCompositionDisplay()->setColorMode(mode, Dataspace::UNKNOWN,
+                                                           RenderIntent::COLORIMETRIC);
         }
     }));
 
@@ -1667,9 +1641,11 @@
     postComposition();
 
     mHadClientComposition = false;
-    for (const auto& [token, display] : mDisplays) {
+    for (const auto& [token, displayDevice] : mDisplays) {
+        auto display = displayDevice->getCompositionDisplay();
+        const auto displayId = display->getId();
         mHadClientComposition =
-                mHadClientComposition || getHwComposer().hasClientComposition(display->getId());
+                mHadClientComposition || getHwComposer().hasClientComposition(displayId);
     }
 
     // Setup RenderEngine sync fences if native sync is supported.
@@ -1709,13 +1685,14 @@
     // build the h/w work list
     if (CC_UNLIKELY(mGeometryInvalid)) {
         mGeometryInvalid = false;
-        for (const auto& [token, display] : mDisplays) {
+        for (const auto& [token, displayDevice] : mDisplays) {
+            auto display = displayDevice->getCompositionDisplay();
             const auto displayId = display->getId();
             if (!displayId) {
                 continue;
             }
 
-            const Vector<sp<Layer>>& currentLayers = display->getVisibleLayersSortedByZ();
+            const Vector<sp<Layer>>& currentLayers = displayDevice->getVisibleLayersSortedByZ();
             for (size_t i = 0; i < currentLayers.size(); i++) {
                 const auto& layer = currentLayers[i];
 
@@ -1726,7 +1703,7 @@
                     }
                 }
 
-                layer->setGeometry(display, i);
+                layer->setGeometry(displayDevice, i);
                 if (mDebugDisableHWC || mDebugRegion) {
                     layer->forceClientComposition(*displayId);
                 }
@@ -1735,7 +1712,8 @@
     }
 
     // Set the per-frame data
-    for (const auto& [token, display] : mDisplays) {
+    for (const auto& [token, displayDevice] : mDisplays) {
+        auto display = displayDevice->getCompositionDisplay();
         const auto displayId = display->getId();
         if (!displayId) {
             continue;
@@ -1743,21 +1721,17 @@
 
         if (mDrawingState.colorMatrixChanged) {
             display->setColorTransform(mDrawingState.colorMatrix);
-            status_t result =
-                    getHwComposer().setColorTransform(*displayId, mDrawingState.colorMatrix);
-            ALOGE_IF(result != NO_ERROR, "Failed to set color transform on display %s: %d",
-                     to_string(*displayId).c_str(), result);
         }
-        for (auto& layer : display->getVisibleLayersSortedByZ()) {
+        for (auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
             if (layer->isHdrY410()) {
                 layer->forceClientComposition(*displayId);
             } else if ((layer->getDataSpace() == Dataspace::BT2020_PQ ||
                         layer->getDataSpace() == Dataspace::BT2020_ITU_PQ) &&
-                    !display->hasHDR10Support()) {
+                       !displayDevice->hasHDR10Support()) {
                 layer->forceClientComposition(*displayId);
             } else if ((layer->getDataSpace() == Dataspace::BT2020_HLG ||
                         layer->getDataSpace() == Dataspace::BT2020_ITU_HLG) &&
-                    !display->hasHLGSupport()) {
+                       !displayDevice->hasHLGSupport()) {
                 layer->forceClientComposition(*displayId);
             }
 
@@ -1776,16 +1750,18 @@
                 continue;
             }
 
-            layer->setPerFrameData(*displayId, display->getTransform(), display->getViewport(),
-                                   display->getSupportedPerFrameMetadata());
+            const auto& displayState = display->getState();
+            layer->setPerFrameData(*displayId, displayState.transform, displayState.viewport,
+                                   displayDevice->getSupportedPerFrameMetadata());
         }
 
         if (useColorManagement) {
             ColorMode  colorMode;
             Dataspace dataSpace;
             RenderIntent renderIntent;
-            pickColorMode(display, &colorMode, &dataSpace, &renderIntent);
-            setActiveColorModeInternal(display, colorMode, dataSpace, renderIntent);
+            pickColorMode(displayDevice, &colorMode, &dataSpace, &renderIntent);
+            displayDevice->setCompositionDataSpace(dataSpace);
+            display->setColorMode(colorMode, dataSpace, renderIntent);
         }
     }
 
@@ -1809,34 +1785,37 @@
     }
 }
 
-void SurfaceFlinger::doDebugFlashRegions(const sp<DisplayDevice>& display, bool repaintEverything)
-{
+void SurfaceFlinger::doDebugFlashRegions(const sp<DisplayDevice>& displayDevice,
+                                         bool repaintEverything) {
+    auto display = displayDevice->getCompositionDisplay();
+    const auto& displayState = display->getState();
+
     // is debugging enabled
     if (CC_LIKELY(!mDebugRegion))
         return;
 
-    if (display->isPoweredOn()) {
+    if (displayState.isEnabled) {
         // transform the dirty region into this screen's coordinate space
-        const Region dirtyRegion(display->getDirtyRegion(repaintEverything));
+        const Region dirtyRegion = display->getPhysicalSpaceDirtyRegion(repaintEverything);
         if (!dirtyRegion.isEmpty()) {
             // redraw the whole screen
-            doComposeSurfaces(display);
+            doComposeSurfaces(displayDevice);
 
             // and draw the dirty region
             auto& engine(getRenderEngine());
             engine.fillRegionWithColor(dirtyRegion, 1, 0, 1, 1);
 
-            display->queueBuffer(getHwComposer());
+            displayDevice->queueBuffer(getHwComposer());
         }
     }
 
-    postFramebuffer(display);
+    postFramebuffer(displayDevice);
 
     if (mDebugRegion > 1) {
         usleep(mDebugRegion * 1000);
     }
 
-    prepareFrame(display);
+    prepareFrame(displayDevice);
 }
 
 void SurfaceFlinger::doTracing(const char* where) {
@@ -2001,16 +1980,16 @@
     ASSERT_ON_STACK_GUARD();
 
     // |mStateLock| not needed as we are on the main thread
-    const auto display = getDefaultDisplayDeviceLocked();
-    DEFINE_STACK_GUARD(display);
+    const auto displayDevice = getDefaultDisplayDeviceLocked();
+    DEFINE_STACK_GUARD(displayDevice);
 
     getBE().mGlCompositionDoneTimeline.updateSignalTimes();
     std::shared_ptr<FenceTime> glCompositionDoneFenceTime;
     DEFINE_STACK_GUARD(glCompositionDoneFenceTime);
 
-    if (display && getHwComposer().hasClientComposition(display->getId())) {
+    if (displayDevice && getHwComposer().hasClientComposition(displayDevice->getId())) {
         glCompositionDoneFenceTime =
-                std::make_shared<FenceTime>(display->getClientTargetAcquireFence());
+                std::make_shared<FenceTime>(displayDevice->getClientTargetAcquireFence());
         getBE().mGlCompositionDoneTimeline.push(glCompositionDoneFenceTime);
     } else {
         glCompositionDoneFenceTime = FenceTime::NO_FENCE;
@@ -2019,8 +1998,8 @@
     ASSERT_ON_STACK_GUARD();
 
     getBE().mDisplayTimeline.updateSignalTimes();
-    mPreviousPresentFence =
-            display ? getHwComposer().getPresentFence(*display->getId()) : Fence::NO_FENCE;
+    mPreviousPresentFence = displayDevice ? getHwComposer().getPresentFence(*displayDevice->getId())
+                                          : Fence::NO_FENCE;
     auto presentFenceTime = std::make_shared<FenceTime>(mPreviousPresentFence);
     DEFINE_STACK_GUARD(presentFenceTime);
     getBE().mDisplayTimeline.push(presentFenceTime);
@@ -2052,8 +2031,9 @@
     }
 
     mDrawingState.traverseInZOrder([&](Layer* layer) {
-        bool frameLatched = layer->onPostComposition(display->getId(), glCompositionDoneFenceTime,
-                                                     presentFenceTime, compositorTiming);
+        bool frameLatched =
+                layer->onPostComposition(displayDevice->getId(), glCompositionDoneFenceTime,
+                                         presentFenceTime, compositorTiming);
         DEFINE_STACK_GUARD(frameLatched);
         if (frameLatched) {
             recordBufferingStats(layer->getName().string(),
@@ -2078,7 +2058,8 @@
     }
 
     if (!hasSyncFramework) {
-        if (display && getHwComposer().isConnected(*display->getId()) && display->isPoweredOn()) {
+        if (displayDevice && getHwComposer().isConnected(*displayDevice->getId()) &&
+            displayDevice->isPoweredOn()) {
             if (mUseScheduler) {
                 mScheduler->enableHardwareVsync();
             } else {
@@ -2097,10 +2078,11 @@
                     std::move(presentFenceTime));
 
             ASSERT_ON_STACK_GUARD();
-        } else if (display && getHwComposer().isConnected(*display->getId())) {
+        } else if (displayDevice && getHwComposer().isConnected(*displayDevice->getId())) {
             // The HWC doesn't support present fences, so use the refresh
             // timestamp instead.
-            const nsecs_t presentTime = getHwComposer().getRefreshTimestamp(*display->getId());
+            const nsecs_t presentTime =
+                    getHwComposer().getRefreshTimestamp(*displayDevice->getId());
             DEFINE_STACK_GUARD(presentTime);
 
             mAnimFrameTracker.setActualPresentTime(presentTime);
@@ -2122,7 +2104,8 @@
 
     ASSERT_ON_STACK_GUARD();
 
-    if (display && getHwComposer().isConnected(*display->getId()) && !display->isPoweredOn()) {
+    if (displayDevice && getHwComposer().isConnected(*displayDevice->getId()) &&
+        !displayDevice->isPoweredOn()) {
         return;
     }
 
@@ -2190,20 +2173,22 @@
         invalidateHwcGeometry();
 
         for (const auto& pair : mDisplays) {
-            const auto& display = pair.second;
+            const auto& displayDevice = pair.second;
+            auto display = displayDevice->getCompositionDisplay();
+            const auto& displayState = display->getState();
             Region opaqueRegion;
             Region dirtyRegion;
             Vector<sp<Layer>> layersSortedByZ;
             Vector<sp<Layer>> layersNeedingFences;
-            const ui::Transform& tr = display->getTransform();
-            const Rect bounds = display->getBounds();
-            if (display->isPoweredOn()) {
-                computeVisibleRegions(display, dirtyRegion, opaqueRegion);
+            const ui::Transform& tr = displayState.transform;
+            const Rect bounds = displayState.bounds;
+            if (displayState.isEnabled) {
+                computeVisibleRegions(displayDevice, dirtyRegion, opaqueRegion);
 
                 mDrawingState.traverseInZOrder([&](Layer* layer) {
                     bool hwcLayerDestroyed = false;
-                    const auto displayId = display->getId();
-                    if (layer->belongsToDisplay(display->getLayerStack(), display->isPrimary())) {
+                    const auto displayId = displayDevice->getId();
+                    if (display->belongsInOutput(layer->getLayerStack())) {
                         Region drawRegion(tr.transform(
                                 layer->visibleNonTransparentRegion));
                         drawRegion.andSelf(bounds);
@@ -2234,11 +2219,14 @@
                     }
                 });
             }
-            display->setVisibleLayersSortedByZ(layersSortedByZ);
-            display->setLayersNeedingFences(layersNeedingFences);
-            display->undefinedRegion.set(bounds);
-            display->undefinedRegion.subtractSelf(tr.transform(opaqueRegion));
-            display->dirtyRegion.orSelf(dirtyRegion);
+            displayDevice->setVisibleLayersSortedByZ(layersSortedByZ);
+            displayDevice->setLayersNeedingFences(layersNeedingFences);
+
+            Region undefinedRegion{bounds};
+            undefinedRegion.subtractSelf(tr.transform(opaqueRegion));
+
+            display->editState().undefinedRegion = undefinedRegion;
+            display->editState().dirtyRegion.orSelf(dirtyRegion);
         }
     }
 }
@@ -2327,11 +2315,13 @@
     display->getBestColorMode(bestDataSpace, intent, outDataSpace, outMode, outRenderIntent);
 }
 
-void SurfaceFlinger::beginFrame(const sp<DisplayDevice>& display)
-{
-    bool dirty = !display->getDirtyRegion(false).isEmpty();
-    bool empty = display->getVisibleLayersSortedByZ().size() == 0;
-    bool wasEmpty = !display->lastCompositionHadVisibleLayers;
+void SurfaceFlinger::beginFrame(const sp<DisplayDevice>& displayDevice) {
+    auto display = displayDevice->getCompositionDisplay();
+    const auto& displayState = display->getState();
+
+    bool dirty = !display->getPhysicalSpaceDirtyRegion(false).isEmpty();
+    bool empty = displayDevice->getVisibleLayersSortedByZ().size() == 0;
+    bool wasEmpty = !displayState.lastCompositionHadVisibleLayers;
 
     // If nothing has changed (!dirty), don't recompose.
     // If something changed, but we don't currently have any visible layers,
@@ -2345,44 +2335,51 @@
 
     const char flagPrefix[] = {'-', '+'};
     static_cast<void>(flagPrefix);
-    ALOGV_IF(display->isVirtual(), "%s: %s composition for %s (%cdirty %cempty %cwasEmpty)",
-             __FUNCTION__, mustRecompose ? "doing" : "skipping", display->getDebugName().c_str(),
-             flagPrefix[dirty], flagPrefix[empty], flagPrefix[wasEmpty]);
+    ALOGV_IF(displayDevice->isVirtual(), "%s: %s composition for %s (%cdirty %cempty %cwasEmpty)",
+             __FUNCTION__, mustRecompose ? "doing" : "skipping",
+             displayDevice->getDebugName().c_str(), flagPrefix[dirty], flagPrefix[empty],
+             flagPrefix[wasEmpty]);
 
-    display->beginFrame(mustRecompose);
+    displayDevice->beginFrame(mustRecompose);
 
     if (mustRecompose) {
-        display->lastCompositionHadVisibleLayers = !empty;
+        display->editState().lastCompositionHadVisibleLayers = !empty;
     }
 }
 
-void SurfaceFlinger::prepareFrame(const sp<DisplayDevice>& display)
-{
-    if (!display->isPoweredOn()) {
+void SurfaceFlinger::prepareFrame(const sp<DisplayDevice>& displayDevice) {
+    auto display = displayDevice->getCompositionDisplay();
+    const auto& displayState = display->getState();
+
+    if (!displayState.isEnabled) {
         return;
     }
 
-    status_t result = display->prepareFrame(getHwComposer(),
-                                            getBE().mCompositionInfo[display->getDisplayToken()]);
+    status_t result =
+            displayDevice->prepareFrame(getHwComposer(),
+                                        getBE().mCompositionInfo[displayDevice->getDisplayToken()]);
     ALOGE_IF(result != NO_ERROR, "prepareFrame failed for %s: %d (%s)",
-             display->getDebugName().c_str(), result, strerror(-result));
+             displayDevice->getDebugName().c_str(), result, strerror(-result));
 }
 
-void SurfaceFlinger::doComposition(const sp<DisplayDevice>& display, bool repaintEverything) {
+void SurfaceFlinger::doComposition(const sp<DisplayDevice>& displayDevice, bool repaintEverything) {
     ATRACE_CALL();
     ALOGV("doComposition");
 
-    if (display->isPoweredOn()) {
+    auto display = displayDevice->getCompositionDisplay();
+    const auto& displayState = display->getState();
+
+    if (displayState.isEnabled) {
         // transform the dirty region into this screen's coordinate space
-        const Region dirtyRegion(display->getDirtyRegion(repaintEverything));
+        const Region dirtyRegion = display->getPhysicalSpaceDirtyRegion(repaintEverything);
 
         // repaint the framebuffer (if needed)
-        doDisplayComposition(display, dirtyRegion);
+        doDisplayComposition(displayDevice, dirtyRegion);
 
-        display->dirtyRegion.clear();
-        display->flip();
+        display->editState().dirtyRegion.clear();
+        displayDevice->flip();
     }
-    postFramebuffer(display);
+    postFramebuffer(displayDevice);
 }
 
 void SurfaceFlinger::postFrame()
@@ -2397,20 +2394,22 @@
     }
 }
 
-void SurfaceFlinger::postFramebuffer(const sp<DisplayDevice>& display)
-{
+void SurfaceFlinger::postFramebuffer(const sp<DisplayDevice>& displayDevice) {
     ATRACE_CALL();
     ALOGV("postFramebuffer");
 
+    auto display = displayDevice->getCompositionDisplay();
+    const auto& displayState = display->getState();
+    const auto displayId = display->getId();
+
     mPostFramebufferTime = systemTime();
 
-    if (display->isPoweredOn()) {
-        const auto displayId = display->getId();
+    if (displayState.isEnabled) {
         if (displayId) {
             getHwComposer().presentAndGetReleaseFences(*displayId);
         }
-        display->onPresentDisplayCompleted();
-        for (auto& layer : display->getVisibleLayersSortedByZ()) {
+        displayDevice->onPresentDisplayCompleted();
+        for (auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
             sp<Fence> releaseFence = Fence::NO_FENCE;
 
             // The layer buffer from the previous frame (if any) is released
@@ -2428,7 +2427,7 @@
             // this is suboptimal.
             if (layer->getCompositionType(displayId) == HWC2::Composition::Client) {
                 releaseFence = Fence::merge("LayerRelease", releaseFence,
-                                            display->getClientTargetAcquireFence());
+                                            displayDevice->getClientTargetAcquireFence());
             }
 
             layer->getBE().onLayerDisplayed(releaseFence);
@@ -2437,10 +2436,10 @@
         // We've got a list of layers needing fences, that are disjoint with
         // display->getVisibleLayersSortedByZ.  The best we can do is to
         // supply them with the present fence.
-        if (!display->getLayersNeedingFences().isEmpty()) {
+        if (!displayDevice->getLayersNeedingFences().isEmpty()) {
             sp<Fence> presentFence =
                     displayId ? getHwComposer().getPresentFence(*displayId) : Fence::NO_FENCE;
-            for (auto& layer : display->getLayersNeedingFences()) {
+            for (auto& layer : displayDevice->getLayersNeedingFences()) {
                 layer->getBE().onLayerDisplayed(presentFence);
             }
         }
@@ -2582,8 +2581,9 @@
         defaultColorMode = ColorMode::SRGB;
         defaultDataSpace = Dataspace::V0_SRGB;
     }
-    setActiveColorModeInternal(display, defaultColorMode, defaultDataSpace,
-                               RenderIntent::COLORIMETRIC);
+    display->setCompositionDataSpace(defaultDataSpace);
+    display->getCompositionDisplay()->setColorMode(defaultColorMode, defaultDataSpace,
+                                                   RenderIntent::COLORIMETRIC);
     if (!state.isVirtual()) {
         LOG_ALWAYS_FATAL_IF(!displayId);
         display->setActiveConfig(getHwComposer().getActiveConfigIndex(*displayId));
@@ -2840,7 +2840,7 @@
                 // if not, pick the only display it's on.
                 hintDisplay = nullptr;
                 for (const auto& [token, display] : mDisplays) {
-                    if (layer->belongsToDisplay(display->getLayerStack(), display->isPrimary())) {
+                    if (display->getCompositionDisplay()->belongsInOutput(layer->getLayerStack())) {
                         if (hintDisplay) {
                             hintDisplay = nullptr;
                             break;
@@ -3007,11 +3007,13 @@
     mTransactionCV.broadcast();
 }
 
-void SurfaceFlinger::computeVisibleRegions(const sp<const DisplayDevice>& display,
+void SurfaceFlinger::computeVisibleRegions(const sp<const DisplayDevice>& displayDevice,
                                            Region& outDirtyRegion, Region& outOpaqueRegion) {
     ATRACE_CALL();
     ALOGV("computeVisibleRegions");
 
+    auto display = displayDevice->getCompositionDisplay();
+
     Region aboveOpaqueLayers;
     Region aboveCoveredLayers;
     Region dirty;
@@ -3023,7 +3025,7 @@
         const Layer::State& s(layer->getDrawingState());
 
         // only consider the layers on the given layer stack
-        if (!layer->belongsToDisplay(display->getLayerStack(), display->isPrimary())) {
+        if (!display->belongsInOutput(layer->getLayerStack())) {
             return;
         }
 
@@ -3147,9 +3149,10 @@
 }
 
 void SurfaceFlinger::invalidateLayerStack(const sp<const Layer>& layer, const Region& dirty) {
-    for (const auto& [token, display] : mDisplays) {
-        if (layer->belongsToDisplay(display->getLayerStack(), display->isPrimary())) {
-            display->dirtyRegion.orSelf(dirty);
+    for (const auto& [token, displayDevice] : mDisplays) {
+        auto display = displayDevice->getCompositionDisplay();
+        if (display->belongsInOutput(layer->getLayerStack())) {
+            display->editState().dirtyRegion.orSelf(dirty);
         }
     }
 }
@@ -3236,30 +3239,35 @@
     mGeometryInvalid = true;
 }
 
-void SurfaceFlinger::doDisplayComposition(const sp<DisplayDevice>& display,
+void SurfaceFlinger::doDisplayComposition(const sp<DisplayDevice>& displayDevice,
                                           const Region& inDirtyRegion) {
+    auto display = displayDevice->getCompositionDisplay();
+
     // We only need to actually compose the display if:
     // 1) It is being handled by hardware composer, which may need this to
     //    keep its virtual display state machine in sync, or
     // 2) There is work to be done (the dirty region isn't empty)
-    if (!display->getId() && inDirtyRegion.isEmpty()) {
+    if (!displayDevice->getId() && inDirtyRegion.isEmpty()) {
         ALOGV("Skipping display composition");
         return;
     }
 
     ALOGV("doDisplayComposition");
-    if (!doComposeSurfaces(display)) return;
+    if (!doComposeSurfaces(displayDevice)) return;
 
     // swap buffers (presentation)
-    display->queueBuffer(getHwComposer());
+    displayDevice->queueBuffer(getHwComposer());
 }
 
-bool SurfaceFlinger::doComposeSurfaces(const sp<DisplayDevice>& display) {
+bool SurfaceFlinger::doComposeSurfaces(const sp<DisplayDevice>& displayDevice) {
     ALOGV("doComposeSurfaces");
 
-    const Region bounds(display->bounds());
-    const DisplayRenderArea renderArea(display);
+    auto display = displayDevice->getCompositionDisplay();
+    const auto& displayState = display->getState();
     const auto displayId = display->getId();
+
+    const Region bounds(displayState.bounds);
+    const DisplayRenderArea renderArea(displayDevice);
     const bool hasClientComposition = getHwComposer().hasClientComposition(displayId);
     ATRACE_INT("hasClientComposition", hasClientComposition);
 
@@ -3272,12 +3280,12 @@
     if (hasClientComposition) {
         ALOGV("hasClientComposition");
 
-        sp<GraphicBuffer> buf = display->dequeueBuffer();
+        sp<GraphicBuffer> buf = displayDevice->dequeueBuffer();
 
         if (buf == nullptr) {
             ALOGW("Dequeuing buffer for display [%s] failed, bailing out of "
                   "client composition for this frame",
-                  display->getDisplayName().c_str());
+                  displayDevice->getDisplayName().c_str());
             return false;
         }
 
@@ -3287,17 +3295,17 @@
 
         if (fbo->getStatus() != NO_ERROR) {
             ALOGW("Binding buffer for display [%s] failed with status: %d",
-                  display->getDisplayName().c_str(), fbo->getStatus());
+                  displayDevice->getDisplayName().c_str(), fbo->getStatus());
             return false;
         }
 
         Dataspace outputDataspace = Dataspace::UNKNOWN;
-        if (display->hasWideColorGamut()) {
-            outputDataspace = display->getCompositionDataSpace();
+        if (displayDevice->hasWideColorGamut()) {
+            outputDataspace = displayState.dataspace;
         }
         getRenderEngine().setOutputDataSpace(outputDataspace);
         getRenderEngine().setDisplayMaxLuminance(
-                display->getHdrCapabilities().getDesiredMaxLuminance());
+                displayDevice->getHdrCapabilities().getDesiredMaxLuminance());
 
         const bool hasDeviceComposition = getHwComposer().hasDeviceComposition(displayId);
         const bool skipClientColorTransform =
@@ -3311,7 +3319,7 @@
             colorMatrix = mDrawingState.colorMatrix;
         }
 
-        display->setViewportAndProjection();
+        displayDevice->setViewportAndProjection();
 
         // Never touch the framebuffer if we don't have any framebuffer layers
         if (hasDeviceComposition) {
@@ -3325,10 +3333,10 @@
             // we start with the whole screen area and remove the scissor part
             // we're left with the letterbox region
             // (common case is that letterbox ends-up being empty)
-            const Region letterbox = bounds.subtract(display->getScissor());
+            const Region letterbox = bounds.subtract(displayState.scissor);
 
             // compute the area to clear
-            const Region region = display->undefinedRegion.merge(letterbox);
+            const Region region = displayState.undefinedRegion.merge(letterbox);
 
             // screen is already cleared here
             if (!region.isEmpty()) {
@@ -3337,8 +3345,8 @@
             }
         }
 
-        const Rect& bounds = display->getBounds();
-        const Rect& scissor = display->getScissor();
+        const Rect& bounds = displayState.bounds;
+        const Rect& scissor = displayState.scissor;
         if (scissor != bounds) {
             // scissor doesn't match the screen's dimensions, so we
             // need to clear everything outside of it and enable
@@ -3354,9 +3362,9 @@
      */
 
     ALOGV("Rendering client layers");
-    const ui::Transform& displayTransform = display->getTransform();
+    const ui::Transform& displayTransform = displayState.transform;
     bool firstLayer = true;
-    for (auto& layer : display->getVisibleLayersSortedByZ()) {
+    for (auto& layer : displayDevice->getVisibleLayersSortedByZ()) {
         const Region clip(bounds.intersect(
                 displayTransform.transform(layer->visibleRegion)));
         ALOGV("Layer: %s", layer->getName().string());
@@ -3405,7 +3413,7 @@
     if (hasClientComposition) {
         getRenderEngine().setColorTransform(mat4());
         getRenderEngine().disableScissor();
-        display->finishBuffer();
+        displayDevice->finishBuffer();
         // Clear out error flags here so that we don't wait until next
         // composition to log.
         getRenderEngine().checkErrors();
@@ -4639,7 +4647,7 @@
             StringAppendF(&result, "    %s (%d)\n", decodeColorMode(mode).c_str(), mode);
         }
 
-        ColorMode currentMode = display->getActiveColorMode();
+        ColorMode currentMode = display->getCompositionDisplay()->getState().colorMode;
         StringAppendF(&result, "    Current color mode: %s (%d)\n",
                       decodeColorMode(currentMode).c_str(), currentMode);
     }
@@ -4675,18 +4683,21 @@
     return layersProto;
 }
 
-LayersProto SurfaceFlinger::dumpVisibleLayersProtoInfo(const DisplayDevice& display) const {
+LayersProto SurfaceFlinger::dumpVisibleLayersProtoInfo(const DisplayDevice& displayDevice) const {
     LayersProto layersProto;
 
     SizeProto* resolution = layersProto.mutable_resolution();
-    resolution->set_w(display.getWidth());
-    resolution->set_h(display.getHeight());
+    resolution->set_w(displayDevice.getWidth());
+    resolution->set_h(displayDevice.getHeight());
 
-    layersProto.set_color_mode(decodeColorMode(display.getActiveColorMode()));
-    layersProto.set_color_transform(decodeColorTransform(display.getColorTransform()));
-    layersProto.set_global_transform(static_cast<int32_t>(display.getOrientationTransform()));
+    auto display = displayDevice.getCompositionDisplay();
+    const auto& displayState = display->getState();
 
-    const auto displayId = display.getId();
+    layersProto.set_color_mode(decodeColorMode(displayState.colorMode));
+    layersProto.set_color_transform(decodeColorTransform(displayState.colorTransform));
+    layersProto.set_global_transform(displayState.orientation);
+
+    const auto displayId = displayDevice.getId();
     LOG_ALWAYS_FATAL_IF(!displayId);
     mDrawingState.traverseInZOrder([&](Layer* layer) {
         if (!layer->visibleRegion.isEmpty() && layer->getBE().mHwcLayers.count(*displayId)) {
@@ -4813,7 +4824,8 @@
     getRenderEngine().dump(result);
 
     if (const auto display = getDefaultDisplayDeviceLocked()) {
-        display->undefinedRegion.dump(result, "undefinedRegion");
+        display->getCompositionDisplay()->getState().undefinedRegion.dump(result,
+                                                                          "undefinedRegion");
         StringAppendF(&result, "  orientation=%d, isPoweredOn=%d\n", display->getOrientation(),
                       display->isPoweredOn());
     }
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index eb7127e..17eed1c 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -512,10 +512,6 @@
     // called on the main thread in response to setPowerMode()
     void setPowerModeInternal(const sp<DisplayDevice>& display, int mode);
 
-    // Called on the main thread in response to setActiveColorMode()
-    void setActiveColorModeInternal(const sp<DisplayDevice>& display, ui::ColorMode colorMode,
-                                    ui::Dataspace dataSpace, ui::RenderIntent renderIntent);
-
     // Returns whether the transaction actually modified any state
     bool handleMessageTransaction();