Home | History | Annotate | Download | only in gpu
      1 /*
      2  * Copyright 2010 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 
      9 #include "GrGpu.h"
     10 
     11 #include "GrBackendSemaphore.h"
     12 #include "GrBackendSurface.h"
     13 #include "GrBuffer.h"
     14 #include "GrCaps.h"
     15 #include "GrContext.h"
     16 #include "GrContextPriv.h"
     17 #include "GrGpuResourcePriv.h"
     18 #include "GrMesh.h"
     19 #include "GrPathRendering.h"
     20 #include "GrPipeline.h"
     21 #include "GrRenderTargetPriv.h"
     22 #include "GrResourceCache.h"
     23 #include "GrResourceProvider.h"
     24 #include "GrSemaphore.h"
     25 #include "GrStencilAttachment.h"
     26 #include "GrStencilSettings.h"
     27 #include "GrSurfacePriv.h"
     28 #include "GrTexturePriv.h"
     29 #include "GrTracing.h"
     30 #include "SkJSONWriter.h"
     31 #include "SkMathPriv.h"
     32 
     33 ////////////////////////////////////////////////////////////////////////////////
     34 
     35 GrGpu::GrGpu(GrContext* context)
     36     : fResetTimestamp(kExpiredTimestamp+1)
     37     , fResetBits(kAll_GrBackendState)
     38     , fContext(context) {
     39 }
     40 
     41 GrGpu::~GrGpu() {}
     42 
     43 void GrGpu::disconnect(DisconnectType) {}
     44 
     45 ////////////////////////////////////////////////////////////////////////////////
     46 
     47 bool GrGpu::isACopyNeededForTextureParams(int width, int height,
     48                                           const GrSamplerState& textureParams,
     49                                           GrTextureProducer::CopyParams* copyParams,
     50                                           SkScalar scaleAdjust[2]) const {
     51     const GrCaps& caps = *this->caps();
     52     if (textureParams.isRepeated() && !caps.npotTextureTileSupport() &&
     53         (!SkIsPow2(width) || !SkIsPow2(height))) {
     54         SkASSERT(scaleAdjust);
     55         copyParams->fWidth = GrNextPow2(width);
     56         copyParams->fHeight = GrNextPow2(height);
     57         SkASSERT(scaleAdjust);
     58         scaleAdjust[0] = ((SkScalar) copyParams->fWidth) / width;
     59         scaleAdjust[1] = ((SkScalar) copyParams->fHeight) / height;
     60         switch (textureParams.filter()) {
     61             case GrSamplerState::Filter::kNearest:
     62                 copyParams->fFilter = GrSamplerState::Filter::kNearest;
     63                 break;
     64             case GrSamplerState::Filter::kBilerp:
     65             case GrSamplerState::Filter::kMipMap:
     66                 // We are only ever scaling up so no reason to ever indicate kMipMap.
     67                 copyParams->fFilter = GrSamplerState::Filter::kBilerp;
     68                 break;
     69         }
     70         return true;
     71     }
     72     return false;
     73 }
     74 
     75 sk_sp<GrTexture> GrGpu::createTexture(const GrSurfaceDesc& origDesc, SkBudgeted budgeted,
     76                                       const GrMipLevel texels[], int mipLevelCount) {
     77     GR_CREATE_TRACE_MARKER_CONTEXT("GrGpu", "createTexture", fContext);
     78     GrSurfaceDesc desc = origDesc;
     79 
     80     GrMipMapped mipMapped = mipLevelCount > 1 ? GrMipMapped::kYes : GrMipMapped::kNo;
     81     if (!this->caps()->validateSurfaceDesc(desc, mipMapped)) {
     82         return nullptr;
     83     }
     84 
     85     bool isRT = desc.fFlags & kRenderTarget_GrSurfaceFlag;
     86     if (isRT) {
     87         desc.fSampleCnt = this->caps()->getRenderTargetSampleCount(desc.fSampleCnt, desc.fConfig);
     88     }
     89     // Attempt to catch un- or wrongly initialized sample counts.
     90     SkASSERT(desc.fSampleCnt > 0 && desc.fSampleCnt <= 64);
     91 
     92     if (mipLevelCount && (desc.fFlags & kPerformInitialClear_GrSurfaceFlag)) {
     93         return nullptr;
     94     }
     95 
     96     this->handleDirtyContext();
     97     sk_sp<GrTexture> tex = this->onCreateTexture(desc, budgeted, texels, mipLevelCount);
     98     if (tex) {
     99         if (!this->caps()->reuseScratchTextures() && !isRT) {
    100             tex->resourcePriv().removeScratchKey();
    101         }
    102         fStats.incTextureCreates();
    103         if (mipLevelCount) {
    104             if (texels[0].fPixels) {
    105                 fStats.incTextureUploads();
    106             }
    107         }
    108     }
    109     return tex;
    110 }
    111 
    112 sk_sp<GrTexture> GrGpu::createTexture(const GrSurfaceDesc& desc, SkBudgeted budgeted) {
    113     return this->createTexture(desc, budgeted, nullptr, 0);
    114 }
    115 
    116 sk_sp<GrTexture> GrGpu::wrapBackendTexture(const GrBackendTexture& backendTex,
    117                                            GrWrapOwnership ownership) {
    118     this->handleDirtyContext();
    119     if (!this->caps()->isConfigTexturable(backendTex.config())) {
    120         return nullptr;
    121     }
    122     if (backendTex.width() > this->caps()->maxTextureSize() ||
    123         backendTex.height() > this->caps()->maxTextureSize()) {
    124         return nullptr;
    125     }
    126     sk_sp<GrTexture> tex = this->onWrapBackendTexture(backendTex, ownership);
    127     if (!tex) {
    128         return nullptr;
    129     }
    130     return tex;
    131 }
    132 
    133 sk_sp<GrTexture> GrGpu::wrapRenderableBackendTexture(const GrBackendTexture& backendTex,
    134                                                      int sampleCnt, GrWrapOwnership ownership) {
    135     this->handleDirtyContext();
    136     if (sampleCnt < 1) {
    137         return nullptr;
    138     }
    139     if (!this->caps()->isConfigTexturable(backendTex.config()) ||
    140         !this->caps()->getRenderTargetSampleCount(sampleCnt, backendTex.config())) {
    141         return nullptr;
    142     }
    143 
    144     if (backendTex.width() > this->caps()->maxRenderTargetSize() ||
    145         backendTex.height() > this->caps()->maxRenderTargetSize()) {
    146         return nullptr;
    147     }
    148     sk_sp<GrTexture> tex = this->onWrapRenderableBackendTexture(backendTex, sampleCnt, ownership);
    149     if (!tex) {
    150         return nullptr;
    151     }
    152     SkASSERT(tex->asRenderTarget());
    153     return tex;
    154 }
    155 
    156 sk_sp<GrRenderTarget> GrGpu::wrapBackendRenderTarget(const GrBackendRenderTarget& backendRT) {
    157     if (0 == this->caps()->getRenderTargetSampleCount(backendRT.sampleCnt(), backendRT.config())) {
    158         return nullptr;
    159     }
    160     this->handleDirtyContext();
    161     return this->onWrapBackendRenderTarget(backendRT);
    162 }
    163 
    164 sk_sp<GrRenderTarget> GrGpu::wrapBackendTextureAsRenderTarget(const GrBackendTexture& tex,
    165                                                               int sampleCnt) {
    166     if (0 == this->caps()->getRenderTargetSampleCount(sampleCnt, tex.config())) {
    167         return nullptr;
    168     }
    169     int maxSize = this->caps()->maxTextureSize();
    170     if (tex.width() > maxSize || tex.height() > maxSize) {
    171         return nullptr;
    172     }
    173     this->handleDirtyContext();
    174     return this->onWrapBackendTextureAsRenderTarget(tex, sampleCnt);
    175 }
    176 
    177 GrBuffer* GrGpu::createBuffer(size_t size, GrBufferType intendedType,
    178                               GrAccessPattern accessPattern, const void* data) {
    179     this->handleDirtyContext();
    180     GrBuffer* buffer = this->onCreateBuffer(size, intendedType, accessPattern, data);
    181     if (!this->caps()->reuseScratchBuffers()) {
    182         buffer->resourcePriv().removeScratchKey();
    183     }
    184     return buffer;
    185 }
    186 
    187 bool GrGpu::copySurface(GrSurface* dst, GrSurfaceOrigin dstOrigin,
    188                         GrSurface* src, GrSurfaceOrigin srcOrigin,
    189                         const SkIRect& srcRect, const SkIPoint& dstPoint) {
    190     GR_CREATE_TRACE_MARKER_CONTEXT("GrGpu", "copySurface", fContext);
    191     SkASSERT(dst && src);
    192     this->handleDirtyContext();
    193     return this->onCopySurface(dst, dstOrigin, src, srcOrigin, srcRect, dstPoint);
    194 }
    195 
    196 bool GrGpu::getReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin, int width,
    197                               int height, size_t rowBytes, GrColorType dstColorType,
    198                               GrSRGBConversion srgbConversion, DrawPreference* drawPreference,
    199                               ReadPixelTempDrawInfo* tempDrawInfo) {
    200     SkASSERT(drawPreference);
    201     SkASSERT(tempDrawInfo);
    202     SkASSERT(srcSurface);
    203     SkASSERT(kGpuPrefersDraw_DrawPreference != *drawPreference);
    204 
    205     // We currently do not support reading into the packed formats 565 or 4444 as they are not
    206     // required to have read back support on all devices and backends.
    207     if (GrColorType::kRGB_565 == dstColorType || GrColorType::kABGR_4444 == dstColorType) {
    208         return false;
    209     }
    210 
    211     GrPixelConfig tempSurfaceConfig = kUnknown_GrPixelConfig;
    212     // GrGpu::readPixels doesn't do any sRGB conversions, so we must draw if there is one.
    213     switch (srgbConversion) {
    214         case GrSRGBConversion::kNone:
    215             // We support reading from RGBA to just A. In that case there is no sRGB version of the
    216             // dst format but we still want to succeed.
    217             if (GrColorTypeIsAlphaOnly(dstColorType)) {
    218                 tempSurfaceConfig = GrColorTypeToPixelConfig(dstColorType, GrSRGBEncoded::kNo);
    219             } else {
    220                 tempSurfaceConfig = GrColorTypeToPixelConfig(
    221                         dstColorType, GrPixelConfigIsSRGBEncoded(srcSurface->config()));
    222             }
    223             break;
    224         case GrSRGBConversion::kLinearToSRGB:
    225             SkASSERT(this->caps()->srgbSupport());
    226             tempSurfaceConfig = GrColorTypeToPixelConfig(dstColorType, GrSRGBEncoded::kYes);
    227             // Currently we don't expect to make a SRGB encoded surface and then read data from it
    228             // such that we treat it as though it were linear and is then converted to sRGB.
    229             if (GrPixelConfigIsSRGB(srcSurface->config())) {
    230                 return false;
    231             }
    232             ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference);
    233             break;
    234         case GrSRGBConversion::kSRGBToLinear:
    235             SkASSERT(this->caps()->srgbSupport());
    236             tempSurfaceConfig = GrColorTypeToPixelConfig(dstColorType, GrSRGBEncoded::kNo);
    237             // We don't currently support reading sRGB encoded data into linear from a surface
    238             // unless it is an sRGB-encoded config. That is likely to change when we need to store
    239             // sRGB encoded data in 101010102 and F16 textures. We'll have to provoke the caller to
    240             // do the conversion in a shader.
    241             if (GrSRGBEncoded::kNo == GrPixelConfigIsSRGBEncoded(srcSurface->config())) {
    242                 return false;
    243             }
    244             ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference);
    245             break;
    246     }
    247     if (kUnknown_GrPixelConfig == tempSurfaceConfig) {
    248         return false;
    249     }
    250 
    251     // Default values for intermediate draws. The intermediate texture config matches the dst's
    252     // config, is approx sized to the read rect, no swizzling or spoofing of the dst config.
    253     tempDrawInfo->fTempSurfaceDesc.fFlags = kRenderTarget_GrSurfaceFlag;
    254     tempDrawInfo->fTempSurfaceDesc.fWidth = width;
    255     tempDrawInfo->fTempSurfaceDesc.fHeight = height;
    256     tempDrawInfo->fTempSurfaceDesc.fSampleCnt = 1;
    257     tempDrawInfo->fTempSurfaceDesc.fOrigin = kTopLeft_GrSurfaceOrigin;  // no CPU y-flip for TL.
    258     tempDrawInfo->fTempSurfaceDesc.fConfig = tempSurfaceConfig;
    259     tempDrawInfo->fTempSurfaceFit = SkBackingFit::kApprox;
    260     tempDrawInfo->fSwizzle = GrSwizzle::RGBA();
    261     tempDrawInfo->fReadColorType = dstColorType;
    262 
    263     if (!this->onGetReadPixelsInfo(srcSurface, srcOrigin, width, height, rowBytes, dstColorType,
    264                                    drawPreference, tempDrawInfo)) {
    265         return false;
    266     }
    267 
    268     // Check to see if we're going to request that the caller draw when drawing is not possible.
    269     if (!srcSurface->asTexture() ||
    270         !this->caps()->isConfigRenderable(tempDrawInfo->fTempSurfaceDesc.fConfig)) {
    271         // If we don't have a fallback to a straight read then fail.
    272         if (kRequireDraw_DrawPreference == *drawPreference) {
    273             return false;
    274         }
    275         *drawPreference = kNoDraw_DrawPreference;
    276     }
    277 
    278     return true;
    279 }
    280 
    281 bool GrGpu::getWritePixelsInfo(GrSurface* dstSurface, GrSurfaceOrigin dstOrigin, int width,
    282                                int height, GrColorType srcColorType,
    283                                GrSRGBConversion srgbConversion, DrawPreference* drawPreference,
    284                                WritePixelTempDrawInfo* tempDrawInfo) {
    285     SkASSERT(drawPreference);
    286     SkASSERT(tempDrawInfo);
    287     SkASSERT(dstSurface);
    288     SkASSERT(kGpuPrefersDraw_DrawPreference != *drawPreference);
    289 
    290     GrPixelConfig tempSurfaceConfig = kUnknown_GrPixelConfig;
    291     // GrGpu::writePixels doesn't do any sRGB conversions, so we must draw if there is one.
    292     switch (srgbConversion) {
    293         case GrSRGBConversion::kNone:
    294             // We support writing just A to a RGBA. In that case there is no sRGB version of the
    295             // src format but we still want to succeed.
    296             if (GrColorTypeIsAlphaOnly(srcColorType)) {
    297                 tempSurfaceConfig = GrColorTypeToPixelConfig(srcColorType, GrSRGBEncoded::kNo);
    298             } else {
    299                 tempSurfaceConfig = GrColorTypeToPixelConfig(
    300                         srcColorType, GrPixelConfigIsSRGBEncoded(dstSurface->config()));
    301             }
    302             break;
    303         case GrSRGBConversion::kLinearToSRGB:
    304             SkASSERT(this->caps()->srgbSupport());
    305             // This assert goes away when we start referring to CPU data using color type.
    306             tempSurfaceConfig = GrColorTypeToPixelConfig(srcColorType, GrSRGBEncoded::kNo);
    307             // We don't currently support storing sRGB encoded data in a surface unless it is
    308             // an SRGB-encoded config. That is likely to change when we need to store sRGB encoded
    309             // data in 101010102 and F16 textures. We'll have to provoke the caller to do the
    310             // conversion in a shader.
    311             if (!GrPixelConfigIsSRGB(dstSurface->config())) {
    312                 return false;
    313             }
    314             ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference);
    315             break;
    316         case GrSRGBConversion::kSRGBToLinear:
    317             SkASSERT(this->caps()->srgbSupport());
    318             tempSurfaceConfig = GrColorTypeToPixelConfig(srcColorType, GrSRGBEncoded::kYes);
    319             // Currently we don't expect to make a SRGB encoded surface and then succeed at
    320             // treating it as though it were linear and then convert to sRGB.
    321             if (GrSRGBEncoded::kYes == GrPixelConfigIsSRGBEncoded(dstSurface->config())) {
    322                 return false;
    323             }
    324             ElevateDrawPreference(drawPreference, kRequireDraw_DrawPreference);
    325             break;
    326     }
    327     if (kUnknown_GrPixelConfig == tempSurfaceConfig) {
    328         return false;
    329     }
    330 
    331     // Default values for intermediate draws. The intermediate texture config matches the dst's
    332     // config, is approx sized to the write rect, no swizzling or sppofing of the src config.
    333     tempDrawInfo->fTempSurfaceDesc.fFlags = kNone_GrSurfaceFlags;
    334     tempDrawInfo->fTempSurfaceDesc.fConfig = tempSurfaceConfig;
    335     tempDrawInfo->fTempSurfaceDesc.fWidth = width;
    336     tempDrawInfo->fTempSurfaceDesc.fHeight = height;
    337     tempDrawInfo->fTempSurfaceDesc.fSampleCnt = 1;
    338     tempDrawInfo->fTempSurfaceDesc.fOrigin = kTopLeft_GrSurfaceOrigin;  // no CPU y-flip for TL.
    339     tempDrawInfo->fSwizzle = GrSwizzle::RGBA();
    340     tempDrawInfo->fWriteColorType = srcColorType;
    341 
    342     if (!this->onGetWritePixelsInfo(dstSurface, dstOrigin, width, height, srcColorType,
    343                                     drawPreference, tempDrawInfo)) {
    344         return false;
    345     }
    346 
    347     // Check to see if we're going to request that the caller draw when drawing is not possible.
    348     if (!dstSurface->asRenderTarget() ||
    349         !this->caps()->isConfigTexturable(tempDrawInfo->fTempSurfaceDesc.fConfig)) {
    350         // If we don't have a fallback to a straight upload then fail.
    351         if (kRequireDraw_DrawPreference == *drawPreference /*TODO ||
    352             !this->caps()->isConfigTexturable(srcConfig)*/) {
    353             return false;
    354         }
    355         *drawPreference = kNoDraw_DrawPreference;
    356     }
    357     return true;
    358 }
    359 
    360 bool GrGpu::readPixels(GrSurface* surface, GrSurfaceOrigin origin, int left, int top, int width,
    361                        int height, GrColorType dstColorType, void* buffer, size_t rowBytes) {
    362     SkASSERT(surface);
    363 
    364     int bpp = GrColorTypeBytesPerPixel(dstColorType);
    365     if (!GrSurfacePriv::AdjustReadPixelParams(surface->width(), surface->height(), bpp,
    366                                               &left, &top, &width, &height,
    367                                               &buffer,
    368                                               &rowBytes)) {
    369         return false;
    370     }
    371 
    372     this->handleDirtyContext();
    373 
    374     return this->onReadPixels(surface, origin, left, top, width, height, dstColorType, buffer,
    375                               rowBytes);
    376 }
    377 
    378 bool GrGpu::writePixels(GrSurface* surface, GrSurfaceOrigin origin, int left, int top, int width,
    379                         int height, GrColorType srcColorType, const GrMipLevel texels[],
    380                         int mipLevelCount) {
    381     SkASSERT(surface);
    382     if (1 == mipLevelCount) {
    383         // We require that if we are not mipped, then the write region is contained in the surface
    384         SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height);
    385         SkIRect bounds = SkIRect::MakeWH(surface->width(), surface->height());
    386         if (!bounds.contains(subRect)) {
    387             return false;
    388         }
    389     } else if (0 != left || 0 != top || width != surface->width() || height != surface->height()) {
    390         // We require that if the texels are mipped, than the write region is the entire surface
    391         return false;
    392     }
    393 
    394     for (int currentMipLevel = 0; currentMipLevel < mipLevelCount; currentMipLevel++) {
    395         if (!texels[currentMipLevel].fPixels ) {
    396             return false;
    397         }
    398     }
    399 
    400     this->handleDirtyContext();
    401     if (this->onWritePixels(surface, origin, left, top, width, height, srcColorType, texels,
    402                             mipLevelCount)) {
    403         SkIRect rect = SkIRect::MakeXYWH(left, top, width, height);
    404         this->didWriteToSurface(surface, origin, &rect, mipLevelCount);
    405         fStats.incTextureUploads();
    406         return true;
    407     }
    408     return false;
    409 }
    410 
    411 bool GrGpu::writePixels(GrSurface* surface, GrSurfaceOrigin origin, int left, int top, int width,
    412                         int height, GrColorType srcColorType, const void* buffer, size_t rowBytes) {
    413     GrMipLevel mipLevel = { buffer, rowBytes };
    414 
    415     return this->writePixels(surface, origin, left, top, width, height, srcColorType, &mipLevel, 1);
    416 }
    417 
    418 bool GrGpu::transferPixels(GrTexture* texture, int left, int top, int width, int height,
    419                            GrColorType bufferColorType, GrBuffer* transferBuffer, size_t offset,
    420                            size_t rowBytes) {
    421     SkASSERT(transferBuffer);
    422 
    423     // We require that the write region is contained in the texture
    424     SkIRect subRect = SkIRect::MakeXYWH(left, top, width, height);
    425     SkIRect bounds = SkIRect::MakeWH(texture->width(), texture->height());
    426     if (!bounds.contains(subRect)) {
    427         return false;
    428     }
    429 
    430     this->handleDirtyContext();
    431     if (this->onTransferPixels(texture, left, top, width, height, bufferColorType, transferBuffer,
    432                                offset, rowBytes)) {
    433         SkIRect rect = SkIRect::MakeXYWH(left, top, width, height);
    434         this->didWriteToSurface(texture, kTopLeft_GrSurfaceOrigin, &rect);
    435         fStats.incTransfersToTexture();
    436 
    437         return true;
    438     }
    439     return false;
    440 }
    441 
    442 void GrGpu::resolveRenderTarget(GrRenderTarget* target) {
    443     SkASSERT(target);
    444     this->handleDirtyContext();
    445     this->onResolveRenderTarget(target);
    446 }
    447 
    448 void GrGpu::didWriteToSurface(GrSurface* surface, GrSurfaceOrigin origin, const SkIRect* bounds,
    449                               uint32_t mipLevels) const {
    450     SkASSERT(surface);
    451     // Mark any MIP chain and resolve buffer as dirty if and only if there is a non-empty bounds.
    452     if (nullptr == bounds || !bounds->isEmpty()) {
    453         if (GrRenderTarget* target = surface->asRenderTarget()) {
    454             SkIRect flippedBounds;
    455             if (kBottomLeft_GrSurfaceOrigin == origin && bounds) {
    456                 flippedBounds = {bounds->fLeft, surface->height() - bounds->fBottom,
    457                                  bounds->fRight, surface->height() - bounds->fTop};
    458                 bounds = &flippedBounds;
    459             }
    460             target->flagAsNeedingResolve(bounds);
    461         }
    462         GrTexture* texture = surface->asTexture();
    463         if (texture && 1 == mipLevels) {
    464             texture->texturePriv().markMipMapsDirty();
    465         }
    466     }
    467 }
    468 
    469 GrSemaphoresSubmitted GrGpu::finishFlush(int numSemaphores,
    470                                          GrBackendSemaphore backendSemaphores[]) {
    471     GrResourceProvider* resourceProvider = fContext->contextPriv().resourceProvider();
    472 
    473     if (this->caps()->fenceSyncSupport()) {
    474         for (int i = 0; i < numSemaphores; ++i) {
    475             sk_sp<GrSemaphore> semaphore;
    476             if (backendSemaphores[i].isInitialized()) {
    477                 semaphore = resourceProvider->wrapBackendSemaphore(
    478                         backendSemaphores[i], GrResourceProvider::SemaphoreWrapType::kWillSignal,
    479                         kBorrow_GrWrapOwnership);
    480             } else {
    481                 semaphore = resourceProvider->makeSemaphore(false);
    482             }
    483             this->insertSemaphore(semaphore, false);
    484 
    485             if (!backendSemaphores[i].isInitialized()) {
    486                 semaphore->setBackendSemaphore(&backendSemaphores[i]);
    487             }
    488         }
    489     }
    490     this->onFinishFlush((numSemaphores > 0 && this->caps()->fenceSyncSupport()));
    491     return this->caps()->fenceSyncSupport() ? GrSemaphoresSubmitted::kYes
    492                                             : GrSemaphoresSubmitted::kNo;
    493 }
    494 
    495 void GrGpu::dumpJSON(SkJSONWriter* writer) const {
    496     writer->beginObject();
    497 
    498     // TODO: Is there anything useful in the base class to dump here?
    499 
    500     this->onDumpJSON(writer);
    501 
    502     writer->endObject();
    503 }
    504