Merge "Fix windows build." into studio-1.4-dev
diff --git a/cc/gapic/coder/gles.h b/cc/gapic/coder/gles.h
index d45598e..647cccc 100644
--- a/cc/gapic/coder/gles.h
+++ b/cc/gapic/coder/gles.h
@@ -45,16 +45,17 @@
     class BackbufferInfo: public Encodable {
     public:
         BackbufferInfo() = default;
-        BackbufferInfo(atom::Observations observations, int32_t Width, int32_t Height, uint32_t ColorFmt, uint32_t DepthFmt, uint32_t StencilFmt, bool ResetViewportScissor) :
+        BackbufferInfo(atom::Observations observations, int32_t Width, int32_t Height, uint32_t ColorFmt, uint32_t DepthFmt, uint32_t StencilFmt, bool ResetViewportScissor, bool PreserveBuffersOnSwap) :
             mobservations(observations),
             mWidth(Width),
             mHeight(Height),
             mColorFmt(ColorFmt),
             mDepthFmt(DepthFmt),
             mStencilFmt(StencilFmt),
-            mResetViewportScissor(ResetViewportScissor) {}
+            mResetViewportScissor(ResetViewportScissor),
+            mPreserveBuffersOnSwap(PreserveBuffersOnSwap) {}
         virtual const gapic::Id& Id() const {
-            static gapic::Id ID{ { 0x18, 0x3c, 0x07, 0xe1, 0x0c, 0x71, 0x64, 0xee, 0xaa, 0x70, 0x7a, 0xb3, 0xca, 0xbe, 0x36, 0x00, 0xe8, 0xeb, 0x8c, 0xed,  } };
+            static gapic::Id ID{ { 0x3b, 0x71, 0x56, 0x1e, 0xbc, 0xdf, 0xd7, 0x44, 0xcf, 0x32, 0xc5, 0x65, 0xe5, 0x66, 0xf0, 0x8f, 0x1b, 0x28, 0x23, 0xa7,  } };
             return ID;
         }
         virtual void Encode(Encoder* e) const {
@@ -65,6 +66,7 @@
             e->Uint32(this->mDepthFmt);
             e->Uint32(this->mStencilFmt);
             e->Bool(this->mResetViewportScissor);
+            e->Bool(this->mPreserveBuffersOnSwap);
         }
 
         atom::Observations mobservations;
@@ -74,6 +76,7 @@
         uint32_t mDepthFmt;
         uint32_t mStencilFmt;
         bool mResetViewportScissor;
+        bool mPreserveBuffersOnSwap;
     };
 
     class Color: public Encodable {
@@ -1283,7 +1286,7 @@
     class Context: public Encodable {
     public:
         Context() = default;
-        Context(uint64_t CreatedAt, uint32_t Identifier, BlendState Blending, RasterizerState Rasterizing, ClearState Clearing, std::unordered_map<uint32_t,uint32_t>* BoundFramebuffers, std::unordered_map<uint32_t,uint32_t>* BoundRenderbuffers, std::unordered_map<uint32_t,uint32_t>* BoundBuffers, uint32_t BoundProgram, uint32_t BoundVertexArray, std::unordered_map<int32_t,VertexAttributeArray*>* VertexAttributeArrays, std::unordered_map<uint32_t,std::unordered_map<uint32_t,uint32_t>*>* TextureUnits, uint32_t ActiveTextureUnit, std::unordered_map<uint32_t,bool>* Capabilities, uint32_t GenerateMipmapHint, std::unordered_map<uint32_t,int32_t>* PixelStorage, Objects Instances) :
+        Context(uint64_t CreatedAt, uint32_t Identifier, BlendState Blending, RasterizerState Rasterizing, ClearState Clearing, std::unordered_map<uint32_t,uint32_t>* BoundFramebuffers, std::unordered_map<uint32_t,uint32_t>* BoundRenderbuffers, std::unordered_map<uint32_t,uint32_t>* BoundBuffers, uint32_t BoundProgram, uint32_t BoundVertexArray, std::unordered_map<int32_t,VertexAttributeArray*>* VertexAttributeArrays, std::unordered_map<uint32_t,std::unordered_map<uint32_t,uint32_t>*>* TextureUnits, uint32_t ActiveTextureUnit, std::unordered_map<uint32_t,bool>* Capabilities, uint32_t GenerateMipmapHint, std::unordered_map<uint32_t,int32_t>* PixelStorage, Objects Instances, bool PreserveBuffersOnSwap) :
             mCreatedAt(CreatedAt),
             mIdentifier(Identifier),
             mBlending(Blending),
@@ -1300,9 +1303,10 @@
             mCapabilities(Capabilities),
             mGenerateMipmapHint(GenerateMipmapHint),
             mPixelStorage(PixelStorage),
-            mInstances(Instances) {}
+            mInstances(Instances),
+            mPreserveBuffersOnSwap(PreserveBuffersOnSwap) {}
         virtual const gapic::Id& Id() const {
-            static gapic::Id ID{ { 0x5d, 0x15, 0x38, 0x06, 0x50, 0x11, 0xdd, 0x3b, 0xf6, 0x52, 0x52, 0xca, 0xc5, 0x29, 0x63, 0xdc, 0xe5, 0x1d, 0x52, 0x4a,  } };
+            static gapic::Id ID{ { 0x2d, 0x31, 0xd7, 0x8d, 0xe9, 0x76, 0xe7, 0x8c, 0xd4, 0xda, 0x77, 0xca, 0x1b, 0x5f, 0x8a, 0xc3, 0xa9, 0x35, 0xb2, 0x2c,  } };
             return ID;
         }
         virtual void Encode(Encoder* e) const {
@@ -1323,6 +1327,7 @@
             e->Uint32(this->mGenerateMipmapHint);
             GAPID_FATAL("C++ map encoding not supported");
             e->Value(this->mInstances);
+            e->Bool(this->mPreserveBuffersOnSwap);
         }
 
         uint64_t mCreatedAt;
@@ -1342,6 +1347,7 @@
         uint32_t mGenerateMipmapHint;
         std::unordered_map<uint32_t,int32_t>* mPixelStorage;
         Objects mInstances;
+        bool mPreserveBuffersOnSwap;
     };
 
     class EGLConfig: public Encodable {
diff --git a/cc/gapii/gles_spy.h b/cc/gapii/gles_spy.h
index 0c3570b..47882dc 100644
--- a/cc/gapii/gles_spy.h
+++ b/cc/gapii/gles_spy.h
@@ -322,7 +322,8 @@
     inline void replayBindRenderer(uint32_t id);
     inline void switchThread(uint64_t threadID);
     inline void backbufferInfo(int32_t width, int32_t height, uint32_t color_fmt,
-                               uint32_t depth_fmt, uint32_t stencil_fmt, bool resetViewportScissor);
+                               uint32_t depth_fmt, uint32_t stencil_fmt, bool resetViewportScissor,
+                               bool preserveBuffersOnSwap);
     inline void startTimer(uint8_t index);
     inline uint64_t stopTimer(uint8_t index);
     inline void flushPostBuffer();
@@ -6207,15 +6208,16 @@
 
 inline void GlesSpy::backbufferInfo(int32_t width, int32_t height, uint32_t color_fmt,
                                     uint32_t depth_fmt, uint32_t stencil_fmt,
-                                    bool resetViewportScissor) {
-    GAPID_INFO("backbufferInfo(%" PRId32 ", %" PRId32 ", %u, %u, %u, %d)\n", width, height,
-               color_fmt, depth_fmt, stencil_fmt, resetViewportScissor);
+                                    bool resetViewportScissor, bool preserveBuffersOnSwap) {
+    GAPID_INFO("backbufferInfo(%" PRId32 ", %" PRId32 ", %u, %u, %u, %d, %d)\n", width, height,
+               color_fmt, depth_fmt, stencil_fmt, resetViewportScissor, preserveBuffersOnSwap);
 
     Observations observations;
     do {
         std::shared_ptr<Context> l_context = this->Contexts[this->CurrentThread];
         std::shared_ptr<Context> l_GetContext_139_result = l_context;
         std::shared_ptr<Context> l_ctx = l_GetContext_139_result;
+        l_ctx->mPreserveBuffersOnSwap = preserveBuffersOnSwap;
         std::shared_ptr<Framebuffer> l_backbuffer =
                 l_ctx->mInstances.mFramebuffers[(FramebufferId)(0)];
         RenderbufferId l_color_id =
@@ -6248,7 +6250,8 @@
     observe(observations.mWrites);
 
     gapic::coder::gles::BackbufferInfo coder(observations, width, height, color_fmt, depth_fmt,
-                                             stencil_fmt, resetViewportScissor);
+                                             stencil_fmt, resetViewportScissor,
+                                             preserveBuffersOnSwap);
     mEncoder->Object(&coder);
 }
 
diff --git a/cc/gapii/gles_types.h b/cc/gapii/gles_types.h
index 27bbefe..4095149 100644
--- a/cc/gapii/gles_types.h
+++ b/cc/gapii/gles_types.h
@@ -1326,6 +1326,10 @@
         mInstances = v;
         return *this;
     }
+    inline Context& SetPreserveBuffersOnSwap(bool v) {
+        mPreserveBuffersOnSwap = v;
+        return *this;
+    }
 
     ContextID mIdentifier;
     BlendState mBlending;
@@ -1343,6 +1347,7 @@
     uint32_t mGenerateMipmapHint;
     GLenumToS32 mPixelStorage;
     Objects mInstances;
+    bool mPreserveBuffersOnSwap;
 };
 
 typedef std::unordered_map<CGLContextObj, std::shared_ptr<Context>> CGLContextObjToContext__R;
diff --git a/cc/gapii/spy.cpp b/cc/gapii/spy.cpp
index ff25f08..66e30d8 100644
--- a/cc/gapii/spy.cpp
+++ b/cc/gapii/spy.cpp
@@ -28,8 +28,10 @@
 
 namespace {
 
-const uint32_t EGL_WIDTH  = 0x3057;
-const uint32_t EGL_HEIGHT = 0x3056;
+const uint32_t EGL_WIDTH            = 0x3057;
+const uint32_t EGL_HEIGHT           = 0x3056;
+const uint32_t EGL_SWAP_BEHAVIOR    = 0x3093;
+const uint32_t EGL_BUFFER_PRESERVED = 0x3094;
 
 const uint32_t GLX_WIDTH  = 0x801D;
 const uint32_t GLX_HEIGHT = 0x801E;
@@ -86,11 +88,20 @@
     if (res != 0 && draw != nullptr) {
         int width = 0;
         int height = 0;
+        int swapBehavior = 0;
         mImports.eglQuerySurface(display, draw, EGL_WIDTH, &width);
         mImports.eglQuerySurface(display, draw, EGL_HEIGHT, &height);
+        mImports.eglQuerySurface(display, draw, EGL_SWAP_BEHAVIOR, &swapBehavior);
+
+        bool resetViewportScissor = true;
+        bool preserveBuffersOnSwap = swapBehavior == EGL_BUFFER_PRESERVED;
 
         // TODO: Probe formats
-        GlesSpy::backbufferInfo(width, height, GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8, true);
+        GlesSpy::backbufferInfo(
+                width, height,
+                GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8,
+                resetViewportScissor,
+                preserveBuffersOnSwap);
     }
 
     return res;
@@ -104,8 +115,11 @@
 #if TARGET_OS == GAPID_OS_WINDOWS
         wgl::FramebufferInfo info;
         wgl::getFramebufferInfo(hdc, info);
-        GlesSpy::backbufferInfo(info.width, info.height,
-                info.colorFormat, info.depthFormat, info.stencilFormat, true);
+        GlesSpy::backbufferInfo(
+                info.width, info.height,
+                info.colorFormat, info.depthFormat, info.stencilFormat,
+                /* resetViewportScissor */ true,
+                /* preserveBuffersOnSwap */ false);
 #endif // TARGET_OS
     }
 
@@ -129,7 +143,11 @@
         int height = bounds[3] - bounds[1]; // size.y - origin.y
 
         // TODO: Probe formats
-        GlesSpy::backbufferInfo(width, height, GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8, true);
+        GlesSpy::backbufferInfo(
+                width, height,
+                GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8,
+                /* resetViewportScissor */ true,
+                /* preserveBuffersOnSwap */ false);
     }
     return err;
 }
@@ -145,7 +163,11 @@
         mImports.glXQueryDrawable(display, draw, GLX_HEIGHT, &height);
 
         // TODO: Probe formats
-        GlesSpy::backbufferInfo(width, height, GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8, true);
+        GlesSpy::backbufferInfo(
+                width, height,
+                GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8,
+                /* resetViewportScissor */ true,
+                /* preserveBuffersOnSwap */ false);
     }
 
     return res;
@@ -163,7 +185,10 @@
 
         // TODO: Probe formats
         GlesSpy::backbufferInfo(
-            width, height, GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8, true);
+                width, height,
+                GL_RGBA8, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8,
+                /* resetViewportScissor */ true,
+                /* preserveBuffersOnSwap */ false);
     }
 
     return res;
diff --git a/cc/gapir/context.cpp b/cc/gapir/context.cpp
index 0da3937..613fe98 100644
--- a/cc/gapir/context.cpp
+++ b/cc/gapir/context.cpp
@@ -154,6 +154,7 @@
     });
 
     interpreter->registerFunction(gfxapi::Ids::BackbufferInfo, [this](Stack* stack, bool) {
+        stack->pop<bool>(); /* preserveBuffersOnSwap - ignored */
         bool resetViewportScissor = stack->pop<bool>();
         uint32_t stencil_fmt = stack->pop<uint32_t>();
         uint32_t depth_fmt = stack->pop<uint32_t>();
@@ -180,10 +181,22 @@
             case static_cast<uint32_t>(gfxapi::GLenum::GL_DEPTH24_STENCIL8):
                 stencilSize = 8;
         }
-        GAPID_INFO("backbufferInfo(%d, %d, 0x%x, 0x%x, 0x%x)\n",
-                   width, height, color_fmt, depth_fmt, stencil_fmt);
-        if (mBoundRenderer == nullptr) {
-            GAPID_INFO("backbufferInfo called without a bound renderer\n");
+        if (stack->isValid()) {
+            GAPID_INFO("backbufferInfo(%d, %d, 0x%x, 0x%x, 0x%x)\n",
+                    width, height, color_fmt, depth_fmt, stencil_fmt,
+                    resetViewportScissor ? "true" : "false");
+            if (mBoundRenderer == nullptr) {
+                GAPID_INFO("backbufferInfo called without a bound renderer\n");
+                return false;
+            }
+            mBoundRenderer->setBackbuffer(width, height, depthSize, stencilSize);
+            if (resetViewportScissor) {
+                gfxapi::glViewport(0, 0, width, height);
+                gfxapi::glScissor(0, 0, width, height);
+            }
+            return true;
+        } else {
+            GAPID_WARNING("Error during calling function replayCreateRenderer\n");
             return false;
         }
         mBoundRenderer->setBackbuffer(width, height, depthSize, stencilSize);
diff --git a/cc/gapir/renderer.h b/cc/gapir/renderer.h
index f0c4d87..ce17fd9 100644
--- a/cc/gapir/renderer.h
+++ b/cc/gapir/renderer.h
@@ -37,7 +37,7 @@
     // Makes the current renderer active.
     virtual void bind() = 0;
 
-    //Makes the current renderer inactive.
+    // Makes the current renderer inactive.
     virtual void unbind() = 0;
 
     // Returns the name of the renderer's created graphics context.
diff --git a/cc/gapir/windows/renderer.cpp b/cc/gapir/windows/renderer.cpp
index e7900bf..c7c23e8 100644
--- a/cc/gapir/windows/renderer.cpp
+++ b/cc/gapir/windows/renderer.cpp
@@ -156,7 +156,7 @@
 
     pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
     pfd.nVersion = 1;
-    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;
     pfd.iPixelType = PFD_TYPE_RGBA;
     pfd.cColorBits = 32;
     pfd.cDepthBits = depthSize;
@@ -235,4 +235,3 @@
 }
 
 }  // namespace gapir
-
diff --git a/gfxapi/gles/api.go b/gfxapi/gles/api.go
index be27456..f6856d4 100644
--- a/gfxapi/gles/api.go
+++ b/gfxapi/gles/api.go
@@ -16319,17 +16319,18 @@
 ////////////////////////////////////////////////////////////////////////////////
 type BackbufferInfo struct {
 	binary.Generate
-	observations         atom.Observations
-	Width                int32
-	Height               int32
-	ColorFmt             GLenum
-	DepthFmt             GLenum
-	StencilFmt           GLenum
-	ResetViewportScissor bool
+	observations          atom.Observations
+	Width                 int32
+	Height                int32
+	ColorFmt              GLenum
+	DepthFmt              GLenum
+	StencilFmt            GLenum
+	ResetViewportScissor  bool
+	PreserveBuffersOnSwap bool
 }
 
 func (a *BackbufferInfo) String() string {
-	return fmt.Sprintf("backbufferInfo(width: %v, height: %v, color_fmt: %v, depth_fmt: %v, stencil_fmt: %v, resetViewportScissor: %v)", a.Width, a.Height, a.ColorFmt, a.DepthFmt, a.StencilFmt, a.ResetViewportScissor)
+	return fmt.Sprintf("backbufferInfo(width: %v, height: %v, color_fmt: %v, depth_fmt: %v, stencil_fmt: %v, resetViewportScissor: %v, preserveBuffersOnSwap: %v)", a.Width, a.Height, a.ColorFmt, a.DepthFmt, a.StencilFmt, a.ResetViewportScissor, a.PreserveBuffersOnSwap)
 }
 
 // AddRead appends a new read observation to the atom of the range rng with
@@ -16872,6 +16873,7 @@
 	GenerateMipmapHint    GLenum
 	PixelStorage          GLenumːs32ᵐ
 	Instances             Objects
+	PreserveBuffersOnSwap bool
 }
 
 func (c *Context) Init() {
@@ -18065,8 +18067,8 @@
 func NewSwitchThread(ThreadID ThreadID) *SwitchThread {
 	return &SwitchThread{ThreadID: ThreadID}
 }
-func NewBackbufferInfo(Width int32, Height int32, Color_fmt GLenum, Depth_fmt GLenum, Stencil_fmt GLenum, ResetViewportScissor bool) *BackbufferInfo {
-	return &BackbufferInfo{Width: Width, Height: Height, ColorFmt: Color_fmt, DepthFmt: Depth_fmt, StencilFmt: Stencil_fmt, ResetViewportScissor: ResetViewportScissor}
+func NewBackbufferInfo(Width int32, Height int32, Color_fmt GLenum, Depth_fmt GLenum, Stencil_fmt GLenum, ResetViewportScissor bool, PreserveBuffersOnSwap bool) *BackbufferInfo {
+	return &BackbufferInfo{Width: Width, Height: Height, ColorFmt: Color_fmt, DepthFmt: Depth_fmt, StencilFmt: Stencil_fmt, ResetViewportScissor: ResetViewportScissor, PreserveBuffersOnSwap: PreserveBuffersOnSwap}
 }
 func NewStartTimer(Index uint8) *StartTimer {
 	return &StartTimer{Index: Index}
diff --git a/gfxapi/gles/custom_replay.go b/gfxapi/gles/custom_replay.go
index 69aa694..43d7977 100644
--- a/gfxapi/gles/custom_replay.go
+++ b/gfxapi/gles/custom_replay.go
@@ -120,7 +120,7 @@
 }
 
 func (i TexturePointer) value(b *builder.Builder, a atom.Atom, s *gfxapi.State) value.Value {
-	if i.Pointer.Address == 0 {
+	if i.Pointer.Address == 0 || getContext(s).BoundBuffers[GLenum_GL_PIXEL_UNPACK_BUFFER] != 0 {
 		return value.AbsolutePointer(i.Pointer.Address)
 	} else {
 		return value.RemappedPointer(i.Pointer.Address)
diff --git a/gfxapi/gles/gles.api b/gfxapi/gles/gles.api
index 8469c8e..b5def80 100644
--- a/gfxapi/gles/gles.api
+++ b/gfxapi/gles/gles.api
@@ -390,6 +390,7 @@
   GLenum                                            GenerateMipmapHint    = GL_DONT_CARE
   map!(GLenum, s32)                                 PixelStorage
   Objects                                           Instances
+  bool                                              PreserveBuffersOnSwap
 }
 
 type u64 ThreadID
diff --git a/gfxapi/gles/gles_binary.go b/gfxapi/gles/gles_binary.go
index ea0243f..9870355 100644
--- a/gfxapi/gles/gles_binary.go
+++ b/gfxapi/gles/gles_binary.go
@@ -388,7 +388,7 @@
 
 var (
 	binaryIDArchitecture                           = binary.ID{0x14, 0x53, 0xe8, 0x31, 0x7d, 0x20, 0x5f, 0xbd, 0xad, 0x68, 0x48, 0x67, 0xeb, 0xf6, 0x1c, 0x4f, 0xe8, 0xfc, 0x0e, 0x01}
-	binaryIDBackbufferInfo                         = binary.ID{0x18, 0x3c, 0x07, 0xe1, 0x0c, 0x71, 0x64, 0xee, 0xaa, 0x70, 0x7a, 0xb3, 0xca, 0xbe, 0x36, 0x00, 0xe8, 0xeb, 0x8c, 0xed}
+	binaryIDBackbufferInfo                         = binary.ID{0x3b, 0x71, 0x56, 0x1e, 0xbc, 0xdf, 0xd7, 0x44, 0xcf, 0x32, 0xc5, 0x65, 0xe5, 0x66, 0xf0, 0x8f, 0x1b, 0x28, 0x23, 0xa7}
 	binaryIDColor                                  = binary.ID{0xe7, 0x31, 0x0f, 0x05, 0x26, 0x27, 0x37, 0x3a, 0xc4, 0xbb, 0x59, 0xea, 0xc0, 0x41, 0xb0, 0xa7, 0x8f, 0x15, 0x58, 0xb4}
 	binaryIDBlendState                             = binary.ID{0x54, 0x6b, 0x98, 0xbd, 0x32, 0x48, 0x57, 0xe0, 0x34, 0x7a, 0xfa, 0x8d, 0x92, 0x95, 0x4b, 0x07, 0xdd, 0x96, 0x3a, 0x2a}
 	binaryIDSliceInfo                              = binary.ID{0x8e, 0xab, 0xab, 0x1b, 0x89, 0x6a, 0x43, 0x9a, 0x3c, 0xa7, 0xb8, 0x43, 0x28, 0x26, 0x72, 0x30, 0x78, 0x26, 0x38, 0xf9}
@@ -441,7 +441,7 @@
 	binaryIDVertexArray                            = binary.ID{0x8c, 0x9a, 0x34, 0xfe, 0x61, 0x2a, 0x2d, 0x57, 0x19, 0x43, 0x24, 0x95, 0xf6, 0x1e, 0x79, 0x97, 0x85, 0x3f, 0xee, 0xc4}
 	binaryIDQuery                                  = binary.ID{0x9e, 0x4e, 0xd0, 0x26, 0x26, 0xf8, 0x9d, 0x8e, 0xb5, 0x02, 0x2f, 0xde, 0x80, 0xb3, 0xe9, 0x09, 0xf5, 0x4c, 0x1e, 0xf2}
 	binaryIDObjects                                = binary.ID{0x8a, 0x05, 0xaa, 0xf5, 0xa8, 0x57, 0xb0, 0xf1, 0x13, 0x48, 0x39, 0x76, 0xec, 0x1e, 0x47, 0x0b, 0x6c, 0x3b, 0xf6, 0xe0}
-	binaryIDContext                                = binary.ID{0x5d, 0x15, 0x38, 0x06, 0x50, 0x11, 0xdd, 0x3b, 0xf6, 0x52, 0x52, 0xca, 0xc5, 0x29, 0x63, 0xdc, 0xe5, 0x1d, 0x52, 0x4a}
+	binaryIDContext                                = binary.ID{0x2d, 0x31, 0xd7, 0x8d, 0xe9, 0x76, 0xe7, 0x8c, 0xd4, 0xda, 0x77, 0xca, 0x1b, 0x5f, 0x8a, 0xc3, 0xa9, 0x35, 0xb2, 0x2c}
 	binaryIDEGLConfig                              = binary.ID{0xc1, 0xea, 0x31, 0x3f, 0xd1, 0xf0, 0x52, 0x99, 0x82, 0x15, 0x2a, 0x15, 0xc0, 0x95, 0x93, 0x16, 0x2d, 0xd0, 0xaa, 0x58}
 	binaryIDEGLContext                             = binary.ID{0x7e, 0xd7, 0x09, 0xd5, 0xdb, 0xde, 0xd4, 0xf4, 0xc2, 0x44, 0xa3, 0x47, 0xb0, 0x05, 0x91, 0x42, 0x91, 0x5f, 0x12, 0x55}
 	binaryIDEGLDisplay                             = binary.ID{0xdd, 0x44, 0x8d, 0x9b, 0x11, 0x43, 0x6e, 0xec, 0x7b, 0xc7, 0x17, 0x93, 0x81, 0x62, 0x0b, 0xaa, 0x5f, 0xe0, 0xdd, 0x10}
@@ -876,6 +876,9 @@
 	if err := e.Bool(o.ResetViewportScissor); err != nil {
 		return err
 	}
+	if err := e.Bool(o.PreserveBuffersOnSwap); err != nil {
+		return err
+	}
 	return nil
 }
 func doDecodeBackbufferInfo(d binary.Decoder, o *BackbufferInfo) error {
@@ -912,6 +915,11 @@
 	} else {
 		o.ResetViewportScissor = bool(obj)
 	}
+	if obj, err := d.Bool(); err != nil {
+		return err
+	} else {
+		o.PreserveBuffersOnSwap = bool(obj)
+	}
 	return nil
 }
 func doSkipBackbufferInfo(d binary.Decoder) error {
@@ -936,6 +944,9 @@
 	if _, err := d.Bool(); err != nil {
 		return err
 	}
+	if _, err := d.Bool(); err != nil {
+		return err
+	}
 	return nil
 }
 func (*binaryClassBackbufferInfo) ID() binary.ID      { return binaryIDBackbufferInfo }
@@ -965,6 +976,7 @@
 		{Declared: "DepthFmt", Type: &schema.Primitive{Name: "GLenum", Method: schema.Uint32}},
 		{Declared: "StencilFmt", Type: &schema.Primitive{Name: "GLenum", Method: schema.Uint32}},
 		{Declared: "ResetViewportScissor", Type: &schema.Primitive{Name: "bool", Method: schema.Bool}},
+		{Declared: "PreserveBuffersOnSwap", Type: &schema.Primitive{Name: "bool", Method: schema.Bool}},
 	},
 }
 
@@ -5620,6 +5632,9 @@
 	if err := e.Value(&o.Instances); err != nil {
 		return err
 	}
+	if err := e.Bool(o.PreserveBuffersOnSwap); err != nil {
+		return err
+	}
 	return nil
 }
 func doDecodeContext(d binary.Decoder, o *Context) error {
@@ -5830,6 +5845,11 @@
 	if err := d.Value(&o.Instances); err != nil {
 		return err
 	}
+	if obj, err := d.Bool(); err != nil {
+		return err
+	} else {
+		o.PreserveBuffersOnSwap = bool(obj)
+	}
 	return nil
 }
 func doSkipContext(d binary.Decoder) error {
@@ -5956,6 +5976,9 @@
 	if err := d.SkipValue((*Objects)(nil)); err != nil {
 		return err
 	}
+	if _, err := d.Bool(); err != nil {
+		return err
+	}
 	return nil
 }
 func (*binaryClassContext) ID() binary.ID      { return binaryIDContext }
@@ -5995,6 +6018,7 @@
 		{Declared: "GenerateMipmapHint", Type: &schema.Primitive{Name: "GLenum", Method: schema.Uint32}},
 		{Declared: "PixelStorage", Type: &schema.Map{Alias: "GLenumːs32ᵐ", KeyType: &schema.Primitive{Name: "GLenum", Method: schema.Uint32}, ValueType: &schema.Primitive{Name: "int32", Method: schema.Int32}}},
 		{Declared: "Instances", Type: &schema.Struct{Name: "Objects", ID: (*Objects)(nil).Class().ID()}},
+		{Declared: "PreserveBuffersOnSwap", Type: &schema.Primitive{Name: "bool", Method: schema.Bool}},
 	},
 }
 
diff --git a/gfxapi/gles/replay.go b/gfxapi/gles/replay.go
index b93d934..fe2526e 100644
--- a/gfxapi/gles/replay.go
+++ b/gfxapi/gles/replay.go
@@ -17,6 +17,7 @@
 import (
 	"android.googlesource.com/platform/tools/gpu/atom"
 	"android.googlesource.com/platform/tools/gpu/atom/transform"
+	"android.googlesource.com/platform/tools/gpu/config"
 	"android.googlesource.com/platform/tools/gpu/database"
 	"android.googlesource.com/platform/tools/gpu/gfxapi"
 	"android.googlesource.com/platform/tools/gpu/log"
@@ -65,13 +66,15 @@
 	flags service.TimingFlags
 }
 
-func (a api) ReplayTransforms(
+func (a api) Replay(
 	ctx replay.Context,
-	config replay.Config,
+	cfg replay.Config,
 	requests []replay.Request,
 	device *service.Device,
-	db database.Database,
-	logger log.Logger) atom.Transforms {
+	atoms atom.List,
+	out atom.Writer,
+	d database.Database,
+	l log.Logger) error {
 
 	transforms := atom.Transforms{}
 
@@ -100,37 +103,62 @@
 
 		case timeCallsRequest:
 			profiling = true
-			transforms.Add(timingInfo(req.flags, req.out, device, db, logger))
+			transforms.Add(timingInfo(req.flags, req.out, device, d, l))
 		}
 	}
 
 	if !profiling {
 		// Not profiling. Add optimisation transforms.
-		transforms.Add(earlyTerminator, skipDrawCalls)
+		transforms.Add(earlyTerminator)
+
+		// Check to see if any contexts use the 'preserveBuffersOnSwap' flag.
+		// If it is used, we can't skip draw calls or display the
+		// undefined-framebuffer pattern.
+		preserveBuffersOnSwap := false
+		for _, a := range atoms.Atoms {
+			if b, ok := a.(*BackbufferInfo); ok && b.PreserveBuffersOnSwap {
+				preserveBuffersOnSwap = true
+				break
+			}
+		}
+
+		if !preserveBuffersOnSwap {
+			transforms.Add(skipDrawCalls, undefinedFramebuffer(d, l))
+		}
 	}
 
 	transforms.Add(
 		injector,
-		remapAttributes())
+		remapAttributes(),
+	)
 
 	// Device-dependent transforms.
 	transforms.Add(
-		decompressTextures(device, &path.Capture{ID: ctx.Capture}, db, logger),
-		precisionStrip(device, db, logger),
+		decompressTextures(device, &path.Capture{ID: ctx.Capture}, d, l),
+		precisionStrip(device, d, l),
 		halfFloatOESToHalfFloatARB(device))
 
-	if c, ok := config.(drawConfig); ok && c.wireframe {
-		transforms.Add(wireframe(db, logger))
+	if c, ok := cfg.(drawConfig); ok && c.wireframe {
+		transforms.Add(wireframe(d, l))
 	}
 
 	// Cleanup
 	transforms.Add(&destroyResourcesAtEOS{
 		state:  gfxapi.NewState(),
-		db:     db,
-		logger: logger,
+		db:     d,
+		logger: l,
 	})
 
-	return transforms
+	if config.DebugReplay {
+		log.Infof(l, "Replaying %d atoms using transform chain:", len(atoms.Atoms))
+		for i, t := range transforms {
+			log.Infof(l, "(%d) %#v", i, t)
+		}
+	}
+
+	transforms.Transform(atoms, out)
+
+	return nil
 }
 
 func (a api) QueryColorBuffer(ctx *replay.Context, mgr *replay.Manager, after atom.ID, width, height uint32, wireframe bool) <-chan replay.Image {
diff --git a/gfxapi/gles/replay_writer.go b/gfxapi/gles/replay_writer.go
index b15db51..adc6ac5 100644
--- a/gfxapi/gles/replay_writer.go
+++ b/gfxapi/gles/replay_writer.go
@@ -250,7 +250,7 @@
 var funcInfoReplayCreateRenderer = builder.FunctionInfo{ID: 216, ReturnType: protocol.TypeVoid, Parameters: 1}
 var funcInfoReplayBindRenderer = builder.FunctionInfo{ID: 217, ReturnType: protocol.TypeVoid, Parameters: 1}
 var funcInfoSwitchThread = builder.FunctionInfo{ID: 218, ReturnType: protocol.TypeVoid, Parameters: 1}
-var funcInfoBackbufferInfo = builder.FunctionInfo{ID: 219, ReturnType: protocol.TypeVoid, Parameters: 6}
+var funcInfoBackbufferInfo = builder.FunctionInfo{ID: 219, ReturnType: protocol.TypeVoid, Parameters: 7}
 var funcInfoStartTimer = builder.FunctionInfo{ID: 220, ReturnType: protocol.TypeVoid, Parameters: 1}
 var funcInfoStopTimer = builder.FunctionInfo{ID: 221, ReturnType: protocol.TypeUint64, Parameters: 1}
 var funcInfoFlushPostBuffer = builder.FunctionInfo{ID: 222, ReturnType: protocol.TypeVoid, Parameters: 0}
@@ -705,7 +705,7 @@
 	ctx := GetContext_15_result                                  // Contextʳ
 	a := ϟa.Arrays.Slice(uint64(int32(0)), uint64(ϟa.Count), ϟs) // VertexArrayIdˢ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.VertexArrays[a.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb)] = (*VertexArray)(nil)
+		delete(ctx.Instances.VertexArrays, a.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb))
 	}
 	ϟb.Push(value.S32(ϟa.Count))
 	ϟb.Push(ϟa.Arrays.value())
@@ -2300,7 +2300,7 @@
 	GetContext_62_result := context                                // Contextʳ
 	ctx := GetContext_62_result                                    // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Textures[t.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb)] = (*Texture)(nil)
+		delete(ctx.Instances.Textures, t.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb))
 	}
 	ϟb.Push(value.S32(ϟa.Count))
 	ϟb.Push(ϟa.Textures.value())
@@ -2733,7 +2733,7 @@
 	GetContext_70_result := context                                    // Contextʳ
 	ctx := GetContext_70_result                                        // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Framebuffers[f.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb)] = (*Framebuffer)(nil)
+		delete(ctx.Instances.Framebuffers, f.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb))
 	}
 	ϟb.Push(value.S32(ϟa.Count))
 	ϟb.Push(ϟa.Framebuffers.value())
@@ -2850,7 +2850,7 @@
 	GetContext_75_result := context                                     // Contextʳ
 	ctx := GetContext_75_result                                         // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Renderbuffers[r.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb)] = (*Renderbuffer)(nil)
+		delete(ctx.Instances.Renderbuffers, r.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb))
 	}
 	ϟb.Push(value.S32(ϟa.Count))
 	ϟb.Push(ϟa.Renderbuffers.value())
@@ -3026,7 +3026,7 @@
 	GetContext_81_result := context                               // Contextʳ
 	ctx := GetContext_81_result                                   // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Buffers[b.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb)] = (*Buffer)(nil)
+		delete(ctx.Instances.Buffers, b.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb))
 	}
 	ϟb.Push(value.S32(ϟa.Count))
 	ϟb.Push(ϟa.Buffers.value())
@@ -3128,7 +3128,7 @@
 	ctx := GetContext_85_result                  // Contextʳ
 	s := ctx.Instances.Shaders.Get(ϟa.Shader)    // Shaderʳ
 	s.Deletable = true
-	ctx.Instances.Shaders[ϟa.Shader] = (*Shader)(nil)
+	delete(ctx.Instances.Shaders, ϟa.Shader)
 	if key, remap := ϟa.Shader.remap(ϟa, ϟs); remap {
 		loadRemap(ϟb, key, protocol.TypeUint32, ϟa.Shader.value(ϟb, ϟa, ϟs))
 	} else {
@@ -3363,7 +3363,7 @@
 	context := ϟc.Contexts.Get(ϟc.CurrentThread) // Contextʳ
 	GetContext_93_result := context              // Contextʳ
 	ctx := GetContext_93_result                  // Contextʳ
-	ctx.Instances.Programs[ϟa.Program] = (*Program)(nil)
+	delete(ctx.Instances.Programs, ϟa.Program)
 	if key, remap := ϟa.Program.remap(ϟa, ϟs); remap {
 		loadRemap(ϟb, key, protocol.TypeUint32, ϟa.Program.value(ϟb, ϟa, ϟs))
 	} else {
@@ -3412,7 +3412,7 @@
 	ctx := GetContext_95_result                  // Contextʳ
 	p := ctx.Instances.Programs.Get(ϟa.Program)  // Programʳ
 	s := ctx.Instances.Shaders.Get(ϟa.Shader)    // Shaderʳ
-	p.Shaders[s.Type] = ShaderId(uint32(0))
+	delete(p.Shaders, s.Type)
 	if key, remap := ϟa.Program.remap(ϟa, ϟs); remap {
 		loadRemap(ϟb, key, protocol.TypeUint32, ϟa.Program.value(ϟb, ϟa, ϟs))
 	} else {
@@ -4708,7 +4708,7 @@
 	GetContext_131_result := context                              // Contextʳ
 	ctx := GetContext_131_result                                  // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Queries[q.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb)] = (*Query)(nil)
+		delete(ctx.Instances.Queries, q.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb))
 	}
 	ϟb.Push(value.S32(ϟa.Count))
 	ϟb.Push(ϟa.Queries.value())
@@ -4930,7 +4930,7 @@
 	ctx := GetContext_135_result                                  // Contextʳ
 	a := ϟa.Arrays.Slice(uint64(uint32(0)), uint64(ϟa.Count), ϟs) // VertexArrayIdˢ
 	for i := uint32(uint32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.VertexArrays[a.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb)] = (*VertexArray)(nil)
+		delete(ctx.Instances.VertexArrays, a.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb))
 	}
 	ϟb.Push(value.U32(ϟa.Count))
 	ϟb.Push(ϟa.Arrays.value())
@@ -5040,7 +5040,7 @@
 	GetContext_137_result := context                              // Contextʳ
 	ctx := GetContext_137_result                                  // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Queries[q.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb)] = (*Query)(nil)
+		delete(ctx.Instances.Queries, q.Index(uint64(i), ϟs).replayRead(ϟa, ϟs, ϟd, ϟl, ϟb))
 	}
 	ϟb.Push(value.S32(ϟa.Count))
 	ϟb.Push(ϟa.Queries.value())
@@ -5198,9 +5198,10 @@
 	ϟc := getState(ϟs)
 	_ = ϟc
 	ϟa.observations.ApplyReads(ϟs.Memory[memory.ApplicationPool])
-	context := ϟc.Contexts.Get(ϟc.CurrentThread)                                                  // Contextʳ
-	GetContext_139_result := context                                                              // Contextʳ
-	ctx := GetContext_139_result                                                                  // Contextʳ
+	context := ϟc.Contexts.Get(ϟc.CurrentThread) // Contextʳ
+	GetContext_139_result := context             // Contextʳ
+	ctx := GetContext_139_result                 // Contextʳ
+	ctx.PreserveBuffersOnSwap = ϟa.PreserveBuffersOnSwap
 	backbuffer := ctx.Instances.Framebuffers.Get(FramebufferId(uint32(0)))                        // Framebufferʳ
 	color_id := RenderbufferId(backbuffer.Attachments.Get(GLenum_GL_COLOR_ATTACHMENT0).Object)    // RenderbufferId
 	color_buffer := ctx.Instances.Renderbuffers.Get(color_id)                                     // Renderbufferʳ
@@ -5229,6 +5230,7 @@
 	ϟb.Push(value.U32(ϟa.DepthFmt))
 	ϟb.Push(value.U32(ϟa.StencilFmt))
 	ϟb.Push(value.Bool(ϟa.ResetViewportScissor))
+	ϟb.Push(value.Bool(ϟa.PreserveBuffersOnSwap))
 	ϟb.Call(funcInfoBackbufferInfo)
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _, _, _, _, _, _, _ = context, GetContext_139_result, ctx, backbuffer, color_id, color_buffer, depth_id, depth_buffer, stencil_id, stencil_buffer
diff --git a/gfxapi/gles/state_mutator.go b/gfxapi/gles/state_mutator.go
index 1b45587..8036812 100644
--- a/gfxapi/gles/state_mutator.go
+++ b/gfxapi/gles/state_mutator.go
@@ -930,7 +930,7 @@
 	ctx := GetContext_15_result                                  // Contextʳ
 	a := ϟa.Arrays.Slice(uint64(int32(0)), uint64(ϟa.Count), ϟs) // VertexArrayIdˢ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.VertexArrays[a.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl)] = (*VertexArray)(nil)
+		delete(ctx.Instances.VertexArrays, a.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl))
 	}
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _ = context, GetContext_15_result, ctx, a
@@ -1994,7 +1994,7 @@
 	GetContext_62_result := context                                // Contextʳ
 	ctx := GetContext_62_result                                    // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Textures[t.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl)] = (*Texture)(nil)
+		delete(ctx.Instances.Textures, t.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl))
 	}
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _ = t, context, GetContext_62_result, ctx
@@ -2306,7 +2306,7 @@
 	GetContext_70_result := context                                    // Contextʳ
 	ctx := GetContext_70_result                                        // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Framebuffers[f.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl)] = (*Framebuffer)(nil)
+		delete(ctx.Instances.Framebuffers, f.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl))
 	}
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _ = f, context, GetContext_70_result, ctx
@@ -2390,7 +2390,7 @@
 	GetContext_75_result := context                                     // Contextʳ
 	ctx := GetContext_75_result                                         // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Renderbuffers[r.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl)] = (*Renderbuffer)(nil)
+		delete(ctx.Instances.Renderbuffers, r.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl))
 	}
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _ = r, context, GetContext_75_result, ctx
@@ -2520,7 +2520,7 @@
 	GetContext_81_result := context                               // Contextʳ
 	ctx := GetContext_81_result                                   // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Buffers[b.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl)] = (*Buffer)(nil)
+		delete(ctx.Instances.Buffers, b.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl))
 	}
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _ = b, context, GetContext_81_result, ctx
@@ -2592,7 +2592,7 @@
 	ctx := GetContext_85_result                  // Contextʳ
 	s := ctx.Instances.Shaders.Get(ϟa.Shader)    // Shaderʳ
 	s.Deletable = true
-	ctx.Instances.Shaders[ϟa.Shader] = (*Shader)(nil)
+	delete(ctx.Instances.Shaders, ϟa.Shader)
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _ = context, GetContext_85_result, ctx, s
 	return nil
@@ -2745,7 +2745,7 @@
 	context := ϟc.Contexts.Get(ϟc.CurrentThread) // Contextʳ
 	GetContext_93_result := context              // Contextʳ
 	ctx := GetContext_93_result                  // Contextʳ
-	ctx.Instances.Programs[ϟa.Program] = (*Program)(nil)
+	delete(ctx.Instances.Programs, ϟa.Program)
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _ = context, GetContext_93_result, ctx
 	return nil
@@ -2773,7 +2773,7 @@
 	ctx := GetContext_95_result                  // Contextʳ
 	p := ctx.Instances.Programs.Get(ϟa.Program)  // Programʳ
 	s := ctx.Instances.Shaders.Get(ϟa.Shader)    // Shaderʳ
-	p.Shaders[s.Type] = ShaderId(uint32(0))
+	delete(p.Shaders, s.Type)
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _, _ = context, GetContext_95_result, ctx, p, s
 	return nil
@@ -3784,7 +3784,7 @@
 	GetContext_131_result := context                              // Contextʳ
 	ctx := GetContext_131_result                                  // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Queries[q.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl)] = (*Query)(nil)
+		delete(ctx.Instances.Queries, q.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl))
 	}
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _ = q, context, GetContext_131_result, ctx
@@ -3910,7 +3910,7 @@
 	ctx := GetContext_135_result                                  // Contextʳ
 	a := ϟa.Arrays.Slice(uint64(uint32(0)), uint64(ϟa.Count), ϟs) // VertexArrayIdˢ
 	for i := uint32(uint32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.VertexArrays[a.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl)] = (*VertexArray)(nil)
+		delete(ctx.Instances.VertexArrays, a.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl))
 	}
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _ = context, GetContext_135_result, ctx, a
@@ -3977,7 +3977,7 @@
 	GetContext_137_result := context                              // Contextʳ
 	ctx := GetContext_137_result                                  // Contextʳ
 	for i := int32(int32(0)); i < ϟa.Count; i++ {
-		ctx.Instances.Queries[q.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl)] = (*Query)(nil)
+		delete(ctx.Instances.Queries, q.Index(uint64(i), ϟs).Read(ϟs, ϟd, ϟl))
 	}
 	ϟa.observations.ApplyWrites(ϟs.Memory[memory.ApplicationPool])
 	_, _, _, _ = q, context, GetContext_137_result, ctx
@@ -4068,9 +4068,10 @@
 	ϟc := getState(ϟs)
 	_ = ϟc
 	ϟa.observations.ApplyReads(ϟs.Memory[memory.ApplicationPool])
-	context := ϟc.Contexts.Get(ϟc.CurrentThread)                                                  // Contextʳ
-	GetContext_139_result := context                                                              // Contextʳ
-	ctx := GetContext_139_result                                                                  // Contextʳ
+	context := ϟc.Contexts.Get(ϟc.CurrentThread) // Contextʳ
+	GetContext_139_result := context             // Contextʳ
+	ctx := GetContext_139_result                 // Contextʳ
+	ctx.PreserveBuffersOnSwap = ϟa.PreserveBuffersOnSwap
 	backbuffer := ctx.Instances.Framebuffers.Get(FramebufferId(uint32(0)))                        // Framebufferʳ
 	color_id := RenderbufferId(backbuffer.Attachments.Get(GLenum_GL_COLOR_ATTACHMENT0).Object)    // RenderbufferId
 	color_buffer := ctx.Instances.Renderbuffers.Get(color_id)                                     // Renderbufferʳ
diff --git a/gfxapi/gles/synthetic.api b/gfxapi/gles/synthetic.api
index f989d12..20affda 100644
--- a/gfxapi/gles/synthetic.api
+++ b/gfxapi/gles/synthetic.api
@@ -40,8 +40,10 @@
                         GLenum color_fmt,
                         GLenum depth_fmt,
                         GLenum stencil_fmt,
-                        bool   resetViewportScissor) {
+                        bool   resetViewportScissor,
+                        bool   preserveBuffersOnSwap) {
   ctx := GetContext()
+  ctx.PreserveBuffersOnSwap = preserveBuffersOnSwap
   backbuffer := ctx.Instances.Framebuffers[0]
 
   color_id := as!RenderbufferId(backbuffer.Attachments[GL_COLOR_ATTACHMENT0].Object)
diff --git a/gfxapi/gles/unknown_framebuffer.go b/gfxapi/gles/unknown_framebuffer.go
new file mode 100644
index 0000000..4d44c16
--- /dev/null
+++ b/gfxapi/gles/unknown_framebuffer.go
@@ -0,0 +1,137 @@
+// Copyright (C) 2015 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.
+
+package gles
+
+import (
+	"android.googlesource.com/platform/tools/gpu/atom"
+	"android.googlesource.com/platform/tools/gpu/database"
+	"android.googlesource.com/platform/tools/gpu/gfxapi"
+	"android.googlesource.com/platform/tools/gpu/log"
+	"android.googlesource.com/platform/tools/gpu/memory"
+)
+
+// undefinedFramebuffer adds a transform that will render a pattern into the
+// color buffer at the end of each frame.
+func undefinedFramebuffer(d database.Database, l log.Logger) atom.Transformer {
+	s := gfxapi.NewState()
+	return atom.Transform("DirtyFramebuffer", func(i atom.ID, a atom.Atom, out atom.Writer) {
+		a.Mutate(s, d, l)
+		out.Write(i, a)
+		if a.Flags().IsEndOfFrame() {
+			drawUndefinedFramebuffer(a, s, d, l, out)
+		}
+	})
+}
+
+func drawUndefinedFramebuffer(a atom.Atom, s *gfxapi.State, d database.Database, l log.Logger, out atom.Writer) error {
+	const (
+		aScreenCoordsLocation AttributeLocation = 0
+
+		vertexShaderSource string = `
+					precision highp float;
+					attribute vec2 aScreenCoords;
+					varying vec2 uv;
+
+					void main() {
+						uv = aScreenCoords;
+						gl_Position = vec4(aScreenCoords.xy, 0., 1.);
+					}`
+		fragmentShaderSource string = `
+					precision highp float;
+					varying vec2 uv;
+
+					float F(float a) { return smoothstep(0.0, 0.1, a) * smoothstep(0.4, 0.3, a); }
+
+					void main() {
+						vec2 v = uv * 5.0;
+						gl_FragColor = vec4(0.8, 0.9, 0.6, 1.0) * F(fract(v.x + v.y));
+					}`
+	)
+
+	c := getContext(s)
+
+	var (
+		origProgramID            = c.BoundProgram
+		origArrayBufferID        = c.BoundBuffers[GLenum_GL_ARRAY_BUFFER]
+		origElementArrayBufferID = c.BoundBuffers[GLenum_GL_ELEMENT_ARRAY_BUFFER]
+		origVertexAttrib         = *(c.VertexAttributeArrays[aScreenCoordsLocation])
+	)
+
+	// Generate new unused object IDs.
+	programID := ProgramId(newUnusedID(func(x uint32) bool { _, ok := c.Instances.Programs[ProgramId(x)]; return ok }))
+	vertexShaderID := ShaderId(newUnusedID(func(x uint32) bool { _, ok := c.Instances.Shaders[ShaderId(x)]; return ok }))
+	fragmentShaderID := ShaderId(newUnusedID(func(x uint32) bool {
+		_, ok := c.Instances.Shaders[ShaderId(x)]
+		return ok || ShaderId(x) == vertexShaderID
+	}))
+
+	// 2D vertices positions for a full screen 2D triangle strip.
+	positions := []float32{-1., -1., 1., -1., -1., 1., 1., 1.}
+
+	// Temporarily change rasterizing/blending state and enable VAP 0.
+	undoList := []atom.Atom{}
+	for _, cap := range []GLenum{
+		GLenum_GL_BLEND,
+		GLenum_GL_CULL_FACE,
+		GLenum_GL_DEPTH_TEST,
+		GLenum_GL_SCISSOR_TEST,
+		GLenum_GL_STENCIL_TEST,
+	} {
+		capability := cap
+		if c.Capabilities[capability] {
+			out.Write(atom.NoID, NewGlDisable(capability))
+			undoList = append(undoList, NewGlEnable(capability))
+		}
+	}
+	if !c.VertexAttributeArrays[aScreenCoordsLocation].Enabled {
+		out.Write(atom.NoID, NewGlEnableVertexAttribArray(aScreenCoordsLocation))
+		undoList = append(undoList, NewGlDisableVertexAttribArray(aScreenCoordsLocation))
+	}
+
+	// Create the shader program
+	for _, a := range NewProgram(s.Architecture, d, l, vertexShaderID, fragmentShaderID, programID, vertexShaderSource, fragmentShaderSource) {
+		out.Write(atom.NoID, a)
+	}
+
+	out.Write(atom.NoID, NewGlBindAttribLocation(programID, aScreenCoordsLocation, "aScreenCoords"))
+	out.Write(atom.NoID, NewGlLinkProgram(programID))
+	out.Write(atom.NoID, NewGlUseProgram(programID))
+	out.Write(atom.NoID, NewGlBindBuffer(GLenum_GL_ARRAY_BUFFER, 0))
+	out.Write(atom.NoID, NewGlBindBuffer(GLenum_GL_ELEMENT_ARRAY_BUFFER, 0))
+	out.Write(atom.NoID, NewGlVertexAttribPointer(aScreenCoordsLocation, 2, GLenum_GL_FLOAT, false, 0, memory.Tmp))
+	out.Write(atom.NoID, NewGlDrawArrays(GLenum_GL_TRIANGLE_STRIP, 0, 4).
+		AddRead(atom.Data(s.Architecture, d, l, memory.Tmp, positions)))
+
+	// Restore conditionally changed state.
+	for _, a := range undoList {
+		out.Write(atom.NoID, a)
+	}
+
+	// Restore buffer/vertexAttrib state.
+	if origVertexAttrib.Buffer != 0 || origVertexAttrib.Pointer.Address != 0 {
+		out.Write(atom.NoID, NewGlBindBuffer(GLenum_GL_ARRAY_BUFFER, origVertexAttrib.Buffer))
+		out.Write(atom.NoID, NewGlVertexAttribPointer(aScreenCoordsLocation, int32(origVertexAttrib.Size), origVertexAttrib.Type, origVertexAttrib.Normalized, origVertexAttrib.Stride, origVertexAttrib.Pointer.Pointer))
+	}
+	out.Write(atom.NoID, NewGlBindBuffer(GLenum_GL_ELEMENT_ARRAY_BUFFER, origElementArrayBufferID))
+	out.Write(atom.NoID, NewGlBindBuffer(GLenum_GL_ARRAY_BUFFER, origArrayBufferID))
+
+	// Restore program state.
+	out.Write(atom.NoID, NewGlUseProgram(origProgramID))
+	out.Write(atom.NoID, NewGlDeleteProgram(programID))
+	out.Write(atom.NoID, NewGlDeleteShader(vertexShaderID))
+	out.Write(atom.NoID, NewGlDeleteShader(fragmentShaderID))
+
+	return nil
+}
diff --git a/gfxapi/templates/go_common.tmpl b/gfxapi/templates/go_common.tmpl
index e2600d8..f8c49f2 100644
--- a/gfxapi/templates/go_common.tmpl
+++ b/gfxapi/templates/go_common.tmpl
@@ -457,8 +457,15 @@
 */}}
 {{define "Go.MapAssign"}}
   {{AssertType $ "MapAssign"}}
-  {{if eq $.Operator "="}}{{Template "Go.Read" $.To.Map}}[{{Template "Go.Read" $.To.Index}}] = {{Template "Go.Read" $.Value}}
-  {{else}}{{Error "Unsupported MapAssign operator %s" $.Operator}}{{end}}
+  {{if eq $.Operator "="}}
+    {{if IsNull $.Value}}
+      delete({{Template "Go.Read" $.To.Map}}, {{Template "Go.Read" $.To.Index}})
+    {{else}}
+      {{Template "Go.Read" $.To.Map}}[{{Template "Go.Read" $.To.Index}}] = {{Template "Go.Read" $.Value}}
+    {{end}}
+  {{else}}
+    {{Error "Unsupported MapAssign operator %s" $.Operator}}
+  {{end}}
 {{end}}
 
 
diff --git a/integration/replay/gles/gles_test.go b/integration/replay/gles/gles_test.go
index 2b3ff0a..d18f26f 100644
--- a/integration/replay/gles/gles_test.go
+++ b/integration/replay/gles/gles_test.go
@@ -93,14 +93,15 @@
 	}
 }
 
-func setBackbuffer(width, height int) atom.Atom {
+func setBackbuffer(width, height int, preserveBuffersOnSwap bool) atom.Atom {
 	color := gles.GLenum_GL_RGB565
 	depth := gles.GLenum_GL_DEPTH_COMPONENT16
 	stencil := gles.GLenum_GL_STENCIL_INDEX8
-	return gles.NewBackbufferInfo(int32(width), int32(height), color, depth, stencil, true /* resetViewportScissor */)
+	return gles.NewBackbufferInfo(int32(width), int32(height), color, depth, stencil,
+		true /* resetViewportScissor */, preserveBuffersOnSwap)
 }
 
-func initContext(a device.Architecture, d database.Database, l log.Logger, width, height int) *atom.List {
+func initContext(a device.Architecture, d database.Database, l log.Logger, width, height int, preserveBuffersOnSwap bool) *atom.List {
 	eglDisplay := p(0x1000)
 	eglConfig := p(0x2000)
 	eglShareContext := memory.Nullptr
@@ -113,7 +114,7 @@
 		gles.NewEglCreateContext(eglDisplay, eglConfig, eglShareContext, p(0x1000000), eglContext).
 			AddRead(atom.Data(a, d, l, p(0x1000000), eglAttribList)),
 		gles.NewEglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext, eglTrue),
-		setBackbuffer(width, height),
+		setBackbuffer(width, height, preserveBuffersOnSwap),
 	)
 	return atoms
 }
@@ -122,7 +123,7 @@
 	d, l := database.NewInMemory(nil), log.Testing(t)
 	mgr := replay.New(d, l)
 	device := utils.FindLocalDevice(t, mgr)
-	atoms := initContext(device.Info().Architecture(), d, l, 64, 64)
+	atoms := initContext(device.Info().Architecture(), d, l, 64, 64, false)
 	red := atoms.Add(
 		gles.NewGlClearColor(1.0, 0.0, 0.0, 1.0),
 		gles.NewGlClear(gles.GLbitfield_GL_COLOR_BUFFER_BIT),
@@ -157,7 +158,7 @@
 	device := utils.FindLocalDevice(t, mgr)
 	a := device.Info().Architecture()
 	vs, fs, prog, pos := gles.ShaderId(0x10), gles.ShaderId(0x20), gles.ProgramId(0x30), gles.AttributeLocation(0)
-	atoms := initContext(a, d, l, 64, 64)
+	atoms := initContext(a, d, l, 64, 64, false)
 	clear := atoms.Add(
 		gles.NewGlClearColor(0.0, 1.0, 0.0, 1.0),
 		gles.NewGlClear(gles.GLbitfield_GL_COLOR_BUFFER_BIT),
@@ -182,14 +183,15 @@
 	checkColorBuffer(t, ctx, mgr, 64, 64, 0.01, "triangle", triangle)
 }
 
-// TestResizeRenderer checks that backbuffers can be resized without destroying the current context.
+// TestResizeRenderer checks that backbuffers can be resized without destroying
+// the current context.
 func TestResizeRenderer(t *testing.T) {
 	d, l := database.NewInMemory(nil), log.Testing(t)
 	mgr := replay.New(d, l)
 	device := utils.FindLocalDevice(t, mgr)
 	a := device.Info().Architecture()
 	vs, fs, prog, pos := gles.ShaderId(0x10), gles.ShaderId(0x20), gles.ProgramId(0x30), gles.AttributeLocation(0)
-	atoms := initContext(a, d, l, 8, 8) // start with a small backbuffer
+	atoms := initContext(a, d, l, 8, 8, false) // start with a small backbuffer
 	atoms.Add(gles.NewProgram(a, d, l, vs, fs, prog, simpleVSSource, simpleFSSource)...)
 	atoms.Add(
 		gles.NewGlLinkProgram(prog),
@@ -200,7 +202,7 @@
 			AddRead(atom.Data(a, d, l, p(0x100000), triangleVertices)),
 	)
 	triangle := atoms.Add(
-		setBackbuffer(64, 64), // Resize just before clearing and drawing.
+		setBackbuffer(64, 64, false), // Resize just before clearing and drawing.
 		gles.NewGlClearColor(0.0, 0.0, 1.0, 1.0),
 		gles.NewGlClear(gles.GLbitfield_GL_COLOR_BUFFER_BIT),
 		gles.NewGlDrawArrays(gles.GLenum_GL_TRIANGLES, 0, 3),
@@ -213,3 +215,30 @@
 
 	checkColorBuffer(t, ctx, mgr, 64, 64, 0.01, "triangle_2", triangle)
 }
+
+// TestPreserveBuffersOnSwap checks that when the preserveBuffersOnSwap flag is
+// set, the backbuffer is preserved between calls to eglSwapBuffers().
+func TestPreserveBuffersOnSwap(t *testing.T) {
+	d, l := database.NewInMemory(nil), log.Testing(t)
+	mgr := replay.New(d, l)
+	device := utils.FindLocalDevice(t, mgr)
+	a := device.Info().Architecture()
+	atoms := initContext(a, d, l, 64, 64, true)
+	clear := atoms.Add(
+		gles.NewGlClearColor(0.0, 0.0, 1.0, 1.0),
+		gles.NewGlClear(gles.GLbitfield_GL_COLOR_BUFFER_BIT),
+	)
+	swapA := atoms.Add(gles.NewEglSwapBuffers(memory.Nullptr, memory.Nullptr, 1))
+	swapB := atoms.Add(gles.NewEglSwapBuffers(memory.Nullptr, memory.Nullptr, 1))
+	swapC := atoms.Add(gles.NewEglSwapBuffers(memory.Nullptr, memory.Nullptr, 1))
+
+	ctx := &replay.Context{
+		Capture: utils.StoreCapture(t, atoms, d, l).ID,
+		Device:  device.ID(),
+	}
+
+	checkColorBuffer(t, ctx, mgr, 64, 64, 0.0, "solid-blue", clear)
+	checkColorBuffer(t, ctx, mgr, 64, 64, 0.0, "solid-blue", swapA)
+	checkColorBuffer(t, ctx, mgr, 64, 64, 0.0, "solid-blue", swapB)
+	checkColorBuffer(t, ctx, mgr, 64, 64, 0.0, "solid-blue", swapC)
+}
diff --git a/replay/batcher.go b/replay/batcher.go
index d9fbf57..0f2d24e 100644
--- a/replay/batcher.go
+++ b/replay/batcher.go
@@ -79,22 +79,7 @@
 		return fmt.Errorf("Failed to load atom stream (%s): %v", c.Atoms, err)
 	}
 
-	td := b.device.Info()
-
-	transforms := b.context.Generator.ReplayTransforms(
-		b.context.Context,
-		b.context.Config,
-		requests,
-		td,
-		b.database,
-		b.logger)
-
-	if config.DebugReplay {
-		log.Infof(b.logger, "Replaying %d atoms using transform chain:", len(list.Atoms))
-		for i, t := range transforms {
-			log.Infof(b.logger, "(%d) %#v", i, t)
-		}
-	}
+	device := b.device.Info()
 
 	architecture := b.device.Info().Architecture()
 
@@ -102,18 +87,28 @@
 
 	if err := func() (err interface{}) {
 		// Prevent panics from causing GAPIS to fall over.
-		// This is temporary, as atoms should return errors instead of causing
-		// runtime panics.
+		// This is temporary, as state mutators should return errors instead of
+		// causing runtime panics.
 		defer func() { err = recover() }()
-		transforms.Transform(*list, &adapter{
+
+		out := &adapter{
 			state:   gfxapi.NewState(),
 			db:      b.database,
 			logger:  b.logger,
 			builder: builder,
-		})
-		return
+		}
+
+		return b.context.Generator.Replay(
+			b.context.Context,
+			b.context.Config,
+			requests,
+			device,
+			*list,
+			out,
+			b.database,
+			b.logger)
 	}(); err != nil {
-		log.Errorf(b.logger, "Panic raised while transforming atoms for replay: %v", err)
+		log.Errorf(b.logger, "Panic raised while writing atoms for replay: %v", err)
 		return fmt.Errorf("%v", err)
 	}
 
@@ -148,12 +143,12 @@
 
 	connection, err := b.device.Connect()
 	if err != nil {
-		return fmt.Errorf("Failed to connect to device %v: %v", td.Name, err)
+		return fmt.Errorf("Failed to connect to device %v: %v", device.Name, err)
 	}
 	defer connection.Close()
 
 	if config.DebugReplay {
-		log.Infof(b.logger, "Sending payload to %v.", td.Name)
+		log.Infof(b.logger, "Sending payload to %v.", device.Name)
 	}
 
 	return executor.Execute(
diff --git a/replay/replay.go b/replay/replay.go
index f535f6d..b68ccaa 100644
--- a/replay/replay.go
+++ b/replay/replay.go
@@ -25,18 +25,19 @@
 
 // Generator is the interface for types that support replay generation.
 type Generator interface {
-	// ReplayTransforms is called when a replay pass is ready to be sent to the
-	// replay device. ReplayTransforms returns an atom transform list that
-	// transforms the original, unaltered atom stream into a stream configured for
-	// the replay pass. The transforms should satisfy all the specified requests
-	// and config.
-	ReplayTransforms(
+	// Replay is called when a replay pass is ready to be sent to the replay
+	// device. Replay may filter or transform the list of atoms, satisfying all
+	// the specified requests and config, before outputting the final atom stream
+	// to out.
+	Replay(
 		ctx Context,
 		cfg Config,
 		requests []Request,
 		device *service.Device,
+		atoms atom.List,
+		out atom.Writer,
 		db database.Database,
-		logger log.Logger) atom.Transforms
+		logger log.Logger) error
 }
 
 // Context describes the source capture and replay target information used for