1/*
2 * Copyright (C) 2009 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(WEBGL)
29
30#include "WebGLTexture.h"
31
32#include "WebGLFramebuffer.h"
33#include "WebGLRenderingContext.h"
34
35namespace WebCore {
36
37PassRefPtr<WebGLTexture> WebGLTexture::create(WebGLRenderingContext* ctx)
38{
39    return adoptRef(new WebGLTexture(ctx));
40}
41
42WebGLTexture::WebGLTexture(WebGLRenderingContext* ctx)
43    : WebGLObject(ctx)
44    , m_target(0)
45    , m_minFilter(GraphicsContext3D::NEAREST_MIPMAP_LINEAR)
46    , m_magFilter(GraphicsContext3D::LINEAR)
47    , m_wrapS(GraphicsContext3D::REPEAT)
48    , m_wrapT(GraphicsContext3D::REPEAT)
49    , m_isNPOT(false)
50    , m_isComplete(false)
51    , m_needToUseBlackTexture(false)
52{
53    setObject(context()->graphicsContext3D()->createTexture());
54}
55
56void WebGLTexture::setTarget(GC3Denum target, GC3Dint maxLevel)
57{
58    if (!object())
59        return;
60    // Target is finalized the first time bindTexture() is called.
61    if (m_target)
62        return;
63    switch (target) {
64    case GraphicsContext3D::TEXTURE_2D:
65        m_target = target;
66        m_info.resize(1);
67        m_info[0].resize(maxLevel);
68        break;
69    case GraphicsContext3D::TEXTURE_CUBE_MAP:
70        m_target = target;
71        m_info.resize(6);
72        for (int ii = 0; ii < 6; ++ii)
73            m_info[ii].resize(maxLevel);
74        break;
75    }
76}
77
78void WebGLTexture::setParameteri(GC3Denum pname, GC3Dint param)
79{
80    if (!object() || !m_target)
81        return;
82    switch (pname) {
83    case GraphicsContext3D::TEXTURE_MIN_FILTER:
84        switch (param) {
85        case GraphicsContext3D::NEAREST:
86        case GraphicsContext3D::LINEAR:
87        case GraphicsContext3D::NEAREST_MIPMAP_NEAREST:
88        case GraphicsContext3D::LINEAR_MIPMAP_NEAREST:
89        case GraphicsContext3D::NEAREST_MIPMAP_LINEAR:
90        case GraphicsContext3D::LINEAR_MIPMAP_LINEAR:
91            m_minFilter = param;
92            break;
93        }
94        break;
95    case GraphicsContext3D::TEXTURE_MAG_FILTER:
96        switch (param) {
97        case GraphicsContext3D::NEAREST:
98        case GraphicsContext3D::LINEAR:
99            m_magFilter = param;
100            break;
101        }
102        break;
103    case GraphicsContext3D::TEXTURE_WRAP_S:
104        switch (param) {
105        case GraphicsContext3D::CLAMP_TO_EDGE:
106        case GraphicsContext3D::MIRRORED_REPEAT:
107        case GraphicsContext3D::REPEAT:
108            m_wrapS = param;
109            break;
110        }
111        break;
112    case GraphicsContext3D::TEXTURE_WRAP_T:
113        switch (param) {
114        case GraphicsContext3D::CLAMP_TO_EDGE:
115        case GraphicsContext3D::MIRRORED_REPEAT:
116        case GraphicsContext3D::REPEAT:
117            m_wrapT = param;
118            break;
119        }
120        break;
121    default:
122        return;
123    }
124    update();
125}
126
127void WebGLTexture::setParameterf(GC3Denum pname, GC3Dfloat param)
128{
129    if (!object() || !m_target)
130        return;
131    GC3Dint iparam = static_cast<GC3Dint>(param);
132    setParameteri(pname, iparam);
133}
134
135void WebGLTexture::setLevelInfo(GC3Denum target, GC3Dint level, GC3Denum internalFormat, GC3Dsizei width, GC3Dsizei height, GC3Denum type)
136{
137    if (!object() || !m_target)
138        return;
139    // We assume level, internalFormat, width, height, and type have all been
140    // validated already.
141    int index = mapTargetToIndex(target);
142    if (index < 0)
143        return;
144    m_info[index][level].setInfo(internalFormat, width, height, type);
145    update();
146}
147
148void WebGLTexture::generateMipmapLevelInfo()
149{
150    if (!object() || !m_target)
151        return;
152    if (!canGenerateMipmaps())
153        return;
154    if (!m_isComplete) {
155        for (size_t ii = 0; ii < m_info.size(); ++ii) {
156            const LevelInfo& info0 = m_info[ii][0];
157            GC3Dsizei width = info0.width;
158            GC3Dsizei height = info0.height;
159            GC3Dint levelCount = computeLevelCount(width, height);
160            for (GC3Dint level = 1; level < levelCount; ++level) {
161                width = std::max(1, width >> 1);
162                height = std::max(1, height >> 1);
163                LevelInfo& info = m_info[ii][level];
164                info.setInfo(info0.internalFormat, width, height, info0.type);
165            }
166        }
167        m_isComplete = true;
168    }
169    m_needToUseBlackTexture = false;
170}
171
172GC3Denum WebGLTexture::getInternalFormat(GC3Denum target, GC3Dint level) const
173{
174    const LevelInfo* info = getLevelInfo(target, level);
175    if (!info)
176        return 0;
177    return info->internalFormat;
178}
179
180GC3Denum WebGLTexture::getType(GC3Denum target, GC3Dint level) const
181{
182    const LevelInfo* info = getLevelInfo(target, level);
183    if (!info)
184        return 0;
185    return info->type;
186}
187
188GC3Dsizei WebGLTexture::getWidth(GC3Denum target, GC3Dint level) const
189{
190    const LevelInfo* info = getLevelInfo(target, level);
191    if (!info)
192        return 0;
193    return info->width;
194}
195
196GC3Dsizei WebGLTexture::getHeight(GC3Denum target, GC3Dint level) const
197{
198    const LevelInfo* info = getLevelInfo(target, level);
199    if (!info)
200        return 0;
201    return info->height;
202}
203
204bool WebGLTexture::isNPOT(GC3Dsizei width, GC3Dsizei height)
205{
206    ASSERT(width >= 0 && height >= 0);
207    if (!width || !height)
208        return false;
209    if ((width & (width - 1)) || (height & (height - 1)))
210        return true;
211    return false;
212}
213
214bool WebGLTexture::isNPOT() const
215{
216    if (!object())
217        return false;
218    return m_isNPOT;
219}
220
221bool WebGLTexture::needToUseBlackTexture() const
222{
223    if (!object())
224        return false;
225    return m_needToUseBlackTexture;
226}
227
228void WebGLTexture::deleteObjectImpl(Platform3DObject object)
229{
230    context()->graphicsContext3D()->deleteTexture(object);
231}
232
233int WebGLTexture::mapTargetToIndex(GC3Denum target) const
234{
235    if (m_target == GraphicsContext3D::TEXTURE_2D) {
236        if (target == GraphicsContext3D::TEXTURE_2D)
237            return 0;
238    } else if (m_target == GraphicsContext3D::TEXTURE_CUBE_MAP) {
239        switch (target) {
240        case GraphicsContext3D::TEXTURE_CUBE_MAP_POSITIVE_X:
241            return 0;
242        case GraphicsContext3D::TEXTURE_CUBE_MAP_NEGATIVE_X:
243            return 1;
244        case GraphicsContext3D::TEXTURE_CUBE_MAP_POSITIVE_Y:
245            return 2;
246        case GraphicsContext3D::TEXTURE_CUBE_MAP_NEGATIVE_Y:
247            return 3;
248        case GraphicsContext3D::TEXTURE_CUBE_MAP_POSITIVE_Z:
249            return 4;
250        case GraphicsContext3D::TEXTURE_CUBE_MAP_NEGATIVE_Z:
251            return 5;
252        }
253    }
254    return -1;
255}
256
257bool WebGLTexture::canGenerateMipmaps()
258{
259    if (isNPOT())
260        return false;
261    const LevelInfo& first = m_info[0][0];
262    for (size_t ii = 0; ii < m_info.size(); ++ii) {
263        const LevelInfo& info = m_info[ii][0];
264        if (!info.valid
265            || info.width != first.width || info.height != first.height
266            || info.internalFormat != first.internalFormat || info.type != first.type)
267            return false;
268    }
269    return true;
270}
271
272GC3Dint WebGLTexture::computeLevelCount(GC3Dsizei width, GC3Dsizei height)
273{
274    // return 1 + log2Floor(std::max(width, height));
275    GC3Dsizei n = std::max(width, height);
276    if (n <= 0)
277        return 0;
278    GC3Dint log = 0;
279    GC3Dsizei value = n;
280    for (int ii = 4; ii >= 0; --ii) {
281        int shift = (1 << ii);
282        GC3Dsizei x = (value >> shift);
283        if (x) {
284            value = x;
285            log += shift;
286        }
287    }
288    ASSERT(value == 1);
289    return log + 1;
290}
291
292void WebGLTexture::update()
293{
294    m_isNPOT = false;
295    for (size_t ii = 0; ii < m_info.size(); ++ii) {
296        if (isNPOT(m_info[ii][0].width, m_info[ii][0].height)) {
297            m_isNPOT = true;
298            break;
299        }
300    }
301    m_isComplete = true;
302    const LevelInfo& first = m_info[0][0];
303    GC3Dint levelCount = computeLevelCount(first.width, first.height);
304    if (levelCount < 1)
305        m_isComplete = false;
306    else {
307        for (size_t ii = 0; ii < m_info.size() && m_isComplete; ++ii) {
308            const LevelInfo& info0 = m_info[ii][0];
309            if (!info0.valid
310                || info0.width != first.width || info0.height != first.height
311                || info0.internalFormat != first.internalFormat || info0.type != first.type) {
312                m_isComplete = false;
313                break;
314            }
315            GC3Dsizei width = info0.width;
316            GC3Dsizei height = info0.height;
317            for (GC3Dint level = 1; level < levelCount; ++level) {
318                width = std::max(1, width >> 1);
319                height = std::max(1, height >> 1);
320                const LevelInfo& info = m_info[ii][level];
321                if (!info.valid
322                    || info.width != width || info.height != height
323                    || info.internalFormat != info0.internalFormat || info.type != info0.type) {
324                    m_isComplete = false;
325                    break;
326                }
327
328            }
329        }
330    }
331
332    m_needToUseBlackTexture = false;
333    // NPOT
334    if (m_isNPOT && ((m_minFilter != GraphicsContext3D::NEAREST && m_minFilter != GraphicsContext3D::LINEAR)
335                     || m_wrapS != GraphicsContext3D::CLAMP_TO_EDGE || m_wrapT != GraphicsContext3D::CLAMP_TO_EDGE))
336        m_needToUseBlackTexture = true;
337    // Completeness
338    if (!m_isComplete && m_minFilter != GraphicsContext3D::NEAREST && m_minFilter != GraphicsContext3D::LINEAR)
339        m_needToUseBlackTexture = true;
340}
341
342const WebGLTexture::LevelInfo* WebGLTexture::getLevelInfo(GC3Denum target, GC3Dint level) const
343{
344    if (!object() || !m_target)
345        return 0;
346    int targetIndex = mapTargetToIndex(target);
347    if (targetIndex < 0 || targetIndex >= static_cast<int>(m_info.size()))
348        return 0;
349    if (level < 0 || level >= static_cast<GC3Dint>(m_info[targetIndex].size()))
350        return 0;
351    return &(m_info[targetIndex][level]);
352}
353
354}
355
356#endif // ENABLE(WEBGL)
357