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