Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(80)

Side by Side Diff: src/gpu/gl/GrGLGpu.cpp

Issue 1490473003: Add transfer buffer support. (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Fix roll issue Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/gpu/gl/GrGLGpu.h ('k') | src/gpu/gl/GrGLIndexBuffer.cpp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 1381 matching lines...) Expand 10 before | Expand all | Expand 10 after
1392 sbDesc, 1392 sbDesc,
1393 width, 1393 width,
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 // GL_STREAM_DRAW triggers an optimization in Chromium's GPU process where a cli ent's vertex buffer
1403 // objects are implemented as client-side-arrays on tile-deferred architectures.
1404 #define DYNAMIC_USAGE_PARAM GR_GL_STREAM_DRAW
1405
1402 GrVertexBuffer* GrGLGpu::onCreateVertexBuffer(size_t size, bool dynamic) { 1406 GrVertexBuffer* GrGLGpu::onCreateVertexBuffer(size_t size, bool dynamic) {
1403 GrGLVertexBuffer::Desc desc; 1407 GrGLVertexBuffer::Desc desc;
1404 desc.fDynamic = dynamic; 1408 desc.fUsage = dynamic ? GrGLBufferImpl::kDynamicDraw_Usage : GrGLBufferImpl: :kStaticDraw_Usage;
1405 desc.fSizeInBytes = size; 1409 desc.fSizeInBytes = size;
1406 1410
1407 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) { 1411 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && dynamic) {
1408 desc.fID = 0; 1412 desc.fID = 0;
1409 GrGLVertexBuffer* vertexBuffer = new GrGLVertexBuffer(this, desc); 1413 GrGLVertexBuffer* vertexBuffer = new GrGLVertexBuffer(this, desc);
1410 return vertexBuffer; 1414 return vertexBuffer;
1411 } else { 1415 } else {
1412 GL_CALL(GenBuffers(1, &desc.fID)); 1416 GL_CALL(GenBuffers(1, &desc.fID));
1413 if (desc.fID) { 1417 if (desc.fID) {
1414 fHWGeometryState.setVertexBufferID(this, desc.fID); 1418 fHWGeometryState.setVertexBufferID(this, desc.fID);
1415 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); 1419 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface());
1416 // make sure driver can allocate memory for this buffer 1420 // make sure driver can allocate memory for this buffer
1417 GL_ALLOC_CALL(this->glInterface(), 1421 GL_ALLOC_CALL(this->glInterface(),
1418 BufferData(GR_GL_ARRAY_BUFFER, 1422 BufferData(GR_GL_ARRAY_BUFFER,
1419 (GrGLsizeiptr) desc.fSizeInBytes, 1423 (GrGLsizeiptr) desc.fSizeInBytes,
1420 nullptr, // data ptr 1424 nullptr, // data ptr
1421 desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_ STATIC_DRAW)); 1425 dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATI C_DRAW));
1422 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { 1426 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) {
1423 GL_CALL(DeleteBuffers(1, &desc.fID)); 1427 GL_CALL(DeleteBuffers(1, &desc.fID));
1424 this->notifyVertexBufferDelete(desc.fID); 1428 this->notifyVertexBufferDelete(desc.fID);
1425 return nullptr; 1429 return nullptr;
1426 } 1430 }
1427 GrGLVertexBuffer* vertexBuffer = new GrGLVertexBuffer(this, desc); 1431 GrGLVertexBuffer* vertexBuffer = new GrGLVertexBuffer(this, desc);
1428 return vertexBuffer; 1432 return vertexBuffer;
1429 } 1433 }
1430 return nullptr; 1434 return nullptr;
1431 } 1435 }
1432 } 1436 }
1433 1437
1434 GrIndexBuffer* GrGLGpu::onCreateIndexBuffer(size_t size, bool dynamic) { 1438 GrIndexBuffer* GrGLGpu::onCreateIndexBuffer(size_t size, bool dynamic) {
1435 GrGLIndexBuffer::Desc desc; 1439 GrGLIndexBuffer::Desc desc;
1436 desc.fDynamic = dynamic; 1440 desc.fUsage = dynamic ? GrGLBufferImpl::kDynamicDraw_Usage : GrGLBufferImpl: :kStaticDraw_Usage;
1437 desc.fSizeInBytes = size; 1441 desc.fSizeInBytes = size;
1438 1442
1439 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && desc.fDynamic) { 1443 if (this->glCaps().useNonVBOVertexAndIndexDynamicData() && dynamic) {
1440 desc.fID = 0; 1444 desc.fID = 0;
1441 GrIndexBuffer* indexBuffer = new GrGLIndexBuffer(this, desc); 1445 GrIndexBuffer* indexBuffer = new GrGLIndexBuffer(this, desc);
1442 return indexBuffer; 1446 return indexBuffer;
1443 } else { 1447 } else {
1444 GL_CALL(GenBuffers(1, &desc.fID)); 1448 GL_CALL(GenBuffers(1, &desc.fID));
1445 if (desc.fID) { 1449 if (desc.fID) {
1446 fHWGeometryState.setIndexBufferIDOnDefaultVertexArray(this, desc.fID ); 1450 fHWGeometryState.setIndexBufferIDOnDefaultVertexArray(this, desc.fID );
1447 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface()); 1451 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface());
1448 // make sure driver can allocate memory for this buffer 1452 // make sure driver can allocate memory for this buffer
1449 GL_ALLOC_CALL(this->glInterface(), 1453 GL_ALLOC_CALL(this->glInterface(),
1450 BufferData(GR_GL_ELEMENT_ARRAY_BUFFER, 1454 BufferData(GR_GL_ELEMENT_ARRAY_BUFFER,
1451 (GrGLsizeiptr) desc.fSizeInBytes, 1455 (GrGLsizeiptr) desc.fSizeInBytes,
1452 nullptr, // data ptr 1456 nullptr, // data ptr
1453 desc.fDynamic ? GR_GL_DYNAMIC_DRAW : GR_GL_ STATIC_DRAW)); 1457 dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATI C_DRAW));
1454 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) { 1458 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) {
1455 GL_CALL(DeleteBuffers(1, &desc.fID)); 1459 GL_CALL(DeleteBuffers(1, &desc.fID));
1456 this->notifyIndexBufferDelete(desc.fID); 1460 this->notifyIndexBufferDelete(desc.fID);
1457 return nullptr; 1461 return nullptr;
1458 } 1462 }
1459 GrIndexBuffer* indexBuffer = new GrGLIndexBuffer(this, desc); 1463 GrIndexBuffer* indexBuffer = new GrGLIndexBuffer(this, desc);
1460 return indexBuffer; 1464 return indexBuffer;
1461 } 1465 }
1462 return nullptr; 1466 return nullptr;
1463 } 1467 }
1464 } 1468 }
1465 1469
1470 GrTransferBuffer* GrGLGpu::onCreateTransferBuffer(size_t size, TransferType type ) {
1471 GrGLTransferBuffer::Desc desc;
1472 bool toGpu = (kCpuToGpu_TransferType == type);
1473 desc.fUsage = toGpu ? GrGLBufferImpl::kStreamDraw_Usage : GrGLBufferImpl::kS treamRead_Usage;
1474
1475 desc.fSizeInBytes = size;
1476
1477 // TODO: check caps to see if we can create a PBO, and which kind
1478 GL_CALL(GenBuffers(1, &desc.fID));
1479 if (desc.fID) {
1480 CLEAR_ERROR_BEFORE_ALLOC(this->glInterface());
1481 // make sure driver can allocate memory for this buffer
1482 GrGLenum type = toGpu ? GR_GL_PIXEL_UNPACK_BUFFER : GR_GL_PIXEL_PACK_BUF FER;
1483 GL_ALLOC_CALL(this->glInterface(),
1484 BufferData(type,
1485 (GrGLsizeiptr) desc.fSizeInBytes,
1486 nullptr, // data ptr
1487 (toGpu ? GR_GL_STREAM_DRAW : GR_GL_STREAM_READ) ));
1488 if (CHECK_ALLOC_ERROR(this->glInterface()) != GR_GL_NO_ERROR) {
1489 GL_CALL(DeleteBuffers(1, &desc.fID));
1490 return nullptr;
1491 }
1492 GrTransferBuffer* transferBuffer = new GrGLTransferBuffer(this, desc, ty pe);
1493 return transferBuffer;
1494 }
1495
1496 return nullptr;
1497 }
1498
1466 void GrGLGpu::flushScissor(const GrScissorState& scissorState, 1499 void GrGLGpu::flushScissor(const GrScissorState& scissorState,
1467 const GrGLIRect& rtViewport, 1500 const GrGLIRect& rtViewport,
1468 GrSurfaceOrigin rtOrigin) { 1501 GrSurfaceOrigin rtOrigin) {
1469 if (scissorState.enabled()) { 1502 if (scissorState.enabled()) {
1470 GrGLIRect scissor; 1503 GrGLIRect scissor;
1471 scissor.setRelativeTo(rtViewport, 1504 scissor.setRelativeTo(rtViewport,
1472 scissorState.rect().fLeft, 1505 scissorState.rect().fLeft,
1473 scissorState.rect().fTop, 1506 scissorState.rect().fTop,
1474 scissorState.rect().width(), 1507 scissorState.rect().width(),
1475 scissorState.rect().height(), 1508 scissorState.rect().height(),
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after
1596 const GrPipeline& pipeline) const { 1629 const GrPipeline& pipeline) const {
1597 if (!GrGLProgramDescBuilder::Build(desc, primProc, pipeline, this)) { 1630 if (!GrGLProgramDescBuilder::Build(desc, primProc, pipeline, this)) {
1598 SkDEBUGFAIL("Failed to generate GL program descriptor"); 1631 SkDEBUGFAIL("Failed to generate GL program descriptor");
1599 } 1632 }
1600 } 1633 }
1601 1634
1602 void GrGLGpu::bindBuffer(GrGLuint id, GrGLenum type) { 1635 void GrGLGpu::bindBuffer(GrGLuint id, GrGLenum type) {
1603 this->handleDirtyContext(); 1636 this->handleDirtyContext();
1604 if (GR_GL_ARRAY_BUFFER == type) { 1637 if (GR_GL_ARRAY_BUFFER == type) {
1605 this->bindVertexBuffer(id); 1638 this->bindVertexBuffer(id);
1606 } else { 1639 } else if (GR_GL_ELEMENT_ARRAY_BUFFER == type) {
1607 SkASSERT(GR_GL_ELEMENT_ARRAY_BUFFER == type);
1608 this->bindIndexBufferAndDefaultVertexArray(id); 1640 this->bindIndexBufferAndDefaultVertexArray(id);
1609 } 1641 }
1610 } 1642 }
1611 1643
1612 void GrGLGpu::releaseBuffer(GrGLuint id, GrGLenum type) { 1644 void GrGLGpu::releaseBuffer(GrGLuint id, GrGLenum type) {
1613 this->handleDirtyContext(); 1645 this->handleDirtyContext();
1614 GL_CALL(DeleteBuffers(1, &id)); 1646 GL_CALL(DeleteBuffers(1, &id));
1615 if (GR_GL_ARRAY_BUFFER == type) { 1647 if (GR_GL_ARRAY_BUFFER == type) {
1616 this->notifyVertexBufferDelete(id); 1648 this->notifyVertexBufferDelete(id);
1617 } else { 1649 } else if (GR_GL_ELEMENT_ARRAY_BUFFER == type) {
1618 SkASSERT(GR_GL_ELEMENT_ARRAY_BUFFER == type);
1619 this->notifyIndexBufferDelete(id); 1650 this->notifyIndexBufferDelete(id);
1620 } 1651 }
1621 } 1652 }
1622 1653
1623 // GL_STREAM_DRAW triggers an optimization in Chromium's GPU process where a cli ent's vertex buffer 1654 static GrGLenum get_gl_usage(GrGLBufferImpl::Usage usage) {
1624 // objects are implemented as client-side-arrays on tile-deferred architectures. 1655 static const GrGLenum grToGL[] = {
1625 #define DYNAMIC_USAGE_PARAM GR_GL_STREAM_DRAW 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");
1626 1662
1627 void* GrGLGpu::mapBuffer(GrGLuint id, GrGLenum type, bool dynamic, size_t curren tSize, 1663 return grToGL[usage];
1628 size_t requestedSize) { 1664 }
1665
1666 void* GrGLGpu::mapBuffer(GrGLuint id, GrGLenum type, GrGLBufferImpl::Usage usage ,
1667 size_t currentSize, size_t requestedSize) {
1629 void* mapPtr = nullptr; 1668 void* mapPtr = nullptr;
1669 GrGLenum glUsage = get_gl_usage(usage);
1630 // Handling dirty context is done in the bindBuffer call 1670 // Handling dirty context is done in the bindBuffer call
1631 switch (this->glCaps().mapBufferType()) { 1671 switch (this->glCaps().mapBufferType()) {
1632 case GrGLCaps::kNone_MapBufferType: 1672 case GrGLCaps::kNone_MapBufferType:
1633 break; 1673 break;
1634 case GrGLCaps::kMapBuffer_MapBufferType: 1674 case GrGLCaps::kMapBuffer_MapBufferType:
1635 this->bindBuffer(id, type); 1675 this->bindBuffer(id, type);
1636 // Let driver know it can discard the old data 1676 // Let driver know it can discard the old data
1637 if (GR_GL_USE_BUFFER_DATA_NULL_HINT || currentSize != requestedSize) { 1677 if (GR_GL_USE_BUFFER_DATA_NULL_HINT || currentSize != requestedSize) {
1638 GL_CALL(BufferData(type, requestedSize, nullptr, 1678 GL_CALL(BufferData(type, requestedSize, nullptr, glUsage));
1639 dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_ DRAW));
1640 } 1679 }
1641 GL_CALL_RET(mapPtr, MapBuffer(type, GR_GL_WRITE_ONLY)); 1680 GL_CALL_RET(mapPtr, MapBuffer(type, GR_GL_WRITE_ONLY));
1642 break; 1681 break;
1643 case GrGLCaps::kMapBufferRange_MapBufferType: { 1682 case GrGLCaps::kMapBufferRange_MapBufferType: {
1644 this->bindBuffer(id, type); 1683 this->bindBuffer(id, type);
1645 // Make sure the GL buffer size agrees with fDesc before mapping. 1684 // Make sure the GL buffer size agrees with fDesc before mapping.
1646 if (currentSize != requestedSize) { 1685 if (currentSize != requestedSize) {
1647 GL_CALL(BufferData(type, requestedSize, nullptr, 1686 GL_CALL(BufferData(type, requestedSize, nullptr, glUsage));
1648 dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_ DRAW));
1649 } 1687 }
1650 static const GrGLbitfield kAccess = GR_GL_MAP_INVALIDATE_BUFFER_BIT | 1688 static const GrGLbitfield kAccess = GR_GL_MAP_INVALIDATE_BUFFER_BIT |
1651 GR_GL_MAP_WRITE_BIT; 1689 GR_GL_MAP_WRITE_BIT;
1652 GL_CALL_RET(mapPtr, MapBufferRange(type, 0, requestedSize, kAccess)) ; 1690 GL_CALL_RET(mapPtr, MapBufferRange(type, 0, requestedSize, kAccess)) ;
1653 break; 1691 break;
1654 } 1692 }
1655 case GrGLCaps::kChromium_MapBufferType: 1693 case GrGLCaps::kChromium_MapBufferType:
1656 this->bindBuffer(id, type); 1694 this->bindBuffer(id, type);
1657 // Make sure the GL buffer size agrees with fDesc before mapping. 1695 // Make sure the GL buffer size agrees with fDesc before mapping.
1658 if (currentSize != requestedSize) { 1696 if (currentSize != requestedSize) {
1659 GL_CALL(BufferData(type, requestedSize, nullptr, 1697 GL_CALL(BufferData(type, requestedSize, nullptr, glUsage));
1660 dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_ DRAW));
1661 } 1698 }
1662 GL_CALL_RET(mapPtr, MapBufferSubData(type, 0, requestedSize, GR_GL_W RITE_ONLY)); 1699 GL_CALL_RET(mapPtr, MapBufferSubData(type, 0, requestedSize, GR_GL_W RITE_ONLY));
1663 break; 1700 break;
1664 } 1701 }
1665 return mapPtr; 1702 return mapPtr;
1666 } 1703 }
1667 1704
1668 void GrGLGpu::bufferData(GrGLuint id, GrGLenum type, bool dynamic, size_t curren tSize, 1705 void GrGLGpu::bufferData(GrGLuint id, GrGLenum type, GrGLBufferImpl::Usage usage ,
1669 const void* src, size_t srcSizeInBytes) { 1706 size_t currentSize, const void* src, size_t srcSizeInBy tes) {
1670 SkASSERT(srcSizeInBytes <= currentSize); 1707 SkASSERT(srcSizeInBytes <= currentSize);
1671 // bindbuffer handles dirty context 1708 // bindbuffer handles dirty context
1672 this->bindBuffer(id, type); 1709 this->bindBuffer(id, type);
1673 GrGLenum usage = dynamic ? DYNAMIC_USAGE_PARAM : GR_GL_STATIC_DRAW; 1710 GrGLenum glUsage = get_gl_usage(usage);
1674 1711
1675 #if GR_GL_USE_BUFFER_DATA_NULL_HINT 1712 #if GR_GL_USE_BUFFER_DATA_NULL_HINT
1676 if (currentSize == srcSizeInBytes) { 1713 if (currentSize == srcSizeInBytes) {
1677 GL_CALL(BufferData(type, (GrGLsizeiptr) srcSizeInBytes, src, usage)); 1714 GL_CALL(BufferData(type, (GrGLsizeiptr) srcSizeInBytes, src, glUsage));
1678 } else { 1715 } else {
1679 // Before we call glBufferSubData we give the driver a hint using 1716 // Before we call glBufferSubData we give the driver a hint using
1680 // glBufferData with nullptr. This makes the old buffer contents 1717 // glBufferData with nullptr. This makes the old buffer contents
1681 // inaccessible to future draws. The GPU may still be processing 1718 // inaccessible to future draws. The GPU may still be processing
1682 // draws that reference the old contents. With this hint it can 1719 // draws that reference the old contents. With this hint it can
1683 // assign a different allocation for the new contents to avoid 1720 // assign a different allocation for the new contents to avoid
1684 // flushing the gpu past draws consuming the old contents. 1721 // flushing the gpu past draws consuming the old contents.
1685 // TODO I think we actually want to try calling bufferData here 1722 // TODO I think we actually want to try calling bufferData here
1686 GL_CALL(BufferData(type, currentSize, nullptr, usage)); 1723 GL_CALL(BufferData(type, currentSize, nullptr, glUsage));
1687 GL_CALL(BufferSubData(type, 0, (GrGLsizeiptr) srcSizeInBytes, src)); 1724 GL_CALL(BufferSubData(type, 0, (GrGLsizeiptr) srcSizeInBytes, src));
1688 } 1725 }
1689 #else 1726 #else
1690 // Note that we're cheating on the size here. Currently no methods 1727 // Note that we're cheating on the size here. Currently no methods
1691 // allow a partial update that preserves contents of non-updated 1728 // allow a partial update that preserves contents of non-updated
1692 // portions of the buffer (map() does a glBufferData(..size, nullptr..)) 1729 // portions of the buffer (map() does a glBufferData(..size, nullptr..))
1693 GL_CALL(BufferData(type, srcSizeInBytes, src, usage)); 1730 GL_CALL(BufferData(type, srcSizeInBytes, src, glUsage));
1694 #endif 1731 #endif
1695 } 1732 }
1696 1733
1697 void GrGLGpu::unmapBuffer(GrGLuint id, GrGLenum type, void* mapPtr) { 1734 void GrGLGpu::unmapBuffer(GrGLuint id, GrGLenum type, void* mapPtr) {
1698 // bind buffer handles the dirty context 1735 // bind buffer handles the dirty context
1699 switch (this->glCaps().mapBufferType()) { 1736 switch (this->glCaps().mapBufferType()) {
1700 case GrGLCaps::kNone_MapBufferType: 1737 case GrGLCaps::kNone_MapBufferType:
1701 SkDEBUGFAIL("Shouldn't get here."); 1738 SkDEBUGFAIL("Shouldn't get here.");
1702 return; 1739 return;
1703 case GrGLCaps::kMapBuffer_MapBufferType: // fall through 1740 case GrGLCaps::kMapBuffer_MapBufferType: // fall through
(...skipping 1694 matching lines...) Expand 10 before | Expand all | Expand 10 after
3398 this->setVertexArrayID(gpu, 0); 3435 this->setVertexArrayID(gpu, 0);
3399 } 3436 }
3400 int attrCount = gpu->glCaps().maxVertexAttributes(); 3437 int attrCount = gpu->glCaps().maxVertexAttributes();
3401 if (fDefaultVertexArrayAttribState.count() != attrCount) { 3438 if (fDefaultVertexArrayAttribState.count() != attrCount) {
3402 fDefaultVertexArrayAttribState.resize(attrCount); 3439 fDefaultVertexArrayAttribState.resize(attrCount);
3403 } 3440 }
3404 attribState = &fDefaultVertexArrayAttribState; 3441 attribState = &fDefaultVertexArrayAttribState;
3405 } 3442 }
3406 return attribState; 3443 return attribState;
3407 } 3444 }
OLDNEW
« no previous file with comments | « src/gpu/gl/GrGLGpu.h ('k') | src/gpu/gl/GrGLIndexBuffer.cpp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698