| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright 2011 Google Inc. | 2 * Copyright 2011 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 | 8 |
| 9 #include "GrGpuGL.h" | 9 #include "GrGpuGL.h" |
| 10 #include "GrGLStencilBuffer.h" | 10 #include "GrGLStencilBuffer.h" |
| (...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 173 GrPrintf("------ VENDOR %s\n", vendor); | 173 GrPrintf("------ VENDOR %s\n", vendor); |
| 174 GrPrintf("------ RENDERER %s\n", renderer); | 174 GrPrintf("------ RENDERER %s\n", renderer); |
| 175 GrPrintf("------ VERSION %s\n", version); | 175 GrPrintf("------ VERSION %s\n", version); |
| 176 GrPrintf("------ EXTENSIONS\n %s \n", ext); | 176 GrPrintf("------ EXTENSIONS\n %s \n", ext); |
| 177 } | 177 } |
| 178 | 178 |
| 179 this->initCaps(); | 179 this->initCaps(); |
| 180 | 180 |
| 181 fProgramCache = SkNEW_ARGS(ProgramCache, (this->glContext())); | 181 fProgramCache = SkNEW_ARGS(ProgramCache, (this->glContext())); |
| 182 | 182 |
| 183 fHWGeometryState.setMaxAttribArrays(this->glCaps().maxVertexAttributes()); | |
| 184 | |
| 185 GrAssert(this->glCaps().maxVertexAttributes() >= GrDrawState::kVertexAttribC
nt); | 183 GrAssert(this->glCaps().maxVertexAttributes() >= GrDrawState::kVertexAttribC
nt); |
| 186 GrAssert(this->glCaps().maxVertexAttributes() > GrDrawState::kColorOverrideA
ttribIndexValue); | 184 GrAssert(this->glCaps().maxVertexAttributes() > GrDrawState::kColorOverrideA
ttribIndexValue); |
| 187 GrAssert(this->glCaps().maxVertexAttributes() > GrDrawState::kCoverageOverri
deAttribIndexValue); | 185 GrAssert(this->glCaps().maxVertexAttributes() > GrDrawState::kCoverageOverri
deAttribIndexValue); |
| 188 | 186 |
| 189 fLastSuccessfulStencilFmtIdx = 0; | 187 fLastSuccessfulStencilFmtIdx = 0; |
| 190 if (false) { // avoid bit rot, suppress warning | 188 if (false) { // avoid bit rot, suppress warning |
| 191 fbo_test(this->glInterface(), 0, 0); | 189 fbo_test(this->glInterface(), 0, 0); |
| 192 } | 190 } |
| 193 } | 191 } |
| 194 | 192 |
| (...skipping 221 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 416 // Desktop-only state that we never change | 414 // Desktop-only state that we never change |
| 417 if (!this->glCaps().isCoreProfile()) { | 415 if (!this->glCaps().isCoreProfile()) { |
| 418 GL_CALL(Disable(GR_GL_POINT_SMOOTH)); | 416 GL_CALL(Disable(GR_GL_POINT_SMOOTH)); |
| 419 GL_CALL(Disable(GR_GL_LINE_SMOOTH)); | 417 GL_CALL(Disable(GR_GL_LINE_SMOOTH)); |
| 420 GL_CALL(Disable(GR_GL_POLYGON_SMOOTH)); | 418 GL_CALL(Disable(GR_GL_POLYGON_SMOOTH)); |
| 421 GL_CALL(Disable(GR_GL_POLYGON_STIPPLE)); | 419 GL_CALL(Disable(GR_GL_POLYGON_STIPPLE)); |
| 422 GL_CALL(Disable(GR_GL_COLOR_LOGIC_OP)); | 420 GL_CALL(Disable(GR_GL_COLOR_LOGIC_OP)); |
| 423 GL_CALL(Disable(GR_GL_INDEX_LOGIC_OP)); | 421 GL_CALL(Disable(GR_GL_INDEX_LOGIC_OP)); |
| 424 } | 422 } |
| 425 if (this->glCaps().imagingSupport()) { | 423 if (this->glCaps().imagingSupport()) { |
| 424 // This produces a GL error on the windows NVIDIA driver when using
a core profile but |
| 425 // I think that is a driver bug since GL_ARB_imaging is in the exten
sion string. |
| 426 GL_CALL(Disable(GR_GL_COLOR_TABLE)); | 426 GL_CALL(Disable(GR_GL_COLOR_TABLE)); |
| 427 } | 427 } |
| 428 GL_CALL(Disable(GR_GL_POLYGON_OFFSET_FILL)); | 428 GL_CALL(Disable(GR_GL_POLYGON_OFFSET_FILL)); |
| 429 // Since ES doesn't support glPointSize at all we always use the VS to | 429 // Since ES doesn't support glPointSize at all we always use the VS to |
| 430 // set the point size | 430 // set the point size |
| 431 GL_CALL(Enable(GR_GL_VERTEX_PROGRAM_POINT_SIZE)); | 431 GL_CALL(Enable(GR_GL_VERTEX_PROGRAM_POINT_SIZE)); |
| 432 | 432 |
| 433 // We should set glPolygonMode(FRONT_AND_BACK,FILL) here, too. It isn't | 433 // We should set glPolygonMode(FRONT_AND_BACK,FILL) here, too. It isn't |
| 434 // currently part of our gl interface. There are probably others as | 434 // currently part of our gl interface. There are probably others as |
| 435 // well. | 435 // well. |
| (...skipping 803 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1239 desc.fSizeInBytes = size; | 1239 desc.fSizeInBytes = size; |
| 1240 desc.fIsWrapped = false; | 1240 desc.fIsWrapped = false; |
| 1241 | 1241 |
| 1242 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) { | 1242 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) { |
| 1243 desc.fID = 0; | 1243 desc.fID = 0; |
| 1244 GrGLVertexBuffer* vertexBuffer = SkNEW_ARGS(GrGLVertexBuffer, (this, des
c)); | 1244 GrGLVertexBuffer* vertexBuffer = SkNEW_ARGS(GrGLVertexBuffer, (this, des
c)); |
| 1245 return vertexBuffer; | 1245 return vertexBuffer; |
| 1246 } else { | 1246 } else { |
| 1247 GL_CALL(GenBuffers(1, &desc.fID)); | 1247 GL_CALL(GenBuffers(1, &desc.fID)); |
| 1248 if (desc.fID) { | 1248 if (desc.fID) { |
| 1249 GL_CALL(BindBuffer(GR_GL_ARRAY_BUFFER, desc.fID)); | 1249 fHWGeometryState.setVertexBufferID(this, desc.fID); |
| 1250 fHWGeometryState.setVertexBufferID(desc.fID); | |
| 1251 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); | 1250 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); |
| 1252 // make sure driver can allocate memory for this buffer | 1251 // make sure driver can allocate memory for this buffer |
| 1253 GL_ALLOC_CALL(this->glInterface(), | 1252 GL_ALLOC_CALL(this->glInterface(), |
| 1254 BufferData(GR_GL_ARRAY_BUFFER, | 1253 BufferData(GR_GL_ARRAY_BUFFER, |
| 1255 desc.fSizeInBytes, | 1254 desc.fSizeInBytes, |
| 1256 NULL, // data ptr | 1255 NULL, // data ptr |
| 1257 desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_
STATIC_DRAW)); | 1256 desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_
STATIC_DRAW)); |
| 1258 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { | 1257 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { |
| 1259 GL_CALL(DeleteBuffers(1, &desc.fID)); | 1258 GL_CALL(DeleteBuffers(1, &desc.fID)); |
| 1260 // deleting bound buffer does implicit bind to 0 | 1259 this->notifyVertexBufferDelete(desc.fID); |
| 1261 fHWGeometryState.setVertexBufferID(0); | |
| 1262 return NULL; | 1260 return NULL; |
| 1263 } | 1261 } |
| 1264 GrGLVertexBuffer* vertexBuffer = SkNEW_ARGS(GrGLVertexBuffer, (this,
desc)); | 1262 GrGLVertexBuffer* vertexBuffer = SkNEW_ARGS(GrGLVertexBuffer, (this,
desc)); |
| 1265 return vertexBuffer; | 1263 return vertexBuffer; |
| 1266 } | 1264 } |
| 1267 return NULL; | 1265 return NULL; |
| 1268 } | 1266 } |
| 1269 } | 1267 } |
| 1270 | 1268 |
| 1271 GrIndexBuffer* GrGpuGL::onCreateIndexBuffer(uint32_t size, bool dynamic) { | 1269 GrIndexBuffer* GrGpuGL::onCreateIndexBuffer(uint32_t size, bool dynamic) { |
| 1272 GrGLIndexBuffer::Desc desc; | 1270 GrGLIndexBuffer::Desc desc; |
| 1273 desc.fDynamic = dynamic; | 1271 desc.fDynamic = dynamic; |
| 1274 desc.fSizeInBytes = size; | 1272 desc.fSizeInBytes = size; |
| 1275 desc.fIsWrapped = false; | 1273 desc.fIsWrapped = false; |
| 1276 | 1274 |
| 1277 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) { | 1275 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) { |
| 1278 desc.fID = 0; | 1276 desc.fID = 0; |
| 1279 GrIndexBuffer* indexBuffer = SkNEW_ARGS(GrGLIndexBuffer, (this, desc)); | 1277 GrIndexBuffer* indexBuffer = SkNEW_ARGS(GrGLIndexBuffer, (this, desc)); |
| 1280 return indexBuffer; | 1278 return indexBuffer; |
| 1281 } else { | 1279 } else { |
| 1282 GL_CALL(GenBuffers(1, &desc.fID)); | 1280 GL_CALL(GenBuffers(1, &desc.fID)); |
| 1283 if (desc.fID) { | 1281 if (desc.fID) { |
| 1284 GL_CALL(BindBuffer(GR_GL_ELEMENT_ARRAY_BUFFER, desc.fID)); | 1282 fHWGeometryState.setIndexBufferIDOnDefaultVertexArray(this, desc.fID
); |
| 1285 fHWGeometryState.setIndexBufferID(desc.fID); | |
| 1286 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); | 1283 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); |
| 1287 // make sure driver can allocate memory for this buffer | 1284 // make sure driver can allocate memory for this buffer |
| 1288 GL_ALLOC_CALL(this->glInterface(), | 1285 GL_ALLOC_CALL(this->glInterface(), |
| 1289 BufferData(GR_GL_ELEMENT_ARRAY_BUFFER, | 1286 BufferData(GR_GL_ELEMENT_ARRAY_BUFFER, |
| 1290 desc.fSizeInBytes, | 1287 desc.fSizeInBytes, |
| 1291 NULL, // data ptr | 1288 NULL, // data ptr |
| 1292 desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_
STATIC_DRAW)); | 1289 desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_
STATIC_DRAW)); |
| 1293 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { | 1290 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { |
| 1294 GL_CALL(DeleteBuffers(1, &desc.fID)); | 1291 GL_CALL(DeleteBuffers(1, &desc.fID)); |
| 1295 // deleting bound buffer does implicit bind to 0 | 1292 this->notifyIndexBufferDelete(desc.fID); |
| 1296 fHWGeometryState.setIndexBufferID(0); | |
| 1297 return NULL; | 1293 return NULL; |
| 1298 } | 1294 } |
| 1299 GrIndexBuffer* indexBuffer = SkNEW_ARGS(GrGLIndexBuffer, (this, desc
)); | 1295 GrIndexBuffer* indexBuffer = SkNEW_ARGS(GrGLIndexBuffer, (this, desc
)); |
| 1300 return indexBuffer; | 1296 return indexBuffer; |
| 1301 } | 1297 } |
| 1302 return NULL; | 1298 return NULL; |
| 1303 } | 1299 } |
| 1304 } | 1300 } |
| 1305 | 1301 |
| 1306 GrPath* GrGpuGL::onCreatePath(const SkPath& inPath) { | 1302 GrPath* GrGpuGL::onCreatePath(const SkPath& inPath) { |
| (...skipping 851 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2158 case GrDrawState::kBoth_DrawFace: | 2154 case GrDrawState::kBoth_DrawFace: |
| 2159 GL_CALL(Disable(GR_GL_CULL_FACE)); | 2155 GL_CALL(Disable(GR_GL_CULL_FACE)); |
| 2160 break; | 2156 break; |
| 2161 default: | 2157 default: |
| 2162 GrCrash("Unknown draw face."); | 2158 GrCrash("Unknown draw face."); |
| 2163 } | 2159 } |
| 2164 fHWDrawFace = drawState.getDrawFace(); | 2160 fHWDrawFace = drawState.getDrawFace(); |
| 2165 } | 2161 } |
| 2166 } | 2162 } |
| 2167 | 2163 |
| 2168 void GrGpuGL::notifyVertexBufferBind(GrGLuint id) { | |
| 2169 fHWGeometryState.setVertexBufferID(id); | |
| 2170 } | |
| 2171 | |
| 2172 void GrGpuGL::notifyVertexBufferDelete(GrGLuint id) { | |
| 2173 fHWGeometryState.notifyVertexBufferDelete(id); | |
| 2174 } | |
| 2175 | |
| 2176 void GrGpuGL::notifyIndexBufferBind(GrGLuint id) { | |
| 2177 fHWGeometryState.setIndexBufferID(id); | |
| 2178 } | |
| 2179 | |
| 2180 void GrGpuGL::notifyIndexBufferDelete(GrGLuint id) { | |
| 2181 fHWGeometryState.notifyIndexBufferDelete(id); | |
| 2182 } | |
| 2183 | |
| 2184 void GrGpuGL::notifyRenderTargetDelete(GrRenderTarget* renderTarget) { | 2164 void GrGpuGL::notifyRenderTargetDelete(GrRenderTarget* renderTarget) { |
| 2185 GrAssert(NULL != renderTarget); | 2165 GrAssert(NULL != renderTarget); |
| 2186 if (fHWBoundRenderTarget == renderTarget) { | 2166 if (fHWBoundRenderTarget == renderTarget) { |
| 2187 fHWBoundRenderTarget = NULL; | 2167 fHWBoundRenderTarget = NULL; |
| 2188 } | 2168 } |
| 2189 } | 2169 } |
| 2190 | 2170 |
| 2191 void GrGpuGL::notifyTextureDelete(GrGLTexture* texture) { | 2171 void GrGpuGL::notifyTextureDelete(GrGLTexture* texture) { |
| 2192 for (int s = 0; s < GrDrawState::kNumStages; ++s) { | 2172 for (int s = 0; s < GrDrawState::kNumStages; ++s) { |
| 2193 if (fHWBoundTextures[s] == texture) { | 2173 if (fHWBoundTextures[s] == texture) { |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2316 } | 2296 } |
| 2317 } | 2297 } |
| 2318 | 2298 |
| 2319 void GrGpuGL::setSpareTextureUnit() { | 2299 void GrGpuGL::setSpareTextureUnit() { |
| 2320 if (fHWActiveTextureUnitIdx != (GR_GL_TEXTURE0 + SPARE_TEX_UNIT)) { | 2300 if (fHWActiveTextureUnitIdx != (GR_GL_TEXTURE0 + SPARE_TEX_UNIT)) { |
| 2321 GL_CALL(ActiveTexture(GR_GL_TEXTURE0 + SPARE_TEX_UNIT)); | 2301 GL_CALL(ActiveTexture(GR_GL_TEXTURE0 + SPARE_TEX_UNIT)); |
| 2322 fHWActiveTextureUnitIdx = SPARE_TEX_UNIT; | 2302 fHWActiveTextureUnitIdx = SPARE_TEX_UNIT; |
| 2323 } | 2303 } |
| 2324 } | 2304 } |
| 2325 | 2305 |
| 2326 GrGLVertexBuffer* GrGpuGL::setBuffers(bool indexed, | |
| 2327 size_t* vertexOffsetInBytes, | |
| 2328 size_t* indexOffsetInBytes) { | |
| 2329 | |
| 2330 GrAssert(NULL != vertexOffsetInBytes); | |
| 2331 | |
| 2332 const GeometryPoolState& geoPoolState = this->getGeomPoolState(); | |
| 2333 | |
| 2334 GrGLVertexBuffer* vbuf; | |
| 2335 switch (this->getGeomSrc().fVertexSrc) { | |
| 2336 case kBuffer_GeometrySrcType: | |
| 2337 *vertexOffsetInBytes = 0; | |
| 2338 vbuf = (GrGLVertexBuffer*) this->getGeomSrc().fVertexBuffer; | |
| 2339 break; | |
| 2340 case kArray_GeometrySrcType: | |
| 2341 case kReserved_GeometrySrcType: | |
| 2342 this->finalizeReservedVertices(); | |
| 2343 *vertexOffsetInBytes = geoPoolState.fPoolStartVertex * this->getGeom
Src().fVertexSize; | |
| 2344 vbuf = (GrGLVertexBuffer*) geoPoolState.fPoolVertexBuffer; | |
| 2345 break; | |
| 2346 default: | |
| 2347 vbuf = NULL; // suppress warning | |
| 2348 GrCrash("Unknown geometry src type!"); | |
| 2349 } | |
| 2350 | |
| 2351 GrAssert(NULL != vbuf); | |
| 2352 GrAssert(!vbuf->isLocked()); | |
| 2353 *vertexOffsetInBytes += vbuf->baseOffset(); | |
| 2354 | |
| 2355 if (indexed) { | |
| 2356 GrAssert(NULL != indexOffsetInBytes); | |
| 2357 | |
| 2358 GrGLIndexBuffer* ibuf; | |
| 2359 switch (this->getGeomSrc().fIndexSrc) { | |
| 2360 case kBuffer_GeometrySrcType: | |
| 2361 *indexOffsetInBytes = 0; | |
| 2362 ibuf = (GrGLIndexBuffer*)this->getGeomSrc().fIndexBuffer; | |
| 2363 break; | |
| 2364 case kArray_GeometrySrcType: | |
| 2365 case kReserved_GeometrySrcType: | |
| 2366 this->finalizeReservedIndices(); | |
| 2367 *indexOffsetInBytes = geoPoolState.fPoolStartIndex * sizeof(GrGLusho
rt); | |
| 2368 ibuf = (GrGLIndexBuffer*) geoPoolState.fPoolIndexBuffer; | |
| 2369 break; | |
| 2370 default: | |
| 2371 ibuf = NULL; // suppress warning | |
| 2372 GrCrash("Unknown geometry src type!"); | |
| 2373 } | |
| 2374 | |
| 2375 GrAssert(NULL != ibuf); | |
| 2376 GrAssert(!ibuf->isLocked()); | |
| 2377 *indexOffsetInBytes += ibuf->baseOffset(); | |
| 2378 if (!fHWGeometryState.isIndexBufferIDBound(ibuf->bufferID())) { | |
| 2379 ibuf->bind(); | |
| 2380 fHWGeometryState.setIndexBufferID(ibuf->bufferID()); | |
| 2381 } | |
| 2382 } | |
| 2383 return vbuf; | |
| 2384 } | |
| 2385 | |
| 2386 /////////////////////////////////////////////////////////////////////////////// | 2306 /////////////////////////////////////////////////////////////////////////////// |
| 2387 | 2307 |
| 2388 void GrGpuGL::HWGeometryState::AttribArray::set(const GrGpuGL* gpu, | 2308 GrGLAttribArrayState* GrGpuGL::HWGeometryState::bindArrayAndBuffersToDraw( |
| 2389 HWGeometryState* geoState, | 2309 GrGpuGL* gpu, |
| 2390 int index, | 2310 const GrGLVertexBuffer* vbuffer, |
| 2391 GrGLVertexBuffer* buffer, | 2311 const GrGLIndexBuffer* ibuffer)
{ |
| 2392 GrGLint size, | 2312 GrAssert(NULL != vbuffer); |
| 2393 GrGLenum type, | 2313 GrGLAttribArrayState* attribState = &fDefaultVertexArrayAttribState; |
| 2394 GrGLboolean normalized, | 2314 // We use a vertex array if we're on a core profile and the verts are in a V
BO. |
| 2395 GrGLsizei stride, | 2315 if (gpu->glCaps().isCoreProfile() && !vbuffer->isCPUBacked()) { |
| 2396 GrGLvoid* offset) { | 2316 if (NULL == fVBOVertexArray || !fVBOVertexArray->isValid()) { |
| 2397 if (!fEnableIsValid || !fEnabled) { | 2317 SkSafeUnref(fVBOVertexArray); |
| 2398 GR_GL_CALL(gpu->glInterface(), EnableVertexAttribArray(index)); | 2318 GrGLuint arrayID; |
| 2399 fEnableIsValid = true; | 2319 GR_GL_CALL(gpu->glInterface(), GenVertexArrays(1, &arrayID)); |
| 2400 fEnabled = true; | 2320 int attrCount = gpu->glCaps().maxVertexAttributes(); |
| 2321 fVBOVertexArray = SkNEW_ARGS(GrGLVertexArray, (gpu, arrayID, attrCou
nt)); |
| 2322 } |
| 2323 attribState = fVBOVertexArray->bindWithIndexBuffer(ibuffer); |
| 2324 } else { |
| 2325 if (NULL != ibuffer) { |
| 2326 this->setIndexBufferIDOnDefaultVertexArray(gpu, ibuffer->bufferID())
; |
| 2327 } else { |
| 2328 this->setVertexArrayID(gpu, 0); |
| 2329 } |
| 2330 int attrCount = gpu->glCaps().maxVertexAttributes(); |
| 2331 if (fDefaultVertexArrayAttribState.count() != attrCount) { |
| 2332 fDefaultVertexArrayAttribState.resize(attrCount); |
| 2333 } |
| 2334 attribState = &fDefaultVertexArrayAttribState; |
| 2401 } | 2335 } |
| 2402 if (!fAttribPointerIsValid || | 2336 return attribState; |
| 2403 fVertexBufferID != buffer->bufferID() || | |
| 2404 fSize != size || | |
| 2405 fNormalized != normalized || | |
| 2406 fStride != stride || | |
| 2407 offset != fOffset) { | |
| 2408 | |
| 2409 GrGLuint bufferID = buffer->bufferID(); | |
| 2410 if (!geoState->isVertexBufferIDBound(bufferID)) { | |
| 2411 buffer->bind(); | |
| 2412 geoState->setVertexBufferID(bufferID); | |
| 2413 } | |
| 2414 GR_GL_CALL(gpu->glInterface(), VertexAttribPointer(index, | |
| 2415 size, | |
| 2416 type, | |
| 2417 normalized, | |
| 2418 stride, | |
| 2419 offset)); | |
| 2420 fAttribPointerIsValid = true; | |
| 2421 fVertexBufferID = bufferID; | |
| 2422 fSize = size; | |
| 2423 fNormalized = normalized; | |
| 2424 fStride = stride; | |
| 2425 fOffset = offset; | |
| 2426 } | |
| 2427 } | 2337 } |
| OLD | NEW |