|  | /* | 
|  | * Copyright (C) 2008 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | #define LOG_TAG "SurfaceFlinger" | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <stdio.h> | 
|  | #include <stdint.h> | 
|  | #include <unistd.h> | 
|  | #include <fcntl.h> | 
|  | #include <errno.h> | 
|  | #include <math.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/ioctl.h> | 
|  |  | 
|  | #include <cutils/log.h> | 
|  | #include <cutils/properties.h> | 
|  |  | 
|  | #include <binder/IBinder.h> | 
|  | #include <binder/MemoryDealer.h> | 
|  | #include <binder/MemoryBase.h> | 
|  | #include <binder/MemoryHeapPmem.h> | 
|  | #include <binder/MemoryHeapBase.h> | 
|  | #include <binder/IPCThreadState.h> | 
|  | #include <utils/StopWatch.h> | 
|  |  | 
|  | #include <ui/ISurfaceComposer.h> | 
|  |  | 
|  | #include "VRamHeap.h" | 
|  | #include "GPUHardware.h" | 
|  |  | 
|  | #if HAVE_ANDROID_OS | 
|  | #include <linux/android_pmem.h> | 
|  | #endif | 
|  |  | 
|  | #include "GPUHardware/GPUHardware.h" | 
|  |  | 
|  |  | 
|  | /* | 
|  | * Manage the GPU. This implementation is very specific to the G1. | 
|  | * There are no abstraction here. | 
|  | * | 
|  | * All this code will soon go-away and be replaced by a new architecture | 
|  | * for managing graphics accelerators. | 
|  | * | 
|  | * In the meantime, it is conceptually possible to instantiate a | 
|  | * GPUHardwareInterface for another GPU (see GPUFactory at the bottom | 
|  | * of this file); practically... doubtful. | 
|  | * | 
|  | */ | 
|  |  | 
|  | namespace android { | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  | class GPUClientHeap; | 
|  | class GPUAreaHeap; | 
|  |  | 
|  | class GPUHardware : public GPUHardwareInterface, public IBinder::DeathRecipient | 
|  | { | 
|  | public: | 
|  | static const int GPU_RESERVED_SIZE; | 
|  | static const int GPUR_SIZE; | 
|  |  | 
|  | GPUHardware(); | 
|  | virtual ~GPUHardware(); | 
|  |  | 
|  | virtual void revoke(int pid); | 
|  | virtual sp<MemoryDealer> request(int pid); | 
|  | virtual status_t request(int pid, | 
|  | const sp<IGPUCallback>& callback, | 
|  | ISurfaceComposer::gpu_info_t* gpu); | 
|  |  | 
|  | virtual status_t friendlyRevoke(); | 
|  | virtual void unconditionalRevoke(); | 
|  |  | 
|  | virtual pid_t getOwner() const { return mOwner; } | 
|  |  | 
|  | // used for debugging only... | 
|  | virtual sp<SimpleBestFitAllocator> getAllocator() const; | 
|  |  | 
|  | private: | 
|  |  | 
|  |  | 
|  | enum { | 
|  | NO_OWNER = -1, | 
|  | }; | 
|  |  | 
|  | struct GPUArea { | 
|  | sp<GPUAreaHeap>     heap; | 
|  | sp<MemoryHeapPmem>  clientHeap; | 
|  | sp<IMemory> map(); | 
|  | }; | 
|  |  | 
|  | struct Client { | 
|  | pid_t       pid; | 
|  | GPUArea     smi; | 
|  | GPUArea     ebi; | 
|  | GPUArea     reg; | 
|  | void createClientHeaps(); | 
|  | void revokeAllHeaps(); | 
|  | }; | 
|  |  | 
|  | Client& getClientLocked(pid_t pid); | 
|  | status_t requestLocked(int pid); | 
|  | void releaseLocked(); | 
|  | void takeBackGPULocked(); | 
|  | void registerCallbackLocked(const sp<IGPUCallback>& callback, | 
|  | Client& client); | 
|  |  | 
|  | virtual void binderDied(const wp<IBinder>& who); | 
|  |  | 
|  | mutable Mutex           mLock; | 
|  | sp<GPUAreaHeap>         mSMIHeap; | 
|  | sp<GPUAreaHeap>         mEBIHeap; | 
|  | sp<GPUAreaHeap>         mREGHeap; | 
|  |  | 
|  | KeyedVector<pid_t, Client> mClients; | 
|  | DefaultKeyedVector< wp<IBinder>, pid_t > mRegisteredClients; | 
|  |  | 
|  | pid_t                   mOwner; | 
|  |  | 
|  | sp<MemoryDealer>        mCurrentAllocator; | 
|  | sp<IGPUCallback>        mCallback; | 
|  |  | 
|  | sp<SimpleBestFitAllocator>  mAllocator; | 
|  |  | 
|  | Condition               mCondition; | 
|  | }; | 
|  |  | 
|  | // size reserved for GPU surfaces | 
|  | // 1200 KB fits exactly: | 
|  | //  - two 320*480 16-bits double-buffered surfaces | 
|  | //  - one 320*480 32-bits double-buffered surface | 
|  | //  - one 320*240 16-bits double-buffered, 4x anti-aliased surface | 
|  | const int GPUHardware::GPU_RESERVED_SIZE  = 1200 * 1024; | 
|  | const int GPUHardware::GPUR_SIZE          = 1 * 1024 * 1024; | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  | /* | 
|  | * GPUHandle is a special IMemory given to the client. It represents their | 
|  | * handle to the GPU. Once they give it up, they loose GPU access, or if | 
|  | * they explicitly revoke their access through the binder code 1000. | 
|  | * In both cases, this triggers a callback to revoke() | 
|  | * first, and then actually powers down the chip. | 
|  | * | 
|  | * In the case of a misbehaving app, GPUHardware can ask for an immediate | 
|  | * release of the GPU to the target process which should answer by calling | 
|  | * code 1000 on GPUHandle. If it doesn't in a timely manner, the GPU will | 
|  | * be revoked from under their feet. | 
|  | * | 
|  | * We should never hold a strong reference on GPUHandle. In practice this | 
|  | * shouldn't be a big issue though because clients should use code 1000 and | 
|  | * not rely on the dtor being called. | 
|  | * | 
|  | */ | 
|  |  | 
|  | class GPUClientHeap : public MemoryHeapPmem | 
|  | { | 
|  | public: | 
|  | GPUClientHeap(const wp<GPUHardware>& gpu, | 
|  | const sp<MemoryHeapBase>& heap) | 
|  | :  MemoryHeapPmem(heap), mGPU(gpu) { } | 
|  | protected: | 
|  | wp<GPUHardware> mGPU; | 
|  | }; | 
|  |  | 
|  | class GPUAreaHeap : public MemoryHeapBase | 
|  | { | 
|  | public: | 
|  | GPUAreaHeap(const wp<GPUHardware>& gpu, | 
|  | const char* const vram, size_t size=0, size_t reserved=0) | 
|  | : MemoryHeapBase(vram, size), mGPU(gpu) { | 
|  | if (base() != MAP_FAILED) { | 
|  | if (reserved == 0) | 
|  | reserved = virtualSize(); | 
|  | mAllocator = new SimpleBestFitAllocator(reserved); | 
|  | } | 
|  | } | 
|  | virtual sp<MemoryHeapPmem> createClientHeap() { | 
|  | sp<MemoryHeapBase> parentHeap(this); | 
|  | return new GPUClientHeap(mGPU, parentHeap); | 
|  | } | 
|  | virtual const sp<SimpleBestFitAllocator>& getAllocator() const { | 
|  | return mAllocator; | 
|  | } | 
|  | private: | 
|  | sp<SimpleBestFitAllocator>  mAllocator; | 
|  | protected: | 
|  | wp<GPUHardware> mGPU; | 
|  | }; | 
|  |  | 
|  | class GPURegisterHeap : public GPUAreaHeap | 
|  | { | 
|  | public: | 
|  | GPURegisterHeap(const sp<GPUHardware>& gpu) | 
|  | : GPUAreaHeap(gpu, "/dev/hw3d", GPUHardware::GPUR_SIZE) { } | 
|  | virtual sp<MemoryHeapPmem> createClientHeap() { | 
|  | sp<MemoryHeapBase> parentHeap(this); | 
|  | return new MemoryHeapRegs(mGPU, parentHeap); | 
|  | } | 
|  | private: | 
|  | class MemoryHeapRegs : public GPUClientHeap  { | 
|  | public: | 
|  | MemoryHeapRegs(const wp<GPUHardware>& gpu, | 
|  | const sp<MemoryHeapBase>& heap) | 
|  | : GPUClientHeap(gpu, heap) { } | 
|  | sp<MemoryHeapPmem::MemoryPmem> createMemory(size_t offset, size_t size); | 
|  | virtual void revoke(); | 
|  | private: | 
|  | class GPUHandle : public MemoryHeapPmem::MemoryPmem { | 
|  | public: | 
|  | GPUHandle(const sp<GPUHardware>& gpu, | 
|  | const sp<MemoryHeapPmem>& heap) | 
|  | : MemoryHeapPmem::MemoryPmem(heap), | 
|  | mGPU(gpu), mOwner(gpu->getOwner()) { } | 
|  | virtual ~GPUHandle(); | 
|  | virtual sp<IMemoryHeap> getMemory( | 
|  | ssize_t* offset, size_t* size) const; | 
|  | virtual void revoke() { }; | 
|  | virtual status_t onTransact( | 
|  | uint32_t code, const Parcel& data, | 
|  | Parcel* reply, uint32_t flags); | 
|  | private: | 
|  | void revokeNotification(); | 
|  | wp<GPUHardware> mGPU; | 
|  | pid_t mOwner; | 
|  | }; | 
|  | }; | 
|  | }; | 
|  |  | 
|  | GPURegisterHeap::MemoryHeapRegs::GPUHandle::~GPUHandle() { | 
|  | //LOGD("GPUHandle %p released, revoking GPU", this); | 
|  | revokeNotification(); | 
|  | } | 
|  | void GPURegisterHeap::MemoryHeapRegs::GPUHandle::revokeNotification()  { | 
|  | sp<GPUHardware> hw(mGPU.promote()); | 
|  | if (hw != 0) { | 
|  | hw->revoke(mOwner); | 
|  | } | 
|  | } | 
|  | sp<IMemoryHeap> GPURegisterHeap::MemoryHeapRegs::GPUHandle::getMemory( | 
|  | ssize_t* offset, size_t* size) const | 
|  | { | 
|  | sp<MemoryHeapPmem> heap = getHeap(); | 
|  | if (offset) *offset = 0; | 
|  | if (size)   *size = heap !=0 ? heap->virtualSize() : 0; | 
|  | return heap; | 
|  | } | 
|  | status_t GPURegisterHeap::MemoryHeapRegs::GPUHandle::onTransact( | 
|  | uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) | 
|  | { | 
|  | status_t err = BnMemory::onTransact(code, data, reply, flags); | 
|  | if (err == UNKNOWN_TRANSACTION && code == 1000) { | 
|  | int callingPid = IPCThreadState::self()->getCallingPid(); | 
|  | //LOGD("pid %d voluntarily revoking gpu", callingPid); | 
|  | if (callingPid == mOwner) { | 
|  | revokeNotification(); | 
|  | // we've revoked the GPU, don't do it again later when we | 
|  | // are destroyed. | 
|  | mGPU.clear(); | 
|  | } else { | 
|  | LOGW("%d revoking someone else's gpu? (owner=%d)", | 
|  | callingPid, mOwner); | 
|  | } | 
|  | err = NO_ERROR; | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  |  | 
|  | sp<MemoryHeapPmem::MemoryPmem> GPURegisterHeap::MemoryHeapRegs::createMemory( | 
|  | size_t offset, size_t size) | 
|  | { | 
|  | sp<GPUHandle> memory; | 
|  | sp<GPUHardware> gpu = mGPU.promote(); | 
|  | if (heapID()>0 && gpu!=0) { | 
|  | #if HAVE_ANDROID_OS | 
|  | /* this is where the GPU is powered on and the registers are mapped | 
|  | * in the client */ | 
|  | //LOGD("ioctl(HW3D_GRANT_GPU)"); | 
|  | int err = ioctl(heapID(), HW3D_GRANT_GPU, base()); | 
|  | if (err) { | 
|  | // it can happen if the master heap has been closed already | 
|  | // in which case the GPU already is revoked (app crash for | 
|  | // instance). | 
|  | LOGW("HW3D_GRANT_GPU failed (%s), mFD=%d, base=%p", | 
|  | strerror(errno), heapID(), base()); | 
|  | } | 
|  | memory = new GPUHandle(gpu, this); | 
|  | #endif | 
|  | } | 
|  | return memory; | 
|  | } | 
|  |  | 
|  | void GPURegisterHeap::MemoryHeapRegs::revoke() | 
|  | { | 
|  | MemoryHeapPmem::revoke(); | 
|  | #if HAVE_ANDROID_OS | 
|  | if (heapID() > 0) { | 
|  | //LOGD("ioctl(HW3D_REVOKE_GPU)"); | 
|  | int err = ioctl(heapID(), HW3D_REVOKE_GPU, base()); | 
|  | LOGE_IF(err, "HW3D_REVOKE_GPU failed (%s), mFD=%d, base=%p", | 
|  | strerror(errno), heapID(), base()); | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /*****************************************************************************/ | 
|  |  | 
|  | GPUHardware::GPUHardware() | 
|  | : mOwner(NO_OWNER) | 
|  | { | 
|  | } | 
|  |  | 
|  | GPUHardware::~GPUHardware() | 
|  | { | 
|  | } | 
|  |  | 
|  | status_t GPUHardware::requestLocked(int pid) | 
|  | { | 
|  | const int self_pid = getpid(); | 
|  | if (pid == self_pid) { | 
|  | // can't use GPU from surfaceflinger's process | 
|  | return PERMISSION_DENIED; | 
|  | } | 
|  |  | 
|  | if (mOwner != pid) { | 
|  | if (mREGHeap != 0) { | 
|  | if (mOwner != NO_OWNER) { | 
|  | // someone already has the gpu. | 
|  | takeBackGPULocked(); | 
|  | releaseLocked(); | 
|  | } | 
|  | } else { | 
|  | // first time, initialize the stuff. | 
|  | if (mSMIHeap == 0) | 
|  | mSMIHeap = new GPUAreaHeap(this, "/dev/pmem_gpu0"); | 
|  | if (mEBIHeap == 0) | 
|  | mEBIHeap = new GPUAreaHeap(this, | 
|  | "/dev/pmem_gpu1", 0, GPU_RESERVED_SIZE); | 
|  | mREGHeap = new GPURegisterHeap(this); | 
|  | mAllocator = mEBIHeap->getAllocator(); | 
|  | if (mAllocator == NULL) { | 
|  | // something went terribly wrong. | 
|  | mSMIHeap.clear(); | 
|  | mEBIHeap.clear(); | 
|  | mREGHeap.clear(); | 
|  | return INVALID_OPERATION; | 
|  | } | 
|  | } | 
|  | Client& client = getClientLocked(pid); | 
|  | mCurrentAllocator = new MemoryDealer(client.ebi.clientHeap, mAllocator); | 
|  | mOwner = pid; | 
|  | } | 
|  | return NO_ERROR; | 
|  | } | 
|  |  | 
|  | sp<MemoryDealer> GPUHardware::request(int pid) | 
|  | { | 
|  | sp<MemoryDealer> dealer; | 
|  | Mutex::Autolock _l(mLock); | 
|  | Client* client; | 
|  | LOGD("pid %d requesting gpu surface (current owner = %d)", pid, mOwner); | 
|  | if (requestLocked(pid) == NO_ERROR) { | 
|  | dealer = mCurrentAllocator; | 
|  | LOGD_IF(dealer!=0, "gpu surface granted to pid %d", mOwner); | 
|  | } | 
|  | return dealer; | 
|  | } | 
|  |  | 
|  | status_t GPUHardware::request(int pid, const sp<IGPUCallback>& callback, | 
|  | ISurfaceComposer::gpu_info_t* gpu) | 
|  | { | 
|  | if (callback == 0) | 
|  | return BAD_VALUE; | 
|  |  | 
|  | sp<IMemory> gpuHandle; | 
|  | LOGD("pid %d requesting gpu core (owner = %d)", pid, mOwner); | 
|  | Mutex::Autolock _l(mLock); | 
|  | status_t err = requestLocked(pid); | 
|  | if (err == NO_ERROR) { | 
|  | // it's guaranteed to be there, be construction | 
|  | Client& client = mClients.editValueFor(pid); | 
|  | registerCallbackLocked(callback, client); | 
|  | gpu->count = 2; | 
|  | gpu->regions[0].region = client.smi.map(); | 
|  | gpu->regions[1].region = client.ebi.map(); | 
|  | gpu->regs              = client.reg.map(); | 
|  | gpu->regions[0].reserved = 0; | 
|  | gpu->regions[1].reserved = GPU_RESERVED_SIZE; | 
|  | if (gpu->regs != 0) { | 
|  | //LOGD("gpu core granted to pid %d, handle base=%p", | 
|  | //        mOwner, gpu->regs->pointer()); | 
|  | } | 
|  | mCallback = callback; | 
|  | } else { | 
|  | LOGW("couldn't grant gpu core to pid %d", pid); | 
|  | } | 
|  | return err; | 
|  | } | 
|  |  | 
|  | void GPUHardware::revoke(int pid) | 
|  | { | 
|  | Mutex::Autolock _l(mLock); | 
|  | if (mOwner > 0) { | 
|  | if (pid != mOwner) { | 
|  | LOGW("GPU owned by %d, revoke from %d", mOwner, pid); | 
|  | return; | 
|  | } | 
|  | //LOGD("revoke pid=%d, owner=%d", pid, mOwner); | 
|  | // mOwner could be <0 if the same process acquired the GPU | 
|  | // several times without releasing it first. | 
|  | mCondition.signal(); | 
|  | releaseLocked(); | 
|  | } | 
|  | } | 
|  |  | 
|  | status_t GPUHardware::friendlyRevoke() | 
|  | { | 
|  | Mutex::Autolock _l(mLock); | 
|  | //LOGD("friendlyRevoke owner=%d", mOwner); | 
|  | takeBackGPULocked(); | 
|  | releaseLocked(); | 
|  | return NO_ERROR; | 
|  | } | 
|  |  | 
|  | void GPUHardware::takeBackGPULocked() | 
|  | { | 
|  | sp<IGPUCallback> callback = mCallback; | 
|  | mCallback.clear(); | 
|  | if (callback != 0) { | 
|  | callback->gpuLost(); // one-way | 
|  | mCondition.waitRelative(mLock, ms2ns(250)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GPUHardware::releaseLocked() | 
|  | { | 
|  | //LOGD("revoking gpu from pid %d", mOwner); | 
|  | if (mOwner != NO_OWNER) { | 
|  | // this may fail because the client might have died, and have | 
|  | // been removed from the list. | 
|  | ssize_t index = mClients.indexOfKey(mOwner); | 
|  | if (index >= 0) { | 
|  | Client& client(mClients.editValueAt(index)); | 
|  | client.revokeAllHeaps(); | 
|  | } | 
|  | mOwner = NO_OWNER; | 
|  | mCurrentAllocator.clear(); | 
|  | mCallback.clear(); | 
|  | } | 
|  | } | 
|  |  | 
|  | GPUHardware::Client& GPUHardware::getClientLocked(pid_t pid) | 
|  | { | 
|  | ssize_t index = mClients.indexOfKey(pid); | 
|  | if (index < 0) { | 
|  | Client client; | 
|  | client.pid = pid; | 
|  | client.smi.heap = mSMIHeap; | 
|  | client.ebi.heap = mEBIHeap; | 
|  | client.reg.heap = mREGHeap; | 
|  | index = mClients.add(pid, client); | 
|  | } | 
|  | Client& client(mClients.editValueAt(index)); | 
|  | client.createClientHeaps(); | 
|  | return client; | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------------------- | 
|  | // for debugging / testing ... | 
|  |  | 
|  | sp<SimpleBestFitAllocator> GPUHardware::getAllocator() const { | 
|  | Mutex::Autolock _l(mLock); | 
|  | return mAllocator; | 
|  | } | 
|  |  | 
|  | void GPUHardware::unconditionalRevoke() | 
|  | { | 
|  | Mutex::Autolock _l(mLock); | 
|  | releaseLocked(); | 
|  | } | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  | sp<IMemory> GPUHardware::GPUArea::map() { | 
|  | sp<IMemory> memory; | 
|  | if (clientHeap != 0 && heap != 0) { | 
|  | memory = clientHeap->mapMemory(0, heap->virtualSize()); | 
|  | } | 
|  | return memory; | 
|  | } | 
|  |  | 
|  | void GPUHardware::Client::createClientHeaps() | 
|  | { | 
|  | if (smi.clientHeap == 0) | 
|  | smi.clientHeap = smi.heap->createClientHeap(); | 
|  | if (ebi.clientHeap == 0) | 
|  | ebi.clientHeap = ebi.heap->createClientHeap(); | 
|  | if (reg.clientHeap == 0) | 
|  | reg.clientHeap = reg.heap->createClientHeap(); | 
|  | } | 
|  |  | 
|  | void GPUHardware::Client::revokeAllHeaps() | 
|  | { | 
|  | if (smi.clientHeap != 0) | 
|  | smi.clientHeap->revoke(); | 
|  | if (ebi.clientHeap != 0) | 
|  | ebi.clientHeap->revoke(); | 
|  | if (reg.clientHeap != 0) | 
|  | reg.clientHeap->revoke(); | 
|  | } | 
|  |  | 
|  | void GPUHardware::registerCallbackLocked(const sp<IGPUCallback>& callback, | 
|  | Client& client) | 
|  | { | 
|  | sp<IBinder> binder = callback->asBinder(); | 
|  | if (mRegisteredClients.add(binder, client.pid) >= 0) { | 
|  | binder->linkToDeath(this); | 
|  | } | 
|  | } | 
|  |  | 
|  | void GPUHardware::binderDied(const wp<IBinder>& who) | 
|  | { | 
|  | Mutex::Autolock _l(mLock); | 
|  | pid_t pid = mRegisteredClients.valueFor(who); | 
|  | if (pid != 0) { | 
|  | ssize_t index = mClients.indexOfKey(pid); | 
|  | if (index >= 0) { | 
|  | //LOGD("*** removing client at %d", index); | 
|  | Client& client(mClients.editValueAt(index)); | 
|  | client.revokeAllHeaps(); // not really needed in theory | 
|  | mClients.removeItemsAt(index); | 
|  | if (mClients.size() == 0) { | 
|  | //LOGD("*** was last client closing everything"); | 
|  | mCallback.clear(); | 
|  | mAllocator.clear(); | 
|  | mCurrentAllocator.clear(); | 
|  | mSMIHeap.clear(); | 
|  | mREGHeap.clear(); | 
|  |  | 
|  | // NOTE: we cannot clear the EBI heap because surfaceflinger | 
|  | // itself may be using it, since this is where surfaces | 
|  | // are allocated. if we're in the middle of compositing | 
|  | // a surface (even if its process just died), we cannot | 
|  | // rip the heap under our feet. | 
|  |  | 
|  | mOwner = NO_OWNER; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  |  | 
|  | sp<GPUHardwareInterface> GPUFactory::getGPU() | 
|  | { | 
|  | sp<GPUHardwareInterface> gpu; | 
|  | if (access("/dev/hw3d", F_OK) == 0) { | 
|  | gpu = new GPUHardware(); | 
|  | } | 
|  | return gpu; | 
|  | } | 
|  |  | 
|  | // --------------------------------------------------------------------------- | 
|  | }; // namespace android | 
|  |  |