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 "GrGLGpu.h" | 9 #include "GrGLGpu.h" |
10 #include "GrGLGLSL.h" | 10 #include "GrGLGLSL.h" |
(...skipping 1383 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1394 height, | 1394 height, |
1395 samples, | 1395 samples, |
1396 format); | 1396 format); |
1397 return stencil; | 1397 return stencil; |
1398 } | 1398 } |
1399 | 1399 |
1400 //////////////////////////////////////////////////////////////////////////////// | 1400 //////////////////////////////////////////////////////////////////////////////// |
1401 | 1401 |
1402 GrVertexBuffer* GrGLGpu::onCreateVertexBuffer(size_t size, bool dynamic) { | 1402 GrVertexBuffer* GrGLGpu::onCreateVertexBuffer(size_t size, bool dynamic) { |
1403 GrGLVertexBuffer::Desc desc; | 1403 GrGLVertexBuffer::Desc desc; |
1404 desc.fDynamic = dynamic; | 1404 desc.fUsage = dynamic ? GrGLBufferImpl::kDynamicDraw_Usage : GrGLBufferImpl: :kStaticDraw_Usage; |
1405 desc.fSizeInBytes = size; | 1405 desc.fSizeInBytes = size; |
1406 | 1406 |
1407 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) { | 1407 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && dynamic) { |
1408 desc.fID = 0; | 1408 desc.fID = 0; |
1409 GrGLVertexBuffer* vertexBuffer = new GrGLVertexBuffer(this, desc); | 1409 GrGLVertexBuffer* vertexBuffer = new GrGLVertexBuffer(this, desc); |
1410 return vertexBuffer; | 1410 return vertexBuffer; |
1411 } else { | 1411 } else { |
1412 GL_CALL(GenBuffers(1, &desc.fID)); | 1412 GL_CALL(GenBuffers(1, &desc.fID)); |
1413 if (desc.fID) { | 1413 if (desc.fID) { |
1414 fHWGeometryState.setVertexBufferID(this, desc.fID); | 1414 fHWGeometryState.setVertexBufferID(this, desc.fID); |
1415 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); | 1415 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); |
1416 // make sure driver can allocate memory for this buffer | 1416 // make sure driver can allocate memory for this buffer |
1417 GL_ALLOC_CALL(this->glInterface(), | 1417 GL_ALLOC_CALL(this->glInterface(), |
1418 BufferData(GR_GL_ARRAY_BUFFER, | 1418 BufferData(GR_GL_ARRAY_BUFFER, |
1419 (GrGLsizeiptr) desc.fSizeInBytes, | 1419 (GrGLsizeiptr) desc.fSizeInBytes, |
1420 nullptr, // data ptr | 1420 nullptr, // data ptr |
1421 desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_ STATIC_DRAW)); | 1421 /* Why DYNAMIC_DRAW here and STREAM_DRAW el sewhere? */ |
bsalomon
2015/12/02 16:38:31
Probably no good reason... we should be consistent
jvanverth1
2015/12/02 18:05:07
It looks like it's always stream_draw -- I've chan
bsalomon
2015/12/02 18:39:22
Looks the same as before. I remember the history h
jvanverth1
2015/12/02 18:41:59
I apparently hadn't saved that file when I uploade
| |
1422 dynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_STATIC _DRAW)); | |
1422 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { | 1423 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { |
1423 GL_CALL(DeleteBuffers(1, &desc.fID)); | 1424 GL_CALL(DeleteBuffers(1, &desc.fID)); |
1424 this->notifyVertexBufferDelete(desc.fID); | 1425 this->notifyVertexBufferDelete(desc.fID); |
1425 return nullptr; | 1426 return nullptr; |
1426 } | 1427 } |
1427 GrGLVertexBuffer* vertexBuffer = new GrGLVertexBuffer(this, desc); | 1428 GrGLVertexBuffer* vertexBuffer = new GrGLVertexBuffer(this, desc); |
1428 return vertexBuffer; | 1429 return vertexBuffer; |
1429 } | 1430 } |
1430 return nullptr; | 1431 return nullptr; |
1431 } | 1432 } |
1432 } | 1433 } |
1433 | 1434 |
1434 GrIndexBuffer* GrGLGpu::onCreateIndexBuffer(size_t size, bool dynamic) { | 1435 GrIndexBuffer* GrGLGpu::onCreateIndexBuffer(size_t size, bool dynamic) { |
1435 GrGLIndexBuffer::Desc desc; | 1436 GrGLIndexBuffer::Desc desc; |
1436 desc.fDynamic = dynamic; | 1437 desc.fUsage = dynamic ? GrGLBufferImpl::kDynamicDraw_Usage : GrGLBufferImpl: :kStaticDraw_Usage; |
1437 desc.fSizeInBytes = size; | 1438 desc.fSizeInBytes = size; |
1438 | 1439 |
1439 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) { | 1440 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && dynamic) { |
1440 desc.fID = 0; | 1441 desc.fID = 0; |
1441 GrIndexBuffer* indexBuffer = new GrGLIndexBuffer(this, desc); | 1442 GrIndexBuffer* indexBuffer = new GrGLIndexBuffer(this, desc); |
1442 return indexBuffer; | 1443 return indexBuffer; |
1443 } else { | 1444 } else { |
1444 GL_CALL(GenBuffers(1, &desc.fID)); | 1445 GL_CALL(GenBuffers(1, &desc.fID)); |
1445 if (desc.fID) { | 1446 if (desc.fID) { |
1446 fHWGeometryState.setIndexBufferIDOnDefaultVertexArray(this, desc.fID ); | 1447 fHWGeometryState.setIndexBufferIDOnDefaultVertexArray(this, desc.fID ); |
1447 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); | 1448 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); |
1448 // make sure driver can allocate memory for this buffer | 1449 // make sure driver can allocate memory for this buffer |
1449 GL_ALLOC_CALL(this->glInterface(), | 1450 GL_ALLOC_CALL(this->glInterface(), |
1450 BufferData(GR_GL_ELEMENT_ARRAY_BUFFER, | 1451 BufferData(GR_GL_ELEMENT_ARRAY_BUFFER, |
1451 (GrGLsizeiptr) desc.fSizeInBytes, | 1452 (GrGLsizeiptr) desc.fSizeInBytes, |
1452 nullptr, // data ptr | 1453 nullptr, // data ptr |
1453 desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_ STATIC_DRAW)); | 1454 dynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_STATIC _DRAW)); |
1454 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { | 1455 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { |
1455 GL_CALL(DeleteBuffers(1, &desc.fID)); | 1456 GL_CALL(DeleteBuffers(1, &desc.fID)); |
1456 this->notifyIndexBufferDelete(desc.fID); | 1457 this->notifyIndexBufferDelete(desc.fID); |
1457 return nullptr; | 1458 return nullptr; |
1458 } | 1459 } |
1459 GrIndexBuffer* indexBuffer = new GrGLIndexBuffer(this, desc); | 1460 GrIndexBuffer* indexBuffer = new GrGLIndexBuffer(this, desc); |
1460 return indexBuffer; | 1461 return indexBuffer; |
1461 } | 1462 } |
1462 return nullptr; | 1463 return nullptr; |
1463 } | 1464 } |
1464 } | 1465 } |
1465 | 1466 |
1467 GrTransferBuffer* GrGLGpu::onCreateTransferBuffer(size_t size, bool toGpu) { | |
1468 GrGLTransferBuffer::Desc desc; | |
1469 desc.fUsage = toGpu ? GrGLBufferImpl::kStreamDraw_Usage : GrGLBufferImpl::kS treamRead_Usage; | |
1470 | |
1471 desc.fSizeInBytes = size; | |
1472 | |
1473 // TODO: check caps to see if we can create a PBO, and which kind | |
1474 GL_CALL(GenBuffers(1, &desc.fID)); | |
1475 if (desc.fID) { | |
1476 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); | |
1477 // make sure driver can allocate memory for this buffer | |
1478 GrGLenum type = toGpu ? GR_GL_PIXEL_UNPACK_BUFFER : GR_GL_PIXEL_PACK_BUF FER; | |
1479 GL_ALLOC_CALL(this->glInterface(), | |
1480 BufferData(type, | |
1481 (GrGLsizeiptr) desc.fSizeInBytes, | |
1482 nullptr, // data ptr | |
1483 (toGpu ? GR_GL_STREAM_DRAW : GR_GL_STREAM_READ) )); | |
1484 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { | |
1485 GL_CALL(DeleteBuffers(1, &desc.fID)); | |
1486 return nullptr; | |
1487 } | |
1488 GrTransferBuffer* transferBuffer = new GrGLTransferBuffer(this, desc, ty pe); | |
1489 return transferBuffer; | |
1490 } | |
1491 | |
1492 return nullptr; | |
1493 } | |
1494 | |
1466 void GrGLGpu::flushScissor(const GrScissorState& scissorState, | 1495 void GrGLGpu::flushScissor(const GrScissorState& scissorState, |
1467 const GrGLIRect& rtViewport, | 1496 const GrGLIRect& rtViewport, |
1468 GrSurfaceOrigin rtOrigin) { | 1497 GrSurfaceOrigin rtOrigin) { |
1469 if (scissorState.enabled()) { | 1498 if (scissorState.enabled()) { |
1470 GrGLIRect scissor; | 1499 GrGLIRect scissor; |
1471 scissor.setRelativeTo(rtViewport, | 1500 scissor.setRelativeTo(rtViewport, |
1472 scissorState.rect().fLeft, | 1501 scissorState.rect().fLeft, |
1473 scissorState.rect().fTop, | 1502 scissorState.rect().fTop, |
1474 scissorState.rect().width(), | 1503 scissorState.rect().width(), |
1475 scissorState.rect().height(), | 1504 scissorState.rect().height(), |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1596 const GrPipeline& pipeline) const { | 1625 const GrPipeline& pipeline) const { |
1597 if (!GrGLProgramDescBuilder::Build(desc, primProc, pipeline, this)) { | 1626 if (!GrGLProgramDescBuilder::Build(desc, primProc, pipeline, this)) { |
1598 SkDEBUGFAIL("Failed to generate GL program descriptor"); | 1627 SkDEBUGFAIL("Failed to generate GL program descriptor"); |
1599 } | 1628 } |
1600 } | 1629 } |
1601 | 1630 |
1602 void GrGLGpu::bindBuffer(GrGLuint id, GrGLenum type) { | 1631 void GrGLGpu::bindBuffer(GrGLuint id, GrGLenum type) { |
1603 this->handleDirtyContext(); | 1632 this->handleDirtyContext(); |
1604 if (GR_GL_ARRAY_BUFFER == type) { | 1633 if (GR_GL_ARRAY_BUFFER == type) { |
1605 this->bindVertexBuffer(id); | 1634 this->bindVertexBuffer(id); |
1606 } else { | 1635 } else if (GR_GL_ELEMENT_ARRAY_BUFFER == type) { |
1607 SkASSERT(GR_GL_ELEMENT_ARRAY_BUFFER == type); | |
1608 this->bindIndexBufferAndDefaultVertexArray(id); | 1636 this->bindIndexBufferAndDefaultVertexArray(id); |
1609 } | 1637 } |
1610 } | 1638 } |
1611 | 1639 |
1612 void GrGLGpu::releaseBuffer(GrGLuint id, GrGLenum type) { | 1640 void GrGLGpu::releaseBuffer(GrGLuint id, GrGLenum type) { |
1613 this->handleDirtyContext(); | 1641 this->handleDirtyContext(); |
1614 GL_CALL(DeleteBuffers(1, &id)); | 1642 GL_CALL(DeleteBuffers(1, &id)); |
1615 if (GR_GL_ARRAY_BUFFER == type) { | 1643 if (GR_GL_ARRAY_BUFFER == type) { |
1616 this->notifyVertexBufferDelete(id); | 1644 this->notifyVertexBufferDelete(id); |
1617 } else { | 1645 } else if (GR_GL_ELEMENT_ARRAY_BUFFER == type) { |
bsalomon
2015/12/02 16:38:31
you don't need equivalent here for pbo?
jvanverth1
2015/12/02 18:05:07
It's used to update the geometry state, which I do
| |
1618 SkASSERT(GR_GL_ELEMENT_ARRAY_BUFFER == type); | |
1619 this->notifyIndexBufferDelete(id); | 1646 this->notifyIndexBufferDelete(id); |
1620 } | 1647 } |
1621 } | 1648 } |
1622 | 1649 |
1623 // GL_STREAM_DRAW triggers an optimization in Chromium's GPU process where a cli ent's vertex buffer | 1650 // GL_STREAM_DRAW triggers an optimization in Chromium's GPU process where a cli ent's vertex buffer |
1624 // objects are implemented as client-side-arrays on tile-deferred architectures. | 1651 // objects are implemented as client-side-arrays on tile-deferred architectures. |
1625 #define DYNAMIC_USAGE_PARAM GR_GL_STREAM_DRAW | 1652 #define DYNAMIC_USAGE_PARAM GR_GL_STREAM_DRAW |
1626 | 1653 |
1627 void* GrGLGpu::mapBuffer(GrGLuint id, GrGLenum type, bool dynamic, size_t curren tSize, | 1654 static GrGLenum get_gl_usage(GrGLBufferImpl::Usage usage) { |
1628 size_t requestedSize) { | 1655 static const GrGLenum grToGL[] = { |
1656 GR_GL_STATIC_DRAW, // GrGLBufferImpl::kStaticDraw_Usage | |
1657 DYNAMIC_USAGE_PARAM, // GrGLBufferImpl::kDynamicDraw_Usage | |
1658 GR_GL_STREAM_DRAW, // GrGLBufferImpl::kStreamDraw_Usage | |
1659 GR_GL_STREAM_READ, // GrGLBufferImpl::kStreamRead_Usage | |
1660 }; | |
1661 static_assert(SK_ARRAY_COUNT(grToGL) == GrGLBufferImpl::kUsageCount, "array_ size_mismatch"); | |
1662 SkASSERT(usage < GrGLBufferImpl::kUsageCount); | |
1663 | |
1664 return grToGL[usage]; | |
1665 } | |
1666 | |
1667 void* GrGLGpu::mapBuffer(GrGLuint id, GrGLenum type, GrGLBufferImpl::Usage usage , | |
1668 size_t currentSize, size_t requestedSize) { | |
1629 void* mapPtr = nullptr; | 1669 void* mapPtr = nullptr; |
1670 GrGLenum glUsage = get_gl_usage(usage); | |
1630 // Handling dirty context is done in the bindBuffer call | 1671 // Handling dirty context is done in the bindBuffer call |
1631 switch (this->glCaps().mapBufferType()) { | 1672 switch (this->glCaps().mapBufferType()) { |
1632 case GrGLCaps::kNone_MapBufferType: | 1673 case GrGLCaps::kNone_MapBufferType: |
1633 break; | 1674 break; |
1634 case GrGLCaps::kMapBuffer_MapBufferType: | 1675 case GrGLCaps::kMapBuffer_MapBufferType: |
1635 this->bindBuffer(id, type); | 1676 this->bindBuffer(id, type); |
1636 // Let driver know it can discard the old data | 1677 // Let driver know it can discard the old data |
1637 if (GR_GL_USE_BUFFER_DATA_NULL_HINT || currentSize != requestedSize) { | 1678 if (GR_GL_USE_BUFFER_DATA_NULL_HINT || currentSize != requestedSize) { |
1638 GL_CALL(BufferData(type, requestedSize, nullptr, | 1679 GL_CALL(BufferData(type, requestedSize, nullptr, glUsage)); |
1639 dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_ DRAW)); | |
1640 } | 1680 } |
1641 GL_CALL_RET(mapPtr, MapBuffer(type, GR_GL_WRITE_ONLY)); | 1681 GL_CALL_RET(mapPtr, MapBuffer(type, GR_GL_WRITE_ONLY)); |
1642 break; | 1682 break; |
1643 case GrGLCaps::kMapBufferRange_MapBufferType: { | 1683 case GrGLCaps::kMapBufferRange_MapBufferType: { |
1644 this->bindBuffer(id, type); | 1684 this->bindBuffer(id, type); |
1645 // Make sure the GL buffer size agrees with fDesc before mapping. | 1685 // Make sure the GL buffer size agrees with fDesc before mapping. |
1646 if (currentSize != requestedSize) { | 1686 if (currentSize != requestedSize) { |
1647 GL_CALL(BufferData(type, requestedSize, nullptr, | 1687 GL_CALL(BufferData(type, requestedSize, nullptr, glUsage)); |
1648 dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_ DRAW)); | |
1649 } | 1688 } |
1650 static const GrGLbitfield kAccess = GR_GL_MAP_INVALIDATE_BUFFER_BIT | | 1689 static const GrGLbitfield kAccess = GR_GL_MAP_INVALIDATE_BUFFER_BIT | |
1651 GR_GL_MAP_WRITE_BIT; | 1690 GR_GL_MAP_WRITE_BIT; |
1652 GL_CALL_RET(mapPtr, MapBufferRange(type, 0, requestedSize, kAccess)) ; | 1691 GL_CALL_RET(mapPtr, MapBufferRange(type, 0, requestedSize, kAccess)) ; |
1653 break; | 1692 break; |
1654 } | 1693 } |
1655 case GrGLCaps::kChromium_MapBufferType: | 1694 case GrGLCaps::kChromium_MapBufferType: |
1656 this->bindBuffer(id, type); | 1695 this->bindBuffer(id, type); |
1657 // Make sure the GL buffer size agrees with fDesc before mapping. | 1696 // Make sure the GL buffer size agrees with fDesc before mapping. |
1658 if (currentSize != requestedSize) { | 1697 if (currentSize != requestedSize) { |
1659 GL_CALL(BufferData(type, requestedSize, nullptr, | 1698 GL_CALL(BufferData(type, requestedSize, nullptr, glUsage)); |
1660 dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_ DRAW)); | |
1661 } | 1699 } |
1662 GL_CALL_RET(mapPtr, MapBufferSubData(type, 0, requestedSize, GR_GL_W RITE_ONLY)); | 1700 GL_CALL_RET(mapPtr, MapBufferSubData(type, 0, requestedSize, GR_GL_W RITE_ONLY)); |
1663 break; | 1701 break; |
1664 } | 1702 } |
1665 return mapPtr; | 1703 return mapPtr; |
1666 } | 1704 } |
1667 | 1705 |
1668 void GrGLGpu::bufferData(GrGLuint id, GrGLenum type, bool dynamic, size_t curren tSize, | 1706 void GrGLGpu::bufferData(GrGLuint id, GrGLenum type, GrGLBufferImpl::Usage usage , |
1669 const void* src, size_t srcSizeInBytes) { | 1707 size_t currentSize, const void* src, size_t srcSizeInBy tes) { |
1670 SkASSERT(srcSizeInBytes <= currentSize); | 1708 SkASSERT(srcSizeInBytes <= currentSize); |
1671 // bindbuffer handles dirty context | 1709 // bindbuffer handles dirty context |
1672 this->bindBuffer(id, type); | 1710 this->bindBuffer(id, type); |
1673 GrGLenum usage = dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_DRAW; | 1711 GrGLenum glUsage = get_gl_usage(usage); |
1674 | 1712 |
1675 #if GR_GL_USE_BUFFER_DATA_NULL_HINT | 1713 #if GR_GL_USE_BUFFER_DATA_NULL_HINT |
1676 if (currentSize == srcSizeInBytes) { | 1714 if (currentSize == srcSizeInBytes) { |
1677 GL_CALL(BufferData(type, (GrGLsizeiptr) srcSizeInBytes, src, usage)); | 1715 GL_CALL(BufferData(type, (GrGLsizeiptr) srcSizeInBytes, src, glUsage)); |
1678 } else { | 1716 } else { |
1679 // Before we call glBufferSubData we give the driver a hint using | 1717 // Before we call glBufferSubData we give the driver a hint using |
1680 // glBufferData with nullptr. This makes the old buffer contents | 1718 // glBufferData with nullptr. This makes the old buffer contents |
1681 // inaccessible to future draws. The GPU may still be processing | 1719 // inaccessible to future draws. The GPU may still be processing |
1682 // draws that reference the old contents. With this hint it can | 1720 // draws that reference the old contents. With this hint it can |
1683 // assign a different allocation for the new contents to avoid | 1721 // assign a different allocation for the new contents to avoid |
1684 // flushing the gpu past draws consuming the old contents. | 1722 // flushing the gpu past draws consuming the old contents. |
1685 // TODO I think we actually want to try calling bufferData here | 1723 // TODO I think we actually want to try calling bufferData here |
1686 GL_CALL(BufferData(type, currentSize, nullptr, usage)); | 1724 GL_CALL(BufferData(type, currentSize, nullptr, glUsage)); |
1687 GL_CALL(BufferSubData(type, 0, (GrGLsizeiptr) srcSizeInBytes, src)); | 1725 GL_CALL(BufferSubData(type, 0, (GrGLsizeiptr) srcSizeInBytes, src)); |
1688 } | 1726 } |
1689 #else | 1727 #else |
1690 // Note that we're cheating on the size here. Currently no methods | 1728 // Note that we're cheating on the size here. Currently no methods |
1691 // allow a partial update that preserves contents of non-updated | 1729 // allow a partial update that preserves contents of non-updated |
1692 // portions of the buffer (map() does a glBufferData(..size, nullptr..)) | 1730 // portions of the buffer (map() does a glBufferData(..size, nullptr..)) |
1693 GL_CALL(BufferData(type, srcSizeInBytes, src, usage)); | 1731 GL_CALL(BufferData(type, srcSizeInBytes, src, usage)); |
1694 #endif | 1732 #endif |
1695 } | 1733 } |
1696 | 1734 |
(...skipping 1701 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
3398 this->setVertexArrayID(gpu, 0); | 3436 this->setVertexArrayID(gpu, 0); |
3399 } | 3437 } |
3400 int attrCount = gpu->glCaps().maxVertexAttributes(); | 3438 int attrCount = gpu->glCaps().maxVertexAttributes(); |
3401 if (fDefaultVertexArrayAttribState.count() != attrCount) { | 3439 if (fDefaultVertexArrayAttribState.count() != attrCount) { |
3402 fDefaultVertexArrayAttribState.resize(attrCount); | 3440 fDefaultVertexArrayAttribState.resize(attrCount); |
3403 } | 3441 } |
3404 attribState = &fDefaultVertexArrayAttribState; | 3442 attribState = &fDefaultVertexArrayAttribState; |
3405 } | 3443 } |
3406 return attribState; | 3444 return attribState; |
3407 } | 3445 } |
OLD | NEW |