@@ -2810,62 +2810,79 @@ private void updateTexImageData(Image img, Texture.Type type, int unit, boolean
28102810 bindTextureAndUnit (target , img , unit );
28112811
28122812 int imageSamples = img .getMultiSamples ();
2813- boolean needsMipmaps = !img .hasMipmaps () && img .isGeneratedMipmapsRequired ();
2813+ boolean sourceMipmapsUsable = img .hasMipmaps () && !scaleToPot ;
2814+ boolean needsMipmaps = !sourceMipmapsUsable && img .isGeneratedMipmapsRequired ();
28142815 boolean hwMipmapSupported = needsMipmaps && isMipmapGenerationSupported (img .getFormat (),
28152816 linearizeSrgbImages ? img .getColorSpace () : ColorSpace .Linear );
2816- Image cpuMipmapImage = null ;
2817+ Image imageForUpload = img ;
2818+ boolean cpuMipmapsGenerated = false ;
28172819 if (imageSamples <= 1 ) {
28182820 boolean cpuMipmapFallbackFailed = false ;
2819- if (needsMipmaps && !hwMipmapSupported
2820- && allowCpuMipmapFallback
2821- && textureWasUnuploaded
2822- && !scaleToPot
2823- && MipMapGenerator .canGenerateMipmaps (img )) {
2824- try {
2825- cpuMipmapImage = createCpuMipmapImage (img );
2826- MipMapGenerator .generateMipMaps (cpuMipmapImage , linearizeSrgbImages ,
2827- img .getColorSpace () == ColorSpace .sRGB );
2828- img .setMipmapsGenerated (true );
2829- } catch (RuntimeException exception ) {
2830- cpuMipmapImage = null ;
2831- cpuMipmapFallbackFailed = true ;
2832- logger .log (Level .WARNING ,
2833- "Texture " + img + " requires mipmaps, but hardware mipmap generation is not supported"
2834- + " and CPU mipmap generation failed. Mipmaps will not be generated." ,
2835- exception );
2821+ if (needsMipmaps ) {
2822+ /*
2823+ * Some formats cannot use glGenerateMipmap because they are not both
2824+ * renderable and filterable. On a first upload, fall back to a CPU-built
2825+ * mip chain when the image data is suitable. If NPOT scaling is also
2826+ * required, build the CPU mips from the resized upload image.
2827+ */
2828+ boolean needsCpuMipmapFallback = !hwMipmapSupported
2829+ && allowCpuMipmapFallback
2830+ && textureWasUnuploaded
2831+ && MipMapGenerator .canGenerateMipmaps (img );
2832+ if (needsCpuMipmapFallback ) {
2833+ try {
2834+ Image cpuMipmapUploadImage = cloneImageForUpload (img , scaleToPot );
2835+ if (cpuMipmapUploadImage != null ) {
2836+ MipMapGenerator .generateMipMaps (cpuMipmapUploadImage , linearizeSrgbImages ,
2837+ img .getColorSpace () == ColorSpace .sRGB );
2838+ imageForUpload = cpuMipmapUploadImage ;
2839+ cpuMipmapsGenerated = true ;
2840+ scaleToPot = false ;
2841+ img .setMipmapsGenerated (true );
2842+ }
2843+ } catch (RuntimeException exception ) {
2844+ cpuMipmapFallbackFailed = true ;
2845+ logger .log (Level .WARNING ,
2846+ "Texture " + img + " requires mipmaps, but hardware mipmap generation is not supported"
2847+ + " and CPU mipmap generation failed. Mipmaps will not be generated." ,
2848+ exception );
2849+ }
28362850 }
2837- }
2838-
2839- if (needsMipmaps && hwMipmapSupported ) {
2840- // Image does not have mipmaps, but they are required.
2841- // Generate from base level.
28422851
2843- if (!caps .contains (Caps .FrameBuffer ) && gl2 != null ) {
2852+ /*
2853+ * Old desktop GL without FBO support can auto-generate mipmaps during
2854+ * texture upload. Newer paths generate explicitly after upload below.
2855+ */
2856+ if (hwMipmapSupported && !caps .contains (Caps .FrameBuffer ) && gl2 != null ) {
28442857 gl2 .glTexParameteri (target , GL2 .GL_GENERATE_MIPMAP , GL .GL_TRUE );
28452858 img .setMipmapsGenerated (true );
2846- } else {
2847- // For OpenGL3 and up.
2848- // We'll generate mipmaps via glGenerateMipmapEXT (see below)
28492859 }
2850- } else if (caps .contains (Caps .OpenGL20 ) || caps .contains (Caps .OpenGLES30 )) {
2851- if (img .hasMipmaps () || cpuMipmapImage != null ) {
2852- int mipCount = cpuMipmapImage != null
2853- ? cpuMipmapImage .getMipMapSizes ().length
2854- : img .getMipMapSizes ().length ;
2855- // Image already has mipmaps, set the max level based on the
2856- // number of mipmaps we have.
2857- gl .glTexParameteri (target , GL2 .GL_TEXTURE_MAX_LEVEL , mipCount - 1 );
2858- } else {
2859- // Image does not have mipmaps, and they are not required.
2860- // Specify that the texture has no mipmaps.
2861- gl .glTexParameteri (target , GL2 .GL_TEXTURE_MAX_LEVEL , 0 );
2862- }
2863- }
2864- if (needsMipmaps && !hwMipmapSupported ) {
2865- if (!img .hasMipmaps () && cpuMipmapImage == null && !cpuMipmapFallbackFailed ) {
2860+
2861+ if (!hwMipmapSupported
2862+ && !sourceMipmapsUsable
2863+ && !cpuMipmapsGenerated
2864+ && !cpuMipmapFallbackFailed ) {
28662865 logger .log (Level .WARNING , "Texture " + img + " requires mipmaps, but hardware mipmaps generation is not supported. Mipmaps will not be generated." );
28672866 }
28682867 }
2868+
2869+ /*
2870+ * Clamp the mip range to the levels actually uploaded. This is still
2871+ * needed when mipmaps are not requested, otherwise GL may sample
2872+ * missing levels left from a previous texture state. When hardware
2873+ * mipmap generation is pending, reopen the full generated range in
2874+ * case an earlier upload clamped this texture to the base level.
2875+ */
2876+ boolean canSetTextureMaxLevel = caps .contains (Caps .OpenGL20 ) || caps .contains (Caps .OpenGLES30 );
2877+ boolean hasUploadMipmaps = sourceMipmapsUsable || cpuMipmapsGenerated ;
2878+ int uploadWidth = scaleToPot ? FastMath .nearestPowerOfTwo (img .getWidth ()) : imageForUpload .getWidth ();
2879+ int uploadHeight = scaleToPot ? FastMath .nearestPowerOfTwo (img .getHeight ()) : imageForUpload .getHeight ();
2880+ int maxLevel = textureMaxLevelForUpload (canSetTextureMaxLevel , needsMipmaps , hwMipmapSupported ,
2881+ hasUploadMipmaps , cpuMipmapsGenerated ? imageForUpload .getMipMapSizes () : img .getMipMapSizes (),
2882+ generatedMipMaxLevel (uploadWidth , uploadHeight , imageForUpload .getDepth ()));
2883+ if (maxLevel >= 0 ) {
2884+ gl .glTexParameteri (target , GL2 .GL_TEXTURE_MAX_LEVEL , maxLevel );
2885+ }
28692886 } else {
28702887 // Check if graphics card doesn't support multisample textures
28712888 if (!caps .contains (Caps .TextureMultisample )) {
@@ -2906,13 +2923,8 @@ private void updateTexImageData(Image img, Texture.Type type, int unit, boolean
29062923 }
29072924 }
29082925
2909- Image imageForUpload ;
2910- if (cpuMipmapImage != null ) {
2911- imageForUpload = cpuMipmapImage ;
2912- } else if (scaleToPot ) {
2926+ if (scaleToPot ) {
29132927 imageForUpload = MipMapGenerator .resizeToPowerOf2 (img );
2914- } else {
2915- imageForUpload = img ;
29162928 }
29172929 if (target == GL .GL_TEXTURE_CUBE_MAP ) {
29182930 List <ByteBuffer > data = imageForUpload .getData ();
@@ -2947,14 +2959,12 @@ private void updateTexImageData(Image img, Texture.Type type, int unit, boolean
29472959 img .setMultiSamples (imageSamples );
29482960 }
29492961
2950- if (caps .contains (Caps .FrameBuffer ) || gl2 == null ) {
2951- if (needsMipmaps && img .getData (0 ) != null
2952- && !img .isMipmapsGenerated ()) {
2953- if (hwMipmapSupported ) {
2954- glfbo .glGenerateMipmapEXT (target );
2955- img .setMipmapsGenerated (true );
2956- }
2957- }
2962+ if (needsMipmaps && hwMipmapSupported
2963+ && (caps .contains (Caps .FrameBuffer ) || gl2 == null )
2964+ && img .getData (0 ) != null
2965+ && !img .isMipmapsGenerated ()) {
2966+ glfbo .glGenerateMipmapEXT (target );
2967+ img .setMipmapsGenerated (true );
29582968 }
29592969
29602970 img .clearUpdateNeeded ();
@@ -2965,9 +2975,44 @@ private boolean isMipmapGenerationSupported(Image.Format format, ColorSpace colo
29652975 return gf != null && gf .colorRenderable && gf .filterable ;
29662976 }
29672977
2968- private Image createCpuMipmapImage (Image image ) {
2978+ static int textureMaxLevelForUpload (boolean canSetTextureMaxLevel ,
2979+ boolean needsMipmaps ,
2980+ boolean hwMipmapSupported ,
2981+ boolean hasUploadMipmaps ,
2982+ int [] uploadMipMapSizes ,
2983+ int generatedMipMaxLevel ) {
2984+ if (!canSetTextureMaxLevel ) {
2985+ return -1 ;
2986+ }
2987+ if (needsMipmaps && hwMipmapSupported ) {
2988+ return generatedMipMaxLevel ;
2989+ }
2990+ if (!hasUploadMipmaps ) {
2991+ return 0 ;
2992+ }
2993+ return uploadMipMapSizes .length - 1 ;
2994+ }
2995+
2996+ static int generatedMipMaxLevel (int width , int height , int depth ) {
2997+ int maxDimension = Math .max (Math .max (width , height ), Math .max (1 , depth ));
2998+ int maxLevel = 0 ;
2999+ while (maxDimension > 1 ) {
3000+ maxDimension >>= 1 ;
3001+ maxLevel ++;
3002+ }
3003+ return maxLevel ;
3004+ }
3005+
3006+ private Image cloneImageForUpload (Image image , boolean scaleToPot ) {
3007+ if (scaleToPot ) {
3008+ return MipMapGenerator .resizeToPowerOf2 (image );
3009+ }
3010+
29693011 ArrayList <ByteBuffer > data = new ArrayList <>(image .getData ().size ());
29703012 for (ByteBuffer buffer : image .getData ()) {
3013+ if (buffer == null ) {
3014+ return null ;
3015+ }
29713016 data .add (buffer .duplicate ());
29723017 }
29733018 return new Image (image .getFormat (), image .getWidth (), image .getHeight (), image .getDepth (),
0 commit comments