fluid-pointer-react
Version:
A dependency-free fluid simulation component with WebGL-based physics - supports both vanilla web components and React
145 lines (144 loc) • 5.03 kB
JavaScript
/**
* Initialize WebGL context and detect supported extensions
*/
export function getWebGLContext(canvas) {
const params = {
alpha: true,
depth: false,
stencil: false,
antialias: false,
preserveDrawingBuffer: false,
};
let gl = canvas.getContext("webgl2", params);
const isWebGL2 = !!gl;
if (!isWebGL2) {
gl =
canvas.getContext("webgl", params) ||
canvas.getContext("experimental-webgl", params);
}
if (!gl) {
console.error("WebGL not supported");
return null;
}
let halfFloat = null;
let supportLinearFiltering = null;
if (isWebGL2) {
const gl2 = gl;
gl2.getExtension("EXT_color_buffer_float");
supportLinearFiltering = gl2.getExtension("OES_texture_float_linear");
}
else {
const gl1 = gl;
halfFloat = gl1.getExtension("OES_texture_half_float");
supportLinearFiltering = gl1.getExtension("OES_texture_half_float_linear");
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);
const halfFloatTexType = isWebGL2
? gl.HALF_FLOAT
: halfFloat
? halfFloat.HALF_FLOAT_OES
: gl.FLOAT;
const formatRGBA = getSupportedFormat(gl, isWebGL2 ? gl.RGBA16F : gl.RGBA, gl.RGBA, halfFloatTexType);
const formatRG = getSupportedFormat(gl, isWebGL2 ? gl.RG16F : gl.RGBA, isWebGL2 ? gl.RG : gl.RGBA, halfFloatTexType);
const formatR = getSupportedFormat(gl, isWebGL2 ? gl.R16F : gl.RGBA, isWebGL2 ? gl.RED : gl.RGBA, halfFloatTexType);
return {
gl,
ext: {
formatRGBA,
formatRG,
formatR,
halfFloatTexType,
supportLinearFiltering: supportLinearFiltering,
},
};
}
function getSupportedFormat(gl, internalFormat, format, type) {
if (!supportRenderTextureFormat(gl, internalFormat, format, type)) {
switch (internalFormat) {
case gl.R16F:
return getSupportedFormat(gl, gl.RG16F, gl.RG, type);
case gl.RG16F:
return getSupportedFormat(gl, gl.RGBA, gl.RGBA, type);
default:
return null;
}
}
return {
internalFormat,
format,
};
}
function supportRenderTextureFormat(gl, internalFormat, format, type) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 4, 4, 0, format, type, null);
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
const supported = status === gl.FRAMEBUFFER_COMPLETE;
// Cleanup
gl.deleteTexture(texture);
gl.deleteFramebuffer(fbo);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return supported;
}
/**
* Compile a shader from source
*/
export function compileShader(gl, type, source) {
const shader = gl.createShader(type);
if (!shader)
return null;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error("Shader compilation failed:");
console.error("Shader source:", source);
console.error("Error:", gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
/**
* Create a shader program from vertex and fragment shaders
*/
export function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
if (!program)
return null;
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// Bind the aPosition attribute to location 0
gl.bindAttribLocation(program, 0, "aPosition");
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error("Program linking failed:");
console.error("Error:", gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
/**
* Get all uniform locations from a program
*/
export function getUniforms(gl, program) {
const uniforms = {};
const uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < uniformCount; i++) {
const uniformInfo = gl.getActiveUniform(program, i);
if (uniformInfo) {
const location = gl.getUniformLocation(program, uniformInfo.name);
if (location) {
uniforms[uniformInfo.name] = location;
}
}
}
return uniforms;
}