1
2/*
3 * Copyright 2010 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
8
9#include "GrAtlas.h"
10#include "GrContext.h"
11#include "GrGpu.h"
12#include "GrRectanizer.h"
13#include "GrTracing.h"
14
15///////////////////////////////////////////////////////////////////////////////
16
17// for testing
18#define FONT_CACHE_STATS 0
19#if FONT_CACHE_STATS
20static int g_UploadCount = 0;
21#endif
22
23GrPlot::GrPlot() : fDrawToken(NULL, 0)
24                 , fTexture(NULL)
25                 , fRects(NULL)
26                 , fAtlasMgr(NULL)
27                 , fBytesPerPixel(1)
28                 , fDirty(false)
29                 , fBatchUploads(false)
30{
31    fOffset.set(0, 0);
32}
33
34GrPlot::~GrPlot() {
35    SkDELETE_ARRAY(fPlotData);
36    fPlotData = NULL;
37    delete fRects;
38}
39
40void GrPlot::init(GrAtlasMgr* mgr, int offX, int offY, int width, int height, size_t bpp,
41                  bool batchUploads) {
42    fRects = GrRectanizer::Factory(width, height);
43    fAtlasMgr = mgr;
44    fOffset.set(offX * width, offY * height);
45    fBytesPerPixel = bpp;
46    fPlotData = NULL;
47    fDirtyRect.setEmpty();
48    fDirty = false;
49    fBatchUploads = batchUploads;
50}
51
52static inline void adjust_for_offset(SkIPoint16* loc, const SkIPoint16& offset) {
53    loc->fX += offset.fX;
54    loc->fY += offset.fY;
55}
56
57bool GrPlot::addSubImage(int width, int height, const void* image,
58                         SkIPoint16* loc) {
59    float percentFull = fRects->percentFull();
60    if (!fRects->addRect(width, height, loc)) {
61        return false;
62    }
63
64    // if batching uploads, create backing memory on first use
65    // once the plot is nearly full we will revert to uploading each subimage individually
66    int plotWidth = fRects->width();
67    int plotHeight = fRects->height();
68    if (fBatchUploads && NULL == fPlotData && 0.0f == percentFull) {
69        fPlotData = SkNEW_ARRAY(unsigned char, fBytesPerPixel*plotWidth*plotHeight);
70        memset(fPlotData, 0, fBytesPerPixel*plotWidth*plotHeight);
71    }
72
73    // if we have backing memory, copy to the memory and set for future upload
74    if (NULL != fPlotData) {
75        const unsigned char* imagePtr = (const unsigned char*) image;
76        // point ourselves at the right starting spot
77        unsigned char* dataPtr = fPlotData;
78        dataPtr += fBytesPerPixel*plotWidth*loc->fY;
79        dataPtr += fBytesPerPixel*loc->fX;
80        // copy into the data buffer
81        for (int i = 0; i < height; ++i) {
82            memcpy(dataPtr, imagePtr, fBytesPerPixel*width);
83            dataPtr += fBytesPerPixel*plotWidth;
84            imagePtr += fBytesPerPixel*width;
85        }
86
87        fDirtyRect.join(loc->fX, loc->fY, loc->fX + width, loc->fY + height);
88        adjust_for_offset(loc, fOffset);
89        fDirty = true;
90    // otherwise, just upload the image directly
91    } else {
92        adjust_for_offset(loc, fOffset);
93        GrContext* context = fTexture->getContext();
94        TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("skia.gpu"), "GrPlot::uploadToTexture");
95        context->writeTexturePixels(fTexture,
96                                    loc->fX, loc->fY, width, height,
97                                    fTexture->config(), image, 0,
98                                    GrContext::kDontFlush_PixelOpsFlag);
99    }
100
101#if FONT_CACHE_STATS
102    ++g_UploadCount;
103#endif
104
105    return true;
106}
107
108void GrPlot::uploadToTexture() {
109    static const float kNearlyFullTolerance = 0.85f;
110
111    // should only do this if batching is enabled
112    SkASSERT(fBatchUploads);
113
114    if (fDirty) {
115        TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("skia.gpu"), "GrPlot::uploadToTexture");
116        SkASSERT(NULL != fTexture);
117        GrContext* context = fTexture->getContext();
118        // We pass the flag that does not force a flush. We assume our caller is
119        // smart and hasn't referenced the part of the texture we're about to update
120        // since the last flush.
121        size_t rowBytes = fBytesPerPixel*fRects->width();
122        const unsigned char* dataPtr = fPlotData;
123        dataPtr += rowBytes*fDirtyRect.fTop;
124        dataPtr += fBytesPerPixel*fDirtyRect.fLeft;
125        context->writeTexturePixels(fTexture,
126                                    fOffset.fX + fDirtyRect.fLeft, fOffset.fY + fDirtyRect.fTop,
127                                    fDirtyRect.width(), fDirtyRect.height(),
128                                    fTexture->config(), dataPtr,
129                                    rowBytes,
130                                    GrContext::kDontFlush_PixelOpsFlag);
131        fDirtyRect.setEmpty();
132        fDirty = false;
133        // If the Plot is nearly full, anything else we add will probably be small and one
134        // at a time, so free up the memory and after this upload any new images directly.
135        if (fRects->percentFull() > kNearlyFullTolerance) {
136            SkDELETE_ARRAY(fPlotData);
137            fPlotData = NULL;
138        }
139    }
140}
141
142void GrPlot::resetRects() {
143    SkASSERT(NULL != fRects);
144    fRects->reset();
145}
146
147///////////////////////////////////////////////////////////////////////////////
148
149GrAtlasMgr::GrAtlasMgr(GrGpu* gpu, GrPixelConfig config,
150                       const SkISize& backingTextureSize,
151                       int numPlotsX, int numPlotsY, bool batchUploads) {
152    fGpu = SkRef(gpu);
153    fPixelConfig = config;
154    fBackingTextureSize = backingTextureSize;
155    fNumPlotsX = numPlotsX;
156    fNumPlotsY = numPlotsY;
157    fBatchUploads = batchUploads;
158    fTexture = NULL;
159
160    int textureWidth = fBackingTextureSize.width();
161    int textureHeight = fBackingTextureSize.height();
162
163    int plotWidth = textureWidth / fNumPlotsX;
164    int plotHeight = textureHeight / fNumPlotsY;
165
166    SkASSERT(plotWidth * fNumPlotsX == textureWidth);
167    SkASSERT(plotHeight * fNumPlotsY == textureHeight);
168
169    // We currently do not support compressed atlases...
170    SkASSERT(!GrPixelConfigIsCompressed(config));
171
172    // set up allocated plots
173    size_t bpp = GrBytesPerPixel(fPixelConfig);
174    fPlotArray = SkNEW_ARRAY(GrPlot, (fNumPlotsX*fNumPlotsY));
175
176    GrPlot* currPlot = fPlotArray;
177    for (int y = numPlotsY-1; y >= 0; --y) {
178        for (int x = numPlotsX-1; x >= 0; --x) {
179            currPlot->init(this, x, y, plotWidth, plotHeight, bpp, batchUploads);
180
181            // build LRU list
182            fPlotList.addToHead(currPlot);
183            ++currPlot;
184        }
185    }
186}
187
188GrAtlasMgr::~GrAtlasMgr() {
189    SkSafeUnref(fTexture);
190    SkDELETE_ARRAY(fPlotArray);
191
192    fGpu->unref();
193#if FONT_CACHE_STATS
194      GrPrintf("Num uploads: %d\n", g_UploadCount);
195#endif
196}
197
198void GrAtlasMgr::moveToHead(GrPlot* plot) {
199    if (fPlotList.head() == plot) {
200        return;
201    }
202
203    fPlotList.remove(plot);
204    fPlotList.addToHead(plot);
205};
206
207GrPlot* GrAtlasMgr::addToAtlas(GrAtlas* atlas,
208                               int width, int height, const void* image,
209                               SkIPoint16* loc) {
210    // iterate through entire plot list for this atlas, see if we can find a hole
211    // last one was most recently added and probably most empty
212    for (int i = atlas->fPlots.count()-1; i >= 0; --i) {
213        GrPlot* plot = atlas->fPlots[i];
214        if (plot->addSubImage(width, height, image, loc)) {
215            this->moveToHead(plot);
216            return plot;
217        }
218    }
219
220    // before we get a new plot, make sure we have a backing texture
221    if (NULL == fTexture) {
222        // TODO: Update this to use the cache rather than directly creating a texture.
223        GrTextureDesc desc;
224        desc.fFlags = kDynamicUpdate_GrTextureFlagBit;
225        desc.fWidth = fBackingTextureSize.width();
226        desc.fHeight = fBackingTextureSize.height();
227        desc.fConfig = fPixelConfig;
228
229        fTexture = fGpu->createTexture(desc, NULL, 0);
230        if (NULL == fTexture) {
231            return NULL;
232        }
233    }
234
235    // now look through all allocated plots for one we can share, in MRU order
236    GrPlotList::Iter plotIter;
237    plotIter.init(fPlotList, GrPlotList::Iter::kHead_IterStart);
238    GrPlot* plot;
239    while (NULL != (plot = plotIter.get())) {
240        // make sure texture is set for quick lookup
241        plot->fTexture = fTexture;
242        if (plot->addSubImage(width, height, image, loc)) {
243            this->moveToHead(plot);
244            // new plot for atlas, put at end of array
245            *(atlas->fPlots.append()) = plot;
246            return plot;
247        }
248        plotIter.next();
249    }
250
251    // If the above fails, then the current plot list has no room
252    return NULL;
253}
254
255bool GrAtlasMgr::removePlot(GrAtlas* atlas, const GrPlot* plot) {
256    // iterate through plot list for this atlas
257    int count = atlas->fPlots.count();
258    for (int i = 0; i < count; ++i) {
259        if (plot == atlas->fPlots[i]) {
260            atlas->fPlots.remove(i);
261            return true;
262        }
263    }
264
265    return false;
266}
267
268// get a plot that's not being used by the current draw
269GrPlot* GrAtlasMgr::getUnusedPlot() {
270    GrPlotList::Iter plotIter;
271    plotIter.init(fPlotList, GrPlotList::Iter::kTail_IterStart);
272    GrPlot* plot;
273    while (NULL != (plot = plotIter.get())) {
274        if (plot->drawToken().isIssued()) {
275            return plot;
276        }
277        plotIter.prev();
278    }
279
280    return NULL;
281}
282
283void GrAtlasMgr::uploadPlotsToTexture() {
284    if (fBatchUploads) {
285        GrPlotList::Iter plotIter;
286        plotIter.init(fPlotList, GrPlotList::Iter::kHead_IterStart);
287        GrPlot* plot;
288        while (NULL != (plot = plotIter.get())) {
289            plot->uploadToTexture();
290            plotIter.next();
291        }
292    }
293}
294