UNPKG

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
/** * 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; }