15d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com/* 25d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com * Copyright 2012 Google Inc. 35d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com * 45d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com * Use of this source code is governed by a BSD-style license that can be 55d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com * found in the LICENSE file. 65d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com */ 75d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com 8afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon#include "SkSurface_Gpu.h" 9afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon 10f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk#include "GrResourceProvider.h" 115d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com#include "SkCanvas.h" 1297b6b0730dcb0feee9224ff04eb3985ca4bd0216robertphillips@google.com#include "SkGpuDevice.h" 13afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon#include "SkImage_Base.h" 148b26b99c97473f020df4b9d4ba789e074e06ceddreed#include "SkImage_Gpu.h" 15afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon#include "SkImagePriv.h" 16afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon#include "SkSurface_Base.h" 175d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com 18f037e0bf138a4e842f39e19864d05010a54950c9reed#if SK_SUPPORT_GPU 19f037e0bf138a4e842f39e19864d05010a54950c9reed 20afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomonSkSurface_Gpu::SkSurface_Gpu(SkGpuDevice* device) 21afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon : INHERITED(device->width(), device->height(), &device->surfaceProps()) 22afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon , fDevice(SkRef(device)) { 2397b6b0730dcb0feee9224ff04eb3985ca4bd0216robertphillips@google.com} 245d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com 2597b6b0730dcb0feee9224ff04eb3985ca4bd0216robertphillips@google.comSkSurface_Gpu::~SkSurface_Gpu() { 26abcfab4d68d53900ef33320bb2622696c14d14b0kkinnunen fDevice->unref(); 275d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com} 285d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com 2981793410a80b1bb147e765caccdd7fb36e34edf8joshualittstatic GrRenderTarget* prepare_rt_for_external_access(SkSurface_Gpu* surface, 3081793410a80b1bb147e765caccdd7fb36e34edf8joshualitt SkSurface::BackendHandleAccess access) { 31fa5e68e4b4a10227d3e2c0725b55260175903a80reed switch (access) { 3281793410a80b1bb147e765caccdd7fb36e34edf8joshualitt case SkSurface::kFlushRead_BackendHandleAccess: 33fa5e68e4b4a10227d3e2c0725b55260175903a80reed break; 3481793410a80b1bb147e765caccdd7fb36e34edf8joshualitt case SkSurface::kFlushWrite_BackendHandleAccess: 3581793410a80b1bb147e765caccdd7fb36e34edf8joshualitt case SkSurface::kDiscardWrite_BackendHandleAccess: 36884200ef76bbd25ab31e061a24cc8c8268dacca0reed // for now we don't special-case on Discard, but we may in the future. 3781793410a80b1bb147e765caccdd7fb36e34edf8joshualitt surface->notifyContentWillChange(SkSurface::kRetain_ContentChangeMode); 38dca20ce4e1a522d6f70c79252a169456b48619dbreed // legacy: need to dirty the bitmap's genID in our device (curse it) 3981793410a80b1bb147e765caccdd7fb36e34edf8joshualitt surface->getDevice()->accessBitmap(false).notifyPixelsChanged(); 40fa5e68e4b4a10227d3e2c0725b55260175903a80reed break; 41fa5e68e4b4a10227d3e2c0725b55260175903a80reed } 42e2639089bddc4fbb129ae039cb12c01be087b397fmalita 43e2639089bddc4fbb129ae039cb12c01be087b397fmalita // Grab the render target *after* firing notifications, as it may get switched if CoW kicks in. 44e2639089bddc4fbb129ae039cb12c01be087b397fmalita GrRenderTarget* rt = surface->getDevice()->accessRenderTarget(); 45c49e8682ab0614e1b6816dadd00f65d770ab6999bsalomon rt->prepareForExternalIO(); 4681793410a80b1bb147e765caccdd7fb36e34edf8joshualitt return rt; 4781793410a80b1bb147e765caccdd7fb36e34edf8joshualitt} 4881793410a80b1bb147e765caccdd7fb36e34edf8joshualitt 4981793410a80b1bb147e765caccdd7fb36e34edf8joshualittGrBackendObject SkSurface_Gpu::onGetTextureHandle(BackendHandleAccess access) { 5081793410a80b1bb147e765caccdd7fb36e34edf8joshualitt GrRenderTarget* rt = prepare_rt_for_external_access(this, access); 5181793410a80b1bb147e765caccdd7fb36e34edf8joshualitt GrTexture* texture = rt->asTexture(); 5281793410a80b1bb147e765caccdd7fb36e34edf8joshualitt if (texture) { 5381793410a80b1bb147e765caccdd7fb36e34edf8joshualitt return texture->getTextureHandle(); 5481793410a80b1bb147e765caccdd7fb36e34edf8joshualitt } 5581793410a80b1bb147e765caccdd7fb36e34edf8joshualitt return 0; 5681793410a80b1bb147e765caccdd7fb36e34edf8joshualitt} 5781793410a80b1bb147e765caccdd7fb36e34edf8joshualitt 5881793410a80b1bb147e765caccdd7fb36e34edf8joshualittbool SkSurface_Gpu::onGetRenderTargetHandle(GrBackendObject* obj, BackendHandleAccess access) { 5981793410a80b1bb147e765caccdd7fb36e34edf8joshualitt GrRenderTarget* rt = prepare_rt_for_external_access(this, access); 6081793410a80b1bb147e765caccdd7fb36e34edf8joshualitt *obj = rt->getRenderTargetHandle(); 6181793410a80b1bb147e765caccdd7fb36e34edf8joshualitt return true; 62fa5e68e4b4a10227d3e2c0725b55260175903a80reed} 63fa5e68e4b4a10227d3e2c0725b55260175903a80reed 645d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.comSkCanvas* SkSurface_Gpu::onNewCanvas() { 654a8126e7f81384526629b1e21bf89b632ea13cd9reed SkCanvas::InitFlags flags = SkCanvas::kDefault_InitFlags; 66b5b497423c581cda24f65dcc7b274fe79ef79588bsalomon flags = static_cast<SkCanvas::InitFlags>(flags | SkCanvas::kConservativeRasterClip_InitFlag); 674a8126e7f81384526629b1e21bf89b632ea13cd9reed 68385fe4d4b62d7d1dd76116dd570df3290a2f487bhalcanary return new SkCanvas(fDevice, flags); 695d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com} 705d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com 712bd8b8100529c96c81c30f749f672f4caf775b04reed@google.comSkSurface* SkSurface_Gpu::onNewSurface(const SkImageInfo& info) { 72b8d00db075b5ea09e353508a26ef5ced50722a6ccommit-bot@chromium.org GrRenderTarget* rt = fDevice->accessRenderTarget(); 73dded69693dd3779f081326cde24c3954505b129dvbuzinov int sampleCount = rt->numColorSamples(); 74afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon // TODO: Make caller specify this (change virtual signature of onNewSurface). 755ec26ae9bfca635ccc98283aad5deda11519d826bsalomon static const SkBudgeted kBudgeted = SkBudgeted::kNo; 76afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon return SkSurface::NewRenderTarget(fDevice->context(), kBudgeted, info, sampleCount, 77afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon &this->props()); 785d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com} 795d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com 805ec26ae9bfca635ccc98283aad5deda11519d826bsalomonSkImage* SkSurface_Gpu::onNewImageSnapshot(SkBudgeted budgeted, ForceCopyMode forceCopyMode) { 81f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon GrRenderTarget* rt = fDevice->accessRenderTarget(); 82f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon SkASSERT(rt); 83f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon GrTexture* tex = rt->asTexture(); 84f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon SkAutoTUnref<GrTexture> copy; 85f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon // TODO: Force a copy when the rt is an external resource. 86f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon if (kYes_ForceCopyMode == forceCopyMode || !tex) { 87f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon GrSurfaceDesc desc = fDevice->accessRenderTarget()->desc(); 88f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon GrContext* ctx = fDevice->context(); 89dcceacf57429131b7b4645fadfdb25937bc8ebf5Brian Salomon desc.fFlags = desc.fFlags & ~kRenderTarget_GrSurfaceFlag; 905ec26ae9bfca635ccc98283aad5deda11519d826bsalomon copy.reset(ctx->textureProvider()->createTexture(desc, budgeted)); 91f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon if (!copy) { 92f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon return nullptr; 93f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon } 94f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon if (!ctx->copySurface(copy, rt)) { 95f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon return nullptr; 96f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon } 97f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon tex = copy; 98f47b9a3b88a037a481eb70f01a4cf9f5be34dc28bsalomon } 998b26b99c97473f020df4b9d4ba789e074e06ceddreed const SkImageInfo info = fDevice->imageInfo(); 10096fcdcc219d2a0d3579719b84b28bede76efba64halcanary SkImage* image = nullptr; 1018b26b99c97473f020df4b9d4ba789e074e06ceddreed if (tex) { 102385fe4d4b62d7d1dd76116dd570df3290a2f487bhalcanary image = new SkImage_Gpu(info.width(), info.height(), kNeedNewImageUniqueID, 1037b6945bc4e639d7cc4a49b84d492690f8e865566reed info.alphaType(), tex, budgeted); 1048b26b99c97473f020df4b9d4ba789e074e06ceddreed } 1054af267b11964d4a8acdb232ac46094c84d890e88reed return image; 1065d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com} 1075d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com 108abcfab4d68d53900ef33320bb2622696c14d14b0kkinnunen// Create a new render target and, if necessary, copy the contents of the old 109abcfab4d68d53900ef33320bb2622696c14d14b0kkinnunen// render target into it. Note that this flushes the SkGpuDevice but 11097b6b0730dcb0feee9224ff04eb3985ca4bd0216robertphillips@google.com// doesn't force an OpenGL flush. 111c4c9870953037be94da00ac9db887d171f6e479ccommit-bot@chromium.orgvoid SkSurface_Gpu::onCopyOnWrite(ContentChangeMode mode) { 112b8d00db075b5ea09e353508a26ef5ced50722a6ccommit-bot@chromium.org GrRenderTarget* rt = fDevice->accessRenderTarget(); 113eaaaf0b16c4e55ff8a48c5ac1ed623a6ba469053bsalomon // are we sharing our render target with the image? Note this call should never create a new 114eaaaf0b16c4e55ff8a48c5ac1ed623a6ba469053bsalomon // image because onCopyOnWrite is only called when there is a cached image. 1155ec26ae9bfca635ccc98283aad5deda11519d826bsalomon SkAutoTUnref<SkImage> image(this->refCachedImage(SkBudgeted::kNo, kNo_ForceUnique)); 116eaaaf0b16c4e55ff8a48c5ac1ed623a6ba469053bsalomon SkASSERT(image); 11755812362f1df3c1f7341f687d5bab0adab8ac954bsalomon if (rt->asTexture() == as_IB(image)->getTexture()) { 118abcfab4d68d53900ef33320bb2622696c14d14b0kkinnunen this->fDevice->replaceRenderTarget(SkSurface::kRetain_ContentChangeMode == mode); 119eaaaf0b16c4e55ff8a48c5ac1ed623a6ba469053bsalomon SkTextureImageApplyBudgetedDecision(image); 12028361fad1054d59ed4e6a320c7a8b8782a1487c7commit-bot@chromium.org } else if (kDiscard_ContentChangeMode == mode) { 12128361fad1054d59ed4e6a320c7a8b8782a1487c7commit-bot@chromium.org this->SkSurface_Gpu::onDiscard(); 12297b6b0730dcb0feee9224ff04eb3985ca4bd0216robertphillips@google.com } 1235d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com} 1245d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com 12528361fad1054d59ed4e6a320c7a8b8782a1487c7commit-bot@chromium.orgvoid SkSurface_Gpu::onDiscard() { 12628361fad1054d59ed4e6a320c7a8b8782a1487c7commit-bot@chromium.org fDevice->accessRenderTarget()->discard(); 12728361fad1054d59ed4e6a320c7a8b8782a1487c7commit-bot@chromium.org} 12828361fad1054d59ed4e6a320c7a8b8782a1487c7commit-bot@chromium.org 129f7b8b8affec91fcfab0d79199e466c16c254fe56ericrkvoid SkSurface_Gpu::onPrepareForExternalIO() { 130f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk fDevice->accessRenderTarget()->prepareForExternalIO(); 131f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk} 132f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk 1335d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com/////////////////////////////////////////////////////////////////////////////// 1345d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com 1354a8126e7f81384526629b1e21bf89b632ea13cd9reedSkSurface* SkSurface::NewRenderTargetDirect(GrRenderTarget* target, const SkSurfaceProps* props) { 13674f681dce2fbadd481596aea15afb3e0fb36ceffbsalomon SkAutoTUnref<SkGpuDevice> device( 13774f681dce2fbadd481596aea15afb3e0fb36ceffbsalomon SkGpuDevice::Create(target, props, SkGpuDevice::kUninit_InitContents)); 138afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon if (!device) { 13996fcdcc219d2a0d3579719b84b28bede76efba64halcanary return nullptr; 1405d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com } 141385fe4d4b62d7d1dd76116dd570df3290a2f487bhalcanary return new SkSurface_Gpu(device); 142372b7a4f565354d2101092c6bac6f9550ad506fdreed@google.com} 143fbfcd5602128ec010c82cb733c9cdc0a3254f9f3rmistry@google.com 1445ec26ae9bfca635ccc98283aad5deda11519d826bsalomonSkSurface* SkSurface::NewRenderTarget(GrContext* ctx, SkBudgeted budgeted, const SkImageInfo& info, 1459a1ed5d81dbfc7d5b67b568dfe12b4033a9af154erikchen int sampleCount, const SkSurfaceProps* props, 1469a1ed5d81dbfc7d5b67b568dfe12b4033a9af154erikchen GrTextureStorageAllocator customAllocator) { 1479a1ed5d81dbfc7d5b67b568dfe12b4033a9af154erikchen SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create( 1489a1ed5d81dbfc7d5b67b568dfe12b4033a9af154erikchen ctx, budgeted, info, sampleCount, props, SkGpuDevice::kClear_InitContents, 1499a1ed5d81dbfc7d5b67b568dfe12b4033a9af154erikchen customAllocator)); 150afe3005be3392e43bc51eb7eb2017eefaed85ad1bsalomon if (!device) { 15196fcdcc219d2a0d3579719b84b28bede76efba64halcanary return nullptr; 1525d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com } 153385fe4d4b62d7d1dd76116dd570df3290a2f487bhalcanary return new SkSurface_Gpu(device); 1545d4ba8869476831ee73b15a052af8003d0a1fa2ereed@google.com} 155f037e0bf138a4e842f39e19864d05010a54950c9reed 156d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomonSkSurface* SkSurface::NewFromBackendTexture(GrContext* context, const GrBackendTextureDesc& desc, 157d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon const SkSurfaceProps* props) { 15896fcdcc219d2a0d3579719b84b28bede76efba64halcanary if (nullptr == context) { 15996fcdcc219d2a0d3579719b84b28bede76efba64halcanary return nullptr; 160e4579adfdfb4b9f195d162835a69d9c2a974a6acbsalomon } 161e4579adfdfb4b9f195d162835a69d9c2a974a6acbsalomon if (!SkToBool(desc.fFlags & kRenderTarget_GrBackendTextureFlag)) { 16296fcdcc219d2a0d3579719b84b28bede76efba64halcanary return nullptr; 163e4579adfdfb4b9f195d162835a69d9c2a974a6acbsalomon } 1646dc6f5f4a153d33ed91565cb3cd397a310a937d0bsalomon SkAutoTUnref<GrSurface> surface(context->textureProvider()->wrapBackendTexture(desc, 1656dc6f5f4a153d33ed91565cb3cd397a310a937d0bsalomon kBorrow_GrWrapOwnership)); 166e4579adfdfb4b9f195d162835a69d9c2a974a6acbsalomon if (!surface) { 16796fcdcc219d2a0d3579719b84b28bede76efba64halcanary return nullptr; 168e4579adfdfb4b9f195d162835a69d9c2a974a6acbsalomon } 16974f681dce2fbadd481596aea15afb3e0fb36ceffbsalomon SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(surface->asRenderTarget(), props, 17074f681dce2fbadd481596aea15afb3e0fb36ceffbsalomon SkGpuDevice::kUninit_InitContents)); 171e4579adfdfb4b9f195d162835a69d9c2a974a6acbsalomon if (!device) { 17296fcdcc219d2a0d3579719b84b28bede76efba64halcanary return nullptr; 173e4579adfdfb4b9f195d162835a69d9c2a974a6acbsalomon } 174385fe4d4b62d7d1dd76116dd570df3290a2f487bhalcanary return new SkSurface_Gpu(device); 175e4579adfdfb4b9f195d162835a69d9c2a974a6acbsalomon} 176e4579adfdfb4b9f195d162835a69d9c2a974a6acbsalomon 177d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomonSkSurface* SkSurface::NewFromBackendRenderTarget(GrContext* context, 178d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon const GrBackendRenderTargetDesc& desc, 179d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon const SkSurfaceProps* props) { 18096fcdcc219d2a0d3579719b84b28bede76efba64halcanary if (nullptr == context) { 18196fcdcc219d2a0d3579719b84b28bede76efba64halcanary return nullptr; 182d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon } 183d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon SkAutoTUnref<GrRenderTarget> rt(context->textureProvider()->wrapBackendRenderTarget(desc)); 184d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon if (!rt) { 18596fcdcc219d2a0d3579719b84b28bede76efba64halcanary return nullptr; 186d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon } 187d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(rt, props, 188d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon SkGpuDevice::kUninit_InitContents)); 189d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon if (!device) { 19096fcdcc219d2a0d3579719b84b28bede76efba64halcanary return nullptr; 191d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon } 192385fe4d4b62d7d1dd76116dd570df3290a2f487bhalcanary return new SkSurface_Gpu(device); 193d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon} 194d3e259a16cf85454f629f5fe75b60b9863c1e138bsalomon 195f7b8b8affec91fcfab0d79199e466c16c254fe56ericrkSkSurface* SkSurface::NewFromBackendTextureAsRenderTarget(GrContext* context, 196f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk const GrBackendTextureDesc& desc, 197f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk const SkSurfaceProps* props) { 198f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk if (nullptr == context) { 199f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk return nullptr; 200f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk } 201f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk SkAutoTUnref<GrRenderTarget> rt( 202f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk context->resourceProvider()->wrapBackendTextureAsRenderTarget(desc)); 203f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk if (!rt) { 204f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk return nullptr; 205f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk } 206f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk SkAutoTUnref<SkGpuDevice> device(SkGpuDevice::Create(rt, props, 207f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk SkGpuDevice::kUninit_InitContents)); 208f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk if (!device) { 209f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk return nullptr; 210f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk } 211f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk return new SkSurface_Gpu(device); 212f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk} 213f7b8b8affec91fcfab0d79199e466c16c254fe56ericrk 214f037e0bf138a4e842f39e19864d05010a54950c9reed#endif 215