/* libs/opengles/texture.cpp ** ** Copyright 2006, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #include #include #include "context.h" #include "fp.h" #include "state.h" #include "texture.h" #include "TextureObjectManager.h" #include namespace android { // ---------------------------------------------------------------------------- static void bindTextureTmu( ogles_context_t* c, int tmu, GLuint texture, const sp& tex); static __attribute__((noinline)) void generateMipmap(ogles_context_t* c, GLint level); // ---------------------------------------------------------------------------- #if 0 #pragma mark - #pragma mark Init #endif void ogles_init_texture(ogles_context_t* c) { c->textures.packAlignment = 4; c->textures.unpackAlignment = 4; // each context has a default named (0) texture (not shared) c->textures.defaultTexture = new EGLTextureObject(); c->textures.defaultTexture->incStrong(c); // bind the default texture to each texture unit for (int i=0; itextures.defaultTexture); memset(c->current.texture[i].v, 0, sizeof(vec4_t)); c->current.texture[i].Q = 0x10000; } } void ogles_uninit_texture(ogles_context_t* c) { if (c->textures.ggl) gglUninit(c->textures.ggl); c->textures.defaultTexture->decStrong(c); for (int i=0; itextures.tmu[i].texture) c->textures.tmu[i].texture->decStrong(c); } } static __attribute__((noinline)) void validate_tmu(ogles_context_t* c, int i) { texture_unit_t& u(c->textures.tmu[i]); if (u.dirty) { u.dirty = 0; c->rasterizer.procs.activeTexture(c, i); c->rasterizer.procs.bindTexture(c, &(u.texture->surface)); c->rasterizer.procs.texGeni(c, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_AUTOMATIC); c->rasterizer.procs.texGeni(c, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_AUTOMATIC); c->rasterizer.procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_WRAP_S, u.texture->wraps); c->rasterizer.procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_WRAP_T, u.texture->wrapt); c->rasterizer.procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_MIN_FILTER, u.texture->min_filter); c->rasterizer.procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_MAG_FILTER, u.texture->mag_filter); // disable this texture unit if it's not complete if (!u.texture->isComplete()) { c->rasterizer.procs.disable(c, GGL_TEXTURE_2D); } } } void ogles_validate_texture(ogles_context_t* c) { for (int i=0 ; irasterizer.state.texture[i].enable) validate_tmu(c, i); } c->rasterizer.procs.activeTexture(c, c->textures.active); } static void invalidate_texture(ogles_context_t* c, int tmu, uint8_t flags = 0xFF) { c->textures.tmu[tmu].dirty = flags; } /* * If the active textures are EGLImage, they need to be locked before * they can be used. * * FIXME: code below is far from being optimal * */ void ogles_lock_textures(ogles_context_t* c) { for (int i=0 ; irasterizer.state.texture[i].enable) { texture_unit_t& u(c->textures.tmu[i]); ANativeWindowBuffer* native_buffer = u.texture->buffer; if (native_buffer) { c->rasterizer.procs.activeTexture(c, i); hw_module_t const* pModule; if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &pModule)) continue; gralloc_module_t const* module = reinterpret_cast(pModule); void* vaddr; int err = module->lock(module, native_buffer->handle, GRALLOC_USAGE_SW_READ_OFTEN, 0, 0, native_buffer->width, native_buffer->height, &vaddr); u.texture->setImageBits(vaddr); c->rasterizer.procs.bindTexture(c, &(u.texture->surface)); } } } } void ogles_unlock_textures(ogles_context_t* c) { for (int i=0 ; irasterizer.state.texture[i].enable) { texture_unit_t& u(c->textures.tmu[i]); ANativeWindowBuffer* native_buffer = u.texture->buffer; if (native_buffer) { c->rasterizer.procs.activeTexture(c, i); hw_module_t const* pModule; if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &pModule)) continue; gralloc_module_t const* module = reinterpret_cast(pModule); module->unlock(module, native_buffer->handle); u.texture->setImageBits(NULL); c->rasterizer.procs.bindTexture(c, &(u.texture->surface)); } } } c->rasterizer.procs.activeTexture(c, c->textures.active); } // ---------------------------------------------------------------------------- #if 0 #pragma mark - #pragma mark Format conversion #endif static uint32_t gl2format_table[6][4] = { // BYTE, 565, 4444, 5551 { GGL_PIXEL_FORMAT_A_8, 0, 0, 0 }, // GL_ALPHA { GGL_PIXEL_FORMAT_RGB_888, GGL_PIXEL_FORMAT_RGB_565, 0, 0 }, // GL_RGB { GGL_PIXEL_FORMAT_RGBA_8888, 0, GGL_PIXEL_FORMAT_RGBA_4444, GGL_PIXEL_FORMAT_RGBA_5551 }, // GL_RGBA { GGL_PIXEL_FORMAT_L_8, 0, 0, 0 }, // GL_LUMINANCE { GGL_PIXEL_FORMAT_LA_88, 0, 0, 0 }, // GL_LUMINANCE_ALPHA }; static int32_t convertGLPixelFormat(GLint format, GLenum type) { int32_t fi = -1; int32_t ti = -1; switch (format) { case GL_ALPHA: fi = 0; break; case GL_RGB: fi = 1; break; case GL_RGBA: fi = 2; break; case GL_LUMINANCE: fi = 3; break; case GL_LUMINANCE_ALPHA: fi = 4; break; } switch (type) { case GL_UNSIGNED_BYTE: ti = 0; break; case GL_UNSIGNED_SHORT_5_6_5: ti = 1; break; case GL_UNSIGNED_SHORT_4_4_4_4: ti = 2; break; case GL_UNSIGNED_SHORT_5_5_5_1: ti = 3; break; } if (fi==-1 || ti==-1) return 0; return gl2format_table[fi][ti]; } // ---------------------------------------------------------------------------- static GLenum validFormatType(ogles_context_t* c, GLenum format, GLenum type) { GLenum error = 0; if (formatGL_LUMINANCE_ALPHA) { error = GL_INVALID_ENUM; } if (type != GL_UNSIGNED_BYTE && type != GL_UNSIGNED_SHORT_4_4_4_4 && type != GL_UNSIGNED_SHORT_5_5_5_1 && type != GL_UNSIGNED_SHORT_5_6_5) { error = GL_INVALID_ENUM; } if (type == GL_UNSIGNED_SHORT_5_6_5 && format != GL_RGB) { error = GL_INVALID_OPERATION; } if ((type == GL_UNSIGNED_SHORT_4_4_4_4 || type == GL_UNSIGNED_SHORT_5_5_5_1) && format != GL_RGBA) { error = GL_INVALID_OPERATION; } if (error) { ogles_error(c, error); } return error; } // ---------------------------------------------------------------------------- GGLContext* getRasterizer(ogles_context_t* c) { GGLContext* ggl = c->textures.ggl; if (ggl_unlikely(!ggl)) { // this is quite heavy the first time... gglInit(&ggl); if (!ggl) { return 0; } GGLfixed colors[4] = { 0, 0, 0, 0x10000 }; c->textures.ggl = ggl; ggl->activeTexture(ggl, 0); ggl->enable(ggl, GGL_TEXTURE_2D); ggl->texEnvi(ggl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE); ggl->disable(ggl, GGL_DITHER); ggl->shadeModel(ggl, GGL_FLAT); ggl->color4xv(ggl, colors); } return ggl; } static __attribute__((noinline)) int copyPixels( ogles_context_t* c, const GGLSurface& dst, GLint xoffset, GLint yoffset, const GGLSurface& src, GLint x, GLint y, GLsizei w, GLsizei h) { if ((dst.format == src.format) && (dst.stride == src.stride) && (dst.width == src.width) && (dst.height == src.height) && (dst.stride > 0) && ((x|y) == 0) && ((xoffset|yoffset) == 0)) { // this is a common case... const GGLFormat& pixelFormat(c->rasterizer.formats[src.format]); const size_t size = src.height * src.stride * pixelFormat.size; memcpy(dst.data, src.data, size); return 0; } // use pixel-flinger to handle all the conversions GGLContext* ggl = getRasterizer(c); if (!ggl) { // the only reason this would fail is because we ran out of memory return GL_OUT_OF_MEMORY; } ggl->colorBuffer(ggl, &dst); ggl->bindTexture(ggl, &src); ggl->texCoord2i(ggl, x-xoffset, y-yoffset); ggl->recti(ggl, xoffset, yoffset, xoffset+w, yoffset+h); return 0; } // ---------------------------------------------------------------------------- static __attribute__((noinline)) sp getAndBindActiveTextureObject(ogles_context_t* c) { sp tex; const int active = c->textures.active; const GLuint name = c->textures.tmu[active].name; // free the reference to the previously bound object texture_unit_t& u(c->textures.tmu[active]); if (u.texture) u.texture->decStrong(c); if (name == 0) { // 0 is our local texture object, not shared with anyone. // But it affects all bound TMUs immediately. // (we need to invalidate all units bound to this texture object) tex = c->textures.defaultTexture; for (int i=0 ; itextures.tmu[i].texture == tex.get()) invalidate_texture(c, i); } } else { // get a new texture object for that name tex = c->surfaceManager->replaceTexture(name); } // bind this texture to the current active texture unit // and add a reference to this texture object u.texture = tex.get(); u.texture->incStrong(c); u.name = name; invalidate_texture(c, active); return tex; } void bindTextureTmu( ogles_context_t* c, int tmu, GLuint texture, const sp& tex) { if (tex.get() == c->textures.tmu[tmu].texture) return; // free the reference to the previously bound object texture_unit_t& u(c->textures.tmu[tmu]); if (u.texture) u.texture->decStrong(c); // bind this texture to the current active texture unit // and add a reference to this texture object u.texture = tex.get(); u.texture->incStrong(c); u.name = texture; invalidate_texture(c, tmu); } int createTextureSurface(ogles_context_t* c, GGLSurface** outSurface, int32_t* outSize, GLint level, GLenum format, GLenum type, GLsizei width, GLsizei height, GLenum compressedFormat = 0) { // find out which texture is bound to the current unit const int active = c->textures.active; const GLuint name = c->textures.tmu[active].name; // convert the pixelformat to one we can handle const int32_t formatIdx = convertGLPixelFormat(format, type); if (formatIdx == 0) { // we don't know what to do with this return GL_INVALID_OPERATION; } // figure out the size we need as well as the stride const GGLFormat& pixelFormat(c->rasterizer.formats[formatIdx]); const int32_t align = c->textures.unpackAlignment-1; const int32_t bpr = ((width * pixelFormat.size) + align) & ~align; const size_t size = bpr * height; const int32_t stride = bpr / pixelFormat.size; if (level > 0) { const int active = c->textures.active; EGLTextureObject* tex = c->textures.tmu[active].texture; status_t err = tex->reallocate(level, width, height, stride, formatIdx, compressedFormat, bpr); if (err != NO_ERROR) return GL_OUT_OF_MEMORY; GGLSurface& surface = tex->editMip(level); *outSurface = &surface; *outSize = size; return 0; } sp tex = getAndBindActiveTextureObject(c); status_t err = tex->reallocate(level, width, height, stride, formatIdx, compressedFormat, bpr); if (err != NO_ERROR) return GL_OUT_OF_MEMORY; tex->internalformat = format; *outSurface = &tex->surface; *outSize = size; return 0; } static size_t dataSizePalette4(int numLevels, int width, int height, int format) { int indexBits = 8; int entrySize = 0; switch (format) { case GL_PALETTE4_RGB8_OES: indexBits = 4; /* FALLTHROUGH */ case GL_PALETTE8_RGB8_OES: entrySize = 3; break; case GL_PALETTE4_RGBA8_OES: indexBits = 4; /* FALLTHROUGH */ case GL_PALETTE8_RGBA8_OES: entrySize = 4; break; case GL_PALETTE4_R5_G6_B5_OES: case GL_PALETTE4_RGBA4_OES: case GL_PALETTE4_RGB5_A1_OES: indexBits = 4; /* FALLTHROUGH */ case GL_PALETTE8_R5_G6_B5_OES: case GL_PALETTE8_RGBA4_OES: case GL_PALETTE8_RGB5_A1_OES: entrySize = 2; break; } size_t size = (1 << indexBits) * entrySize; // palette size for (int i=0 ; i< numLevels ; i++) { int w = (width >> i) ? : 1; int h = (height >> i) ? : 1; int levelSize = h * ((w * indexBits) / 8) ? : 1; size += levelSize; } return size; } static void decodePalette4(const GLvoid *data, int level, int width, int height, void *surface, int stride, int format) { int indexBits = 8; int entrySize = 0; switch (format) { case GL_PALETTE4_RGB8_OES: indexBits = 4; /* FALLTHROUGH */ case GL_PALETTE8_RGB8_OES: entrySize = 3; break; case GL_PALETTE4_RGBA8_OES: indexBits = 4; /* FALLTHROUGH */ case GL_PALETTE8_RGBA8_OES: entrySize = 4; break; case GL_PALETTE4_R5_G6_B5_OES: case GL_PALETTE4_RGBA4_OES: case GL_PALETTE4_RGB5_A1_OES: indexBits = 4; /* FALLTHROUGH */ case GL_PALETTE8_R5_G6_B5_OES: case GL_PALETTE8_RGBA4_OES: case GL_PALETTE8_RGB5_A1_OES: entrySize = 2; break; } const int paletteSize = (1 << indexBits) * entrySize; uint8_t const* pixels = (uint8_t *)data + paletteSize; for (int i=0 ; i> i) ? : 1; int h = (height >> i) ? : 1; pixels += h * ((w * indexBits) / 8); } width = (width >> level) ? : 1; height = (height >> level) ? : 1; if (entrySize == 2) { uint8_t const* const palette = (uint8_t*)data; for (int y=0 ; y> 4); *p++ = palette[index + 0]; *p++ = palette[index + 1]; if (x+1 < width) { index = 2 * (v & 0xF); *p++ = palette[index + 0]; *p++ = palette[index + 1]; } } } } } else if (entrySize == 3) { uint8_t const* const palette = (uint8_t*)data; for (int y=0 ; y> 4); *p++ = palette[index + 0]; *p++ = palette[index + 1]; *p++ = palette[index + 2]; if (x+1 < width) { index = 3 * (v & 0xF); *p++ = palette[index + 0]; *p++ = palette[index + 1]; *p++ = palette[index + 2]; } } } } } else if (entrySize == 4) { uint8_t const* const palette = (uint8_t*)data; for (int y=0 ; y> 4); *p++ = palette[index + 0]; *p++ = palette[index + 1]; *p++ = palette[index + 2]; *p++ = palette[index + 3]; if (x+1 < width) { index = 4 * (v & 0xF); *p++ = palette[index + 0]; *p++ = palette[index + 1]; *p++ = palette[index + 2]; *p++ = palette[index + 3]; } } } } } } static __attribute__((noinline)) void set_depth_and_fog(ogles_context_t* c, GGLfixed z) { const uint32_t enables = c->rasterizer.state.enables; // we need to compute Zw int32_t iterators[3]; iterators[1] = iterators[2] = 0; GGLfixed Zw; GGLfixed n = gglFloatToFixed(c->transforms.vpt.zNear); GGLfixed f = gglFloatToFixed(c->transforms.vpt.zFar); if (z<=0) Zw = n; else if (z>=0x10000) Zw = f; else Zw = gglMulAddx(z, (f-n), n); if (enables & GGL_ENABLE_FOG) { // set up fog if needed... iterators[0] = c->fog.fog(c, Zw); c->rasterizer.procs.fogGrad3xv(c, iterators); } if (enables & GGL_ENABLE_DEPTH_TEST) { // set up z-test if needed... int32_t z = (Zw & ~(Zw>>31)); if (z >= 0x10000) z = 0xFFFF; iterators[0] = (z << 16) | z; c->rasterizer.procs.zGrad3xv(c, iterators); } } // ---------------------------------------------------------------------------- #if 0 #pragma mark - #pragma mark Generate mimaps #endif extern status_t buildAPyramid(ogles_context_t* c, EGLTextureObject* tex); void generateMipmap(ogles_context_t* c, GLint level) { if (level == 0) { const int active = c->textures.active; EGLTextureObject* tex = c->textures.tmu[active].texture; if (tex->generate_mipmap) { if (buildAPyramid(c, tex) != NO_ERROR) { ogles_error(c, GL_OUT_OF_MEMORY); return; } } } } static void texParameterx( GLenum target, GLenum pname, GLfixed param, ogles_context_t* c) { if (target != GL_TEXTURE_2D && target != GL_TEXTURE_EXTERNAL_OES) { ogles_error(c, GL_INVALID_ENUM); return; } EGLTextureObject* textureObject = c->textures.tmu[c->textures.active].texture; switch (pname) { case GL_TEXTURE_WRAP_S: if ((param == GL_REPEAT) || (param == GL_CLAMP_TO_EDGE)) { textureObject->wraps = param; } else { goto invalid_enum; } break; case GL_TEXTURE_WRAP_T: if ((param == GL_REPEAT) || (param == GL_CLAMP_TO_EDGE)) { textureObject->wrapt = param; } else { goto invalid_enum; } break; case GL_TEXTURE_MIN_FILTER: if ((param == GL_NEAREST) || (param == GL_LINEAR) || (param == GL_NEAREST_MIPMAP_NEAREST) || (param == GL_LINEAR_MIPMAP_NEAREST) || (param == GL_NEAREST_MIPMAP_LINEAR) || (param == GL_LINEAR_MIPMAP_LINEAR)) { textureObject->min_filter = param; } else { goto invalid_enum; } break; case GL_TEXTURE_MAG_FILTER: if ((param == GL_NEAREST) || (param == GL_LINEAR)) { textureObject->mag_filter = param; } else { goto invalid_enum; } break; case GL_GENERATE_MIPMAP: textureObject->generate_mipmap = param; break; default: invalid_enum: ogles_error(c, GL_INVALID_ENUM); return; } invalidate_texture(c, c->textures.active); } static void drawTexxOESImp(GLfixed x, GLfixed y, GLfixed z, GLfixed w, GLfixed h, ogles_context_t* c) { ogles_lock_textures(c); const GGLSurface& cbSurface = c->rasterizer.state.buffers.color.s; y = gglIntToFixed(cbSurface.height) - (y + h); w >>= FIXED_BITS; h >>= FIXED_BITS; // set up all texture units for (int i=0 ; irasterizer.state.texture[i].enable) continue; int32_t texcoords[8]; texture_unit_t& u(c->textures.tmu[i]); // validate this tmu (bind, wrap, filter) validate_tmu(c, i); // we CLAMP here, which works with premultiplied (s,t) c->rasterizer.procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_WRAP_S, GGL_CLAMP); c->rasterizer.procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_WRAP_T, GGL_CLAMP); u.dirty = 0xFF; // XXX: should be more subtle EGLTextureObject* textureObject = u.texture; const GLint Ucr = textureObject->crop_rect[0] << 16; const GLint Vcr = textureObject->crop_rect[1] << 16; const GLint Wcr = textureObject->crop_rect[2] << 16; const GLint Hcr = textureObject->crop_rect[3] << 16; // computes texture coordinates (pre-multiplied) int32_t dsdx = Wcr / w; // dsdx = ((Wcr/w)/Wt)*Wt int32_t dtdy =-Hcr / h; // dtdy = -((Hcr/h)/Ht)*Ht int32_t s0 = Ucr - gglMulx(dsdx, x); // s0 = Ucr - x * dsdx int32_t t0 = (Vcr+Hcr) - gglMulx(dtdy, y); // t0 = (Vcr+Hcr) - y*dtdy texcoords[0] = s0; texcoords[1] = dsdx; texcoords[2] = 0; texcoords[3] = t0; texcoords[4] = 0; texcoords[5] = dtdy; texcoords[6] = 0; texcoords[7] = 0; c->rasterizer.procs.texCoordGradScale8xv(c, i, texcoords); } const uint32_t enables = c->rasterizer.state.enables; if (ggl_unlikely(enables & (GGL_ENABLE_DEPTH_TEST|GGL_ENABLE_FOG))) set_depth_and_fog(c, z); c->rasterizer.procs.activeTexture(c, c->textures.active); c->rasterizer.procs.color4xv(c, c->currentColorClamped.v); c->rasterizer.procs.disable(c, GGL_W_LERP); c->rasterizer.procs.disable(c, GGL_AA); c->rasterizer.procs.shadeModel(c, GL_FLAT); c->rasterizer.procs.recti(c, gglFixedToIntRound(x), gglFixedToIntRound(y), gglFixedToIntRound(x)+w, gglFixedToIntRound(y)+h); ogles_unlock_textures(c); } static void drawTexxOES(GLfixed x, GLfixed y, GLfixed z, GLfixed w, GLfixed h, ogles_context_t* c) { // quickly reject empty rects if ((w|h) <= 0) return; drawTexxOESImp(x, y, z, w, h, c); } static void drawTexiOES(GLint x, GLint y, GLint z, GLint w, GLint h, ogles_context_t* c) { // All coordinates are integer, so if we have only one // texture unit active and no scaling is required // THEN, we can use our special 1:1 mapping // which is a lot faster. if (ggl_likely(c->rasterizer.state.enabled_tmu == 1)) { const int tmu = 0; texture_unit_t& u(c->textures.tmu[tmu]); EGLTextureObject* textureObject = u.texture; const GLint Wcr = textureObject->crop_rect[2]; const GLint Hcr = textureObject->crop_rect[3]; if ((w == Wcr) && (h == -Hcr)) { if ((w|h) <= 0) return; // quickly reject empty rects if (u.dirty) { c->rasterizer.procs.activeTexture(c, tmu); c->rasterizer.procs.bindTexture(c, &(u.texture->surface)); c->rasterizer.procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_MIN_FILTER, u.texture->min_filter); c->rasterizer.procs.texParameteri(c, GGL_TEXTURE_2D, GGL_TEXTURE_MAG_FILTER, u.texture->mag_filter); } c->rasterizer.procs.texGeni(c, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); c->rasterizer.procs.texGeni(c, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE); u.dirty = 0xFF; // XXX: should be more subtle c->rasterizer.procs.activeTexture(c, c->textures.active); const GGLSurface& cbSurface = c->rasterizer.state.buffers.color.s; y = cbSurface.height - (y + h); const GLint Ucr = textureObject->crop_rect[0]; const GLint Vcr = textureObject->crop_rect[1]; const GLint s0 = Ucr - x; const GLint t0 = (Vcr + Hcr) - y; const GLuint tw = textureObject->surface.width; const GLuint th = textureObject->surface.height; if ((uint32_t(s0+x+w) > tw) || (uint32_t(t0+y+h) > th)) { // The GL spec is unclear about what should happen // in this case, so we just use the slow case, which // at least won't crash goto slow_case; } ogles_lock_textures(c); c->rasterizer.procs.texCoord2i(c, s0, t0); const uint32_t enables = c->rasterizer.state.enables; if (ggl_unlikely(enables & (GGL_ENABLE_DEPTH_TEST|GGL_ENABLE_FOG))) set_depth_and_fog(c, gglIntToFixed(z)); c->rasterizer.procs.color4xv(c, c->currentColorClamped.v); c->rasterizer.procs.disable(c, GGL_W_LERP); c->rasterizer.procs.disable(c, GGL_AA); c->rasterizer.procs.shadeModel(c, GL_FLAT); c->rasterizer.procs.recti(c, x, y, x+w, y+h); ogles_unlock_textures(c); return; } } slow_case: drawTexxOESImp( gglIntToFixed(x), gglIntToFixed(y), gglIntToFixed(z), gglIntToFixed(w), gglIntToFixed(h), c); } }; // namespace android // ---------------------------------------------------------------------------- using namespace android; #if 0 #pragma mark - #pragma mark Texture API #endif void glActiveTexture(GLenum texture) { ogles_context_t* c = ogles_context_t::get(); if (uint32_t(texture-GL_TEXTURE0) > uint32_t(GGL_TEXTURE_UNIT_COUNT)) { ogles_error(c, GL_INVALID_ENUM); return; } c->textures.active = texture - GL_TEXTURE0; c->rasterizer.procs.activeTexture(c, c->textures.active); } void glBindTexture(GLenum target, GLuint texture) { ogles_context_t* c = ogles_context_t::get(); if (target != GL_TEXTURE_2D && target != GL_TEXTURE_EXTERNAL_OES) { ogles_error(c, GL_INVALID_ENUM); return; } // Bind or create a texture sp tex; if (texture == 0) { // 0 is our local texture object tex = c->textures.defaultTexture; } else { tex = c->surfaceManager->texture(texture); if (ggl_unlikely(tex == 0)) { tex = c->surfaceManager->createTexture(texture); if (tex == 0) { ogles_error(c, GL_OUT_OF_MEMORY); return; } } } bindTextureTmu(c, c->textures.active, texture, tex); } void glGenTextures(GLsizei n, GLuint *textures) { ogles_context_t* c = ogles_context_t::get(); if (n<0) { ogles_error(c, GL_INVALID_VALUE); return; } // generate unique (shared) texture names c->surfaceManager->getToken(n, textures); } void glDeleteTextures(GLsizei n, const GLuint *textures) { ogles_context_t* c = ogles_context_t::get(); if (n<0) { ogles_error(c, GL_INVALID_VALUE); return; } // If deleting a bound texture, bind this unit to 0 for (int t=0 ; ttextures.tmu[t].name == 0) continue; for (int i=0 ; itextures.tmu[t].name)) { // bind this tmu to texture 0 sp tex(c->textures.defaultTexture); bindTextureTmu(c, t, 0, tex); } } } c->surfaceManager->deleteTextures(n, textures); c->surfaceManager->recycleTokens(n, textures); } void glMultiTexCoord4f( GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q) { ogles_context_t* c = ogles_context_t::get(); if (uint32_t(target-GL_TEXTURE0) > uint32_t(GGL_TEXTURE_UNIT_COUNT)) { ogles_error(c, GL_INVALID_ENUM); return; } const int tmu = target-GL_TEXTURE0; c->current.texture[tmu].S = gglFloatToFixed(s); c->current.texture[tmu].T = gglFloatToFixed(t); c->current.texture[tmu].R = gglFloatToFixed(r); c->current.texture[tmu].Q = gglFloatToFixed(q); } void glMultiTexCoord4x( GLenum target, GLfixed s, GLfixed t, GLfixed r, GLfixed q) { ogles_context_t* c = ogles_context_t::get(); if (uint32_t(target-GL_TEXTURE0) > uint32_t(GGL_TEXTURE_UNIT_COUNT)) { ogles_error(c, GL_INVALID_ENUM); return; } const int tmu = target-GL_TEXTURE0; c->current.texture[tmu].S = s; c->current.texture[tmu].T = t; c->current.texture[tmu].R = r; c->current.texture[tmu].Q = q; } void glPixelStorei(GLenum pname, GLint param) { ogles_context_t* c = ogles_context_t::get(); if ((pname != GL_PACK_ALIGNMENT) && (pname != GL_UNPACK_ALIGNMENT)) { ogles_error(c, GL_INVALID_ENUM); return; } if ((param<=0 || param>8) || (param & (param-1))) { ogles_error(c, GL_INVALID_VALUE); return; } if (pname == GL_PACK_ALIGNMENT) c->textures.packAlignment = param; if (pname == GL_UNPACK_ALIGNMENT) c->textures.unpackAlignment = param; } void glTexEnvf(GLenum target, GLenum pname, GLfloat param) { ogles_context_t* c = ogles_context_t::get(); c->rasterizer.procs.texEnvi(c, target, pname, GLint(param)); } void glTexEnvfv( GLenum target, GLenum pname, const GLfloat *params) { ogles_context_t* c = ogles_context_t::get(); if (pname == GL_TEXTURE_ENV_MODE) { c->rasterizer.procs.texEnvi(c, target, pname, GLint(*params)); return; } if (pname == GL_TEXTURE_ENV_COLOR) { GGLfixed fixed[4]; for (int i=0 ; i<4 ; i++) fixed[i] = gglFloatToFixed(params[i]); c->rasterizer.procs.texEnvxv(c, target, pname, fixed); return; } ogles_error(c, GL_INVALID_ENUM); } void glTexEnvx(GLenum target, GLenum pname, GLfixed param) { ogles_context_t* c = ogles_context_t::get(); c->rasterizer.procs.texEnvi(c, target, pname, param); } void glTexEnvxv( GLenum target, GLenum pname, const GLfixed *params) { ogles_context_t* c = ogles_context_t::get(); c->rasterizer.procs.texEnvxv(c, target, pname, params); } void glTexParameteriv( GLenum target, GLenum pname, const GLint* params) { ogles_context_t* c = ogles_context_t::get(); if (target != GL_TEXTURE_2D && target != GL_TEXTURE_EXTERNAL_OES) { ogles_error(c, GL_INVALID_ENUM); return; } EGLTextureObject* textureObject = c->textures.tmu[c->textures.active].texture; switch (pname) { case GL_TEXTURE_CROP_RECT_OES: memcpy(textureObject->crop_rect, params, 4*sizeof(GLint)); break; default: texParameterx(target, pname, GLfixed(params[0]), c); return; } } void glTexParameterf( GLenum target, GLenum pname, GLfloat param) { ogles_context_t* c = ogles_context_t::get(); texParameterx(target, pname, GLfixed(param), c); } void glTexParameterx( GLenum target, GLenum pname, GLfixed param) { ogles_context_t* c = ogles_context_t::get(); texParameterx(target, pname, param, c); } void glTexParameteri( GLenum target, GLenum pname, GLint param) { ogles_context_t* c = ogles_context_t::get(); texParameterx(target, pname, GLfixed(param), c); } // ---------------------------------------------------------------------------- #if 0 #pragma mark - #endif void glCompressedTexImage2D( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data) { ogles_context_t* c = ogles_context_t::get(); if (target != GL_TEXTURE_2D) { ogles_error(c, GL_INVALID_ENUM); return; } if (width<0 || height<0 || border!=0) { ogles_error(c, GL_INVALID_VALUE); return; } // "uncompress" the texture since pixelflinger doesn't support // any compressed texture format natively. GLenum format; GLenum type; switch (internalformat) { case GL_PALETTE8_RGB8_OES: case GL_PALETTE4_RGB8_OES: format = GL_RGB; type = GL_UNSIGNED_BYTE; break; case GL_PALETTE8_RGBA8_OES: case GL_PALETTE4_RGBA8_OES: format = GL_RGBA; type = GL_UNSIGNED_BYTE; break; case GL_PALETTE8_R5_G6_B5_OES: case GL_PALETTE4_R5_G6_B5_OES: format = GL_RGB; type = GL_UNSIGNED_SHORT_5_6_5; break; case GL_PALETTE8_RGBA4_OES: case GL_PALETTE4_RGBA4_OES: format = GL_RGBA; type = GL_UNSIGNED_SHORT_4_4_4_4; break; case GL_PALETTE8_RGB5_A1_OES: case GL_PALETTE4_RGB5_A1_OES: format = GL_RGBA; type = GL_UNSIGNED_SHORT_5_5_5_1; break; #ifdef GL_OES_compressed_ETC1_RGB8_texture case GL_ETC1_RGB8_OES: format = GL_RGB; type = GL_UNSIGNED_BYTE; break; #endif default: ogles_error(c, GL_INVALID_ENUM); return; } if (!data || !width || !height) { // unclear if this is an error or not... return; } int32_t size; GGLSurface* surface; #ifdef GL_OES_compressed_ETC1_RGB8_texture if (internalformat == GL_ETC1_RGB8_OES) { GLsizei compressedSize = etc1_get_encoded_data_size(width, height); if (compressedSize > imageSize) { ogles_error(c, GL_INVALID_VALUE); return; } int error = createTextureSurface(c, &surface, &size, level, format, type, width, height); if (error) { ogles_error(c, error); return; } if (etc1_decode_image( (const etc1_byte*)data, (etc1_byte*)surface->data, width, height, 3, surface->stride*3) != 0) { ogles_error(c, GL_INVALID_OPERATION); } return; } #endif // all mipmap levels are specified at once. const int numLevels = level<0 ? -level : 1; if (dataSizePalette4(numLevels, width, height, format) > imageSize) { ogles_error(c, GL_INVALID_VALUE); return; } for (int i=0 ; i> i) ? : 1; int lod_h = (height >> i) ? : 1; int error = createTextureSurface(c, &surface, &size, i, format, type, lod_w, lod_h); if (error) { ogles_error(c, error); return; } decodePalette4(data, i, width, height, surface->data, surface->stride, internalformat); } } void glTexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) { ogles_context_t* c = ogles_context_t::get(); if (target != GL_TEXTURE_2D) { ogles_error(c, GL_INVALID_ENUM); return; } if (width<0 || height<0 || border!=0 || level < 0) { ogles_error(c, GL_INVALID_VALUE); return; } if (format != (GLenum)internalformat) { ogles_error(c, GL_INVALID_OPERATION); return; } if (validFormatType(c, format, type)) { return; } int32_t size = 0; GGLSurface* surface = 0; int error = createTextureSurface(c, &surface, &size, level, format, type, width, height); if (error) { ogles_error(c, error); return; } if (pixels) { const int32_t formatIdx = convertGLPixelFormat(format, type); const GGLFormat& pixelFormat(c->rasterizer.formats[formatIdx]); const int32_t align = c->textures.unpackAlignment-1; const int32_t bpr = ((width * pixelFormat.size) + align) & ~align; const size_t size = bpr * height; const int32_t stride = bpr / pixelFormat.size; GGLSurface userSurface; userSurface.version = sizeof(userSurface); userSurface.width = width; userSurface.height = height; userSurface.stride = stride; userSurface.format = formatIdx; userSurface.compressedFormat = 0; userSurface.data = (GLubyte*)pixels; int err = copyPixels(c, *surface, 0, 0, userSurface, 0, 0, width, height); if (err) { ogles_error(c, err); return; } generateMipmap(c, level); } } // ---------------------------------------------------------------------------- void glCompressedTexSubImage2D( GLenum /*target*/, GLint /*level*/, GLint /*xoffset*/, GLint /*yoffset*/, GLsizei /*width*/, GLsizei /*height*/, GLenum /*format*/, GLsizei /*imageSize*/, const GLvoid* /*data*/) { ogles_context_t* c = ogles_context_t::get(); ogles_error(c, GL_INVALID_ENUM); } void glTexSubImage2D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) { ogles_context_t* c = ogles_context_t::get(); if (target != GL_TEXTURE_2D) { ogles_error(c, GL_INVALID_ENUM); return; } if (xoffset<0 || yoffset<0 || width<0 || height<0 || level<0) { ogles_error(c, GL_INVALID_VALUE); return; } if (validFormatType(c, format, type)) { return; } // find out which texture is bound to the current unit const int active = c->textures.active; EGLTextureObject* tex = c->textures.tmu[active].texture; const GGLSurface& surface(tex->mip(level)); if (!tex->internalformat || tex->direct) { ogles_error(c, GL_INVALID_OPERATION); return; } if (format != tex->internalformat) { ogles_error(c, GL_INVALID_OPERATION); return; } if ((xoffset + width > GLsizei(surface.width)) || (yoffset + height > GLsizei(surface.height))) { ogles_error(c, GL_INVALID_VALUE); return; } if (!width || !height) { return; // okay, but no-op. } // figure out the size we need as well as the stride const int32_t formatIdx = convertGLPixelFormat(format, type); if (formatIdx == 0) { // we don't know what to do with this ogles_error(c, GL_INVALID_OPERATION); return; } const GGLFormat& pixelFormat(c->rasterizer.formats[formatIdx]); const int32_t align = c->textures.unpackAlignment-1; const int32_t bpr = ((width * pixelFormat.size) + align) & ~align; const size_t size = bpr * height; const int32_t stride = bpr / pixelFormat.size; GGLSurface userSurface; userSurface.version = sizeof(userSurface); userSurface.width = width; userSurface.height = height; userSurface.stride = stride; userSurface.format = formatIdx; userSurface.compressedFormat = 0; userSurface.data = (GLubyte*)pixels; int err = copyPixels(c, surface, xoffset, yoffset, userSurface, 0, 0, width, height); if (err) { ogles_error(c, err); return; } generateMipmap(c, level); // since we only changed the content of the texture, we don't need // to call bindTexture on the main rasterizer. } // ---------------------------------------------------------------------------- void glCopyTexImage2D( GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { ogles_context_t* c = ogles_context_t::get(); if (target != GL_TEXTURE_2D) { ogles_error(c, GL_INVALID_ENUM); return; } if (internalformatGL_LUMINANCE_ALPHA) { ogles_error(c, GL_INVALID_ENUM); return; } if (width<0 || height<0 || border!=0 || level<0) { ogles_error(c, GL_INVALID_VALUE); return; } GLenum format = 0; GLenum type = GL_UNSIGNED_BYTE; const GGLSurface& cbSurface = c->rasterizer.state.buffers.color.s; const int cbFormatIdx = cbSurface.format; switch (cbFormatIdx) { case GGL_PIXEL_FORMAT_RGB_565: type = GL_UNSIGNED_SHORT_5_6_5; break; case GGL_PIXEL_FORMAT_RGBA_5551: type = GL_UNSIGNED_SHORT_5_5_5_1; break; case GGL_PIXEL_FORMAT_RGBA_4444: type = GL_UNSIGNED_SHORT_4_4_4_4; break; } switch (internalformat) { case GL_ALPHA: case GL_LUMINANCE_ALPHA: case GL_LUMINANCE: type = GL_UNSIGNED_BYTE; break; } // figure out the format to use for the new texture switch (cbFormatIdx) { case GGL_PIXEL_FORMAT_RGBA_8888: case GGL_PIXEL_FORMAT_A_8: case GGL_PIXEL_FORMAT_RGBA_5551: case GGL_PIXEL_FORMAT_RGBA_4444: format = internalformat; break; case GGL_PIXEL_FORMAT_RGBX_8888: case GGL_PIXEL_FORMAT_RGB_888: case GGL_PIXEL_FORMAT_RGB_565: case GGL_PIXEL_FORMAT_L_8: switch (internalformat) { case GL_LUMINANCE: case GL_RGB: format = internalformat; break; } break; } if (format == 0) { // invalid combination ogles_error(c, GL_INVALID_ENUM); return; } // create the new texture... int32_t size; GGLSurface* surface; int error = createTextureSurface(c, &surface, &size, level, format, type, width, height); if (error) { ogles_error(c, error); return; } // The bottom row is stored first in textures GGLSurface txSurface(*surface); txSurface.stride = -txSurface.stride; // (x,y) is the lower-left corner of colorBuffer y = cbSurface.height - (y + height); /* The GLES spec says: * If any of the pixels within the specified rectangle are outside * the framebuffer associated with the current rendering context, * then the values obtained for those pixels are undefined. */ if (x+width > GLint(cbSurface.width)) width = cbSurface.width - x; if (y+height > GLint(cbSurface.height)) height = cbSurface.height - y; int err = copyPixels(c, txSurface, 0, 0, cbSurface, x, y, width, height); if (err) { ogles_error(c, err); } generateMipmap(c, level); } void glCopyTexSubImage2D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { ogles_context_t* c = ogles_context_t::get(); if (target != GL_TEXTURE_2D) { ogles_error(c, GL_INVALID_ENUM); return; } if (xoffset<0 || yoffset<0 || width<0 || height<0 || level<0) { ogles_error(c, GL_INVALID_VALUE); return; } if (!width || !height) { return; // okay, but no-op. } // find out which texture is bound to the current unit const int active = c->textures.active; EGLTextureObject* tex = c->textures.tmu[active].texture; const GGLSurface& surface(tex->mip(level)); if (!tex->internalformat) { ogles_error(c, GL_INVALID_OPERATION); return; } if ((xoffset + width > GLsizei(surface.width)) || (yoffset + height > GLsizei(surface.height))) { ogles_error(c, GL_INVALID_VALUE); return; } // The bottom row is stored first in textures GGLSurface txSurface(surface); txSurface.stride = -txSurface.stride; // (x,y) is the lower-left corner of colorBuffer const GGLSurface& cbSurface = c->rasterizer.state.buffers.color.s; y = cbSurface.height - (y + height); /* The GLES spec says: * If any of the pixels within the specified rectangle are outside * the framebuffer associated with the current rendering context, * then the values obtained for those pixels are undefined. */ if (x+width > GLint(cbSurface.width)) width = cbSurface.width - x; if (y+height > GLint(cbSurface.height)) height = cbSurface.height - y; int err = copyPixels(c, txSurface, xoffset, yoffset, cbSurface, x, y, width, height); if (err) { ogles_error(c, err); return; } generateMipmap(c, level); } void glReadPixels( GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) { ogles_context_t* c = ogles_context_t::get(); if ((format != GL_RGBA) && (format != GL_RGB)) { ogles_error(c, GL_INVALID_ENUM); return; } if ((type != GL_UNSIGNED_BYTE) && (type != GL_UNSIGNED_SHORT_5_6_5)) { ogles_error(c, GL_INVALID_ENUM); return; } if (width<0 || height<0) { ogles_error(c, GL_INVALID_VALUE); return; } if (x<0 || y<0) { ogles_error(c, GL_INVALID_VALUE); return; } int32_t formatIdx = GGL_PIXEL_FORMAT_NONE; if ((format == GL_RGBA) && (type == GL_UNSIGNED_BYTE)) { formatIdx = GGL_PIXEL_FORMAT_RGBA_8888; } else if ((format == GL_RGB) && (type == GL_UNSIGNED_SHORT_5_6_5)) { formatIdx = GGL_PIXEL_FORMAT_RGB_565; } else { ogles_error(c, GL_INVALID_OPERATION); return; } const GGLSurface& readSurface = c->rasterizer.state.buffers.read.s; if ((x+width > GLint(readSurface.width)) || (y+height > GLint(readSurface.height))) { ogles_error(c, GL_INVALID_VALUE); return; } const GGLFormat& pixelFormat(c->rasterizer.formats[formatIdx]); const int32_t align = c->textures.packAlignment-1; const int32_t bpr = ((width * pixelFormat.size) + align) & ~align; const int32_t stride = bpr / pixelFormat.size; GGLSurface userSurface; userSurface.version = sizeof(userSurface); userSurface.width = width; userSurface.height = height; userSurface.stride = -stride; // bottom row is transfered first userSurface.format = formatIdx; userSurface.compressedFormat = 0; userSurface.data = (GLubyte*)pixels; // use pixel-flinger to handle all the conversions GGLContext* ggl = getRasterizer(c); if (!ggl) { // the only reason this would fail is because we ran out of memory ogles_error(c, GL_OUT_OF_MEMORY); return; } ggl->colorBuffer(ggl, &userSurface); // destination is user buffer ggl->bindTexture(ggl, &readSurface); // source is read-buffer ggl->texCoord2i(ggl, x, readSurface.height - (y + height)); ggl->recti(ggl, 0, 0, width, height); } // ---------------------------------------------------------------------------- #if 0 #pragma mark - #pragma mark DrawTexture Extension #endif void glDrawTexsvOES(const GLshort* coords) { ogles_context_t* c = ogles_context_t::get(); drawTexiOES(coords[0], coords[1], coords[2], coords[3], coords[4], c); } void glDrawTexivOES(const GLint* coords) { ogles_context_t* c = ogles_context_t::get(); drawTexiOES(coords[0], coords[1], coords[2], coords[3], coords[4], c); } void glDrawTexsOES(GLshort x , GLshort y, GLshort z, GLshort w, GLshort h) { ogles_context_t* c = ogles_context_t::get(); drawTexiOES(x, y, z, w, h, c); } void glDrawTexiOES(GLint x, GLint y, GLint z, GLint w, GLint h) { ogles_context_t* c = ogles_context_t::get(); drawTexiOES(x, y, z, w, h, c); } void glDrawTexfvOES(const GLfloat* coords) { ogles_context_t* c = ogles_context_t::get(); drawTexxOES( gglFloatToFixed(coords[0]), gglFloatToFixed(coords[1]), gglFloatToFixed(coords[2]), gglFloatToFixed(coords[3]), gglFloatToFixed(coords[4]), c); } void glDrawTexxvOES(const GLfixed* coords) { ogles_context_t* c = ogles_context_t::get(); drawTexxOES(coords[0], coords[1], coords[2], coords[3], coords[4], c); } void glDrawTexfOES(GLfloat x, GLfloat y, GLfloat z, GLfloat w, GLfloat h){ ogles_context_t* c = ogles_context_t::get(); drawTexxOES( gglFloatToFixed(x), gglFloatToFixed(y), gglFloatToFixed(z), gglFloatToFixed(w), gglFloatToFixed(h), c); } void glDrawTexxOES(GLfixed x, GLfixed y, GLfixed z, GLfixed w, GLfixed h) { ogles_context_t* c = ogles_context_t::get(); drawTexxOES(x, y, z, w, h, c); } // ---------------------------------------------------------------------------- #if 0 #pragma mark - #pragma mark EGL Image Extension #endif void glEGLImageTargetTexture2DOES(GLenum target, GLeglImageOES image) { ogles_context_t* c = ogles_context_t::get(); if (target != GL_TEXTURE_2D && target != GL_TEXTURE_EXTERNAL_OES) { ogles_error(c, GL_INVALID_ENUM); return; } if (image == EGL_NO_IMAGE_KHR) { ogles_error(c, GL_INVALID_VALUE); return; } ANativeWindowBuffer* native_buffer = (ANativeWindowBuffer*)image; if (native_buffer->common.magic != ANDROID_NATIVE_BUFFER_MAGIC) { ogles_error(c, GL_INVALID_VALUE); return; } if (native_buffer->common.version != sizeof(ANativeWindowBuffer)) { ogles_error(c, GL_INVALID_VALUE); return; } // bind it to the texture unit sp tex = getAndBindActiveTextureObject(c); tex->setImage(native_buffer); } void glEGLImageTargetRenderbufferStorageOES(GLenum target, GLeglImageOES image) { ogles_context_t* c = ogles_context_t::get(); if (target != GL_RENDERBUFFER_OES) { ogles_error(c, GL_INVALID_ENUM); return; } if (image == EGL_NO_IMAGE_KHR) { ogles_error(c, GL_INVALID_VALUE); return; } ANativeWindowBuffer* native_buffer = (ANativeWindowBuffer*)image; if (native_buffer->common.magic != ANDROID_NATIVE_BUFFER_MAGIC) { ogles_error(c, GL_INVALID_VALUE); return; } if (native_buffer->common.version != sizeof(ANativeWindowBuffer)) { ogles_error(c, GL_INVALID_VALUE); return; } // well, we're not supporting this extension anyways }