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 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 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 "WebGLContextGroup.h"
33#include "WebGLFramebuffer.h"
34#include "WebGLRenderingContext.h"
35
36namespace WebCore {
37
38PassRefPtr<WebGLTexture> WebGLTexture::create(WebGLRenderingContext* ctx)
39{
40    return adoptRef(new WebGLTexture(ctx));
41}
42
43WebGLTexture::WebGLTexture(WebGLRenderingContext* ctx)
44    : WebGLSharedObject(ctx)
45    , m_target(0)
46    , m_minFilter(GraphicsContext3D::NEAREST_MIPMAP_LINEAR)
47    , m_magFilter(GraphicsContext3D::LINEAR)
48    , m_wrapS(GraphicsContext3D::REPEAT)
49    , m_wrapT(GraphicsContext3D::REPEAT)
50    , m_isNPOT(false)
51    , m_isComplete(false)
52    , m_needToUseBlackTexture(false)
53    , m_isCompressed(false)
54    , m_isFloatType(false)
55    , m_isHalfFloatType(false)
56{
57    setObject(ctx->graphicsContext3D()->createTexture());
58}
59
60WebGLTexture::~WebGLTexture()
61{
62    deleteObject(0);
63}
64
65void WebGLTexture::setTarget(GC3Denum target, GC3Dint maxLevel)
66{
67    if (!object())
68        return;
69    // Target is finalized the first time bindTexture() is called.
70    if (m_target)
71        return;
72    switch (target) {
73    case GraphicsContext3D::TEXTURE_2D:
74        m_target = target;
75        m_info.resize(1);
76        m_info[0].resize(maxLevel);
77        break;
78    case GraphicsContext3D::TEXTURE_CUBE_MAP:
79        m_target = target;
80        m_info.resize(6);
81        for (int ii = 0; ii < 6; ++ii)
82            m_info[ii].resize(maxLevel);
83        break;
84    }
85}
86
87void WebGLTexture::setParameteri(GC3Denum pname, GC3Dint param)
88{
89    if (!object() || !m_target)
90        return;
91    switch (pname) {
92    case GraphicsContext3D::TEXTURE_MIN_FILTER:
93        switch (param) {
94        case GraphicsContext3D::NEAREST:
95        case GraphicsContext3D::LINEAR:
96        case GraphicsContext3D::NEAREST_MIPMAP_NEAREST:
97        case GraphicsContext3D::LINEAR_MIPMAP_NEAREST:
98        case GraphicsContext3D::NEAREST_MIPMAP_LINEAR:
99        case GraphicsContext3D::LINEAR_MIPMAP_LINEAR:
100            m_minFilter = param;
101            break;
102        }
103        break;
104    case GraphicsContext3D::TEXTURE_MAG_FILTER:
105        switch (param) {
106        case GraphicsContext3D::NEAREST:
107        case GraphicsContext3D::LINEAR:
108            m_magFilter = param;
109            break;
110        }
111        break;
112    case GraphicsContext3D::TEXTURE_WRAP_S:
113        switch (param) {
114        case GraphicsContext3D::CLAMP_TO_EDGE:
115        case GraphicsContext3D::MIRRORED_REPEAT:
116        case GraphicsContext3D::REPEAT:
117            m_wrapS = param;
118            break;
119        }
120        break;
121    case GraphicsContext3D::TEXTURE_WRAP_T:
122        switch (param) {
123        case GraphicsContext3D::CLAMP_TO_EDGE:
124        case GraphicsContext3D::MIRRORED_REPEAT:
125        case GraphicsContext3D::REPEAT:
126            m_wrapT = param;
127            break;
128        }
129        break;
130    default:
131        return;
132    }
133    update();
134}
135
136void WebGLTexture::setParameterf(GC3Denum pname, GC3Dfloat param)
137{
138    if (!object() || !m_target)
139        return;
140    GC3Dint iparam = static_cast<GC3Dint>(param);
141    setParameteri(pname, iparam);
142}
143
144void WebGLTexture::setLevelInfo(GC3Denum target, GC3Dint level, GC3Denum internalFormat, GC3Dsizei width, GC3Dsizei height, GC3Denum type)
145{
146    if (!object() || !m_target)
147        return;
148    // We assume level, internalFormat, width, height, and type have all been
149    // validated already.
150    int index = mapTargetToIndex(target);
151    if (index < 0)
152        return;
153    m_info[index][level].setInfo(internalFormat, width, height, type);
154    update();
155}
156
157void WebGLTexture::generateMipmapLevelInfo()
158{
159    if (!object() || !m_target)
160        return;
161    if (!canGenerateMipmaps())
162        return;
163    if (!m_isComplete) {
164        for (size_t ii = 0; ii < m_info.size(); ++ii) {
165            const LevelInfo& info0 = m_info[ii][0];
166            GC3Dsizei width = info0.width;
167            GC3Dsizei height = info0.height;
168            GC3Dint levelCount = computeLevelCount(width, height);
169            for (GC3Dint level = 1; level < levelCount; ++level) {
170                width = std::max(1, width >> 1);
171                height = std::max(1, height >> 1);
172                LevelInfo& info = m_info[ii][level];
173                info.setInfo(info0.internalFormat, width, height, info0.type);
174            }
175        }
176        m_isComplete = true;
177    }
178    m_needToUseBlackTexture = false;
179}
180
181GC3Denum WebGLTexture::getInternalFormat(GC3Denum target, GC3Dint level) const
182{
183    const LevelInfo* info = getLevelInfo(target, level);
184    if (!info)
185        return 0;
186    return info->internalFormat;
187}
188
189GC3Denum WebGLTexture::getType(GC3Denum target, GC3Dint level) const
190{
191    const LevelInfo* info = getLevelInfo(target, level);
192    if (!info)
193        return 0;
194    return info->type;
195}
196
197GC3Dsizei WebGLTexture::getWidth(GC3Denum target, GC3Dint level) const
198{
199    const LevelInfo* info = getLevelInfo(target, level);
200    if (!info)
201        return 0;
202    return info->width;
203}
204
205GC3Dsizei WebGLTexture::getHeight(GC3Denum target, GC3Dint level) const
206{
207    const LevelInfo* info = getLevelInfo(target, level);
208    if (!info)
209        return 0;
210    return info->height;
211}
212
213bool WebGLTexture::isValid(GC3Denum target, GC3Dint level) const
214{
215    const LevelInfo* info = getLevelInfo(target, level);
216    if (!info)
217        return 0;
218    return info->valid;
219}
220
221void WebGLTexture::markInvalid(GC3Denum target, GC3Dint level)
222{
223    int index = mapTargetToIndex(target);
224    if (index < 0)
225        return;
226    m_info[index][level].valid = false;
227    update();
228}
229
230bool WebGLTexture::isNPOT(GC3Dsizei width, GC3Dsizei height)
231{
232    ASSERT(width >= 0 && height >= 0);
233    if (!width || !height)
234        return false;
235    if ((width & (width - 1)) || (height & (height - 1)))
236        return true;
237    return false;
238}
239
240bool WebGLTexture::isNPOT() const
241{
242    if (!object())
243        return false;
244    return m_isNPOT;
245}
246
247bool WebGLTexture::needToUseBlackTexture(TextureExtensionFlag extensions) const
248{
249    if (!object())
250        return false;
251    if (m_needToUseBlackTexture)
252        return true;
253    if ((m_isFloatType && !(extensions & TextureExtensionFloatLinearEnabled)) || (m_isHalfFloatType && !(extensions & TextureExtensionHalfFloatLinearEnabled))) {
254        if (m_magFilter != GraphicsContext3D::NEAREST || (m_minFilter != GraphicsContext3D::NEAREST && m_minFilter != GraphicsContext3D::NEAREST_MIPMAP_NEAREST))
255            return true;
256    }
257    return false;
258}
259
260bool WebGLTexture::isCompressed() const
261{
262    if (!object())
263        return false;
264    return m_isCompressed;
265}
266
267void WebGLTexture::setCompressed()
268{
269    ASSERT(object());
270    m_isCompressed = true;
271}
272
273void WebGLTexture::deleteObjectImpl(GraphicsContext3D* context3d, Platform3DObject object)
274{
275    context3d->deleteTexture(object);
276}
277
278int WebGLTexture::mapTargetToIndex(GC3Denum target) const
279{
280    if (m_target == GraphicsContext3D::TEXTURE_2D) {
281        if (target == GraphicsContext3D::TEXTURE_2D)
282            return 0;
283    } else if (m_target == GraphicsContext3D::TEXTURE_CUBE_MAP) {
284        switch (target) {
285        case GraphicsContext3D::TEXTURE_CUBE_MAP_POSITIVE_X:
286            return 0;
287        case GraphicsContext3D::TEXTURE_CUBE_MAP_NEGATIVE_X:
288            return 1;
289        case GraphicsContext3D::TEXTURE_CUBE_MAP_POSITIVE_Y:
290            return 2;
291        case GraphicsContext3D::TEXTURE_CUBE_MAP_NEGATIVE_Y:
292            return 3;
293        case GraphicsContext3D::TEXTURE_CUBE_MAP_POSITIVE_Z:
294            return 4;
295        case GraphicsContext3D::TEXTURE_CUBE_MAP_NEGATIVE_Z:
296            return 5;
297        }
298    }
299    return -1;
300}
301
302bool WebGLTexture::canGenerateMipmaps()
303{
304    if (isNPOT())
305        return false;
306    const LevelInfo& first = m_info[0][0];
307    for (size_t ii = 0; ii < m_info.size(); ++ii) {
308        const LevelInfo& info = m_info[ii][0];
309        if (!info.valid
310            || info.width != first.width || info.height != first.height
311            || info.internalFormat != first.internalFormat || info.type != first.type)
312            return false;
313    }
314    return true;
315}
316
317GC3Dint WebGLTexture::computeLevelCount(GC3Dsizei width, GC3Dsizei height)
318{
319    // return 1 + log2Floor(std::max(width, height));
320    GC3Dsizei n = std::max(width, height);
321    if (n <= 0)
322        return 0;
323    GC3Dint log = 0;
324    GC3Dsizei value = n;
325    for (int ii = 4; ii >= 0; --ii) {
326        int shift = (1 << ii);
327        GC3Dsizei x = (value >> shift);
328        if (x) {
329            value = x;
330            log += shift;
331        }
332    }
333    ASSERT(value == 1);
334    return log + 1;
335}
336
337void WebGLTexture::update()
338{
339    m_isNPOT = false;
340    for (size_t ii = 0; ii < m_info.size(); ++ii) {
341        if (isNPOT(m_info[ii][0].width, m_info[ii][0].height)) {
342            m_isNPOT = true;
343            break;
344        }
345    }
346    m_isComplete = true;
347    const LevelInfo& first = m_info[0][0];
348    GC3Dint levelCount = computeLevelCount(first.width, first.height);
349    if (levelCount < 1)
350        m_isComplete = false;
351    else {
352        for (size_t ii = 0; ii < m_info.size() && m_isComplete; ++ii) {
353            const LevelInfo& info0 = m_info[ii][0];
354            if (!info0.valid
355                || info0.width != first.width || info0.height != first.height
356                || info0.internalFormat != first.internalFormat || info0.type != first.type) {
357                m_isComplete = false;
358                break;
359            }
360            GC3Dsizei width = info0.width;
361            GC3Dsizei height = info0.height;
362            for (GC3Dint level = 1; level < levelCount; ++level) {
363                width = std::max(1, width >> 1);
364                height = std::max(1, height >> 1);
365                const LevelInfo& info = m_info[ii][level];
366                if (!info.valid
367                    || info.width != width || info.height != height
368                    || info.internalFormat != info0.internalFormat || info.type != info0.type) {
369                    m_isComplete = false;
370                    break;
371                }
372
373            }
374        }
375    }
376
377    m_isFloatType = false;
378    if (m_isComplete)
379        m_isFloatType = m_info[0][0].type == GraphicsContext3D::FLOAT;
380    else {
381        for (size_t ii = 0; ii < m_info.size(); ++ii) {
382            if (m_info[ii][0].type == GraphicsContext3D::FLOAT) {
383                m_isFloatType = true;
384                break;
385            }
386        }
387    }
388
389    m_isHalfFloatType = false;
390    if (m_isComplete)
391        m_isHalfFloatType = m_info[0][0].type == GraphicsContext3D::HALF_FLOAT_OES;
392    else {
393        for (size_t ii = 0; ii < m_info.size(); ++ii) {
394            if (m_info[ii][0].type == GraphicsContext3D::HALF_FLOAT_OES) {
395                m_isHalfFloatType = true;
396                break;
397            }
398        }
399    }
400
401    m_needToUseBlackTexture = false;
402    // NPOT
403    if (m_isNPOT && ((m_minFilter != GraphicsContext3D::NEAREST && m_minFilter != GraphicsContext3D::LINEAR)
404                     || m_wrapS != GraphicsContext3D::CLAMP_TO_EDGE || m_wrapT != GraphicsContext3D::CLAMP_TO_EDGE))
405        m_needToUseBlackTexture = true;
406    // Completeness
407    if (!m_isComplete && m_minFilter != GraphicsContext3D::NEAREST && m_minFilter != GraphicsContext3D::LINEAR)
408        m_needToUseBlackTexture = true;
409}
410
411const WebGLTexture::LevelInfo* WebGLTexture::getLevelInfo(GC3Denum target, GC3Dint level) const
412{
413    if (!object() || !m_target)
414        return 0;
415    int targetIndex = mapTargetToIndex(target);
416    if (targetIndex < 0 || targetIndex >= static_cast<int>(m_info.size()))
417        return 0;
418    if (level < 0 || level >= static_cast<GC3Dint>(m_info[targetIndex].size()))
419        return 0;
420    return &(m_info[targetIndex][level]);
421}
422
423}
424
425#endif // ENABLE(WEBGL)
426