Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright 2015 Google Inc. | 2 * Copyright 2015 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 #include "GrVkMemory.h" | 8 #include "GrVkMemory.h" |
| 9 | 9 |
| 10 #include "GrVkGpu.h" | 10 #include "GrVkGpu.h" |
| 11 #include "GrVkUtil.h" | 11 #include "GrVkUtil.h" |
| 12 | 12 |
| 13 #ifdef SK_DEBUG | |
| 14 // for simple tracking of how much we're using in each heap | |
| 15 // last counter is for non-subheap allocations | |
| 16 VkDeviceSize gHeapUsage[VK_MAX_MEMORY_HEAPS+1] = { 0 }; | |
| 17 #endif | |
| 18 | |
| 13 static bool get_valid_memory_type_index(const VkPhysicalDeviceMemoryProperties& physDevMemProps, | 19 static bool get_valid_memory_type_index(const VkPhysicalDeviceMemoryProperties& physDevMemProps, |
| 14 uint32_t typeBits, | 20 uint32_t typeBits, |
| 15 VkMemoryPropertyFlags requestedMemFlags, | 21 VkMemoryPropertyFlags requestedMemFlags, |
| 16 uint32_t* typeIndex) { | 22 uint32_t* typeIndex) { |
| 17 for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) { | 23 for (uint32_t i = 0; i < physDevMemProps.memoryTypeCount; ++i) { |
| 18 if (typeBits & (1 << i)) { | 24 if (typeBits & (1 << i)) { |
| 19 uint32_t supportedFlags = physDevMemProps.memoryTypes[i].propertyFla gs & | 25 uint32_t supportedFlags = physDevMemProps.memoryTypes[i].propertyFla gs & |
| 20 requestedMemFlags; | 26 requestedMemFlags; |
| 21 if (supportedFlags == requestedMemFlags) { | 27 if (supportedFlags == requestedMemFlags) { |
| 22 *typeIndex = i; | 28 *typeIndex = i; |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 75 alloc->fFlags = mpf & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 0x0 | 81 alloc->fFlags = mpf & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ? 0x0 |
| 76 : GrVkAlloc:: kNoncoherent_Flag; | 82 : GrVkAlloc:: kNoncoherent_Flag; |
| 77 } else { | 83 } else { |
| 78 // device-local memory should always be available for static buffers | 84 // device-local memory should always be available for static buffers |
| 79 SkASSERT_RELEASE(get_valid_memory_type_index(phDevMemProps, | 85 SkASSERT_RELEASE(get_valid_memory_type_index(phDevMemProps, |
| 80 memReqs.memoryTypeBits, | 86 memReqs.memoryTypeBits, |
| 81 VK_MEMORY_PROPERTY_DEVICE_L OCAL_BIT, | 87 VK_MEMORY_PROPERTY_DEVICE_L OCAL_BIT, |
| 82 &typeIndex)); | 88 &typeIndex)); |
| 83 alloc->fFlags = 0x0; | 89 alloc->fFlags = 0x0; |
| 84 } | 90 } |
| 91 uint32_t heapIndex = phDevMemProps.memoryTypes[typeIndex].heapIndex; | |
| 85 | 92 |
| 86 GrVkHeap* heap = gpu->getHeap(buffer_type_to_heap(type)); | 93 GrVkHeap* heap = gpu->getHeap(buffer_type_to_heap(type)); |
| 87 | 94 |
| 88 if (!heap->alloc(memReqs.size, memReqs.alignment, typeIndex, alloc)) { | 95 if (!heap->alloc(memReqs.size, memReqs.alignment, typeIndex, heapIndex, allo c)) { |
| 89 SkDebugf("Failed to alloc buffer\n"); | 96 // if static, try to allocate from non-host-visible non-device-local mem ory instead |
| 90 return false; | 97 if (dynamic || |
| 98 !get_valid_memory_type_index(phDevMemProps, memReqs.memoryTypeBits, 0, &typeIndex) || | |
| 99 !heap->alloc(memReqs.size, memReqs.alignment, typeIndex, heapIndex, alloc)) { | |
|
egdaniel
2016/09/22 13:25:46
do we need to update heap index between these?
jvanverth1
2016/09/22 14:14:51
Done.
| |
| 100 SkDebugf("Failed to alloc buffer\n"); | |
| 101 return false; | |
| 102 } | |
| 91 } | 103 } |
| 92 | 104 |
| 93 // Bind buffer | 105 // Bind buffer |
| 94 VkResult err = GR_VK_CALL(iface, BindBufferMemory(device, buffer, | 106 VkResult err = GR_VK_CALL(iface, BindBufferMemory(device, buffer, |
| 95 alloc->fMemory, alloc->fOf fset)); | 107 alloc->fMemory, alloc->fOf fset)); |
| 96 if (err) { | 108 if (err) { |
| 97 SkASSERT_RELEASE(heap->free(*alloc)); | 109 SkASSERT_RELEASE(heap->free(*alloc)); |
| 98 return false; | 110 return false; |
| 99 } | 111 } |
| 100 | 112 |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 156 VK_MEMORY_PROPERTY_DEVICE_L OCAL_BIT, | 168 VK_MEMORY_PROPERTY_DEVICE_L OCAL_BIT, |
| 157 &typeIndex)); | 169 &typeIndex)); |
| 158 if (memReqs.size <= kMaxSmallImageSize) { | 170 if (memReqs.size <= kMaxSmallImageSize) { |
| 159 heap = gpu->getHeap(GrVkGpu::kSmallOptimalImage_Heap); | 171 heap = gpu->getHeap(GrVkGpu::kSmallOptimalImage_Heap); |
| 160 } else { | 172 } else { |
| 161 heap = gpu->getHeap(GrVkGpu::kOptimalImage_Heap); | 173 heap = gpu->getHeap(GrVkGpu::kOptimalImage_Heap); |
| 162 } | 174 } |
| 163 alloc->fFlags = 0x0; | 175 alloc->fFlags = 0x0; |
| 164 } | 176 } |
| 165 | 177 |
| 166 if (!heap->alloc(memReqs.size, memReqs.alignment, typeIndex, alloc)) { | 178 uint32_t heapIndex = phDevMemProps.memoryTypes[typeIndex].heapIndex; |
| 167 SkDebugf("Failed to alloc image\n"); | 179 if (!heap->alloc(memReqs.size, memReqs.alignment, typeIndex, heapIndex, allo c)) { |
| 168 return false; | 180 // if optimal, try to allocate from non-host-visible non-device-local me mory instead |
| 181 if (linearTiling || | |
| 182 !get_valid_memory_type_index(phDevMemProps, memReqs.memoryTypeBits, 0, &typeIndex) || | |
| 183 !heap->alloc(memReqs.size, memReqs.alignment, typeIndex, heapIndex, alloc)) { | |
| 184 SkDebugf("Failed to alloc image\n"); | |
| 185 return false; | |
| 186 } | |
| 169 } | 187 } |
| 170 | 188 |
| 171 // Bind image | 189 // Bind image |
| 172 VkResult err = GR_VK_CALL(iface, BindImageMemory(device, image, | 190 VkResult err = GR_VK_CALL(iface, BindImageMemory(device, image, |
| 173 alloc->fMemory, alloc->fOffset)); | 191 alloc->fMemory, alloc->fOffset)); |
| 174 if (err) { | 192 if (err) { |
| 175 SkASSERT_RELEASE(heap->free(*alloc)); | 193 SkASSERT_RELEASE(heap->free(*alloc)); |
| 176 return false; | 194 return false; |
| 177 } | 195 } |
| 178 | 196 |
| (...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 424 Block* block = iter.get(); | 442 Block* block = iter.get(); |
| 425 if (largestSize < block->fSize) { | 443 if (largestSize < block->fSize) { |
| 426 largestSize = block->fSize; | 444 largestSize = block->fSize; |
| 427 } | 445 } |
| 428 iter.next(); | 446 iter.next(); |
| 429 } | 447 } |
| 430 SkASSERT(fLargestBlockSize == largestSize); | 448 SkASSERT(fLargestBlockSize == largestSize); |
| 431 #endif | 449 #endif |
| 432 } | 450 } |
| 433 | 451 |
| 434 GrVkSubHeap::GrVkSubHeap(const GrVkGpu* gpu, uint32_t memoryTypeIndex, | 452 GrVkSubHeap::GrVkSubHeap(const GrVkGpu* gpu, uint32_t memoryTypeIndex, uint32_t heapIndex, |
| 435 VkDeviceSize size, VkDeviceSize alignment) | 453 VkDeviceSize size, VkDeviceSize alignment) |
| 436 : INHERITED(size, alignment) | 454 : INHERITED(size, alignment) |
| 437 , fGpu(gpu) | 455 , fGpu(gpu) |
| 438 , fMemoryTypeIndex(memoryTypeIndex) { | 456 , fMemoryTypeIndex(memoryTypeIndex) |
| 457 , fHeapIndex(heapIndex) { | |
| 439 | 458 |
| 440 VkMemoryAllocateInfo allocInfo = { | 459 VkMemoryAllocateInfo allocInfo = { |
| 441 VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // sType | 460 VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // sType |
| 442 NULL, // pNext | 461 NULL, // pNext |
| 443 size, // allocationSize | 462 size, // allocationSize |
| 444 memoryTypeIndex, // memoryTypeIndex | 463 memoryTypeIndex, // memoryTypeIndex |
| 445 }; | 464 }; |
| 446 | 465 |
| 447 VkResult err = GR_VK_CALL(gpu->vkInterface(), AllocateMemory(gpu->device(), | 466 VkResult err = GR_VK_CALL(gpu->vkInterface(), AllocateMemory(gpu->device(), |
| 448 &allocInfo, | 467 &allocInfo, |
| 449 nullptr, | 468 nullptr, |
| 450 &fAlloc)); | 469 &fAlloc)); |
| 451 if (VK_SUCCESS != err) { | 470 if (VK_SUCCESS != err) { |
| 452 this->reset(); | 471 this->reset(); |
| 472 } | |
| 473 #ifdef SK_DEBUG | |
| 474 else { | |
| 475 gHeapUsage[heapIndex] += size; | |
| 453 } | 476 } |
| 477 #endif | |
| 454 } | 478 } |
| 455 | 479 |
| 456 GrVkSubHeap::~GrVkSubHeap() { | 480 GrVkSubHeap::~GrVkSubHeap() { |
| 457 const GrVkInterface* iface = fGpu->vkInterface(); | 481 const GrVkInterface* iface = fGpu->vkInterface(); |
| 458 GR_VK_CALL(iface, FreeMemory(fGpu->device(), fAlloc, nullptr)); | 482 GR_VK_CALL(iface, FreeMemory(fGpu->device(), fAlloc, nullptr)); |
| 483 #ifdef SK_DEBUG | |
| 484 gHeapUsage[fHeapIndex] -= fSize; | |
| 485 #endif | |
| 459 } | 486 } |
| 460 | 487 |
| 461 bool GrVkSubHeap::alloc(VkDeviceSize size, GrVkAlloc* alloc) { | 488 bool GrVkSubHeap::alloc(VkDeviceSize size, GrVkAlloc* alloc) { |
| 462 alloc->fMemory = fAlloc; | 489 alloc->fMemory = fAlloc; |
| 463 return INHERITED::alloc(size, &alloc->fOffset, &alloc->fSize); | 490 return INHERITED::alloc(size, &alloc->fOffset, &alloc->fSize); |
| 464 } | 491 } |
| 465 | 492 |
| 466 void GrVkSubHeap::free(const GrVkAlloc& alloc) { | 493 void GrVkSubHeap::free(const GrVkAlloc& alloc) { |
| 467 SkASSERT(alloc.fMemory == fAlloc); | 494 SkASSERT(alloc.fMemory == fAlloc); |
| 468 | 495 |
| 469 INHERITED::free(alloc.fOffset, alloc.fSize); | 496 INHERITED::free(alloc.fOffset, alloc.fSize); |
| 470 } | 497 } |
| 471 | 498 |
| 472 bool GrVkHeap::subAlloc(VkDeviceSize size, VkDeviceSize alignment, | 499 bool GrVkHeap::subAlloc(VkDeviceSize size, VkDeviceSize alignment, |
| 473 uint32_t memoryTypeIndex, GrVkAlloc* alloc) { | 500 uint32_t memoryTypeIndex, uint32_t heapIndex, GrVkAlloc* alloc) { |
| 474 VkDeviceSize alignedSize = align_size(size, alignment); | 501 VkDeviceSize alignedSize = align_size(size, alignment); |
| 475 | 502 |
| 476 // if requested is larger than our subheap allocation, just alloc directly | 503 // if requested is larger than our subheap allocation, just alloc directly |
| 477 if (alignedSize > fSubHeapSize) { | 504 if (alignedSize > fSubHeapSize) { |
| 478 VkMemoryAllocateInfo allocInfo = { | 505 VkMemoryAllocateInfo allocInfo = { |
| 479 VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // sType | 506 VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // sType |
| 480 NULL, // pNext | 507 NULL, // pNext |
| 481 size, // allocationSize | 508 size, // allocationSize |
| 482 memoryTypeIndex, // memoryTypeIndex | 509 memoryTypeIndex, // memoryTypeIndex |
| 483 }; | 510 }; |
| 484 | 511 |
| 485 VkResult err = GR_VK_CALL(fGpu->vkInterface(), AllocateMemory(fGpu->devi ce(), | 512 VkResult err = GR_VK_CALL(fGpu->vkInterface(), AllocateMemory(fGpu->devi ce(), |
| 486 &allocInfo , | 513 &allocInfo , |
| 487 nullptr, | 514 nullptr, |
| 488 &alloc->fM emory)); | 515 &alloc->fM emory)); |
| 489 if (VK_SUCCESS != err) { | 516 if (VK_SUCCESS != err) { |
| 490 return false; | 517 return false; |
| 491 } | 518 } |
| 492 alloc->fOffset = 0; | 519 alloc->fOffset = 0; |
| 493 alloc->fSize = 0; // hint that this is not a subheap allocation | 520 alloc->fSize = 0; // hint that this is not a subheap allocation |
| 521 #ifdef SK_DEBUG | |
| 522 gHeapUsage[VK_MAX_MEMORY_HEAPS] += alignedSize; | |
| 523 #endif | |
| 494 | 524 |
| 495 return true; | 525 return true; |
| 496 } | 526 } |
| 497 | 527 |
| 498 // first try to find a subheap that fits our allocation request | 528 // first try to find a subheap that fits our allocation request |
| 499 int bestFitIndex = -1; | 529 int bestFitIndex = -1; |
| 500 VkDeviceSize bestFitSize = 0x7FFFFFFF; | 530 VkDeviceSize bestFitSize = 0x7FFFFFFF; |
| 501 for (auto i = 0; i < fSubHeaps.count(); ++i) { | 531 for (auto i = 0; i < fSubHeaps.count(); ++i) { |
| 502 if (fSubHeaps[i]->memoryTypeIndex() == memoryTypeIndex && | 532 if (fSubHeaps[i]->memoryTypeIndex() == memoryTypeIndex && |
| 503 fSubHeaps[i]->alignment() == alignment) { | 533 fSubHeaps[i]->alignment() == alignment) { |
| 504 VkDeviceSize heapSize = fSubHeaps[i]->largestBlockSize(); | 534 VkDeviceSize heapSize = fSubHeaps[i]->largestBlockSize(); |
| 505 if (heapSize >= alignedSize && heapSize < bestFitSize) { | 535 if (heapSize >= alignedSize && heapSize < bestFitSize) { |
| 506 bestFitIndex = i; | 536 bestFitIndex = i; |
| 507 bestFitSize = heapSize; | 537 bestFitSize = heapSize; |
| 508 } | 538 } |
| 509 } | 539 } |
| 510 } | 540 } |
| 511 | 541 |
| 512 if (bestFitIndex >= 0) { | 542 if (bestFitIndex >= 0) { |
| 513 SkASSERT(fSubHeaps[bestFitIndex]->alignment() == alignment); | 543 SkASSERT(fSubHeaps[bestFitIndex]->alignment() == alignment); |
| 514 if (fSubHeaps[bestFitIndex]->alloc(size, alloc)) { | 544 if (fSubHeaps[bestFitIndex]->alloc(size, alloc)) { |
| 515 fUsedSize += alloc->fSize; | 545 fUsedSize += alloc->fSize; |
| 516 return true; | 546 return true; |
| 517 } | 547 } |
| 518 return false; | 548 return false; |
| 519 } | 549 } |
| 520 | 550 |
| 521 // need to allocate a new subheap | 551 // need to allocate a new subheap |
| 522 SkAutoTDelete<GrVkSubHeap>& subHeap = fSubHeaps.push_back(); | 552 SkAutoTDelete<GrVkSubHeap>& subHeap = fSubHeaps.push_back(); |
| 523 subHeap.reset(new GrVkSubHeap(fGpu, memoryTypeIndex, fSubHeapSize, alignment )); | 553 subHeap.reset(new GrVkSubHeap(fGpu, memoryTypeIndex, heapIndex, fSubHeapSize , alignment)); |
| 524 // try to recover from failed allocation by only allocating what we need | 554 // try to recover from failed allocation by only allocating what we need |
| 525 if (subHeap->size() == 0) { | 555 if (subHeap->size() == 0) { |
| 526 VkDeviceSize alignedSize = align_size(size, alignment); | 556 VkDeviceSize alignedSize = align_size(size, alignment); |
| 527 subHeap.reset(new GrVkSubHeap(fGpu, memoryTypeIndex, alignedSize, alignm ent)); | 557 subHeap.reset(new GrVkSubHeap(fGpu, memoryTypeIndex, heapIndex, alignedS ize, alignment)); |
| 528 if (subHeap->size() == 0) { | 558 if (subHeap->size() == 0) { |
| 529 return false; | 559 return false; |
| 530 } | 560 } |
| 531 } | 561 } |
| 532 fAllocSize += fSubHeapSize; | 562 fAllocSize += fSubHeapSize; |
| 533 if (subHeap->alloc(size, alloc)) { | 563 if (subHeap->alloc(size, alloc)) { |
| 534 fUsedSize += alloc->fSize; | 564 fUsedSize += alloc->fSize; |
| 535 return true; | 565 return true; |
| 536 } | 566 } |
| 537 | 567 |
| 538 return false; | 568 return false; |
| 539 } | 569 } |
| 540 | 570 |
| 541 bool GrVkHeap::singleAlloc(VkDeviceSize size, VkDeviceSize alignment, | 571 bool GrVkHeap::singleAlloc(VkDeviceSize size, VkDeviceSize alignment, |
| 542 uint32_t memoryTypeIndex, GrVkAlloc* alloc) { | 572 uint32_t memoryTypeIndex, uint32_t heapIndex, GrVkAll oc* alloc) { |
| 543 VkDeviceSize alignedSize = align_size(size, alignment); | 573 VkDeviceSize alignedSize = align_size(size, alignment); |
| 544 | 574 |
| 545 // first try to find an unallocated subheap that fits our allocation request | 575 // first try to find an unallocated subheap that fits our allocation request |
| 546 int bestFitIndex = -1; | 576 int bestFitIndex = -1; |
| 547 VkDeviceSize bestFitSize = 0x7FFFFFFF; | 577 VkDeviceSize bestFitSize = 0x7FFFFFFF; |
| 548 for (auto i = 0; i < fSubHeaps.count(); ++i) { | 578 for (auto i = 0; i < fSubHeaps.count(); ++i) { |
| 549 if (fSubHeaps[i]->memoryTypeIndex() == memoryTypeIndex && | 579 if (fSubHeaps[i]->memoryTypeIndex() == memoryTypeIndex && |
| 550 fSubHeaps[i]->alignment() == alignment && | 580 fSubHeaps[i]->alignment() == alignment && |
| 551 fSubHeaps[i]->unallocated()) { | 581 fSubHeaps[i]->unallocated()) { |
| 552 VkDeviceSize heapSize = fSubHeaps[i]->size(); | 582 VkDeviceSize heapSize = fSubHeaps[i]->size(); |
| 553 if (heapSize >= alignedSize && heapSize < bestFitSize) { | 583 if (heapSize >= alignedSize && heapSize < bestFitSize) { |
| 554 bestFitIndex = i; | 584 bestFitIndex = i; |
| 555 bestFitSize = heapSize; | 585 bestFitSize = heapSize; |
| 556 } | 586 } |
| 557 } | 587 } |
| 558 } | 588 } |
| 559 | 589 |
| 560 if (bestFitIndex >= 0) { | 590 if (bestFitIndex >= 0) { |
| 561 SkASSERT(fSubHeaps[bestFitIndex]->alignment() == alignment); | 591 SkASSERT(fSubHeaps[bestFitIndex]->alignment() == alignment); |
| 562 if (fSubHeaps[bestFitIndex]->alloc(size, alloc)) { | 592 if (fSubHeaps[bestFitIndex]->alloc(size, alloc)) { |
| 563 fUsedSize += alloc->fSize; | 593 fUsedSize += alloc->fSize; |
| 564 return true; | 594 return true; |
| 565 } | 595 } |
| 566 return false; | 596 return false; |
| 567 } | 597 } |
| 568 | 598 |
| 569 // need to allocate a new subheap | 599 // need to allocate a new subheap |
| 570 SkAutoTDelete<GrVkSubHeap>& subHeap = fSubHeaps.push_back(); | 600 SkAutoTDelete<GrVkSubHeap>& subHeap = fSubHeaps.push_back(); |
| 571 subHeap.reset(new GrVkSubHeap(fGpu, memoryTypeIndex, alignedSize, alignment) ); | 601 subHeap.reset(new GrVkSubHeap(fGpu, memoryTypeIndex, heapIndex, alignedSize, alignment)); |
| 572 fAllocSize += alignedSize; | 602 fAllocSize += alignedSize; |
| 573 if (subHeap->alloc(size, alloc)) { | 603 if (subHeap->alloc(size, alloc)) { |
| 574 fUsedSize += alloc->fSize; | 604 fUsedSize += alloc->fSize; |
| 575 return true; | 605 return true; |
| 576 } | 606 } |
| 577 | 607 |
| 578 return false; | 608 return false; |
| 579 } | 609 } |
| 580 | 610 |
| 581 bool GrVkHeap::free(const GrVkAlloc& alloc) { | 611 bool GrVkHeap::free(const GrVkAlloc& alloc) { |
| 582 // a size of 0 means we're using the system heap | 612 // a size of 0 means we're using the system heap |
| 583 if (0 == alloc.fSize) { | 613 if (0 == alloc.fSize) { |
| 584 const GrVkInterface* iface = fGpu->vkInterface(); | 614 const GrVkInterface* iface = fGpu->vkInterface(); |
| 585 GR_VK_CALL(iface, FreeMemory(fGpu->device(), alloc.fMemory, nullptr)); | 615 GR_VK_CALL(iface, FreeMemory(fGpu->device(), alloc.fMemory, nullptr)); |
| 586 return true; | 616 return true; |
| 587 } | 617 } |
| 588 | 618 |
| 589 for (auto i = 0; i < fSubHeaps.count(); ++i) { | 619 for (auto i = 0; i < fSubHeaps.count(); ++i) { |
| 590 if (fSubHeaps[i]->memory() == alloc.fMemory) { | 620 if (fSubHeaps[i]->memory() == alloc.fMemory) { |
| 591 fSubHeaps[i]->free(alloc); | 621 fSubHeaps[i]->free(alloc); |
| 592 fUsedSize -= alloc.fSize; | 622 fUsedSize -= alloc.fSize; |
| 593 return true; | 623 return true; |
| 594 } | 624 } |
| 595 } | 625 } |
| 596 | 626 |
| 597 return false; | 627 return false; |
| 598 } | 628 } |
| 599 | 629 |
| 600 | 630 |
| OLD | NEW |