GPU device preserves pixel values across read/write/read of unpremul pixel values

Review URL: http://codereview.appspot.com/5695047/



git-svn-id: http://skia.googlecode.com/svn/trunk@3237 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index dd1b276..5f2c43b 100644
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -1787,6 +1787,35 @@
     }
 }
 
+#include "SkConfig8888.h"
+
+namespace {
+/**
+ * Converts a GrPixelConfig to a SkCanvas::Config8888. Only byte-per-channel
+ * formats are representable as Config8888 and so the function returns false
+ * if the GrPixelConfig has no equivalent Config8888.
+ */
+bool grconfig_to_config8888(GrPixelConfig config,
+                            SkCanvas::Config8888* config8888) {
+    switch (config) {
+        case kRGBA_8888_PM_GrPixelConfig:
+            *config8888 = SkCanvas::kRGBA_Premul_Config8888;
+            return true;
+        case kRGBA_8888_UPM_GrPixelConfig:
+            *config8888 = SkCanvas::kRGBA_Unpremul_Config8888;
+            return true;
+        case kBGRA_8888_PM_GrPixelConfig:
+            *config8888 = SkCanvas::kBGRA_Premul_Config8888;
+            return true;
+        case kBGRA_8888_UPM_GrPixelConfig:
+            *config8888 = SkCanvas::kBGRA_Unpremul_Config8888;
+            return true;
+        default:
+            return false;
+    }
+}
+}
+
 bool GrContext::internalReadRenderTargetPixels(GrRenderTarget* target,
                                                int left, int top,
                                                int width, int height,
@@ -1803,19 +1832,34 @@
             return false;
         }
     }
-    
-    // PM <-> UPM conversion requires a draw. Currently we only support drawing
-    // into a UPM target, not reading from a UPM texture. Thus, UPM->PM is not
-    // not supported at this time.
-    if (GrPixelConfigIsUnpremultiplied(target->config()) && 
-        !GrPixelConfigIsUnpremultiplied(config)) {
-        return false;
-    }
 
     if (!(kDontFlush_PixelOpsFlag & flags)) {
         this->flush();
     }
 
+    if (!GrPixelConfigIsUnpremultiplied(target->config()) &&
+        GrPixelConfigIsUnpremultiplied(config) &&
+        !fGpu->canPreserveReadWriteUnpremulPixels()) {
+        SkCanvas::Config8888 srcConfig8888, dstConfig8888;
+        if (!grconfig_to_config8888(target->config(), &srcConfig8888) ||
+            !grconfig_to_config8888(config, &dstConfig8888)) {
+            return false;
+        }
+        // do read back using target's own config
+        this->internalReadRenderTargetPixels(target,
+                                             left, top,
+                                             width, height,
+                                             target->config(),
+                                             buffer, rowBytes,
+                                             kDontFlush_PixelOpsFlag);
+        // sw convert the pixels to unpremul config
+        uint32_t* pixels = reinterpret_cast<uint32_t*>(buffer);
+        SkConvertConfig8888Pixels(pixels, rowBytes, dstConfig8888,
+                                  pixels, rowBytes, srcConfig8888,
+                                  width, height);
+        return true;
+    }
+
     GrTexture* src = target->asTexture();
     bool swapRAndB = NULL != src &&
                      fGpu->preferredReadPixelsConfig(config) ==
@@ -1968,6 +2012,28 @@
         return;
     }
 #endif
+    if (!GrPixelConfigIsUnpremultiplied(target->config()) &&
+        GrPixelConfigIsUnpremultiplied(config) &&
+        !fGpu->canPreserveReadWriteUnpremulPixels()) {
+        SkCanvas::Config8888 srcConfig8888, dstConfig8888;
+        if (!grconfig_to_config8888(config, &srcConfig8888) ||
+            !grconfig_to_config8888(target->config(), &dstConfig8888)) {
+            return;
+        }
+        // allocate a tmp buffer and sw convert the pixels to premul
+        SkAutoSTMalloc<128 * 128, uint32_t> tmpPixels(width * height);
+        const uint32_t* src = reinterpret_cast<const uint32_t*>(buffer);
+        SkConvertConfig8888Pixels(tmpPixels.get(), 4 * width, dstConfig8888,
+                                  src, rowBytes, srcConfig8888,
+                                  width, height);
+        // upload the already premul pixels
+        this->internalWriteRenderTargetPixels(target,
+                                             left, top,
+                                             width, height,
+                                             target->config(),
+                                             tmpPixels, 4 * width, flags);
+        return;
+    }
 
     bool swapRAndB = fGpu->preferredReadPixelsConfig(config) ==
                      GrPixelConfigSwapRAndB(config);
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index 28a0063..52282ed 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -184,6 +184,13 @@
     void forceRenderTargetFlush();
 
     /**
+     * If this returns true then a sequence that reads unpremultiplied pixels
+     * from a surface, writes back the same values, and reads them again will
+     * give the same pixel values back in both reads.
+     */
+    virtual bool canPreserveReadWriteUnpremulPixels() = 0;
+
+    /**
      * readPixels with some configs may be slow. Given a desired config this
      * function returns a fast-path config. The returned config must have the
      * same components, component sizes, and not require conversion between
diff --git a/src/gpu/gl/GrGLProgram.cpp b/src/gpu/gl/GrGLProgram.cpp
index 868149f..7eecf91 100644
--- a/src/gpu/gl/GrGLProgram.cpp
+++ b/src/gpu/gl/GrGLProgram.cpp
@@ -953,13 +953,22 @@
                             inCoverage.c_str(),
                             &segments.fFSCode);
         }
-        if (ProgramDesc::kNo_OutputPM == fProgramDesc.fOutputPM) {
-            segments.fFSCode.appendf("\t%s = %s.a <= 0.0 ? vec4(0,0,0,0) : vec4(%s.rgb / %s.a, %s.a);\n",
-                                     colorOutput.getName().c_str(),
-                                     colorOutput.getName().c_str(),
-                                     colorOutput.getName().c_str(),
-                                     colorOutput.getName().c_str(),
-                                     colorOutput.getName().c_str());
+        if (ProgramDesc::kUnpremultiplied_RoundDown_OutputConfig ==
+            fProgramDesc.fOutputConfig) {
+            segments.fFSCode.appendf("\t%s = %s.a <= 0.0 ? vec4(0,0,0,0) : vec4(floor(%s.rgb / %s.a * 255.0)/255.0, %s.a);\n",
+                                        colorOutput.getName().c_str(),
+                                        colorOutput.getName().c_str(),
+                                        colorOutput.getName().c_str(),
+                                        colorOutput.getName().c_str(),
+                                        colorOutput.getName().c_str());
+        } else if (ProgramDesc::kUnpremultiplied_RoundUp_OutputConfig ==
+                   fProgramDesc.fOutputConfig) {
+            segments.fFSCode.appendf("\t%s = %s.a <= 0.0 ? vec4(0,0,0,0) : vec4(ceil(%s.rgb / %s.a * 255.0)/255.0, %s.a);\n",
+                                        colorOutput.getName().c_str(),
+                                        colorOutput.getName().c_str(),
+                                        colorOutput.getName().c_str(),
+                                        colorOutput.getName().c_str(),
+                                        colorOutput.getName().c_str());
         }
     }
 
@@ -1809,13 +1818,16 @@
 
     };
 
+    static const uint32_t kMulByAlphaMask =
+        (StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag |
+         StageDesc::kMulRGBByAlpha_RoundDown_InConfigFlag);
+
     const char* swizzle = "";
     if (desc.fInConfigFlags & StageDesc::kSwapRAndB_InConfigFlag) {
         GrAssert(!(desc.fInConfigFlags & StageDesc::kSmearAlpha_InConfigFlag));
         swizzle = ".bgra";
     } else if (desc.fInConfigFlags & StageDesc::kSmearAlpha_InConfigFlag) {
-        GrAssert(!(desc.fInConfigFlags &
-                   StageDesc::kMulRGBByAlpha_InConfigFlag));
+        GrAssert(!(desc.fInConfigFlags & kMulByAlphaMask));
         swizzle = ".aaaa";
     } 
 
@@ -1843,30 +1855,37 @@
 
     switch (desc.fFetchMode) {
     case StageDesc::k2x2_FetchMode:
-        GrAssert(!(desc.fInConfigFlags &
-                   StageDesc::kMulRGBByAlpha_InConfigFlag));
+        GrAssert(!(desc.fInConfigFlags & kMulByAlphaMask));
         gen2x2FS(stageNum, segments, locations, &sampleCoords,
             samplerName, texelSizeName, swizzle, fsOutColor,
             texFunc, modulate, complexCoord, coordDims);
         break;
     case StageDesc::kConvolution_FetchMode:
-        GrAssert(!(desc.fInConfigFlags &
-                   StageDesc::kMulRGBByAlpha_InConfigFlag));
+        GrAssert(!(desc.fInConfigFlags & kMulByAlphaMask));
         genConvolutionFS(stageNum, desc, segments,
             samplerName, kernel, swizzle, imageIncrementName, fsOutColor,
             sampleCoords, texFunc, modulate);
         break;
     default:
-        if (desc.fInConfigFlags & StageDesc::kMulRGBByAlpha_InConfigFlag) {
+        if (desc.fInConfigFlags & kMulByAlphaMask) {
+            // only one of the mul by alpha flags should be set
+            GrAssert(GrIsPow2(kMulByAlphaMask & desc.fInConfigFlags));
             GrAssert(!(desc.fInConfigFlags & 
                        StageDesc::kSmearAlpha_InConfigFlag));
             segments->fFSCode.appendf("\t%s = %s(%s, %s)%s;\n",
                                       fsOutColor, texFunc.c_str(), 
                                       samplerName, sampleCoords.c_str(),
                                       swizzle);
-            segments->fFSCode.appendf("\t%s = vec4(%s.rgb*%s.a,%s.a)%s;\n",
-                                      fsOutColor, fsOutColor, fsOutColor,
-                                      fsOutColor, modulate.c_str());
+            if (desc.fInConfigFlags &
+                StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag) {
+                segments->fFSCode.appendf("\t%s = vec4(ceil(%s.rgb*%s.a*255.0)/255.0,%s.a)%s;\n",
+                                          fsOutColor, fsOutColor, fsOutColor,
+                                          fsOutColor, modulate.c_str());
+            } else {
+                segments->fFSCode.appendf("\t%s = vec4(floor(%s.rgb*%s.a*255.0)/255.0,%s.a)%s;\n",
+                                          fsOutColor, fsOutColor, fsOutColor,
+                                          fsOutColor, modulate.c_str());
+            }
         } else {
             segments->fFSCode.appendf("\t%s = %s(%s, %s)%s%s;\n",
                                       fsOutColor, texFunc.c_str(), 
diff --git a/src/gpu/gl/GrGLProgram.h b/src/gpu/gl/GrGLProgram.h
index 5c13679..e9030bc 100644
--- a/src/gpu/gl/GrGLProgram.h
+++ b/src/gpu/gl/GrGLProgram.h
@@ -86,13 +86,17 @@
             memset(this, 0, sizeof(ProgramDesc));
         }
 
-        enum OutputPM {
+        enum OutputConfig {
             // PM-color OR color with no alpha channel
-            kYes_OutputPM,
-            // nonPM-color with alpha channel
-            kNo_OutputPM,
+            kPremultiplied_OutputConfig,
+            // nonPM-color with alpha channel. Round components up after
+            // dividing by alpha. Assumes output is 8 bits for r, g, and b
+            kUnpremultiplied_RoundUp_OutputConfig,
+            // nonPM-color with alpha channel. Round components down after
+            // dividing by alpha. Assumes output is 8 bits for r, g, and b
+            kUnpremultiplied_RoundDown_OutputConfig,
 
-            kOutputPMCnt
+            kOutputConfigCnt
         };
 
         struct StageDesc {
@@ -114,7 +118,7 @@
               described are performed after reading a texel.
              */
             enum InConfigFlags {
-                kNone_InConfigFlag              = 0x0,
+                kNone_InConfigFlag                      = 0x0,
 
                 /**
                   Swap the R and B channels. This is incompatible with
@@ -122,21 +126,27 @@
                   the shader using GL_ARB_texture_swizzle if possible rather
                   than setting this flag.
                  */
-                kSwapRAndB_InConfigFlag         = 0x1,
+                kSwapRAndB_InConfigFlag                 = 0x1,
 
                 /**
                  Smear alpha across all four channels. This is incompatible with
-                 kSwapRAndB and kPremul.  It is prefereable to perform the
-                 smear outside the shader using GL_ARB_texture_swizzle if
+                 kSwapRAndB and kMulRGBByAlpha*. It is prefereable to perform
+                 the smear outside the shader using GL_ARB_texture_swizzle if
                  possible rather than setting this flag.
                 */
-                kSmearAlpha_InConfigFlag        = 0x2,
+                kSmearAlpha_InConfigFlag                = 0x2,
 
                 /**
                  Multiply r,g,b by a after texture reads. This flag incompatible
                  with kSmearAlpha and may only be used with FetchMode kSingle.
+
+                 It is assumed the src texture has 8bit color components. After
+                 reading the texture one version rounds up to the next multiple
+                 of 1/255.0 and the other rounds down. At most one of these
+                 flags may be set.
                  */
-                kMulRGBByAlpha_InConfigFlag     =  0x4,
+                kMulRGBByAlpha_RoundUp_InConfigFlag     =  0x4,
+                kMulRGBByAlpha_RoundDown_InConfigFlag   =  0x8,
 
                 kDummyInConfigFlag,
                 kInConfigBitMask = (kDummyInConfigFlag-1) |
@@ -211,7 +221,7 @@
 
         uint8_t fColorInput;        // casts to enum ColorInput
         uint8_t fCoverageInput;     // casts to enum CoverageInput
-        uint8_t fOutputPM;          // cases to enum OutputPM
+        uint8_t fOutputConfig;      // casts to enum OutputConfig
         uint8_t fDualSrcOutput;     // casts to enum DualSrcOutput
         int8_t fFirstCoverageStage;
         SkBool8 fEmitsPointSize;
diff --git a/src/gpu/gl/GrGpuGL.cpp b/src/gpu/gl/GrGpuGL.cpp
index 68f33b1..bee2017 100644
--- a/src/gpu/gl/GrGpuGL.cpp
+++ b/src/gpu/gl/GrGpuGL.cpp
@@ -207,6 +207,7 @@
     this->initCaps();
 
     fLastSuccessfulStencilFmtIdx = 0;
+    fCanPreserveUnpremulRoundtrip = kUnknown_CanPreserveUnpremulRoundtrip;
 }
 
 GrGpuGL::~GrGpuGL() {
@@ -292,6 +293,85 @@
     fCaps.fFSAASupport = GrGLCaps::kNone_MSFBOType != this->glCaps().msFBOType();
 }
 
+bool GrGpuGL::canPreserveReadWriteUnpremulPixels() {
+    if (kUnknown_CanPreserveUnpremulRoundtrip ==
+        fCanPreserveUnpremulRoundtrip) {
+
+        SkAutoTMalloc<uint32_t> data(256 * 256 * 3);
+        uint32_t* srcData = data.get();
+        uint32_t* firstRead = data.get() + 256 * 256;
+        uint32_t* secondRead = data.get() + 2 * 256 * 256;
+
+        for (int y = 0; y < 256; ++y) {
+            for (int x = 0; x < 256; ++x) {
+                uint8_t* color = reinterpret_cast<uint8_t*>(&srcData[256*y + x]);
+                color[3] = y;
+                color[2] = x;
+                color[1] = x;
+                color[0] = x;
+            }
+        }
+
+        // We have broader support for read/write pixels on render targets
+        // than on textures.
+        GrTextureDesc dstDesc;
+        dstDesc.fFlags = kRenderTarget_GrTextureFlagBit |
+                         kNoStencil_GrTextureFlagBit;
+        dstDesc.fWidth = 256;
+        dstDesc.fHeight = 256;
+        dstDesc.fConfig = kRGBA_8888_GrPixelConfig;
+        dstDesc.fSampleCnt = 0;
+
+        SkAutoTUnref<GrTexture> dstTex(this->createTexture(dstDesc, NULL, 0));
+        if (!dstTex.get()) {
+            return false;
+        }
+        GrRenderTarget* rt = dstTex.get()->asRenderTarget();
+        GrAssert(NULL != rt);
+
+        bool failed = true;
+        static const UnpremulConversion gMethods[] = {
+            kUpOnWrite_DownOnRead_UnpremulConversion,
+            kDownOnWrite_UpOnRead_UnpremulConversion,
+        };
+
+        // pretend that we can do the roundtrip to avoid recursive calls to
+        // this function
+        fCanPreserveUnpremulRoundtrip = kYes_CanPreserveUnpremulRoundtrip;
+        for (size_t i = 0; i < GR_ARRAY_COUNT(gMethods) && failed; ++i) {
+            fUnpremulConversion = gMethods[i];
+            rt->writePixels(0, 0,
+                            256, 256,
+                            kRGBA_8888_UPM_GrPixelConfig, srcData, 0);
+            rt->readPixels(0, 0,
+                           256, 256,
+                           kRGBA_8888_UPM_GrPixelConfig, firstRead, 0);
+            rt->writePixels(0, 0,
+                            256, 256,
+                            kRGBA_8888_UPM_GrPixelConfig, firstRead, 0);
+            rt->readPixels(0, 0,
+                           256, 256,
+                           kRGBA_8888_UPM_GrPixelConfig, secondRead, 0);
+            failed = false;
+            for (int j = 0; j < 256 * 256; ++j) {
+                if (firstRead[j] != secondRead[j]) {
+                    failed = true;
+                    break;
+                }
+            }
+        }
+        fCanPreserveUnpremulRoundtrip = failed ? 
+                        kNo_CanPreserveUnpremulRoundtrip :
+                        kYes_CanPreserveUnpremulRoundtrip;
+    }
+
+    if (kYes_CanPreserveUnpremulRoundtrip == fCanPreserveUnpremulRoundtrip) {
+        return true;
+    } else {
+        return false;
+    }
+}
+
 GrPixelConfig GrGpuGL::preferredReadPixelsConfig(GrPixelConfig config) const {
     if (GR_GL_RGBA_8888_PIXEL_OPS_SLOW && GrPixelConfigIsRGBA8888(config)) {
         return GrPixelConfigSwapRAndB(config);
diff --git a/src/gpu/gl/GrGpuGL.h b/src/gpu/gl/GrGpuGL.h
index eac8d4f..398a2fc 100644
--- a/src/gpu/gl/GrGpuGL.h
+++ b/src/gpu/gl/GrGpuGL.h
@@ -46,6 +46,8 @@
                                     size_t rowBytes) const SK_OVERRIDE;
     virtual bool fullReadPixelsIsFasterThanPartial() const SK_OVERRIDE;
 
+    virtual bool canPreserveReadWriteUnpremulPixels() SK_OVERRIDE;
+
 protected:
     GrGpuGL(const GrGLContextInfo& ctxInfo);
 
@@ -62,6 +64,11 @@
         bool fSmoothLineEnabled;
     } fHWAAState;
 
+    enum UnpremulConversion {
+        kUpOnWrite_DownOnRead_UnpremulConversion,
+        kDownOnWrite_UpOnRead_UnpremulConversion
+    } fUnpremulConversion;
+
     GrDrawState fHWDrawState;
     bool        fHWStencilClip;
 
@@ -246,6 +253,11 @@
     // from our loop that tries stencil formats and calls check fb status.
     int fLastSuccessfulStencilFmtIdx;
 
+    enum CanPreserveUnpremulRoundtrip {
+        kUnknown_CanPreserveUnpremulRoundtrip,
+        kNo_CanPreserveUnpremulRoundtrip,
+        kYes_CanPreserveUnpremulRoundtrip,
+    } fCanPreserveUnpremulRoundtrip;
 
     bool fPrintedCaps;
 
diff --git a/src/gpu/gl/GrGpuGLShaders.cpp b/src/gpu/gl/GrGpuGLShaders.cpp
index 4093a0d..a0a2df5 100644
--- a/src/gpu/gl/GrGpuGLShaders.cpp
+++ b/src/gpu/gl/GrGpuGLShaders.cpp
@@ -173,8 +173,9 @@
     static const int IN_CONFIG_FLAGS[] = {
         StageDesc::kNone_InConfigFlag,
         StageDesc::kSwapRAndB_InConfigFlag,
-        StageDesc::kSwapRAndB_InConfigFlag | StageDesc::kMulRGBByAlpha_InConfigFlag,
-        StageDesc::kMulRGBByAlpha_InConfigFlag,
+        StageDesc::kSwapRAndB_InConfigFlag |
+        StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag,
+        StageDesc::kMulRGBByAlpha_RoundDown_InConfigFlag,
         StageDesc::kSmearAlpha_InConfigFlag,
     };
     GrGLProgram program;
@@ -210,7 +211,7 @@
         pdesc.fExperimentalGS = this->getCaps().fGeometryShaderSupport &&
                                 random_bool(&random);
 #endif
-        pdesc.fOutputPM =  random_int(&random, ProgramDesc::kOutputPMCnt);
+        pdesc.fOutputConfig =  random_int(&random, ProgramDesc::kOutputConfigCnt);
 
         bool edgeAA = random_bool(&random);
         if (edgeAA) {
@@ -264,17 +265,20 @@
                 stage.fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
             }
             stage.setEnabled(VertexUsesStage(s, pdesc.fVertexLayout));
+            static const uint32_t kMulByAlphaMask =
+                StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag |
+                StageDesc::kMulRGBByAlpha_RoundDown_InConfigFlag;
             switch (stage.fFetchMode) {
                 case StageDesc::kSingle_FetchMode:
                     stage.fKernelWidth = 0;
                     break;
                 case StageDesc::kConvolution_FetchMode:
                     stage.fKernelWidth = random_int(&random, 2, 8);
-                    stage.fInConfigFlags &= ~StageDesc::kMulRGBByAlpha_InConfigFlag;
+                    stage.fInConfigFlags &= ~kMulByAlphaMask;
                     break;
                 case StageDesc::k2x2_FetchMode:
                     stage.fKernelWidth = 0;
-                    stage.fInConfigFlags &= ~StageDesc::kMulRGBByAlpha_InConfigFlag;
+                    stage.fInConfigFlags &= ~kMulByAlphaMask;
                     break;
             }
         }
@@ -1102,7 +1106,17 @@
                 }
             }
             if (GrPixelConfigIsUnpremultiplied(texture->config())) {
-                stage.fInConfigFlags |= StageDesc::kMulRGBByAlpha_InConfigFlag;
+                // The shader generator assumes that color channels are bytes
+                // when rounding.
+                GrAssert(4 == GrBytesPerPixel(texture->config()));
+                if (kUpOnWrite_DownOnRead_UnpremulConversion ==
+                    fUnpremulConversion) {
+                    stage.fInConfigFlags |=
+                        StageDesc::kMulRGBByAlpha_RoundDown_InConfigFlag;
+                } else {
+                    stage.fInConfigFlags |=
+                        StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag;
+                }
             }
 
             if (sampler.getFilter() == GrSamplerState::kConvolution_Filter) {
@@ -1120,9 +1134,18 @@
     }
 
     if (GrPixelConfigIsUnpremultiplied(drawState.getRenderTarget()->config())) {
-        desc.fOutputPM = ProgramDesc::kNo_OutputPM;
+        // The shader generator assumes that color channels are bytes
+        // when rounding.
+        GrAssert(4 == GrBytesPerPixel(drawState.getRenderTarget()->config()));
+        if (kUpOnWrite_DownOnRead_UnpremulConversion == fUnpremulConversion) {
+            desc.fOutputConfig =
+                ProgramDesc::kUnpremultiplied_RoundUp_OutputConfig;
+        } else {
+            desc.fOutputConfig =
+                ProgramDesc::kUnpremultiplied_RoundDown_OutputConfig;
+        }
     } else {
-        desc.fOutputPM = ProgramDesc::kYes_OutputPM;
+        desc.fOutputConfig = ProgramDesc::kPremultiplied_OutputConfig;
     }
 
     desc.fDualSrcOutput = ProgramDesc::kNone_DualSrcOutput;