| /* |
| * Copyright 2011 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "SkImageRef_ashmem.h" |
| #include "SkImageDecoder.h" |
| #include "SkFlattenableBuffers.h" |
| #include "SkThread.h" |
| |
| #include "android/ashmem.h" |
| |
| #include <sys/mman.h> |
| #include <unistd.h> |
| |
| //#define TRACE_ASH_PURGE // just trace purges |
| |
| #ifdef DUMP_IMAGEREF_LIFECYCLE |
| #define DUMP_ASHMEM_LIFECYCLE |
| #else |
| // #define DUMP_ASHMEM_LIFECYCLE |
| #endif |
| |
| // ashmem likes lengths on page boundaries |
| static size_t roundToPageSize(size_t size) { |
| const size_t mask = getpagesize() - 1; |
| size_t newsize = (size + mask) & ~mask; |
| // SkDebugf("---- oldsize %d newsize %d\n", size, newsize); |
| return newsize; |
| } |
| |
| SkImageRef_ashmem::SkImageRef_ashmem(const SkImageInfo& info, |
| SkStreamRewindable* stream, |
| int sampleSize) |
| : SkImageRef(info, stream, sampleSize) |
| { |
| fRec.fFD = -1; |
| fRec.fAddr = NULL; |
| fRec.fSize = 0; |
| fRec.fPinned = false; |
| |
| fCT = NULL; |
| } |
| |
| SkImageRef_ashmem::~SkImageRef_ashmem() { |
| SkSafeUnref(fCT); |
| this->closeFD(); |
| } |
| |
| void SkImageRef_ashmem::closeFD() { |
| if (-1 != fRec.fFD) { |
| #ifdef DUMP_ASHMEM_LIFECYCLE |
| SkDebugf("=== ashmem close %d\n", fRec.fFD); |
| #endif |
| SkASSERT(fRec.fAddr); |
| SkASSERT(fRec.fSize); |
| munmap(fRec.fAddr, fRec.fSize); |
| close(fRec.fFD); |
| fRec.fFD = -1; |
| } |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| class AshmemAllocator : public SkBitmap::Allocator { |
| public: |
| AshmemAllocator(SkAshmemRec* rec, const char name[]) |
| : fRec(rec), fName(name) {} |
| |
| virtual bool allocPixelRef(SkBitmap* bm, SkColorTable* ct) { |
| const size_t size = roundToPageSize(bm->getSize()); |
| int fd = fRec->fFD; |
| void* addr = fRec->fAddr; |
| |
| SkASSERT(!fRec->fPinned); |
| |
| if (-1 == fd) { |
| SkASSERT(NULL == addr); |
| SkASSERT(0 == fRec->fSize); |
| |
| fd = ashmem_create_region(fName, size); |
| #ifdef DUMP_ASHMEM_LIFECYCLE |
| SkDebugf("=== ashmem_create_region %s size=%d fd=%d\n", fName, size, fd); |
| #endif |
| if (-1 == fd) { |
| SkDebugf("------- imageref_ashmem create failed <%s> %d\n", |
| fName, size); |
| return false; |
| } |
| |
| int err = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE); |
| if (err) { |
| SkDebugf("------ ashmem_set_prot_region(%d) failed %d\n", |
| fd, err); |
| close(fd); |
| return false; |
| } |
| |
| addr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); |
| if (-1 == (long)addr) { |
| SkDebugf("---------- mmap failed for imageref_ashmem size=%d\n", |
| size); |
| close(fd); |
| return false; |
| } |
| |
| fRec->fFD = fd; |
| fRec->fAddr = addr; |
| fRec->fSize = size; |
| } else { |
| SkASSERT(addr); |
| SkASSERT(size == fRec->fSize); |
| (void)ashmem_pin_region(fd, 0, 0); |
| } |
| |
| bm->setPixels(addr, ct); |
| fRec->fPinned = true; |
| return true; |
| } |
| |
| private: |
| // we just point to our caller's memory, these are not copies |
| SkAshmemRec* fRec; |
| const char* fName; |
| }; |
| |
| bool SkImageRef_ashmem::onDecode(SkImageDecoder* codec, SkStreamRewindable* stream, |
| SkBitmap* bitmap, SkBitmap::Config config, |
| SkImageDecoder::Mode mode) { |
| |
| if (SkImageDecoder::kDecodeBounds_Mode == mode) { |
| return this->INHERITED::onDecode(codec, stream, bitmap, config, mode); |
| } |
| |
| // Ashmem memory is guaranteed to be initialized to 0. |
| codec->setSkipWritingZeroes(true); |
| |
| AshmemAllocator alloc(&fRec, this->getURI()); |
| |
| codec->setAllocator(&alloc); |
| bool success = this->INHERITED::onDecode(codec, stream, bitmap, config, |
| mode); |
| // remove the allocator, since its on the stack |
| codec->setAllocator(NULL); |
| |
| if (success) { |
| // remember the colortable (if any) |
| SkRefCnt_SafeAssign(fCT, bitmap->getColorTable()); |
| return true; |
| } else { |
| if (fRec.fPinned) { |
| ashmem_unpin_region(fRec.fFD, 0, 0); |
| fRec.fPinned = false; |
| } |
| this->closeFD(); |
| return false; |
| } |
| } |
| |
| bool SkImageRef_ashmem::onNewLockPixels(LockRec* rec) { |
| SkASSERT(fBitmap.getPixels() == NULL); |
| SkASSERT(fBitmap.getColorTable() == NULL); |
| |
| // fast case: check if we can just pin and get the cached data |
| if (-1 != fRec.fFD) { |
| SkASSERT(fRec.fAddr); |
| SkASSERT(!fRec.fPinned); |
| int pin = ashmem_pin_region(fRec.fFD, 0, 0); |
| |
| if (ASHMEM_NOT_PURGED == pin) { // yea, fast case! |
| fBitmap.setPixels(fRec.fAddr, fCT); |
| fRec.fPinned = true; |
| } else if (ASHMEM_WAS_PURGED == pin) { |
| ashmem_unpin_region(fRec.fFD, 0, 0); |
| // let go of our colortable if we lost the pixels. Well get it back |
| // again when we re-decode |
| if (fCT) { |
| fCT->unref(); |
| fCT = NULL; |
| } |
| #if defined(DUMP_ASHMEM_LIFECYCLE) || defined(TRACE_ASH_PURGE) |
| SkDebugf("===== ashmem purged %d\n", fBitmap.getSize()); |
| #endif |
| } else { |
| SkDebugf("===== ashmem pin_region(%d) returned %d\n", fRec.fFD, pin); |
| return false; |
| } |
| } else { |
| // no FD, will create an ashmem region in allocator |
| } |
| |
| return this->INHERITED::onNewLockPixels(rec); |
| } |
| |
| void SkImageRef_ashmem::onUnlockPixels() { |
| this->INHERITED::onUnlockPixels(); |
| |
| if (-1 != fRec.fFD) { |
| SkASSERT(fRec.fAddr); |
| SkASSERT(fRec.fPinned); |
| |
| ashmem_unpin_region(fRec.fFD, 0, 0); |
| fRec.fPinned = false; |
| } |
| |
| // we clear this with or without an error, since we've either closed or |
| // unpinned the region |
| fBitmap.setPixels(NULL, NULL); |
| } |
| |
| void SkImageRef_ashmem::flatten(SkFlattenableWriteBuffer& buffer) const { |
| this->INHERITED::flatten(buffer); |
| buffer.writeString(getURI()); |
| } |
| |
| SkImageRef_ashmem::SkImageRef_ashmem(SkFlattenableReadBuffer& buffer) |
| : INHERITED(buffer) { |
| fRec.fFD = -1; |
| fRec.fAddr = NULL; |
| fRec.fSize = 0; |
| fRec.fPinned = false; |
| fCT = NULL; |
| |
| SkString uri; |
| buffer.readString(&uri); |
| this->setURI(uri); |
| } |