/* * Copyright 2013-2019, kaj dijkstra, * Author, Kaj Dijkstra. * All rights reserved. * */ import texture from "./texture.js"; import { math } from "./math.js"; /** * Sampler2D **/ class sampler2D { constructor ( ) { this.texture = null; this.id = ++kepler.samplerId; this.alpha = 1.0; this.binded = false; this.anisotropic = false; // If true, do not generate mipmaps (reduces normal-map shimmer) this.isNormalMap = false; } setViewport ( viewport ) { this.viewport = viewport; this.gl = viewport.gl; this.FLIP_Y = true; this.filter = this.gl.LINEAR; this.MIN_FILTER = this.gl.LINEAR; this.MAG_FILTER = this.gl.LINEAR; this.WRAP_S = this.gl.REPEAT; this.WRAP_T = this.gl.REPEAT; this.datatype = this.gl.RGBA; this.format = this.gl.RGBA; this.internalFormat = this.gl.RGBA; this.target = this.gl.TEXTURE_2D; this.type = this.gl.FLOAT; } setTarget ( viewport ) { var textureObject = new texture(); textureObject.setViewport( viewport ); this.addTexture( textureObject ); } getTexture ( face ) { return this.texture; } getTextures ( ) { return [ this.texture ]; } addTexture ( textureObject ) { this.texture = textureObject; } setType ( type ) { // this.texture.type = type; } setAsNormalMap ( ) { this.isNormalMap = true; } /** * bind sampler to shader * @param {(shader)} shader **/ bind ( shader ) { var txt = this.texture; var data = this.texture.data; var type = txt.dataType; var gl = this.gl; if ( type == "framebuffer" ) { // Preserve your original behaviour: // framebuffer "data" is already a GL texture handle this.texture.glTexture = this.texture.data; // Continue as if it is a normal GL texture object (no texImage2D) // NOTE: do NOT change filtering/mips logic here beyond safe defaults. } else { var width = txt.width; var height = txt.height; // Always assign sampler slot like you did this.id = 1 + shader.samplerId++; gl.activeTexture( gl.TEXTURE0 + this.id ); gl.enable( this.gl.BLEND ); gl.bindTexture( gl.TEXTURE_2D, txt.glTexture ); gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, this.FLIP_Y ); // serialize txt data type switch ( type ) { case "float": { this.type = this.gl.FLOAT; // WebGL2 prefers sized internal formats for float textures. // Keep fallback to gl.RGBA for compatibility if RGBA32F is not present. var internal = gl.RGBA32F ? gl.RGBA32F : gl.RGBA; gl.texImage2D( gl.TEXTURE_2D, 0, internal, width, height, 0, this.internalFormat, this.type, data ); } break; case "int": { this.type = this.gl.UNSIGNED_BYTE; gl.texImage2D( gl.TEXTURE_2D, 0, this.format, width, height, 0, this.internalFormat, this.type, data ); } break; case "depth": { gl.texImage2D( gl.TEXTURE_2D, 0, this.gl.DEPTH_COMPONENT, width, height, 0, this.gl.DEPTH_COMPONENT, this.gl.UNSIGNED_SHORT, null ); } break; case "image": { this.type = this.gl.UNSIGNED_BYTE; // Keep your original call shape (works because format == internalFormat == RGBA) gl.texImage2D( gl.TEXTURE_2D, 0, this.format, this.internalFormat, this.type, data ); } break; case "canvas": { gl.texImage2D( gl.TEXTURE_2D, 0, this.format, width, height, 0, this.internalFormat, this.UNSIGNED_BYTE, data ); } break; case "COMPRESSED_RGBA": { var mipmaps = txt.mipmaps; for ( var i = 0; i < mipmaps.length; i++ ) { var mipmap = mipmaps[i]; gl.compressedTexImage2D( gl.TEXTURE_2D, i, mipmap.internalFormat, mipmap.width, mipmap.height, 0, mipmap.byteArray ); } } break; } // Anisotropy kept as in your original (disabled) if ( this.anisotropic ) { // var extension = kepler.extensions.anisotropic; // gl.texParameteri( this.gl.TEXTURE_2D, extension.TEXTURE_MAX_ANISOTROPY_EXT, this.anisotropic ); } // POT logic (this is where we apply the safe normal-map fix) if ( math.isPowerOfTwo( width ) && math.isPowerOfTwo( height ) ) { gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR ); if ( this.isNormalMap ) { // Safe fix: do not use mipmaps for normal maps gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR ); } else { gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR_MIPMAP_LINEAR ); if ( this.type != this.gl.FLOAT ) { gl.generateMipmap( gl.TEXTURE_2D ); } } gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.WRAP_S ); gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.WRAP_T ); } else { gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR ); gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE ); gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE ); } gl.bindTexture( gl.TEXTURE_2D, null ); this.binded = true; return; } // framebuffer path: bind the already-created GL texture handle // and apply basic sampling state without re-upload. { var width_fb = txt.width; var height_fb = txt.height; this.id = 1 + shader.samplerId++; gl.activeTexture( gl.TEXTURE0 + this.id ); gl.bindTexture( gl.TEXTURE_2D, txt.glTexture ); // Clamp + linear for attachments (safe default) gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR ); gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR ); gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE ); gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); gl.bindTexture( gl.TEXTURE_2D, null ); this.binded = true; } } } export { sampler2D as default };