diff options
author | Brian Salomon <bsalomon@google.com> | 2018-02-14 13:53:55 -0500 |
---|---|---|
committer | Skia Commit-Bot <skia-commit-bot@chromium.org> | 2018-02-14 19:58:14 +0000 |
commit | 9b009bb069aa81425438d5403a1a29f2d047f77f (patch) | |
tree | a3ff6b83971d9c1564d1de84e219a187f703ed8b /src | |
parent | 95edb43251e8fcef4286c91d334c3259940a0095 (diff) |
Prepare sRGB encoding conversion for the removal of GrPixelConfig
Standardizes that GrGpu subclass's onRead/WritePixels never do sRGB<->linear conversion. This means that they can eventually take a color type rather than config. It also means direct callers of GrGpu::read/writePixels can never expect conversion (which in practice is no change).
Consolidate logic about whether to do sRGB<->linear encoding conversions in GrContext::read/writeSurfacePixels helpers. No change in when conversions are done (yet). This prepares this logic to operate on SkColorSpace and color type rather than config WRT the CPU data.
Bug: skia:6718
Change-Id: I346d669624861578f1bb9ea465a7ab4b549117fa
Reviewed-on: https://skia-review.googlesource.com/105286
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Salomon <bsalomon@google.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/gpu/GrContext.cpp | 69 | ||||
-rw-r--r-- | src/gpu/GrGpu.cpp | 84 | ||||
-rw-r--r-- | src/gpu/GrGpu.h | 56 | ||||
-rw-r--r-- | src/gpu/GrOpFlushState.cpp | 7 | ||||
-rw-r--r-- | src/gpu/gl/GrGLGpu.cpp | 130 | ||||
-rw-r--r-- | src/gpu/gl/GrGLGpu.h | 16 | ||||
-rw-r--r-- | src/gpu/mock/GrMockGpu.h | 42 | ||||
-rw-r--r-- | src/gpu/vk/GrVkGpu.cpp | 50 | ||||
-rw-r--r-- | src/gpu/vk/GrVkGpu.h | 37 |
9 files changed, 287 insertions, 204 deletions
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp index 09985e6f79..f8522f07bd 100644 --- a/src/gpu/GrContext.cpp +++ b/src/gpu/GrContext.cpp @@ -542,6 +542,63 @@ static bool pm_upm_must_round_trip(GrPixelConfig config, SkColorSpace* colorSpac (kRGBA_8888_GrPixelConfig == config || kBGRA_8888_GrPixelConfig == config); } +static GrSRGBConversion determine_write_pixels_srgb_conversion( + GrPixelConfig srcConfig, + const SkColorSpace* srcColorSpace, + GrSRGBEncoded dstSRGBEncoded, + const SkColorSpace* dstColorSpace) { + // No support for sRGB-encoded alpha. + if (GrPixelConfigIsAlphaOnly(srcConfig)) { + return GrSRGBConversion::kNone; + } + // When the destination has no color space or it has a ~sRGB gamma but isn't sRGB encoded + // (because of caps) then we act in "legacy" mode where no conversions are performed. + if (!dstColorSpace || + (dstColorSpace->gammaCloseToSRGB() && GrSRGBEncoded::kNo == dstSRGBEncoded)) { + return GrSRGBConversion::kNone; + } + // Similarly, if the src was sRGB gamma and 8888 but we didn't choose a sRGB config we must be + // in legacy mode. For now, anyway. + if (srcColorSpace && srcColorSpace->gammaCloseToSRGB() && + GrSRGBEncoded::kNo == GrPixelConfigIsSRGBEncoded(srcConfig) && + GrPixelConfigIs8888Unorm(srcConfig)) { + return GrSRGBConversion::kNone; + } + + bool srcColorSpaceIsSRGB = srcColorSpace && srcColorSpace->gammaCloseToSRGB(); + bool dstColorSpaceIsSRGB = dstColorSpace->gammaCloseToSRGB(); + + // For now we are assuming that if color space of the dst does not have sRGB gamma then the + // texture format is not sRGB encoded and vice versa. Note that we already checked for "legacy" + // mode being forced on by caps above. This may change in the future. + SkASSERT(dstColorSpaceIsSRGB == (GrSRGBEncoded::kYes == dstSRGBEncoded)); + + // Similarly we are assuming that if the color space of the src does not have sRGB gamma then + // the CPU pixels don't have a sRGB pixel config. This will become moot soon as we will not + // be using GrPixelConfig to describe CPU pixel allocations. + SkASSERT(srcColorSpaceIsSRGB == GrPixelConfigIsSRGB(srcConfig)); + + if (srcColorSpaceIsSRGB == dstColorSpaceIsSRGB) { + return GrSRGBConversion::kNone; + } + return srcColorSpaceIsSRGB ? GrSRGBConversion::kSRGBToLinear : GrSRGBConversion::kLinearToSRGB; +} + +static GrSRGBConversion determine_read_pixels_srgb_conversion( + GrSRGBEncoded srcSRGBEncoded, + const SkColorSpace* srcColorSpace, + GrPixelConfig dstConfig, + const SkColorSpace* dstColorSpace) { + // This is symmetrical with the write version. + switch (determine_write_pixels_srgb_conversion(dstConfig, dstColorSpace, srcSRGBEncoded, + srcColorSpace)) { + case GrSRGBConversion::kNone: return GrSRGBConversion::kNone; + case GrSRGBConversion::kLinearToSRGB: return GrSRGBConversion::kSRGBToLinear; + case GrSRGBConversion::kSRGBToLinear: return GrSRGBConversion::kLinearToSRGB; + } + return GrSRGBConversion::kNone; +} + bool GrContextPriv::writeSurfacePixels(GrSurfaceContext* dst, int left, int top, int width, int height, GrPixelConfig srcConfig, SkColorSpace* srcColorSpace, @@ -590,8 +647,12 @@ bool GrContextPriv::writeSurfacePixels(GrSurfaceContext* dst, GrGpu::DrawPreference drawPreference = premulOnGpu ? GrGpu::kCallerPrefersDraw_DrawPreference : GrGpu::kNoDraw_DrawPreference; GrGpu::WritePixelTempDrawInfo tempDrawInfo; + GrSRGBConversion srgbConversion = determine_write_pixels_srgb_conversion( + srcConfig, srcColorSpace, GrPixelConfigIsSRGBEncoded(dstProxy->config()), + dst->colorSpaceInfo().colorSpace()); if (!fContext->fGpu->getWritePixelsInfo(dstSurface, dstProxy->origin(), width, height, - srcConfig, &drawPreference, &tempDrawInfo)) { + srcConfig, srgbConversion, &drawPreference, + &tempDrawInfo)) { return false; } @@ -723,8 +784,12 @@ bool GrContextPriv::readSurfacePixels(GrSurfaceContext* src, GrGpu::DrawPreference drawPreference = unpremulOnGpu ? GrGpu::kCallerPrefersDraw_DrawPreference : GrGpu::kNoDraw_DrawPreference; GrGpu::ReadPixelTempDrawInfo tempDrawInfo; + GrSRGBConversion srgbConversion = determine_read_pixels_srgb_conversion( + GrPixelConfigIsSRGBEncoded(srcProxy->config()), src->colorSpaceInfo().colorSpace(), + dstConfig, dstColorSpace); if (!fContext->fGpu->getReadPixelsInfo(srcSurface, srcProxy->origin(), width, height, rowBytes, - dstConfig, &drawPreference, &tempDrawInfo)) { + dstConfig, srgbConversion, &drawPreference, + &tempDrawInfo)) { return false; } diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp index afe9cd813f..ff26c799c9 100644 --- a/src/gpu/GrGpu.cpp +++ b/src/gpu/GrGpu.cpp @@ -193,35 +193,65 @@ bool GrGpu::copySurface(GrSurface* dst, GrSurfaceOrigin dstOrigin, return this->onCopySurface(dst, dstOrigin, src, srcOrigin, srcRect, dstPoint); } -bool GrGpu::getReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, - int width, int height, size_t rowBytes, - GrPixelConfig readConfig, DrawPreference* drawPreference, +bool GrGpu::getReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, int width, + int height, size_t rowBytes, GrPixelConfig dstConfig, + GrSRGBConversion srgbConversion, DrawPreference* drawPreference, ReadPixelTempDrawInfo* tempDrawInfo) { SkASSERT(drawPreference); SkASSERT(tempDrawInfo); SkASSERT(srcSurface); SkASSERT(kGpuPrefersDraw_DrawPreference != *drawPreference); - // Default values for intermediate draws. The intermediate texture config matches the src's + // Default values for intermediate draws. The intermediate texture config matches the dst's // config, is approx sized to the read rect, no swizzling or spoofing of the dst config. tempDrawInfo->fTempSurfaceDesc.fFlags = kRenderTarget_GrSurfaceFlag; tempDrawInfo->fTempSurfaceDesc.fWidth = width; tempDrawInfo->fTempSurfaceDesc.fHeight = height; tempDrawInfo->fTempSurfaceDesc.fSampleCnt = 1; tempDrawInfo->fTempSurfaceDesc.fOrigin = kTopLeft_GrSurfaceOrigin; // no CPU y-flip for TL. - tempDrawInfo->fTempSurfaceDesc.fConfig = srcSurface->config(); + tempDrawInfo->fTempSurfaceDesc.fConfig = dstConfig; tempDrawInfo->fTempSurfaceFit = SkBackingFit::kApprox; tempDrawInfo->fSwizzle = GrSwizzle::RGBA(); - tempDrawInfo->fReadConfig = readConfig; + tempDrawInfo->fReadConfig = dstConfig; // We currently do not support reading into the packed formats 565 or 4444 as they are not // required to have read back support on all devices and backends. - if (kRGB_565_GrPixelConfig == readConfig || kRGBA_4444_GrPixelConfig == readConfig) { + if (kRGB_565_GrPixelConfig == dstConfig || kRGBA_4444_GrPixelConfig == dstConfig) { return false; } - if (!this->onGetReadPixelsInfo(srcSurface, srcOrigin, width, height, rowBytes, readConfig, - drawPreference, tempDrawInfo)) { + // GrGpu::readPixels doesn't do any sRGB conversions, so we must draw if there is one. + switch (srgbConversion) { + case GrSRGBConversion::kNone: + break; + case GrSRGBConversion::kLinearToSRGB: + SkASSERT(this->caps()->srgbSupport()); + // This check goes away when we start referring to CPU data using color type. + SkASSERT(GrSRGBEncoded::kYes == GrPixelConfigIsSRGBEncoded(dstConfig)); + // Currently we don't expect to make a SRGB encoded surface and then read data from it + // such that we treat it as though it were linear and is then converted to sRGB. + if (GrSRGBEncoded::kYes == GrPixelConfigIsSRGBEncoded(srcSurface->config())) { + return false; + } + ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); + break; + case GrSRGBConversion::kSRGBToLinear: + SkASSERT(this->caps()->srgbSupport()); + // This assert goes away when we start referring to CPU data using color type. + SkASSERT(GrSRGBEncoded::kNo == GrPixelConfigIsSRGBEncoded(dstConfig)); + // We don't currently support reading sRGB encoded data into linear from a surface + // unless it is an sRGB-encoded config. That is likely to change when we need to store + // sRGB encoded data in 101010102 and F16 textures. We'll have to provoke the caller to + // do the conversion in a shader. + if (GrSRGBEncoded::kNo == GrPixelConfigIsSRGBEncoded(srcSurface->config())) { + return false; + } + ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); + break; + } + + if (!this->onGetReadPixelsInfo(srcSurface, srcOrigin, width, height, rowBytes, dstConfig, + drawPreference, tempDrawInfo)) { return false; } @@ -237,9 +267,9 @@ bool GrGpu::getReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, return true; } -bool GrGpu::getWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOrigin, - int width, int height, - GrPixelConfig srcConfig, DrawPreference* drawPreference, +bool GrGpu::getWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOrigin, int width, + int height, GrPixelConfig srcConfig, GrSRGBConversion srgbConversion, + DrawPreference* drawPreference, WritePixelTempDrawInfo* tempDrawInfo) { SkASSERT(drawPreference); SkASSERT(tempDrawInfo); @@ -257,6 +287,36 @@ bool GrGpu::getWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOrigin, tempDrawInfo->fSwizzle = GrSwizzle::RGBA(); tempDrawInfo->fWriteConfig = srcConfig; + // GrGpu::writePixels doesn't do any sRGB conversions, so we must draw if there is one. + switch (srgbConversion) { + case GrSRGBConversion::kNone: + break; + case GrSRGBConversion::kLinearToSRGB: + SkASSERT(this->caps()->srgbSupport()); + // This assert goes away when we start referring to CPU data using color type. + SkASSERT(GrSRGBEncoded::kNo == GrPixelConfigIsSRGBEncoded(srcConfig)); + // We don't currently support storing sRGB encoded data in a surface unless it is + // an SRGB-encoded config. That is likely to change when we need to store sRGB encoded + // data in 101010102 and F16 textures. We'll have to provoke the caller to do the + // conversion in a shader. + if (GrSRGBEncoded::kNo == GrPixelConfigIsSRGBEncoded(dstSurface->config())) { + return false; + } + ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); + break; + case GrSRGBConversion::kSRGBToLinear: + SkASSERT(this->caps()->srgbSupport()); + // This assert goes away when we start referring to CPU data using color type. + SkASSERT(GrSRGBEncoded::kYes == GrPixelConfigIsSRGBEncoded(srcConfig)); + // Currently we don't expect to make a SRGB encoded surface and then succeed at + // treating it as though it were linear and then convert to sRGB. + if (GrSRGBEncoded::kYes == GrPixelConfigIsSRGBEncoded(dstSurface->config())) { + return false; + } + ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); + break; + } + if (!this->onGetWritePixelsInfo(dstSurface, dstOrigin, width, height, srcConfig, drawPreference, tempDrawInfo)) { return false; diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h index baeb46ae75..87ddb76910 100644 --- a/src/gpu/GrGpu.h +++ b/src/gpu/GrGpu.h @@ -199,9 +199,9 @@ public: * that would allow a successful readPixels call. The passed width, height, and rowBytes, * must be non-zero and already reflect clipping to the src bounds. */ - bool getReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, - int readWidth, int readHeight, size_t rowBytes, - GrPixelConfig readConfig, DrawPreference*, ReadPixelTempDrawInfo*); + bool getReadPixelsInfo(GrSurface*, GrSurfaceOrigin, int width, + int height, size_t rowBytes, GrPixelConfig, + GrSRGBConversion, DrawPreference*, ReadPixelTempDrawInfo*); /** Info struct returned by getWritePixelsInfo about performing an intermediate draw in order to write pixels to a GrSurface for either performance or correctness reasons. */ @@ -226,18 +226,19 @@ public: * that would allow a successful transfer of the src pixels to the dst. The passed width, * height, and rowBytes, must be non-zero and already reflect clipping to the dst bounds. */ - bool getWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOrigin, int width, int height, - GrPixelConfig srcConfig, DrawPreference*, WritePixelTempDrawInfo*); + bool getWritePixelsInfo(GrSurface*, GrSurfaceOrigin, int width, int height, + GrPixelConfig, GrSRGBConversion, DrawPreference*, + WritePixelTempDrawInfo*); /** - * Reads a rectangle of pixels from a render target. + * Reads a rectangle of pixels from a render target. No sRGB/linear conversions are performed. * * @param surface The surface to read from * @param left left edge of the rectangle to read (inclusive) * @param top top edge of the rectangle to read (inclusive) * @param width width of rectangle to read in pixels. * @param height height of rectangle to read in pixels. - * @param config the pixel config of the destination buffer + * @param dstConfig the pixel config of the destination buffer * @param buffer memory to read the rectangle into. * @param rowBytes the number of bytes between consecutive rows. Zero * means rows are tightly packed. @@ -248,39 +249,32 @@ public: * because of a unsupported pixel config or because no render * target is currently set. */ - bool readPixels(GrSurface* surface, GrSurfaceOrigin, - int left, int top, int width, int height, - GrPixelConfig config, void* buffer, size_t rowBytes); + bool readPixels(GrSurface* surface, GrSurfaceOrigin, int left, int top, int width, int height, + GrPixelConfig dstConfig, void* buffer, size_t rowBytes); /** - * Updates the pixels in a rectangle of a surface. + * Updates the pixels in a rectangle of a surface. No sRGB/linear conversions are performed. * * @param surface The surface to write to. * @param left left edge of the rectangle to write (inclusive) * @param top top edge of the rectangle to write (inclusive) * @param width width of rectangle to write in pixels. * @param height height of rectangle to write in pixels. - * @param config the pixel config of the source buffer + * @param srcConfig the pixel config of the source buffer * @param texels array of mipmap levels containing texture data * @param mipLevelCount number of levels in 'texels' */ bool writePixels(GrSurface* surface, GrSurfaceOrigin origin, int left, int top, int width, int height, - GrPixelConfig config, + GrPixelConfig srcConfig, const GrMipLevel texels[], int mipLevelCount); /** * This function is a shim which creates a SkTArray<GrMipLevel> of size 1. * It then calls writePixels with that SkTArray. - * - * @param buffer memory to read pixels from. - * @param rowBytes number of bytes between consecutive rows. Zero - * means rows are tightly packed. */ - bool writePixels(GrSurface* surface, GrSurfaceOrigin origin, - int left, int top, int width, int height, - GrPixelConfig config, const void* buffer, - size_t rowBytes); + bool writePixels(GrSurface*, GrSurfaceOrigin, int left, int top, int width, + int height, GrPixelConfig, const void* buffer, size_t rowBytes); /** * Updates the pixels in a rectangle of a texture using a buffer @@ -293,16 +287,15 @@ public: * @param top top edge of the rectangle to write (inclusive) * @param width width of rectangle to write in pixels. * @param height height of rectangle to write in pixels. - * @param config the pixel config of the source buffer + * @param bufferConfig the pixel config of the source buffer * @param transferBuffer GrBuffer to read pixels from (type must be "kXferCpuToGpu") * @param offset offset from the start of the buffer * @param rowBytes number of bytes between consecutive rows in the buffer. Zero * means rows are tightly packed. */ - bool transferPixels(GrTexture* texture, - int left, int top, int width, int height, - GrPixelConfig config, GrBuffer* transferBuffer, - size_t offset, size_t rowBytes); + bool transferPixels(GrTexture* texture, int left, int top, int width, int height, + GrPixelConfig bufferConfig, GrBuffer* transferBuffer, size_t offset, + size_t rowBytes); // After the client interacts directly with the 3D context state the GrGpu // must resync its internal state and assumptions about 3D context state. @@ -567,14 +560,11 @@ private: return false; } - virtual bool onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, - int readWidth, int readHeight, - size_t rowBytes, GrPixelConfig readConfig, DrawPreference*, + virtual bool onGetReadPixelsInfo(GrSurface*, GrSurfaceOrigin, int width, int height, + size_t rowBytes, GrPixelConfig, DrawPreference*, ReadPixelTempDrawInfo*) = 0; - virtual bool onGetWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOrigin, - int width, int height, - GrPixelConfig srcConfig, DrawPreference*, - WritePixelTempDrawInfo*) = 0; + virtual bool onGetWritePixelsInfo(GrSurface*, GrSurfaceOrigin, int width, int height, + GrPixelConfig, DrawPreference*, WritePixelTempDrawInfo*) = 0; // overridden by backend-specific derived class to perform the surface read virtual bool onReadPixels(GrSurface*, GrSurfaceOrigin, diff --git a/src/gpu/GrOpFlushState.cpp b/src/gpu/GrOpFlushState.cpp index 06ee4d96db..4088addfc9 100644 --- a/src/gpu/GrOpFlushState.cpp +++ b/src/gpu/GrOpFlushState.cpp @@ -81,12 +81,13 @@ void GrOpFlushState::doUpload(GrDeferredTextureUploadFn& upload) { GrDeferredTextureUploadWritePixelsFn wp = [this](GrTextureProxy* dstProxy, int left, int top, int width, int height, GrPixelConfig srcConfig, const void* buffer, size_t rowBytes) { + // We don't allow srgb conversions via op flush state uploads. + static constexpr auto kSRGBConversion = GrSRGBConversion::kNone; GrSurface* dstSurface = dstProxy->priv().peekSurface(); GrGpu::DrawPreference drawPreference = GrGpu::kNoDraw_DrawPreference; GrGpu::WritePixelTempDrawInfo tempInfo; - if (!fGpu->getWritePixelsInfo(dstSurface, dstProxy->origin(), - width, height, srcConfig, - &drawPreference, &tempInfo)) { + if (!fGpu->getWritePixelsInfo(dstSurface, dstProxy->origin(), width, height, srcConfig, + kSRGBConversion, &drawPreference, &tempInfo)) { return false; } if (GrGpu::kNoDraw_DrawPreference == drawPreference) { diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp index 6563215acd..3cae7d3f3c 100644 --- a/src/gpu/gl/GrGLGpu.cpp +++ b/src/gpu/gl/GrGLGpu.cpp @@ -665,6 +665,14 @@ bool GrGLGpu::onGetWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOri GrPixelConfig srcConfig, DrawPreference* drawPreference, WritePixelTempDrawInfo* tempDrawInfo) { + if (*drawPreference != kNoDraw_DrawPreference) { + // We assume the base class has only inserted a draw for sRGB reasons. So the temp surface + // has the config of the original src data. There is no swizzling nor src config spoofing. + SkASSERT(tempDrawInfo->fWriteConfig == srcConfig); + SkASSERT(tempDrawInfo->fTempSurfaceDesc.fConfig == srcConfig); + SkASSERT(tempDrawInfo->fSwizzle == GrSwizzle::RGBA()); + } + if (SkToBool(dstSurface->asRenderTarget())) { if (this->glCaps().useDrawInsteadOfAllRenderTargetWrites()) { ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); @@ -694,10 +702,6 @@ bool GrGLGpu::onGetWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOri ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); } - if (GrPixelConfigIsSRGB(dstSurface->config()) != GrPixelConfigIsSRGB(srcConfig)) { - ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); - } - bool configsAreRBSwaps = GrPixelConfigSwapRAndB(srcConfig) == dstSurface->config(); if (configsAreRBSwaps) { @@ -731,16 +735,11 @@ bool GrGLGpu::onGetWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOri } static bool check_write_and_transfer_input(GrGLTexture* glTex, GrSurface* surface, - GrPixelConfig config) { + GrPixelConfig config) { if (!glTex) { return false; } - // OpenGL doesn't do sRGB <-> linear conversions when reading and writing pixels. - if (GrPixelConfigIsSRGB(surface->config()) != GrPixelConfigIsSRGB(config)) { - return false; - } - // Write or transfer of pixels is not implemented for TEXTURE_EXTERNAL textures if (GR_GL_TEXTURE_EXTERNAL == glTex->target()) { return false; @@ -749,23 +748,21 @@ static bool check_write_and_transfer_input(GrGLTexture* glTex, GrSurface* surfac return true; } -bool GrGLGpu::onWritePixels(GrSurface* surface, GrSurfaceOrigin origin, - int left, int top, int width, int height, - GrPixelConfig config, - const GrMipLevel texels[], - int mipLevelCount) { +bool GrGLGpu::onWritePixels(GrSurface* surface, GrSurfaceOrigin origin, int left, int top, + int width, int height, GrPixelConfig dstConfig, + const GrMipLevel texels[], int mipLevelCount) { GrGLTexture* glTex = static_cast<GrGLTexture*>(surface->asTexture()); - if (!check_write_and_transfer_input(glTex, surface, config)) { + if (!check_write_and_transfer_input(glTex, surface, dstConfig)) { return false; } this->setScratchTextureUnit(); GL_CALL(BindTexture(glTex->target(), glTex->textureID())); - return this->uploadTexData(glTex->config(), glTex->width(), glTex->height(), - origin, glTex->target(), kWrite_UploadType, - left, top, width, height, config, texels, mipLevelCount); + return this->uploadTexData(glTex->config(), glTex->width(), glTex->height(), origin, + glTex->target(), kWrite_UploadType, left, top, width, height, + dstConfig, texels, mipLevelCount); } // For GL_[UN]PACK_ALIGNMENT. @@ -798,10 +795,9 @@ static inline GrGLint config_alignment(GrPixelConfig config) { return 0; } -bool GrGLGpu::onTransferPixels(GrTexture* texture, - int left, int top, int width, int height, - GrPixelConfig config, GrBuffer* transferBuffer, - size_t offset, size_t rowBytes) { +bool GrGLGpu::onTransferPixels(GrTexture* texture, int left, int top, int width, int height, + GrPixelConfig config, GrBuffer* transferBuffer, size_t offset, + size_t rowBytes) { GrGLTexture* glTex = static_cast<GrGLTexture*>(texture); GrPixelConfig texConfig = glTex->config(); SkASSERT(this->caps()->isConfigTexturable(texConfig)); @@ -2163,44 +2159,31 @@ bool GrGLGpu::readPixelsSupported(GrSurface* surfaceForConfig, GrPixelConfig rea } } -static bool requires_srgb_conversion(GrPixelConfig a, GrPixelConfig b) { - if (GrPixelConfigIsSRGB(a)) { - return !GrPixelConfigIsSRGB(b) && !GrPixelConfigIsAlphaOnly(b); - } else if (GrPixelConfigIsSRGB(b)) { - return !GrPixelConfigIsSRGB(a) && !GrPixelConfigIsAlphaOnly(a); - } - return false; -} - -bool GrGLGpu::onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, - int width, int height, size_t rowBytes, - GrPixelConfig readConfig, DrawPreference* drawPreference, +bool GrGLGpu::onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, int width, + int height, size_t rowBytes, GrPixelConfig dstConfig, + DrawPreference* drawPreference, ReadPixelTempDrawInfo* tempDrawInfo) { + if (*drawPreference != kNoDraw_DrawPreference) { + // We assume the base class has only inserted a draw for sRGB reasons. So the + // the temp surface has the config of the dst data. There is no swizzling, nor dst config + // spoofing. + SkASSERT(tempDrawInfo->fReadConfig == dstConfig); + SkASSERT(tempDrawInfo->fTempSurfaceDesc.fConfig == dstConfig); + SkASSERT(tempDrawInfo->fSwizzle == GrSwizzle::RGBA()); + } GrPixelConfig srcConfig = srcSurface->config(); tempDrawInfo->fTempSurfaceFit = this->glCaps().partialFBOReadIsSlow() ? SkBackingFit::kExact : SkBackingFit::kApprox; - if (requires_srgb_conversion(srcConfig, readConfig)) { - if (!this->readPixelsSupported(readConfig, readConfig)) { - return false; - } - // Draw to do srgb to linear conversion or vice versa. - ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); - tempDrawInfo->fTempSurfaceDesc.fConfig = readConfig; - tempDrawInfo->fReadConfig = readConfig; - return true; - } - - if (this->glCaps().rgba8888PixelsOpsAreSlow() && kRGBA_8888_GrPixelConfig == readConfig && + if (this->glCaps().rgba8888PixelsOpsAreSlow() && kRGBA_8888_GrPixelConfig == dstConfig && this->readPixelsSupported(kBGRA_8888_GrPixelConfig, kBGRA_8888_GrPixelConfig)) { tempDrawInfo->fTempSurfaceDesc.fConfig = kBGRA_8888_GrPixelConfig; tempDrawInfo->fSwizzle = GrSwizzle::BGRA(); tempDrawInfo->fReadConfig = kBGRA_8888_GrPixelConfig; ElevateDrawPreference(drawPreference, kGpuPrefersDraw_DrawPreference); } else if (this->glCaps().rgbaToBgraReadbackConversionsAreSlow() && - GrBytesPerPixel(readConfig) == 4 && - GrPixelConfigSwapRAndB(readConfig) == srcConfig && + GrBytesPerPixel(dstConfig) == 4 && GrPixelConfigSwapRAndB(dstConfig) == srcConfig && this->readPixelsSupported(srcSurface, srcConfig)) { // Mesa 3D takes a slow path on when reading back BGRA from an RGBA surface and vice-versa. // Better to do a draw with a R/B swap and then read as the original config. @@ -2208,8 +2191,8 @@ bool GrGLGpu::onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrig tempDrawInfo->fSwizzle = GrSwizzle::BGRA(); tempDrawInfo->fReadConfig = srcConfig; ElevateDrawPreference(drawPreference, kGpuPrefersDraw_DrawPreference); - } else if (!this->readPixelsSupported(srcSurface, readConfig)) { - if (readConfig == kBGRA_8888_GrPixelConfig && + } else if (!this->readPixelsSupported(srcSurface, dstConfig)) { + if (dstConfig == kBGRA_8888_GrPixelConfig && this->glCaps().canConfigBeFBOColorAttachment(kRGBA_8888_GrPixelConfig) && this->readPixelsSupported(kRGBA_8888_GrPixelConfig, kRGBA_8888_GrPixelConfig)) { // We're trying to read BGRA but it's not supported. If RGBA is renderable and @@ -2219,9 +2202,10 @@ bool GrGLGpu::onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrig tempDrawInfo->fSwizzle = GrSwizzle::BGRA(); tempDrawInfo->fReadConfig = kRGBA_8888_GrPixelConfig; ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); - } else if (readConfig == kSBGRA_8888_GrPixelConfig && - this->glCaps().canConfigBeFBOColorAttachment(kSRGBA_8888_GrPixelConfig) && - this->readPixelsSupported(kSRGBA_8888_GrPixelConfig, kSRGBA_8888_GrPixelConfig)) { + } else if (dstConfig == kSBGRA_8888_GrPixelConfig && + this->glCaps().canConfigBeFBOColorAttachment(kSRGBA_8888_GrPixelConfig) && + this->readPixelsSupported(kSRGBA_8888_GrPixelConfig, + kSRGBA_8888_GrPixelConfig)) { // We're trying to read sBGRA but it's not supported. If sRGBA is renderable and // we can read it back, then do a swizzling draw to a sRGBA and read it back (which // will effectively be sBGRA). @@ -2229,45 +2213,40 @@ bool GrGLGpu::onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrig tempDrawInfo->fSwizzle = GrSwizzle::BGRA(); tempDrawInfo->fReadConfig = kSRGBA_8888_GrPixelConfig; ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); - } else if (readConfig == kAlpha_8_GrPixelConfig) { - // onReadPixels implements a fallback for cases where we are want to read kAlpha_8, + } else if (dstConfig == kAlpha_8_GrPixelConfig) { + // onReadPixels implements a fallback for cases where we want to read kAlpha_8, // it's unsupported, but 32bit RGBA reads are supported. - // Don't attempt to do any srgb conversions since we only care about alpha. - GrPixelConfig cpuTempConfig = kRGBA_8888_GrPixelConfig; - if (GrPixelConfigIsSRGB(srcSurface->config())) { - cpuTempConfig = kSRGBA_8888_GrPixelConfig; - } - if (!this->readPixelsSupported(srcSurface, cpuTempConfig)) { + if (!this->readPixelsSupported(srcSurface, kRGBA_8888_GrPixelConfig)) { // If we can't read RGBA from the src try to draw to a kRGBA_8888 (or kSRGBA_8888) // first and then onReadPixels will read that to a 32bit temporary buffer. - if (this->glCaps().canConfigBeFBOColorAttachment(cpuTempConfig)) { + if (this->glCaps().canConfigBeFBOColorAttachment(kRGBA_8888_GrPixelConfig)) { ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); - tempDrawInfo->fTempSurfaceDesc.fConfig = cpuTempConfig; + tempDrawInfo->fTempSurfaceDesc.fConfig = kRGBA_8888_GrPixelConfig; tempDrawInfo->fReadConfig = kAlpha_8_GrPixelConfig; } else { return false; } } else { - SkASSERT(tempDrawInfo->fTempSurfaceDesc.fConfig == srcConfig); + tempDrawInfo->fTempSurfaceDesc.fConfig = srcConfig; SkASSERT(tempDrawInfo->fReadConfig == kAlpha_8_GrPixelConfig); } - } else if (readConfig == kRGBA_half_GrPixelConfig && + } else if (dstConfig == kRGBA_half_GrPixelConfig && this->readPixelsSupported(srcSurface, kRGBA_float_GrPixelConfig)) { // If reading in half float format is not supported, then read in float format. return true; - } else if (this->glCaps().canConfigBeFBOColorAttachment(readConfig) && - this->readPixelsSupported(readConfig, readConfig)) { + } else if (this->glCaps().canConfigBeFBOColorAttachment(dstConfig) && + this->readPixelsSupported(dstConfig, dstConfig)) { // Do a draw to convert from the src config to the read config. ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); - tempDrawInfo->fTempSurfaceDesc.fConfig = readConfig; - tempDrawInfo->fReadConfig = readConfig; + tempDrawInfo->fTempSurfaceDesc.fConfig = dstConfig; + tempDrawInfo->fReadConfig = dstConfig; } else { return false; } } if ((srcSurface->asRenderTarget() || this->glCaps().canConfigBeFBOColorAttachment(srcConfig)) && - read_pixels_pays_for_y_flip(srcOrigin, this->glCaps(), width, height, readConfig, + read_pixels_pays_for_y_flip(srcOrigin, this->glCaps(), width, height, dstConfig, rowBytes)) { ElevateDrawPreference(drawPreference, kGpuPrefersDraw_DrawPreference); } @@ -2288,19 +2267,10 @@ bool GrGLGpu::onReadPixels(GrSurface* surface, GrSurfaceOrigin origin, return false; } - // OpenGL doesn't do sRGB <-> linear conversions when reading and writing pixels. - if (requires_srgb_conversion(surface->config(), config)) { - return false; - } - // We have a special case fallback for reading eight bit alpha. We will read back all four 8 // bit channels as RGBA and then extract A. if (!this->readPixelsSupported(surface, config)) { - // Don't attempt to do any srgb conversions since we only care about alpha. GrPixelConfig tempConfig = kRGBA_8888_GrPixelConfig; - if (GrPixelConfigIsSRGB(surface->config())) { - tempConfig = kSRGBA_8888_GrPixelConfig; - } if (kAlpha_8_GrPixelConfig == config && this->readPixelsSupported(surface, tempConfig)) { std::unique_ptr<uint32_t[]> temp(new uint32_t[width * height * 4]); diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h index 48a0e2bcf9..93b4de167d 100644 --- a/src/gpu/gl/GrGLGpu.h +++ b/src/gpu/gl/GrGLGpu.h @@ -63,16 +63,6 @@ public: void generateMipmaps(const GrSamplerState& params, bool allowSRGBInputs, GrGLTexture* texture, GrSurfaceOrigin textureOrigin); - bool onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, - int readWidth, int readHeight, size_t rowBytes, - GrPixelConfig readConfig, DrawPreference*, - ReadPixelTempDrawInfo*) override; - - bool onGetWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOrigin, - int width, int height, - GrPixelConfig srcConfig, DrawPreference*, - WritePixelTempDrawInfo*) override; - // These functions should be used to bind GL objects. They track the GL state and skip redundant // bindings. Making the equivalent glBind calls directly will confuse the state tracking. void bindVertexArray(GrGLuint id) { @@ -240,6 +230,12 @@ private: // variations above, depending on whether the surface is a render target or not. bool readPixelsSupported(GrSurface* surfaceForConfig, GrPixelConfig readConfig); + bool onGetReadPixelsInfo(GrSurface*, GrSurfaceOrigin, int width, int height, size_t rowBytes, + GrPixelConfig, DrawPreference*, ReadPixelTempDrawInfo*) override; + + bool onGetWritePixelsInfo(GrSurface*, GrSurfaceOrigin, int width, int height, GrPixelConfig, + DrawPreference*, WritePixelTempDrawInfo*) override; + bool onReadPixels(GrSurface*, GrSurfaceOrigin, int left, int top, int width, int height, diff --git a/src/gpu/mock/GrMockGpu.h b/src/gpu/mock/GrMockGpu.h index 6221d9178f..4cbd49ca30 100644 --- a/src/gpu/mock/GrMockGpu.h +++ b/src/gpu/mock/GrMockGpu.h @@ -25,25 +25,6 @@ public: ~GrMockGpu() override {} - bool onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, - int readWidth, int readHeight, size_t rowBytes, - GrPixelConfig readConfig, DrawPreference*, - ReadPixelTempDrawInfo*) override { return true; } - - bool onGetWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOrigin, - int width, int height, - GrPixelConfig srcConfig, DrawPreference*, - WritePixelTempDrawInfo*) override { return true; } - - bool onCopySurface(GrSurface* dst, GrSurfaceOrigin dstOrigin, - GrSurface* src, GrSurfaceOrigin srcOrigin, - const SkIRect& srcRect, const SkIPoint& dstPoint) override { return true; } - - void onQueryMultisampleSpecs(GrRenderTarget* rt, GrSurfaceOrigin, const GrStencilSettings&, - int* effectiveSampleCnt, SamplePattern*) override { - *effectiveSampleCnt = rt->numStencilSamples(); - } - GrGpuRTCommandBuffer* createCommandBuffer( GrRenderTarget*, GrSurfaceOrigin, const GrGpuRTCommandBuffer::LoadAndStoreInfo&, @@ -99,6 +80,18 @@ private: GrBuffer* onCreateBuffer(size_t sizeInBytes, GrBufferType, GrAccessPattern, const void*) override; + bool onGetReadPixelsInfo(GrSurface*, GrSurfaceOrigin, int width, int height, + size_t rowBytes, GrPixelConfig, DrawPreference*, + ReadPixelTempDrawInfo*) override { + return true; + } + + bool onGetWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin, int width, + int height, GrPixelConfig, DrawPreference*, + WritePixelTempDrawInfo*) override { + return true; + } + bool onReadPixels(GrSurface* surface, GrSurfaceOrigin, int left, int top, int width, int height, GrPixelConfig, @@ -121,6 +114,17 @@ private: return true; } + bool onCopySurface(GrSurface* dst, GrSurfaceOrigin dstOrigin, GrSurface* src, + GrSurfaceOrigin srcOrigin, const SkIRect& srcRect, + const SkIPoint& dstPoint) override { + return true; + } + + void onQueryMultisampleSpecs(GrRenderTarget* rt, GrSurfaceOrigin, const GrStencilSettings&, + int* effectiveSampleCnt, SamplePattern*) override { + *effectiveSampleCnt = rt->numStencilSamples(); + } + void onResolveRenderTarget(GrRenderTarget* target) override { return; } void onFinishFlush(bool insertedSemaphores) override {} diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp index 35c2fcc815..daa4057d6d 100644 --- a/src/gpu/vk/GrVkGpu.cpp +++ b/src/gpu/vk/GrVkGpu.cpp @@ -340,6 +340,14 @@ bool GrVkGpu::onGetWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOri int width, int height, GrPixelConfig srcConfig, DrawPreference* drawPreference, WritePixelTempDrawInfo* tempDrawInfo) { + if (*drawPreference != kNoDraw_DrawPreference) { + // We assume the base class has only inserted a draw for sRGB reasons. So the temp surface + // has the config of the original src data. There is no swizzling nor src config spoofing. + SkASSERT(tempDrawInfo->fWriteConfig == srcConfig); + SkASSERT(tempDrawInfo->fTempSurfaceDesc.fConfig == srcConfig); + SkASSERT(tempDrawInfo->fSwizzle == GrSwizzle::RGBA()); + } + GrRenderTarget* renderTarget = dstSurface->asRenderTarget(); if (dstSurface->config() == srcConfig) { @@ -381,11 +389,6 @@ bool GrVkGpu::onWritePixels(GrSurface* surface, GrSurfaceOrigin origin, return false; } - // We assume Vulkan doesn't do sRGB <-> linear conversions when reading and writing pixels. - if (GrPixelConfigIsSRGB(surface->config()) != GrPixelConfigIsSRGB(config)) { - return false; - } - bool success = false; bool linearTiling = vkTex->isLinearTiled(); if (linearTiling) { @@ -435,11 +438,6 @@ bool GrVkGpu::onTransferPixels(GrTexture* texture, return false; } - // We assume Vulkan doesn't do sRGB <-> linear conversions when reading and writing pixels. - if (GrPixelConfigIsSRGB(texture->config()) != GrPixelConfigIsSRGB(config)) { - return false; - } - SkDEBUGCODE( SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height); SkIRect bounds = SkIRect::MakeWH(texture->width(), texture->height()); @@ -1859,29 +1857,35 @@ void GrVkGpu::onQueryMultisampleSpecs(GrRenderTarget* rt, GrSurfaceOrigin, const *effectiveSampleCnt = rt->numStencilSamples(); } -bool GrVkGpu::onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, - int width, int height, size_t rowBytes, - GrPixelConfig readConfig, DrawPreference* drawPreference, +bool GrVkGpu::onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, int width, + int height, size_t rowBytes, GrPixelConfig dstConfig, + DrawPreference* drawPreference, ReadPixelTempDrawInfo* tempDrawInfo) { - if (srcSurface->config() == readConfig) { + if (*drawPreference != kNoDraw_DrawPreference) { + // We assume the base class has only inserted a draw for sRGB reasons. So the + // the temp surface has the config of the dst data. There is no swizzling nor dst config. + // spoofing. + SkASSERT(tempDrawInfo->fReadConfig == dstConfig); + SkASSERT(tempDrawInfo->fTempSurfaceDesc.fConfig == dstConfig); + SkASSERT(tempDrawInfo->fSwizzle == GrSwizzle::RGBA()); + } + + if (srcSurface->config() == dstConfig) { return true; } // Any config change requires a draw ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference); - tempDrawInfo->fTempSurfaceDesc.fConfig = readConfig; - tempDrawInfo->fReadConfig = readConfig; + tempDrawInfo->fTempSurfaceDesc.fConfig = dstConfig; + tempDrawInfo->fReadConfig = dstConfig; return true; } -bool GrVkGpu::onReadPixels(GrSurface* surface, GrSurfaceOrigin origin, - int left, int top, int width, int height, - GrPixelConfig config, - void* buffer, - size_t rowBytes) { +bool GrVkGpu::onReadPixels(GrSurface* surface, GrSurfaceOrigin origin, int left, int top, int width, + int height, GrPixelConfig dstConfig, void* buffer, size_t rowBytes) { VkFormat pixelFormat; - if (!GrPixelConfigToVkFormat(config, &pixelFormat)) { + if (!GrPixelConfigToVkFormat(dstConfig, &pixelFormat)) { return false; } @@ -1916,7 +1920,7 @@ bool GrVkGpu::onReadPixels(GrSurface* surface, GrSurfaceOrigin origin, VK_PIPELINE_STAGE_TRANSFER_BIT, false); - size_t bpp = GrBytesPerPixel(config); + size_t bpp = GrBytesPerPixel(dstConfig); size_t tightRowBytes = bpp * width; bool flipY = kBottomLeft_GrSurfaceOrigin == origin; diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h index 5481bf2c42..4302f7f619 100644 --- a/src/gpu/vk/GrVkGpu.h +++ b/src/gpu/vk/GrVkGpu.h @@ -64,23 +64,6 @@ public: kSkip_SyncQueue }; - bool onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, - int readWidth, int readHeight, size_t rowBytes, - GrPixelConfig readConfig, DrawPreference*, - ReadPixelTempDrawInfo*) override; - - bool onGetWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOrigin, - int width, int height, - GrPixelConfig srcConfig, DrawPreference*, - WritePixelTempDrawInfo*) override; - - bool onCopySurface(GrSurface* dst, GrSurfaceOrigin dstOrigin, - GrSurface* src, GrSurfaceOrigin srcOrigin, - const SkIRect& srcRect, const SkIPoint& dstPoint) override; - - void onQueryMultisampleSpecs(GrRenderTarget*, GrSurfaceOrigin, const GrStencilSettings&, - int* effectiveSampleCnt, SamplePattern*) override; - void xferBarrier(GrRenderTarget*, GrXferBarrierType) override {} GrBackendTexture createTestingOnlyBackendTexture(void* pixels, int w, int h, @@ -193,11 +176,14 @@ private: GrBuffer* onCreateBuffer(size_t size, GrBufferType type, GrAccessPattern, const void* data) override; - bool onReadPixels(GrSurface* surface, GrSurfaceOrigin, - int left, int top, int width, int height, - GrPixelConfig, - void* buffer, - size_t rowBytes) override; + bool onGetReadPixelsInfo(GrSurface*, GrSurfaceOrigin, int width, int height, size_t rowBytes, + GrPixelConfig, DrawPreference*, ReadPixelTempDrawInfo*) override; + + bool onGetWritePixelsInfo(GrSurface*, GrSurfaceOrigin, int width, int height, GrPixelConfig, + DrawPreference*, WritePixelTempDrawInfo*) override; + + bool onReadPixels(GrSurface* surface, GrSurfaceOrigin, int left, int top, int width, int height, + GrPixelConfig, void* buffer, size_t rowBytes) override; bool onWritePixels(GrSurface* surface, GrSurfaceOrigin, int left, int top, int width, int height, @@ -208,6 +194,13 @@ private: GrPixelConfig config, GrBuffer* transferBuffer, size_t offset, size_t rowBytes) override; + bool onCopySurface(GrSurface* dst, GrSurfaceOrigin dstOrigin, GrSurface* src, + GrSurfaceOrigin srcOrigin, const SkIRect& srcRect, + const SkIPoint& dstPoint) override; + + void onQueryMultisampleSpecs(GrRenderTarget*, GrSurfaceOrigin, const GrStencilSettings&, + int* effectiveSampleCnt, SamplePattern*) override; + void onFinishFlush(bool insertedSemaphores) override; // Ends and submits the current command buffer to the queue and then creates a new command |