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        TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("skia.gpu"), "GrPlot::uploadToTexture");
96        fTexture->writePixels(loc->fX, loc->fY, width, height, fTexture->config(), image, 0,
97                              GrContext::kDontFlush_PixelOpsFlag);
98    } else {
99        adjust_for_offset(loc, fOffset);
100    }
101
102#if FONT_CACHE_STATS
103    ++g_UploadCount;
104#endif
105
106    return true;
107}
108
109void GrPlot::uploadToTexture() {
110    static const float kNearlyFullTolerance = 0.85f;
111
112    // should only do this if batching is enabled
113    SkASSERT(fBatchUploads);
114
115    if (fDirty) {
116        TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("skia.gpu"), "GrPlot::uploadToTexture");
117        SkASSERT(fTexture);
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        fTexture->writePixels(fOffset.fX + fDirtyRect.fLeft, fOffset.fY + fDirtyRect.fTop,
126                              fDirtyRect.width(), fDirtyRect.height(), fTexture->config(), dataPtr,
127                              rowBytes, GrContext::kDontFlush_PixelOpsFlag);
128        fDirtyRect.setEmpty();
129        fDirty = false;
130        // If the Plot is nearly full, anything else we add will probably be small and one
131        // at a time, so free up the memory and after this upload any new images directly.
132        if (fRects->percentFull() > kNearlyFullTolerance) {
133            SkDELETE_ARRAY(fPlotData);
134            fPlotData = NULL;
135        }
136    }
137}
138
139void GrPlot::resetRects() {
140    SkASSERT(fRects);
141    fRects->reset();
142}
143
144///////////////////////////////////////////////////////////////////////////////
145
146GrAtlas::GrAtlas(GrGpu* gpu, GrPixelConfig config, GrSurfaceFlags flags,
147                 const SkISize& backingTextureSize,
148                 int numPlotsX, int numPlotsY, bool batchUploads) {
149    fGpu = SkRef(gpu);
150    fPixelConfig = config;
151    fFlags = flags;
152    fBackingTextureSize = backingTextureSize;
153    fNumPlotsX = numPlotsX;
154    fNumPlotsY = numPlotsY;
155    fBatchUploads = batchUploads;
156    fTexture = NULL;
157
158    int textureWidth = fBackingTextureSize.width();
159    int textureHeight = fBackingTextureSize.height();
160
161    int plotWidth = textureWidth / fNumPlotsX;
162    int plotHeight = textureHeight / fNumPlotsY;
163
164    SkASSERT(plotWidth * fNumPlotsX == textureWidth);
165    SkASSERT(plotHeight * fNumPlotsY == textureHeight);
166
167    // We currently do not support compressed atlases...
168    SkASSERT(!GrPixelConfigIsCompressed(config));
169
170    // set up allocated plots
171    size_t bpp = GrBytesPerPixel(fPixelConfig);
172    fPlotArray = SkNEW_ARRAY(GrPlot, (fNumPlotsX*fNumPlotsY));
173
174    GrPlot* currPlot = fPlotArray;
175    for (int y = numPlotsY-1; y >= 0; --y) {
176        for (int x = numPlotsX-1; x >= 0; --x) {
177            currPlot->init(this, y*numPlotsX+x, x, y, plotWidth, plotHeight, bpp, batchUploads);
178
179            // build LRU list
180            fPlotList.addToHead(currPlot);
181            ++currPlot;
182        }
183    }
184}
185
186GrAtlas::~GrAtlas() {
187    SkSafeUnref(fTexture);
188    SkDELETE_ARRAY(fPlotArray);
189
190    fGpu->unref();
191#if FONT_CACHE_STATS
192      SkDebugf("Num uploads: %d\n", g_UploadCount);
193#endif
194}
195
196void GrAtlas::makeMRU(GrPlot* plot) {
197    if (fPlotList.head() == plot) {
198        return;
199    }
200
201    fPlotList.remove(plot);
202    fPlotList.addToHead(plot);
203};
204
205GrPlot* GrAtlas::addToAtlas(ClientPlotUsage* usage,
206                            int width, int height, const void* image,
207                            SkIPoint16* loc) {
208    // iterate through entire plot list for this atlas, see if we can find a hole
209    // last one was most recently added and probably most empty
210    for (int i = usage->fPlots.count()-1; i >= 0; --i) {
211        GrPlot* plot = usage->fPlots[i];
212        // client may have plots from more than one atlas, must check for ours before adding
213        if (this == plot->fAtlas && plot->addSubImage(width, height, image, loc)) {
214            this->makeMRU(plot);
215            return plot;
216        }
217    }
218
219    // before we get a new plot, make sure we have a backing texture
220    if (NULL == fTexture) {
221        // TODO: Update this to use the cache rather than directly creating a texture.
222        GrSurfaceDesc desc;
223        desc.fFlags = fFlags;
224        desc.fWidth = fBackingTextureSize.width();
225        desc.fHeight = fBackingTextureSize.height();
226        desc.fConfig = fPixelConfig;
227
228        fTexture = fGpu->createTexture(desc, true, NULL, 0);
229        if (NULL == fTexture) {
230            return NULL;
231        }
232    }
233
234    // now look through all allocated plots for one we can share, in MRU order
235    GrPlotList::Iter plotIter;
236    plotIter.init(fPlotList, GrPlotList::Iter::kHead_IterStart);
237    GrPlot* plot;
238    while ((plot = plotIter.get())) {
239        // make sure texture is set for quick lookup
240        plot->fTexture = fTexture;
241        if (plot->addSubImage(width, height, image, loc)) {
242            this->makeMRU(plot);
243            // new plot for atlas, put at end of array
244            SkASSERT(!usage->fPlots.contains(plot));
245            *(usage->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
255void GrAtlas::RemovePlot(ClientPlotUsage* usage, const GrPlot* plot) {
256    int index = usage->fPlots.find(const_cast<GrPlot*>(plot));
257    if (index >= 0) {
258        usage->fPlots.remove(index);
259    }
260}
261
262// get a plot that's not being used by the current draw
263GrPlot* GrAtlas::getUnusedPlot() {
264    GrPlotList::Iter plotIter;
265    plotIter.init(fPlotList, GrPlotList::Iter::kTail_IterStart);
266    GrPlot* plot;
267    while ((plot = plotIter.get())) {
268        if (plot->drawToken().isIssued()) {
269            return plot;
270        }
271        plotIter.prev();
272    }
273
274    return NULL;
275}
276
277void GrAtlas::uploadPlotsToTexture() {
278    if (fBatchUploads) {
279        GrPlotList::Iter plotIter;
280        plotIter.init(fPlotList, GrPlotList::Iter::kHead_IterStart);
281        GrPlot* plot;
282        while ((plot = plotIter.get())) {
283            plot->uploadToTexture();
284            plotIter.next();
285        }
286    }
287}
288