/* * Copyright (c) 2009-2014 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.renderer.opengl; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderContext; import com.jme3.renderer.RendererException; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; import com.jme3.texture.image.ColorSpace; import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.logging.Level; import java.util.logging.Logger; /** * Internal utility class used by {@link GLRenderer} to manage textures. * * @author Kirill Vainer */ final class TextureUtil { private static final Logger logger = Logger.getLogger(TextureUtil.class.getName()); private final GL gl; private final GL2 gl2; private final GLExt glext; private GLImageFormat[][] formats; public TextureUtil(GL gl, GL2 gl2, GLExt glext) { this.gl = gl; this.gl2 = gl2; this.glext = glext; } public void initialize(EnumSet<Caps> caps) { this.formats = GLImageFormats.getFormatsForCaps(caps); if (logger.isLoggable(Level.FINE)) { StringBuilder sb = new StringBuilder(); sb.append("Supported texture formats: \n"); for (int i = 0; i < Format.values().length; i++) { Format format = Format.values()[i]; if (formats[0][i] != null) { boolean srgb = formats[1][i] != null; sb.append("\t").append(format.toString()); sb.append(" (Linear"); if (srgb) sb.append("/sRGB"); sb.append(")\n"); } } logger.log(Level.FINE, sb.toString()); } } public GLImageFormat getImageFormat(Format fmt, boolean isSrgb) { if (isSrgb) { return formats[1][fmt.ordinal()]; } else { return formats[0][fmt.ordinal()]; } } public GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) { //if the passed format is one kind of depth there isno point in getting the srgb format; isSrgb = isSrgb && fmt != Format.Depth && fmt != Format.Depth16 && fmt != Format.Depth24 && fmt != Format.Depth24Stencil8 && fmt != Format.Depth32 && fmt != Format.Depth32F; GLImageFormat glFmt = getImageFormat(fmt, isSrgb); if (glFmt == null && isSrgb) { glFmt = getImageFormat(fmt, false); logger.log(Level.WARNING, "No sRGB format available for ''{0}''. Failling back to linear.", fmt); } if (glFmt == null) { throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware."); } return glFmt; } private void setupTextureSwizzle(int target, Format format) { // Needed for OpenGL 3.3 to support luminance / alpha formats switch (format) { case Alpha8: gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_ZERO); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_ZERO); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_ZERO); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_RED); break; case Luminance8: case Luminance16F: case Luminance32F: gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_RED); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_RED); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_ONE); break; case Luminance8Alpha8: case Luminance16FAlpha16F: gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_R, GL.GL_RED); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_G, GL.GL_RED); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_B, GL.GL_RED); gl.glTexParameteri(target, GL3.GL_TEXTURE_SWIZZLE_A, GL.GL_GREEN); break; } } private void uploadTextureLevel(GLImageFormat format, int target, int level, int slice, int sliceCount, int width, int height, int depth, int samples, ByteBuffer data) { if (format.compressed && data != null) { if (target == GL2.GL_TEXTURE_3D) { // For 3D textures, we upload the entire mipmap level. gl2.glCompressedTexImage3D(target, level, format.internalFormat, width, height, depth, 0, data); } else if (target == GLExt.GL_TEXTURE_2D_ARRAY_EXT) { // For texture arrays, only upload 1 slice at a time. // zoffset specifies slice index, and depth is 1 to indicate // a single texture in the array. gl2.glCompressedTexSubImage3D(target, level, 0, 0, slice, width, height, 1, format.internalFormat, data); } else { // Cubemaps also use 2D upload. gl2.glCompressedTexImage2D(target, level, format.internalFormat, width, height, 0, data); } } else { // (Non-compressed OR allocating texture storage for FBO) if (target == GL2.GL_TEXTURE_3D) { gl2.glTexImage3D(target, level, format.internalFormat, width, height, depth, 0, format.format, format.dataType, data); } else if (target == GLExt.GL_TEXTURE_2D_ARRAY_EXT) { if (slice == -1) { // Allocate texture storage (data is NULL) gl2.glTexImage3D(target, level, format.internalFormat, width, height, sliceCount, //# of slices 0, format.format, format.dataType, data); } else { // For texture arrays, only upload 1 slice at a time. // zoffset specifies slice index, and depth is 1 to indicate // a single texture in the array. gl2.glTexSubImage3D(target, level, // level 0, // xoffset 0, // yoffset slice, // zoffset width, // width height, // height 1, // depth format.format, format.dataType, data); } } else { // 2D multisampled image. if (samples > 1) { glext.glTexImage2DMultisample(target, samples, format.internalFormat, width, height, true); } else { // Regular 2D image gl.glTexImage2D(target, level, format.internalFormat, width, height, 0, format.format, format.dataType, data); } } } } public void uploadTexture(Image image, int target, int index, boolean linearizeSrgb) { boolean getSrgbFormat = image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb; Image.Format jmeFormat = image.getFormat(); GLImageFormat oglFormat = getImageFormatWithError(jmeFormat, getSrgbFormat); ByteBuffer data = null; int sliceCount = 1; if (index >= 0) { data = image.getData(index); } if (image.getData() != null && image.getData().size() > 0) { sliceCount = image.getData().size(); } int width = image.getWidth(); int height = image.getHeight(); int depth = image.getDepth(); int[] mipSizes = image.getMipMapSizes(); int pos = 0; // TODO: Remove unneccessary allocation if (mipSizes == null) { if (data != null) { mipSizes = new int[]{data.capacity()}; } else { mipSizes = new int[]{width * height * jmeFormat.getBitsPerPixel() / 8}; } } int samples = image.getMultiSamples(); // For OGL3 core: setup texture swizzle. if (oglFormat.swizzleRequired) { setupTextureSwizzle(target, jmeFormat); } for (int i = 0; i < mipSizes.length; i++) { int mipWidth = Math.max(1, width >> i); int mipHeight = Math.max(1, height >> i); int mipDepth = Math.max(1, depth >> i); if (data != null) { data.position(pos); data.limit(pos + mipSizes[i]); } uploadTextureLevel(oglFormat, target, i, index, sliceCount, mipWidth, mipHeight, mipDepth, samples, data); pos += mipSizes[i]; } } public void uploadSubTexture(Image image, int target, int index, int x, int y, boolean linearizeSrgb) { if (target != GL.GL_TEXTURE_2D || image.getDepth() > 1) { throw new UnsupportedOperationException("Updating non-2D texture is not supported"); } if (image.getMipMapSizes() != null) { throw new UnsupportedOperationException("Updating mip-mappped images is not supported"); } if (image.getMultiSamples() > 1) { throw new UnsupportedOperationException("Updating multisampled images is not supported"); } Image.Format jmeFormat = image.getFormat(); if (jmeFormat.isCompressed()) { throw new UnsupportedOperationException("Updating compressed images is not supported"); } else if (jmeFormat.isDepthFormat()) { throw new UnsupportedOperationException("Updating depth images is not supported"); } boolean getSrgbFormat = image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb; GLImageFormat oglFormat = getImageFormatWithError(jmeFormat, getSrgbFormat); ByteBuffer data = null; if (index >= 0) { data = image.getData(index); } if (data == null) { throw new IndexOutOfBoundsException("The image index " + index + " is not valid for the given image"); } data.position(0); data.limit(data.capacity()); gl.glTexSubImage2D(target, 0, x, y, image.getWidth(), image.getHeight(), oglFormat.format, oglFormat.dataType, data); } }