diff --git a/gn/tests.gni b/gn/tests.gni index f488058d5e..b893768379 100644 --- a/gn/tests.gni +++ b/gn/tests.gni @@ -24,7 +24,6 @@ tests_sources = [ "$_tests/BlendTest.cpp", "$_tests/BlitMaskClip.cpp", "$_tests/BlurTest.cpp", - "$_tests/BulkRectTest.cpp", "$_tests/CTest.cpp", "$_tests/CachedDataTest.cpp", "$_tests/CachedDecodingPixelRefTest.cpp", diff --git a/src/gpu/GrOpsTask.h b/src/gpu/GrOpsTask.h index fd6950909c..1a4c517cc5 100644 --- a/src/gpu/GrOpsTask.h +++ b/src/gpu/GrOpsTask.h @@ -106,11 +106,6 @@ public: SkDEBUGCODE(int numClips() const override { return fNumClips; }) SkDEBUGCODE(void visitProxies_debugOnly(const VisitSurfaceProxyFunc&) const override;) -#if GR_TEST_UTILS - int numOpChains() const { return fOpChains.count(); } - const GrOp* getChain(int index) const { return fOpChains[index].head(); } -#endif - private: bool isNoOp() const { // TODO: GrLoadOp::kDiscard (i.e., storing a discard) should also be grounds for skipping diff --git a/src/gpu/GrRenderTargetContext.cpp b/src/gpu/GrRenderTargetContext.cpp index 41b3e60e0b..1e0cc19218 100644 --- a/src/gpu/GrRenderTargetContext.cpp +++ b/src/gpu/GrRenderTargetContext.cpp @@ -903,15 +903,15 @@ void GrRenderTargetContext::drawTextureSet(const GrClip& clip, const TextureSetE srcQuad, domain); } } else { - // Create the minimum number of GrTextureOps needed to draw this set. Individual - // GrTextureOps can rebind the texture between draws thus avoiding GrPaint (re)creation. + // Can use a single op, avoiding GrPaint creation, and can batch across proxies AutoCheckFlush acf(this->drawingManager()); GrAAType aaType = this->chooseAAType(aa); auto clampType = GrColorTypeClampType(this->colorInfo().colorType()); auto saturate = clampType == GrClampType::kManual ? GrTextureOp::Saturate::kYes : GrTextureOp::Saturate::kNo; - GrTextureOp::CreateTextureSetOps(this, clip, fContext, set, cnt, filter, saturate, aaType, - constraint, viewMatrix, std::move(texXform)); + auto op = GrTextureOp::MakeSet(fContext, set, cnt, filter, saturate, aaType, constraint, + viewMatrix, std::move(texXform)); + this->addDrawOp(clip, std::move(op)); } } diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h index 3543dbe757..eacff51bf5 100644 --- a/src/gpu/GrRenderTargetContext.h +++ b/src/gpu/GrRenderTargetContext.h @@ -549,7 +549,6 @@ private: friend class GrTessellatingPathRenderer; // for access to add[Mesh]DrawOp friend class GrCCPerFlushResources; // for access to addDrawOp friend class GrCoverageCountingPathRenderer; // for access to addDrawOp - friend class GrTextureOp; // for access to addDrawOp // for a unit test friend void test_draw_op(GrContext*, GrRenderTargetContext*, diff --git a/src/gpu/ops/GrDrawOp.h b/src/gpu/ops/GrDrawOp.h index f366a3b838..a9e97520f5 100644 --- a/src/gpu/ops/GrDrawOp.h +++ b/src/gpu/ops/GrDrawOp.h @@ -54,11 +54,6 @@ public: } #endif -#if GR_TEST_UTILS - // This is really only intended for GrTextureOp to override - virtual int numQuads() const { return -1; } -#endif - private: typedef GrOp INHERITED; }; diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp index 8048f203cb..86fe00f128 100644 --- a/src/gpu/ops/GrQuadPerEdgeAA.cpp +++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp @@ -166,18 +166,6 @@ sk_sp GetIndexBuffer(GrMeshDrawOp::Target* target, } } -#ifdef SK_DEBUG -int QuadLimit(IndexBufferOption option) { - switch (option) { - case IndexBufferOption::kPictureFramed: return GrResourceProvider::MaxNumAAQuads(); - case IndexBufferOption::kIndexedRects: return GrResourceProvider::MaxNumNonAAQuads(); - case IndexBufferOption::kTriStrips: return SK_MaxS32; // not limited by an indexBuffer - } - - SkUNREACHABLE; -} -#endif - void ConfigureMesh(GrMesh* mesh, const VertexSpec& spec, int runningQuadCount, int quadsInDraw, int maxVerts, diff --git a/src/gpu/ops/GrQuadPerEdgeAA.h b/src/gpu/ops/GrQuadPerEdgeAA.h index 5f6f21e043..ae245eaa9f 100644 --- a/src/gpu/ops/GrQuadPerEdgeAA.h +++ b/src/gpu/ops/GrQuadPerEdgeAA.h @@ -151,11 +151,6 @@ namespace GrQuadPerEdgeAA { // It will, correctly, return nullptr if the indexBufferOption is kTriStrips. sk_sp GetIndexBuffer(GrMeshDrawOp::Target*, IndexBufferOption); -#ifdef SK_DEBUG - // What is the maximum number of quads allowed for the specified indexBuffer option? - int QuadLimit(IndexBufferOption); -#endif - // This method will configure the vertex and index data of the provided 'mesh' to comply // with the indexing method specified in the vertexSpec. It is up to the calling code // to allocate and fill in the vertex data and acquire the correct indexBuffer if it is needed. diff --git a/src/gpu/ops/GrTextureOp.cpp b/src/gpu/ops/GrTextureOp.cpp index 50d20c1530..7c19c4e414 100644 --- a/src/gpu/ops/GrTextureOp.cpp +++ b/src/gpu/ops/GrTextureOp.cpp @@ -159,7 +159,6 @@ public: color, saturate, aaType, aaFlags, deviceQuad, localQuad, domain); } - static std::unique_ptr Make(GrRecordingContext* context, const GrRenderTargetContext::TextureSetEntry set[], int cnt, @@ -596,11 +595,9 @@ private: auto textureType = fViewCountPairs[0].fProxyView.asTextureProxy()->textureType(); GrAAType aaType = this->aaType(); - int quadCount = 0; for (const auto& op : ChainRange(this)) { for (unsigned p = 0; p < op.fProxyCnt; ++p) { auto* proxy = op.fViewCountPairs[p].fProxyView.asTextureProxy(); - quadCount += op.fViewCountPairs[p].fQuadCnt; SkASSERT(proxy); SkASSERT(proxy->textureType() == textureType); SkASSERT(op.fViewCountPairs[p].fProxyView.swizzle() == @@ -615,15 +612,9 @@ private: SkASSERT(aaType == GrAAType::kMSAA && op.aaType() == GrAAType::kMSAA); } } - - SkASSERT(quadCount == this->numChainedQuads()); } #endif -#if GR_TEST_UTILS - int numQuads() const final { return this->totNumQuads(); } -#endif - void characterize(PrePreparedDesc* desc) const { GrQuad::Type quadType = GrQuad::Type::kAxisAligned; ColorType colorType = ColorType::kNone; @@ -668,8 +659,6 @@ private: desc->fVertexSpec = VertexSpec(quadType, colorType, srcQuadType, /* hasLocal */ true, domain, overallAAType, /* alpha as coverage */ true, indexBufferOption); - - SkASSERT(desc->fNumTotalQuads <= GrQuadPerEdgeAA::QuadLimit(indexBufferOption)); } int totNumQuads() const { @@ -890,25 +879,21 @@ private: } // anonymous namespace -#if GR_TEST_UTILS -uint32_t GrTextureOp::ClassID() { - return TextureOp::ClassID(); -} -#endif +namespace GrTextureOp { -std::unique_ptr GrTextureOp::Make(GrRecordingContext* context, - GrSurfaceProxyView proxyView, - GrColorType srcColorType, - sk_sp textureXform, - GrSamplerState::Filter filter, - const SkPMColor4f& color, - Saturate saturate, - SkBlendMode blendMode, - GrAAType aaType, - GrQuadAAFlags aaFlags, - const GrQuad& deviceQuad, - const GrQuad& localQuad, - const SkRect* domain) { +std::unique_ptr Make(GrRecordingContext* context, + GrSurfaceProxyView proxyView, + GrColorType srcColorType, + sk_sp textureXform, + GrSamplerState::Filter filter, + const SkPMColor4f& color, + Saturate saturate, + SkBlendMode blendMode, + GrAAType aaType, + GrQuadAAFlags aaFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const SkRect* domain) { GrTextureProxy* proxy = proxyView.asTextureProxy(); // Apply optimizations that are valid whether or not using GrTextureOp or GrFillRectOp if (domain && domain->contains(proxy->backingStoreBoundsRect())) { @@ -953,144 +938,21 @@ std::unique_ptr GrTextureOp::Make(GrRecordingContext* context, } } -// A helper class that assists in breaking up bulk API quad draws into manageable chunks. -class GrTextureOp::BatchSizeLimiter { -public: - BatchSizeLimiter(GrRenderTargetContext* rtc, - const GrClip& clip, - GrRecordingContext* context, - int numEntries, - GrSamplerState::Filter filter, - GrTextureOp::Saturate saturate, - SkCanvas::SrcRectConstraint constraint, - const SkMatrix& viewMatrix, - sk_sp textureColorSpaceXform) - : fRTC(rtc) - , fClip(clip) - , fContext(context) - , fFilter(filter) - , fSaturate(saturate) - , fConstraint(constraint) - , fViewMatrix(viewMatrix) - , fTextureColorSpaceXform(textureColorSpaceXform) - , fNumLeft(numEntries) { - } - - void createOp(const GrRenderTargetContext::TextureSetEntry set[], - int clumpSize, - GrAAType aaType) { - std::unique_ptr op = TextureOp::Make(fContext, &set[fNumClumped], clumpSize, - fFilter, fSaturate, aaType, - fConstraint, fViewMatrix, - fTextureColorSpaceXform); - fRTC->addDrawOp(fClip, std::move(op)); - - fNumLeft -= clumpSize; - fNumClumped += clumpSize; - } - - int numLeft() const { return fNumLeft; } - int baseIndex() const { return fNumClumped; } - -private: - GrRenderTargetContext* fRTC; - const GrClip& fClip; - GrRecordingContext* fContext; - GrSamplerState::Filter fFilter; - GrTextureOp::Saturate fSaturate; - SkCanvas::SrcRectConstraint fConstraint; - const SkMatrix& fViewMatrix; - sk_sp fTextureColorSpaceXform; - - int fNumLeft; - int fNumClumped = 0; // also the offset for the start of the next clump -}; - -// Greedily clump quad draws together until the index buffer limit is exceeded. -void GrTextureOp::CreateTextureSetOps(GrRenderTargetContext* rtc, - const GrClip& clip, - GrRecordingContext* context, - const GrRenderTargetContext::TextureSetEntry set[], - int cnt, - GrSamplerState::Filter filter, - Saturate saturate, - GrAAType aaType, - SkCanvas::SrcRectConstraint constraint, - const SkMatrix& viewMatrix, - sk_sp textureColorSpaceXform) { - - // First check if we can always just make a single op and avoid the extra iteration - // needed to clump things together. - if (cnt <= SkTMin(GrResourceProvider::MaxNumNonAAQuads(), - GrResourceProvider::MaxNumAAQuads())) { - auto op = TextureOp::Make(context, set, cnt, filter, saturate, aaType, - constraint, viewMatrix, std::move(textureColorSpaceXform)); - rtc->addDrawOp(clip, std::move(op)); - return; - } - - BatchSizeLimiter state(rtc, clip, context, cnt, filter, saturate, constraint, viewMatrix, +std::unique_ptr MakeSet(GrRecordingContext* context, + const GrRenderTargetContext::TextureSetEntry set[], + int cnt, + GrSamplerState::Filter filter, + Saturate saturate, + GrAAType aaType, + SkCanvas::SrcRectConstraint constraint, + const SkMatrix& viewMatrix, + sk_sp textureColorSpaceXform) { + return TextureOp::Make(context, set, cnt, filter, saturate, aaType, constraint, viewMatrix, std::move(textureColorSpaceXform)); - - // kNone and kMSAA never get altered - if (aaType == GrAAType::kNone || aaType == GrAAType::kMSAA) { - // Clump these into series of MaxNumNonAAQuads-sized GrTextureOps - while (state.numLeft() > 0) { - int clumpSize = SkTMin(state.numLeft(), GrResourceProvider::MaxNumNonAAQuads()); - - state.createOp(set, clumpSize, aaType); - } - } else { - // kCoverage can be downgraded to kNone. Note that the following is conservative. kCoverage - // can also get downgraded to kNone if all the quads are on integer coordinates and - // axis-aligned. - SkASSERT(aaType == GrAAType::kCoverage); - - while (state.numLeft() > 0) { - GrAAType runningAA = GrAAType::kNone; - bool clumped = false; - - for (int i = 0; i < state.numLeft(); ++i) { - int absIndex = state.baseIndex() + i; - - if (set[absIndex].fAAFlags != GrQuadAAFlags::kNone) { - - if (i >= GrResourceProvider::MaxNumAAQuads()) { - // Here we either need to boost the AA type to kCoverage, but doing so with - // all the accumulated quads would overflow, or we have a set of AA quads - // that has just gotten too large. In either case, calve off the existing - // quads as their own TextureOp. - state.createOp( - set, - runningAA == GrAAType::kNone ? i : GrResourceProvider::MaxNumAAQuads(), - runningAA); // maybe downgrading AA here - clumped = true; - break; - } - - runningAA = GrAAType::kCoverage; - } else if (runningAA == GrAAType::kNone) { - - if (i >= GrResourceProvider::MaxNumNonAAQuads()) { - // Here we've found a consistent batch of non-AA quads that has gotten too - // large. Calve it off as its own GrTextureOp. - state.createOp(set, GrResourceProvider::MaxNumNonAAQuads(), - GrAAType::kNone); // definitely downgrading AA here - clumped = true; - break; - } - } - } - - if (!clumped) { - // We ran through the above loop w/o hitting a limit. Spit out this last clump of - // quads and call it a day. - state.createOp(set, state.numLeft(), runningAA); // maybe downgrading AA here - } - } - } } +} // namespace GrTextureOp + #if GR_TEST_UTILS #include "include/private/GrRecordingContext.h" #include "src/gpu/GrProxyProvider.h" diff --git a/src/gpu/ops/GrTextureOp.h b/src/gpu/ops/GrTextureOp.h index 600bcc8a1a..a4eb15b596 100644 --- a/src/gpu/ops/GrTextureOp.h +++ b/src/gpu/ops/GrTextureOp.h @@ -20,59 +20,49 @@ class GrTextureProxy; struct SkRect; class SkMatrix; -class GrTextureOp { -public: +namespace GrTextureOp { - /** - * Controls whether saturate() is called after the texture is color-converted to ensure all - * color values are in 0..1 range. - */ - enum class Saturate : bool { kNo = false, kYes = true }; +/** + * Controls whether saturate() is called after the texture is color-converted to ensure all + * color values are in 0..1 range. + */ +enum class Saturate : bool { kNo = false, kYes = true }; - /** - * Creates an op that draws a sub-quadrilateral of a texture. The passed color is modulated by - * the texture's color. 'deviceQuad' specifies the device-space coordinates to draw, using - * 'localQuad' to map into the proxy's texture space. If non-null, 'domain' represents the - * boundary for the strict src rect constraint. If GrAAType is kCoverage then AA is applied to - * the edges indicated by GrQuadAAFlags. Otherwise, GrQuadAAFlags is ignored. - * - * This is functionally very similar to GrFillRectOp::Make, except that the GrPaint has been - * deconstructed into the texture, filter, modulating color, and blend mode. When blend mode is - * src over, this will return a GrFillRectOp with a paint that samples the proxy. - */ - static std::unique_ptr Make(GrRecordingContext*, - GrSurfaceProxyView, - GrColorType srcColorType, - sk_sp, - GrSamplerState::Filter, - const SkPMColor4f&, - Saturate, - SkBlendMode, - GrAAType, - GrQuadAAFlags, - const GrQuad& deviceQuad, - const GrQuad& localQuad, - const SkRect* domain = nullptr); +/** + * Creates an op that draws a sub-quadrilateral of a texture. The passed color is modulated by the + * texture's color. 'deviceQuad' specifies the device-space coordinates to draw, using 'localQuad' + * to map into the proxy's texture space. If non-null, 'domain' represents the boundary for the + * strict src rect constraint. If GrAAType is kCoverage then AA is applied to the edges + * indicated by GrQuadAAFlags. Otherwise, GrQuadAAFlags is ignored. + * + * This is functionally very similar to GrFillRectOp::Make, except that the GrPaint has been + * deconstructed into the texture, filter, modulating color, and blend mode. When blend mode is + * src over, this will return a GrFillRectOp with a paint that samples the proxy. + */ +std::unique_ptr Make(GrRecordingContext*, + GrSurfaceProxyView, + GrColorType srcColorType, + sk_sp, + GrSamplerState::Filter, + const SkPMColor4f&, + Saturate, + SkBlendMode, + GrAAType, + GrQuadAAFlags, + const GrQuad& deviceQuad, + const GrQuad& localQuad, + const SkRect* domain = nullptr); - // Unlike the single-proxy factory, this only supports src-over blending. - static void CreateTextureSetOps(GrRenderTargetContext*, - const GrClip& clip, - GrRecordingContext*, - const GrRenderTargetContext::TextureSetEntry[], - int cnt, - GrSamplerState::Filter, - Saturate, - GrAAType, - SkCanvas::SrcRectConstraint, - const SkMatrix& viewMatrix, - sk_sp textureXform); - -#if GR_TEST_UTILS - static uint32_t ClassID(); -#endif - -private: - class BatchSizeLimiter; -}; +// Unlike the single-proxy factory, this only supports src-over blending. +std::unique_ptr MakeSet(GrRecordingContext*, + const GrRenderTargetContext::TextureSetEntry[], + int cnt, + GrSamplerState::Filter, + Saturate, + GrAAType, + SkCanvas::SrcRectConstraint, + const SkMatrix& viewMatrix, + sk_sp textureXform); +} #endif // GrTextureOp_DEFINED diff --git a/tests/BulkRectTest.cpp b/tests/BulkRectTest.cpp deleted file mode 100644 index b89ccc9f99..0000000000 --- a/tests/BulkRectTest.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -#include "src/gpu/GrClip.h" -#include "src/gpu/GrContextPriv.h" -#include "src/gpu/GrMemoryPool.h" -#include "src/gpu/GrRenderTargetContext.h" -#include "src/gpu/ops/GrTextureOp.h" -#include "tests/Test.h" - -static std::unique_ptr new_RTC(GrContext* context) { - return context->priv().makeDeferredRenderTargetContext(SkBackingFit::kExact, 128, 128, - GrColorType::kRGBA_8888, nullptr); -} - -sk_sp create_proxy(GrContext* context) { - GrSurfaceDesc desc; - desc.fConfig = kRGBA_8888_GrPixelConfig; - desc.fWidth = 128; - desc.fHeight = 128; - - const GrBackendFormat format = context->priv().caps()->getDefaultBackendFormat( - GrColorType::kRGBA_8888, - GrRenderable::kYes); - - return context->priv().proxyProvider()->createProxy( - format, desc, GrRenderable::kYes, 1, kTopLeft_GrSurfaceOrigin, GrMipMapped::kNo, - SkBackingFit::kExact, SkBudgeted::kNo, GrProtected::kNo, GrInternalSurfaceFlags::kNone); -} - -typedef GrQuadAAFlags (*GimmeSomeAAFunc)(int i); - -static void bulk_rect_create_test(skiatest::Reporter* reporter, GrContext* context, - GimmeSomeAAFunc gimmeSomeAA, GrAAType overallAA, - int requestedTotNumQuads, int expectedNumOps) { - - std::unique_ptr rtc = new_RTC(context); - - sk_sp proxy = create_proxy(context); - - GrSurfaceProxyView proxyView(std::move(proxy), kTopLeft_GrSurfaceOrigin, GrSwizzle::RGBA()); - - auto set = new GrRenderTargetContext::TextureSetEntry[requestedTotNumQuads]; - - for (int i = 0; i < requestedTotNumQuads; ++i) { - set[i].fProxyView = proxyView; - set[i].fSrcColorType = GrColorType::kRGBA_8888; - set[i].fSrcRect = SkRect::MakeWH(100.0f, 100.0f); - set[i].fDstRect = SkRect::MakeWH(100.5f, 100.5f); // prevent the int non-AA optimization - set[i].fDstClipQuad = nullptr; - set[i].fPreViewMatrix = nullptr; - set[i].fAlpha = 1.0f; - set[i].fAAFlags = gimmeSomeAA(i); - } - - GrTextureOp::CreateTextureSetOps(rtc.get(), GrNoClip(), context, set, requestedTotNumQuads, - GrSamplerState::Filter::kNearest, - GrTextureOp::Saturate::kYes, - overallAA, - SkCanvas::kStrict_SrcRectConstraint, - SkMatrix::I(), nullptr); - - GrOpsTask* opsTask = rtc->testingOnly_PeekLastOpsTask(); - int actualNumOps = opsTask->numOpChains(); - - int actualTotNumQuads = 0; - - for (int i = 0; i < actualNumOps; ++i) { - const GrOp* tmp = opsTask->getChain(i); - REPORTER_ASSERT(reporter, tmp->classID() == GrTextureOp::ClassID()); - REPORTER_ASSERT(reporter, tmp->isChainTail()); - actualTotNumQuads += ((GrDrawOp*) tmp)->numQuads(); - } - - REPORTER_ASSERT(reporter, expectedNumOps == actualNumOps); - REPORTER_ASSERT(reporter, requestedTotNumQuads == actualTotNumQuads); - - context->flush(); - - delete[] set; -} - -DEF_GPUTEST_FOR_RENDERING_CONTEXTS(BulkRect, reporter, ctxInfo) { - GrContext* context = ctxInfo.grContext(); - - if (!context->priv().caps()->dynamicStateArrayGeometryProcessorTextureSupport()) { - return; - } - - // This is the simple case where there is no AA at all. We expect 2 non-AA clumps of quads. - { - auto noAA = [](int i) -> GrQuadAAFlags { - return GrQuadAAFlags::kNone; - }; - - static const int kNumExpectedOps = 2; - - bulk_rect_create_test(reporter, context, noAA, GrAAType::kNone, - 2*GrResourceProvider::MaxNumNonAAQuads(), kNumExpectedOps); - } - - // This is the same as the above case except the overall AA is kCoverage. However, since - // the per-quad AA is still none, all the quads should be downgraded to non-AA. - { - auto noAA = [](int i) -> GrQuadAAFlags { - return GrQuadAAFlags::kNone; - }; - - static const int kNumExpectedOps = 2; - - bulk_rect_create_test(reporter, context, noAA, GrAAType::kCoverage, - 2*GrResourceProvider::MaxNumNonAAQuads(), kNumExpectedOps); - } - - // This case has an overall AA of kCoverage but the per-quad AA alternates. - // We should end up with several aa-sized clumps - { - auto alternateAA = [](int i) -> GrQuadAAFlags { - return (i % 2) ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone; - }; - - int numExpectedOps = 2*GrResourceProvider::MaxNumNonAAQuads() / - GrResourceProvider::MaxNumAAQuads(); - - bulk_rect_create_test(reporter, context, alternateAA, GrAAType::kCoverage, - 2*GrResourceProvider::MaxNumNonAAQuads(), numExpectedOps); - } - - // In this case we have a run of MaxNumAAQuads non-AA quads and then AA quads. This - // exercises the case where we have a clump of quads that can't be upgraded to AA bc of - // its size. We expect one clump of non-AA quads followed by one clump of AA quads. - { - auto runOfNonAA = [](int i) -> GrQuadAAFlags { - return (i < GrResourceProvider::MaxNumAAQuads()) ? GrQuadAAFlags::kNone - : GrQuadAAFlags::kAll; - }; - - static const int kNumExpectedOps = 2; - - bulk_rect_create_test(reporter, context, runOfNonAA, GrAAType::kCoverage, - 2*GrResourceProvider::MaxNumAAQuads(), kNumExpectedOps); - } -}