2025-11-17 17:18:43 +01:00
|
|
|
/*
|
|
|
|
|
* Copyright 2013-2019, kaj dijkstra,
|
|
|
|
|
* Author, Kaj Dijkstra.
|
|
|
|
|
* All rights reserved.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
import texture from "./texture.js";
|
|
|
|
|
import { math } from "./math.js";
|
2025-11-17 17:18:43 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sampler2D
|
|
|
|
|
**/
|
2026-01-04 16:11:18 +01:00
|
|
|
class sampler2D {
|
|
|
|
|
|
|
|
|
|
constructor ( ) {
|
|
|
|
|
|
|
|
|
|
this.texture = null;
|
|
|
|
|
|
|
|
|
|
this.id = ++kepler.samplerId;
|
|
|
|
|
|
|
|
|
|
this.alpha = 1.0;
|
2025-11-17 17:18:43 +01:00
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
this.binded = false;
|
|
|
|
|
|
|
|
|
|
this.anisotropic = false;
|
|
|
|
|
|
|
|
|
|
// If true, do not generate mipmaps (reduces normal-map shimmer)
|
|
|
|
|
this.isNormalMap = false;
|
2025-11-17 17:18:43 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
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;
|
2025-11-17 17:18:43 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
setTarget ( viewport ) {
|
|
|
|
|
|
|
|
|
|
var textureObject = new texture();
|
2025-11-17 17:18:43 +01:00
|
|
|
|
|
|
|
|
textureObject.setViewport( viewport );
|
2026-01-04 16:11:18 +01:00
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
this.addTexture( textureObject );
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
getTexture ( face ) {
|
2025-11-17 17:18:43 +01:00
|
|
|
|
|
|
|
|
return this.texture;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
getTextures ( ) {
|
|
|
|
|
|
|
|
|
|
return [ this.texture ];
|
2025-11-17 17:18:43 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
addTexture ( textureObject ) {
|
2025-11-17 17:18:43 +01:00
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
this.texture = textureObject;
|
2025-11-17 17:18:43 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
setType ( type ) {
|
|
|
|
|
|
|
|
|
|
// this.texture.type = type;
|
|
|
|
|
}
|
2025-11-17 17:18:43 +01:00
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
setAsNormalMap ( ) {
|
2025-11-17 17:18:43 +01:00
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
this.isNormalMap = true;
|
2025-11-17 17:18:43 +01:00
|
|
|
}
|
2026-01-04 16:11:18 +01:00
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
/**
|
|
|
|
|
* bind sampler to shader
|
|
|
|
|
* @param {(shader)} shader
|
|
|
|
|
**/
|
2026-01-04 16:11:18 +01:00
|
|
|
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.
|
|
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
} else {
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
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 ) {
|
|
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
case "float":
|
2026-01-04 16:11:18 +01:00
|
|
|
{
|
2025-11-17 17:18:43 +01:00
|
|
|
this.type = this.gl.FLOAT;
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-17 17:18:43 +01:00
|
|
|
break;
|
2026-01-04 16:11:18 +01:00
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
case "int":
|
2026-01-04 16:11:18 +01:00
|
|
|
{
|
2025-11-17 17:18:43 +01:00
|
|
|
this.type = this.gl.UNSIGNED_BYTE;
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
gl.texImage2D(
|
|
|
|
|
gl.TEXTURE_2D,
|
|
|
|
|
0,
|
|
|
|
|
this.format,
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
0,
|
|
|
|
|
this.internalFormat,
|
|
|
|
|
this.type,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-17 17:18:43 +01:00
|
|
|
break;
|
2026-01-04 16:11:18 +01:00
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
case "depth":
|
2026-01-04 16:11:18 +01:00
|
|
|
{
|
|
|
|
|
gl.texImage2D(
|
|
|
|
|
gl.TEXTURE_2D,
|
|
|
|
|
0,
|
|
|
|
|
this.gl.DEPTH_COMPONENT,
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
0,
|
|
|
|
|
this.gl.DEPTH_COMPONENT,
|
|
|
|
|
this.gl.UNSIGNED_SHORT,
|
|
|
|
|
null
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-17 17:18:43 +01:00
|
|
|
break;
|
2026-01-04 16:11:18 +01:00
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
case "image":
|
2026-01-04 16:11:18 +01:00
|
|
|
{
|
2025-11-17 17:18:43 +01:00
|
|
|
this.type = this.gl.UNSIGNED_BYTE;
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
// Keep your original call shape (works because format == internalFormat == RGBA)
|
|
|
|
|
gl.texImage2D(
|
|
|
|
|
gl.TEXTURE_2D,
|
|
|
|
|
0,
|
|
|
|
|
this.format,
|
|
|
|
|
this.internalFormat,
|
|
|
|
|
this.type,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-17 17:18:43 +01:00
|
|
|
break;
|
2026-01-04 16:11:18 +01:00
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
case "canvas":
|
2026-01-04 16:11:18 +01:00
|
|
|
{
|
|
|
|
|
gl.texImage2D(
|
|
|
|
|
gl.TEXTURE_2D,
|
|
|
|
|
0,
|
|
|
|
|
this.format,
|
|
|
|
|
width,
|
|
|
|
|
height,
|
|
|
|
|
0,
|
|
|
|
|
this.internalFormat,
|
|
|
|
|
this.UNSIGNED_BYTE,
|
|
|
|
|
data
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-11-17 17:18:43 +01:00
|
|
|
break;
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
case "COMPRESSED_RGBA":
|
|
|
|
|
{
|
2025-11-17 17:18:43 +01:00
|
|
|
var mipmaps = txt.mipmaps;
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
for ( var i = 0; i < mipmaps.length; i++ ) {
|
|
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
var mipmap = mipmaps[i];
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
gl.compressedTexImage2D(
|
|
|
|
|
gl.TEXTURE_2D,
|
|
|
|
|
i,
|
|
|
|
|
mipmap.internalFormat,
|
|
|
|
|
mipmap.width,
|
|
|
|
|
mipmap.height,
|
|
|
|
|
0,
|
|
|
|
|
mipmap.byteArray
|
|
|
|
|
);
|
2025-11-17 17:18:43 +01:00
|
|
|
}
|
2026-01-04 16:11:18 +01:00
|
|
|
}
|
2025-11-17 17:18:43 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
// Anisotropy kept as in your original (disabled)
|
|
|
|
|
if ( this.anisotropic ) {
|
2025-11-17 17:18:43 +01:00
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
// var extension = kepler.extensions.anisotropic;
|
|
|
|
|
// gl.texParameteri( this.gl.TEXTURE_2D, extension.TEXTURE_MAX_ANISOTROPY_EXT, this.anisotropic );
|
2025-11-17 17:18:43 +01:00
|
|
|
}
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
// 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 );
|
|
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
} else {
|
|
|
|
|
|
2026-01-04 16:11:18 +01:00
|
|
|
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 );
|
2025-11-17 17:18:43 +01:00
|
|
|
}
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
gl.bindTexture( gl.TEXTURE_2D, null );
|
|
|
|
|
|
2025-11-17 17:18:43 +01:00
|
|
|
this.binded = true;
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
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;
|
2025-11-17 17:18:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-04 16:11:18 +01:00
|
|
|
|
|
|
|
|
export { sampler2D as default };
|