Optimize the texture packing for texImage2D() and texSubImage2D() in WebGL
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / GraphicsContext3DCG.cpp
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  * Copyright (C) 2010 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28
29 #if USE(3D_GRAPHICS)
30
31 #include "GraphicsContext3D.h"
32
33 #include "BitmapImage.h"
34 #include "GraphicsContextCG.h"
35 #include "Image.h"
36
37 #include <CoreGraphics/CGBitmapContext.h>
38 #include <CoreGraphics/CGContext.h>
39 #include <CoreGraphics/CGDataProvider.h>
40 #include <CoreGraphics/CGImage.h>
41
42 #include <wtf/RetainPtr.h>
43
44 namespace WebCore {
45
46 enum SourceDataFormatBase {
47     SourceFormatBaseR = 0,
48     SourceFormatBaseA,
49     SourceFormatBaseRA,
50     SourceFormatBaseAR,
51     SourceFormatBaseRGB,
52     SourceFormatBaseRGBA,
53     SourceFormatBaseARGB,
54     SourceFormatBaseNumFormats
55 };
56
57 enum AlphaFormat {
58     AlphaFormatNone = 0,
59     AlphaFormatFirst,
60     AlphaFormatLast,
61     AlphaFormatNumFormats
62 };
63
64 // This returns SourceFormatNumFormats if the combination of input parameters is unsupported.
65 static GraphicsContext3D::DataFormat getSourceDataFormat(unsigned componentsPerPixel, AlphaFormat alphaFormat, bool is16BitFormat, bool bigEndian)
66 {
67     const static SourceDataFormatBase formatTableBase[4][AlphaFormatNumFormats] = { // componentsPerPixel x AlphaFormat
68         // AlphaFormatNone            AlphaFormatFirst            AlphaFormatLast
69         { SourceFormatBaseR,          SourceFormatBaseA,          SourceFormatBaseA          }, // 1 componentsPerPixel
70         { SourceFormatBaseNumFormats, SourceFormatBaseAR,         SourceFormatBaseRA         }, // 2 componentsPerPixel
71         { SourceFormatBaseRGB,        SourceFormatBaseNumFormats, SourceFormatBaseNumFormats }, // 3 componentsPerPixel
72         { SourceFormatBaseNumFormats, SourceFormatBaseARGB,       SourceFormatBaseRGBA        } // 4 componentsPerPixel
73     };
74     const static GraphicsContext3D::DataFormat formatTable[SourceFormatBaseNumFormats][4] = { // SourceDataFormatBase x bitsPerComponent x endian
75         // 8bits, little endian                 8bits, big endian                     16bits, little endian                        16bits, big endian
76         { GraphicsContext3D::DataFormatR8,    GraphicsContext3D::DataFormatR8,    GraphicsContext3D::DataFormatR16Little,    GraphicsContext3D::DataFormatR16Big },
77         { GraphicsContext3D::DataFormatA8,    GraphicsContext3D::DataFormatA8,    GraphicsContext3D::DataFormatA16Little,    GraphicsContext3D::DataFormatA16Big },
78         { GraphicsContext3D::DataFormatAR8,   GraphicsContext3D::DataFormatRA8,   GraphicsContext3D::DataFormatRA16Little,   GraphicsContext3D::DataFormatRA16Big },
79         { GraphicsContext3D::DataFormatRA8,   GraphicsContext3D::DataFormatAR8,   GraphicsContext3D::DataFormatAR16Little,   GraphicsContext3D::DataFormatAR16Big },
80         { GraphicsContext3D::DataFormatBGR8,  GraphicsContext3D::DataFormatRGB8,  GraphicsContext3D::DataFormatRGB16Little,  GraphicsContext3D::DataFormatRGB16Big },
81         { GraphicsContext3D::DataFormatABGR8, GraphicsContext3D::DataFormatRGBA8, GraphicsContext3D::DataFormatRGBA16Little, GraphicsContext3D::DataFormatRGBA16Big },
82         { GraphicsContext3D::DataFormatBGRA8, GraphicsContext3D::DataFormatARGB8, GraphicsContext3D::DataFormatARGB16Little, GraphicsContext3D::DataFormatARGB16Big }
83     };
84
85     ASSERT(componentsPerPixel <= 4 && componentsPerPixel > 0);
86     SourceDataFormatBase formatBase = formatTableBase[componentsPerPixel - 1][alphaFormat];
87     if (formatBase == SourceFormatBaseNumFormats)
88         return GraphicsContext3D::DataFormatNumFormats;
89     return formatTable[formatBase][(is16BitFormat ? 2 : 0) + (bigEndian ? 1 : 0)];
90 }
91
92 GraphicsContext3D::ImageExtractor::~ImageExtractor()
93 {
94 }
95
96 bool GraphicsContext3D::ImageExtractor::extractImage(bool premultiplyAlpha, bool ignoreGammaAndColorProfile)
97 {
98     if (!m_image)
99         return false;
100     bool hasAlpha = m_image->isBitmapImage() ? m_image->currentFrameHasAlpha() : true;
101     if ((ignoreGammaAndColorProfile || (hasAlpha && !premultiplyAlpha)) && m_image->data()) {
102         ImageSource decoder(ImageSource::AlphaNotPremultiplied,
103                             ignoreGammaAndColorProfile ? ImageSource::GammaAndColorProfileIgnored : ImageSource::GammaAndColorProfileApplied);
104         decoder.setData(m_image->data(), true);
105         if (!decoder.frameCount())
106             return false;
107         m_decodedImage.adoptCF(decoder.createFrameAtIndex(0));
108         m_cgImage = m_decodedImage.get();
109     } else
110         m_cgImage = m_image->nativeImageForCurrentFrame();
111     if (!m_cgImage)
112         return false;
113
114     m_imageWidth = CGImageGetWidth(m_cgImage);
115     m_imageHeight = CGImageGetHeight(m_cgImage);
116     if (!m_imageWidth || !m_imageHeight)
117         return false;
118
119     // See whether the image is using an indexed color space, and if
120     // so, re-render it into an RGB color space. The image re-packing
121     // code requires color data, not color table indices, for the
122     // image data.
123     CGColorSpaceRef colorSpace = CGImageGetColorSpace(m_cgImage);
124     CGColorSpaceModel model = CGColorSpaceGetModel(colorSpace);
125     if (model == kCGColorSpaceModelIndexed) {
126         RetainPtr<CGContextRef> bitmapContext;
127         // FIXME: we should probably manually convert the image by indexing into
128         // the color table, which would allow us to avoid premultiplying the
129         // alpha channel. Creation of a bitmap context with an alpha channel
130         // doesn't seem to work unless it's premultiplied.
131         bitmapContext.adoptCF(CGBitmapContextCreate(0, m_imageWidth, m_imageHeight, 8, m_imageWidth * 4,
132                                                     deviceRGBColorSpaceRef(),
133                                                     kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
134         if (!bitmapContext)
135             return false;
136
137         CGContextSetBlendMode(bitmapContext.get(), kCGBlendModeCopy);
138         CGContextSetInterpolationQuality(bitmapContext.get(), kCGInterpolationNone);
139         CGContextDrawImage(bitmapContext.get(), CGRectMake(0, 0, m_imageWidth, m_imageHeight), m_cgImage);
140
141         // Now discard the original CG image and replace it with a copy from the bitmap context.
142         m_decodedImage.adoptCF(CGBitmapContextCreateImage(bitmapContext.get()));
143         m_cgImage = m_decodedImage.get();
144     }
145
146     size_t bitsPerComponent = CGImageGetBitsPerComponent(m_cgImage);
147     size_t bitsPerPixel = CGImageGetBitsPerPixel(m_cgImage);
148     if (bitsPerComponent != 8 && bitsPerComponent != 16)
149         return false;
150     if (bitsPerPixel % bitsPerComponent)
151         return false;
152     size_t componentsPerPixel = bitsPerPixel / bitsPerComponent;
153
154     CGBitmapInfo bitInfo = CGImageGetBitmapInfo(m_cgImage);
155     bool bigEndianSource = false;
156     // These could technically be combined into one large switch
157     // statement, but we prefer not to so that we fail fast if we
158     // encounter an unexpected image configuration.
159     if (bitsPerComponent == 16) {
160         switch (bitInfo & kCGBitmapByteOrderMask) {
161         case kCGBitmapByteOrder16Big:
162             bigEndianSource = true;
163             break;
164         case kCGBitmapByteOrder16Little:
165             bigEndianSource = false;
166             break;
167         case kCGBitmapByteOrderDefault:
168             // This is a bug in earlier version of cg where the default endian
169             // is little whereas the decoded 16-bit png image data is actually
170             // Big. Later version (10.6.4) no longer returns ByteOrderDefault.
171             bigEndianSource = true;
172             break;
173         default:
174             return false;
175         }
176     } else {
177         switch (bitInfo & kCGBitmapByteOrderMask) {
178         case kCGBitmapByteOrder32Big:
179             bigEndianSource = true;
180             break;
181         case kCGBitmapByteOrder32Little:
182             bigEndianSource = false;
183             break;
184         case kCGBitmapByteOrderDefault:
185             // It appears that the default byte order is actually big
186             // endian even on little endian architectures.
187             bigEndianSource = true;
188             break;
189         default:
190             return false;
191         }
192     }
193
194     m_alphaOp = AlphaDoNothing;
195     AlphaFormat alphaFormat = AlphaFormatNone;
196     switch (CGImageGetAlphaInfo(m_cgImage)) {
197     case kCGImageAlphaPremultipliedFirst:
198         if (!premultiplyAlpha)
199             m_alphaOp = AlphaDoUnmultiply;
200         alphaFormat = AlphaFormatFirst;
201         break;
202     case kCGImageAlphaFirst:
203         // This path is only accessible for MacOS earlier than 10.6.4.
204         if (premultiplyAlpha)
205             m_alphaOp = AlphaDoPremultiply;
206         alphaFormat = AlphaFormatFirst;
207         break;
208     case kCGImageAlphaNoneSkipFirst:
209         // This path is only accessible for MacOS earlier than 10.6.4.
210         alphaFormat = AlphaFormatFirst;
211         break;
212     case kCGImageAlphaPremultipliedLast:
213         if (!premultiplyAlpha)
214             m_alphaOp = AlphaDoUnmultiply;
215         alphaFormat = AlphaFormatLast;
216         break;
217     case kCGImageAlphaLast:
218         if (premultiplyAlpha)
219             m_alphaOp = AlphaDoPremultiply;
220         alphaFormat = AlphaFormatLast;
221         break;
222     case kCGImageAlphaNoneSkipLast:
223         alphaFormat = AlphaFormatLast;
224         break;
225     case kCGImageAlphaNone:
226         alphaFormat = AlphaFormatNone;
227         break;
228     default:
229         return false;
230     }
231
232     m_imageSourceFormat = getSourceDataFormat(componentsPerPixel, alphaFormat, bitsPerComponent == 16, bigEndianSource);
233     if (m_imageSourceFormat == DataFormatNumFormats)
234         return false;
235
236     m_pixelData.adoptCF(CGDataProviderCopyData(CGImageGetDataProvider(m_cgImage)));
237     if (!m_pixelData)
238         return false;
239
240     m_imagePixelData = reinterpret_cast<const void*>(CFDataGetBytePtr(m_pixelData.get()));
241
242     unsigned int srcUnpackAlignment = 0;
243     size_t bytesPerRow = CGImageGetBytesPerRow(m_cgImage);
244     unsigned padding = bytesPerRow - bitsPerPixel / 8 * m_imageWidth;
245     if (padding) {
246         srcUnpackAlignment = padding + 1;
247         while (bytesPerRow % srcUnpackAlignment)
248             ++srcUnpackAlignment;
249     }
250
251     m_imageSourceUnpackAlignment = srcUnpackAlignment;
252
253     return true;
254 }
255
256 void GraphicsContext3D::paintToCanvas(const unsigned char* imagePixels, int imageWidth, int imageHeight, int canvasWidth, int canvasHeight, CGContextRef context)
257 {
258     if (!imagePixels || imageWidth <= 0 || imageHeight <= 0 || canvasWidth <= 0 || canvasHeight <= 0 || !context)
259         return;
260     int rowBytes = imageWidth * 4;
261     RetainPtr<CGDataProviderRef> dataProvider(AdoptCF, CGDataProviderCreateWithData(0, imagePixels, rowBytes * imageHeight, 0));
262     RetainPtr<CGImageRef> cgImage(AdoptCF, CGImageCreate(imageWidth, imageHeight, 8, 32, rowBytes, deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
263         dataProvider.get(), 0, false, kCGRenderingIntentDefault));
264     // CSS styling may cause the canvas's content to be resized on
265     // the page. Go back to the Canvas to figure out the correct
266     // width and height to draw.
267     CGRect rect = CGRectMake(0, 0, canvasWidth, canvasHeight);
268     // We want to completely overwrite the previous frame's
269     // rendering results.
270     CGContextSaveGState(context);
271     CGContextSetBlendMode(context, kCGBlendModeCopy);
272     CGContextSetInterpolationQuality(context, kCGInterpolationNone);
273     CGContextDrawImage(context, rect, cgImage.get());
274     CGContextRestoreGState(context);
275 }
276
277 } // namespace WebCore
278
279 #endif // USE(3D_GRAPHICS)