commit
4b0e7a99cc
26 changed files with 8912 additions and 0 deletions
@ -0,0 +1,3 @@ |
||||
*.DS_Store |
||||
*.wasm |
||||
*_shader.h |
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@ |
||||
#ifndef __gl3platform_h_ |
||||
#define __gl3platform_h_ |
||||
|
||||
/*
|
||||
** Copyright 2017-2020 The Khronos Group Inc. |
||||
** SPDX-License-Identifier: Apache-2.0 |
||||
*/ |
||||
|
||||
/* Platform-specific types and definitions for OpenGL ES 3.X gl3.h
|
||||
* |
||||
* Adopters may modify khrplatform.h and this file to suit their platform. |
||||
* Please contribute modifications back to Khronos as pull requests on the |
||||
* public github repository: |
||||
* https://github.com/KhronosGroup/OpenGL-Registry
|
||||
*/ |
||||
|
||||
#include <KHR/khrplatform.h> |
||||
|
||||
#ifndef GL_APICALL |
||||
#define GL_APICALL KHRONOS_APICALL |
||||
#endif |
||||
|
||||
#ifndef GL_APIENTRY |
||||
#define GL_APIENTRY KHRONOS_APIENTRY |
||||
#endif |
||||
|
||||
#endif /* __gl3platform_h_ */ |
||||
@ -0,0 +1,292 @@ |
||||
#ifndef __khrplatform_h_ |
||||
#define __khrplatform_h_ |
||||
|
||||
/*
|
||||
** Copyright (c) 2008-2018 The Khronos Group Inc. |
||||
** |
||||
** Permission is hereby granted, free of charge, to any person obtaining a |
||||
** copy of this software and/or associated documentation files (the |
||||
** "Materials"), to deal in the Materials without restriction, including |
||||
** without limitation the rights to use, copy, modify, merge, publish, |
||||
** distribute, sublicense, and/or sell copies of the Materials, and to |
||||
** permit persons to whom the Materials are furnished to do so, subject to |
||||
** the following conditions: |
||||
** |
||||
** The above copyright notice and this permission notice shall be included |
||||
** in all copies or substantial portions of the Materials. |
||||
** |
||||
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. |
||||
*/ |
||||
|
||||
/* Khronos platform-specific types and definitions.
|
||||
* |
||||
* The master copy of khrplatform.h is maintained in the Khronos EGL |
||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
||||
* The last semantic modification to khrplatform.h was at commit ID: |
||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692 |
||||
* |
||||
* Adopters may modify this file to suit their platform. Adopters are |
||||
* encouraged to submit platform specific modifications to the Khronos |
||||
* group so that they can be included in future versions of this file. |
||||
* Please submit changes by filing pull requests or issues on |
||||
* the EGL Registry repository linked above. |
||||
* |
||||
* |
||||
* See the Implementer's Guidelines for information about where this file |
||||
* should be located on your system and for more details of its use: |
||||
* http://www.khronos.org/registry/implementers_guide.pdf
|
||||
* |
||||
* This file should be included as |
||||
* #include <KHR/khrplatform.h> |
||||
* by Khronos client API header files that use its types and defines. |
||||
* |
||||
* The types in khrplatform.h should only be used to define API-specific types. |
||||
* |
||||
* Types defined in khrplatform.h: |
||||
* khronos_int8_t signed 8 bit |
||||
* khronos_uint8_t unsigned 8 bit |
||||
* khronos_int16_t signed 16 bit |
||||
* khronos_uint16_t unsigned 16 bit |
||||
* khronos_int32_t signed 32 bit |
||||
* khronos_uint32_t unsigned 32 bit |
||||
* khronos_int64_t signed 64 bit |
||||
* khronos_uint64_t unsigned 64 bit |
||||
* khronos_intptr_t signed same number of bits as a pointer |
||||
* khronos_uintptr_t unsigned same number of bits as a pointer |
||||
* khronos_ssize_t signed size |
||||
* khronos_usize_t unsigned size |
||||
* khronos_float_t signed 32 bit floating point |
||||
* khronos_time_ns_t unsigned 64 bit time in nanoseconds |
||||
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in |
||||
* nanoseconds |
||||
* khronos_stime_nanoseconds_t signed time interval in nanoseconds |
||||
* khronos_boolean_enum_t enumerated boolean type. This should |
||||
* only be used as a base type when a client API's boolean type is |
||||
* an enum. Client APIs which use an integer or other type for |
||||
* booleans cannot use this as the base type for their boolean. |
||||
* |
||||
* Tokens defined in khrplatform.h: |
||||
* |
||||
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. |
||||
* |
||||
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. |
||||
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. |
||||
* |
||||
* Calling convention macros defined in this file: |
||||
* KHRONOS_APICALL |
||||
* KHRONOS_APIENTRY |
||||
* KHRONOS_APIATTRIBUTES |
||||
* |
||||
* These may be used in function prototypes as: |
||||
* |
||||
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname( |
||||
* int arg1, |
||||
* int arg2) KHRONOS_APIATTRIBUTES; |
||||
*/ |
||||
|
||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) |
||||
# define KHRONOS_STATIC 1 |
||||
#endif |
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APICALL |
||||
*------------------------------------------------------------------------- |
||||
* This precedes the return type of the function in the function prototype. |
||||
*/ |
||||
#if defined(KHRONOS_STATIC) |
||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
||||
* header compatible with static linking. */ |
||||
# define KHRONOS_APICALL |
||||
#elif defined(_WIN32) |
||||
# define KHRONOS_APICALL __declspec(dllimport) |
||||
#elif defined (__SYMBIAN32__) |
||||
# define KHRONOS_APICALL IMPORT_C |
||||
#elif (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 303) \ |
||||
|| (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590)) |
||||
/* KHRONOS_APIATTRIBUTES is not used by the client API headers yet */ |
||||
# define KHRONOS_APICALL __attribute__((visibility("default"))) |
||||
#else |
||||
# define KHRONOS_APICALL |
||||
#endif |
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIENTRY |
||||
*------------------------------------------------------------------------- |
||||
* This follows the return type of the function and precedes the function |
||||
* name in the function prototype. |
||||
*/ |
||||
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(KHRONOS_STATIC) |
||||
/* Win32 but not WinCE */ |
||||
# define KHRONOS_APIENTRY __stdcall |
||||
#else |
||||
# define KHRONOS_APIENTRY |
||||
#endif |
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APIATTRIBUTES |
||||
*------------------------------------------------------------------------- |
||||
* This follows the closing parenthesis of the function prototype arguments. |
||||
*/ |
||||
#if defined (__ARMCC_2__) |
||||
#define KHRONOS_APIATTRIBUTES __softfp |
||||
#else |
||||
#define KHRONOS_APIATTRIBUTES |
||||
#endif |
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* basic type definitions |
||||
*-----------------------------------------------------------------------*/ |
||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) |
||||
|
||||
|
||||
/*
|
||||
* Using <stdint.h> |
||||
*/ |
||||
#include <stdint.h> |
||||
typedef int32_t khronos_int32_t; |
||||
typedef uint32_t khronos_uint32_t; |
||||
typedef int64_t khronos_int64_t; |
||||
typedef uint64_t khronos_uint64_t; |
||||
#define KHRONOS_SUPPORT_INT64 1 |
||||
#define KHRONOS_SUPPORT_FLOAT 1 |
||||
|
||||
#elif defined(__VMS ) || defined(__sgi) |
||||
|
||||
/*
|
||||
* Using <inttypes.h> |
||||
*/ |
||||
#include <inttypes.h> |
||||
typedef int32_t khronos_int32_t; |
||||
typedef uint32_t khronos_uint32_t; |
||||
typedef int64_t khronos_int64_t; |
||||
typedef uint64_t khronos_uint64_t; |
||||
#define KHRONOS_SUPPORT_INT64 1 |
||||
#define KHRONOS_SUPPORT_FLOAT 1 |
||||
|
||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) |
||||
|
||||
/*
|
||||
* Win32 |
||||
*/ |
||||
typedef __int32 khronos_int32_t; |
||||
typedef unsigned __int32 khronos_uint32_t; |
||||
typedef __int64 khronos_int64_t; |
||||
typedef unsigned __int64 khronos_uint64_t; |
||||
#define KHRONOS_SUPPORT_INT64 1 |
||||
#define KHRONOS_SUPPORT_FLOAT 1 |
||||
|
||||
#elif defined(__sun__) || defined(__digital__) |
||||
|
||||
/*
|
||||
* Sun or Digital |
||||
*/ |
||||
typedef int khronos_int32_t; |
||||
typedef unsigned int khronos_uint32_t; |
||||
#if defined(__arch64__) || defined(_LP64) |
||||
typedef long int khronos_int64_t; |
||||
typedef unsigned long int khronos_uint64_t; |
||||
#else |
||||
typedef long long int khronos_int64_t; |
||||
typedef unsigned long long int khronos_uint64_t; |
||||
#endif /* __arch64__ */ |
||||
#define KHRONOS_SUPPORT_INT64 1 |
||||
#define KHRONOS_SUPPORT_FLOAT 1 |
||||
|
||||
#elif 0 |
||||
|
||||
/*
|
||||
* Hypothetical platform with no float or int64 support |
||||
*/ |
||||
typedef int khronos_int32_t; |
||||
typedef unsigned int khronos_uint32_t; |
||||
#define KHRONOS_SUPPORT_INT64 0 |
||||
#define KHRONOS_SUPPORT_FLOAT 0 |
||||
|
||||
#else |
||||
|
||||
/*
|
||||
* Generic fallback |
||||
*/ |
||||
#include <stdint.h> |
||||
typedef int32_t khronos_int32_t; |
||||
typedef uint32_t khronos_uint32_t; |
||||
typedef int64_t khronos_int64_t; |
||||
typedef uint64_t khronos_uint64_t; |
||||
#define KHRONOS_SUPPORT_INT64 1 |
||||
#define KHRONOS_SUPPORT_FLOAT 1 |
||||
|
||||
#endif |
||||
|
||||
|
||||
/*
|
||||
* Types that are (so far) the same on all platforms |
||||
*/ |
||||
typedef signed char khronos_int8_t; |
||||
typedef unsigned char khronos_uint8_t; |
||||
typedef signed short int khronos_int16_t; |
||||
typedef unsigned short int khronos_uint16_t; |
||||
|
||||
/*
|
||||
* Types that differ between LLP64 and LP64 architectures - in LLP64, |
||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears |
||||
* to be the only LLP64 architecture in current use. |
||||
*/ |
||||
#ifdef _WIN64 |
||||
typedef signed long long int khronos_intptr_t; |
||||
typedef unsigned long long int khronos_uintptr_t; |
||||
typedef signed long long int khronos_ssize_t; |
||||
typedef unsigned long long int khronos_usize_t; |
||||
#else |
||||
typedef signed long int khronos_intptr_t; |
||||
typedef unsigned long int khronos_uintptr_t; |
||||
typedef signed long int khronos_ssize_t; |
||||
typedef unsigned long int khronos_usize_t; |
||||
#endif |
||||
|
||||
#if KHRONOS_SUPPORT_FLOAT |
||||
/*
|
||||
* Float type |
||||
*/ |
||||
typedef float khronos_float_t; |
||||
#endif |
||||
|
||||
#if KHRONOS_SUPPORT_INT64 |
||||
/* Time types
|
||||
* |
||||
* These types can be used to represent a time interval in nanoseconds or |
||||
* an absolute Unadjusted System Time. Unadjusted System Time is the number |
||||
* of nanoseconds since some arbitrary system event (e.g. since the last |
||||
* time the system booted). The Unadjusted System Time is an unsigned |
||||
* 64 bit value that wraps back to 0 every 584 years. Time intervals |
||||
* may be either signed or unsigned. |
||||
*/ |
||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t; |
||||
typedef khronos_int64_t khronos_stime_nanoseconds_t; |
||||
#endif |
||||
|
||||
/*
|
||||
* Dummy value used to pad enum types to 32 bits. |
||||
*/ |
||||
#ifndef KHRONOS_MAX_ENUM |
||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF |
||||
#endif |
||||
|
||||
/*
|
||||
* Enumerated boolean type |
||||
* |
||||
* Values other than zero should be considered to be true. Therefore |
||||
* comparisons should not be made against KHRONOS_TRUE. |
||||
*/ |
||||
typedef enum { |
||||
KHRONOS_FALSE = 0, |
||||
KHRONOS_TRUE = 1, |
||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM |
||||
} khronos_boolean_enum_t; |
||||
|
||||
#endif /* __khrplatform_h_ */ |
||||
@ -0,0 +1,16 @@ |
||||
#!/bin/bash |
||||
flags="--target=wasm32 \ |
||||
--no-standard-libraries \ |
||||
-fno-builtin \ |
||||
-Wl,--no-entry \ |
||||
-Wl,--export-all \ |
||||
-Wl,--allow-undefined \ |
||||
-I ./" |
||||
|
||||
# generate header files for shaders |
||||
./shader2header.sh |
||||
|
||||
# compile to wasm |
||||
CC=/usr/local/opt/llvm/bin/clang |
||||
|
||||
$CC $flags -Isrc -o main.wasm src/main.c |
||||
Binary file not shown.
@ -0,0 +1,10 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<title>Wasm/WebGL fluid example</title> |
||||
</head> |
||||
<body> |
||||
<canvas id="wa_canvas" style="display:block;margin:0 auto 10px" oncontextmenu="event.preventDefault()" width="0" height="0"></canvas> |
||||
<script type="text/javascript" src="loader.js"></script> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,944 @@ |
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): global app state
|
||||
//--------------------------------------------------------------------------------
|
||||
var APP = |
||||
{ |
||||
canvas: document.getElementById('wa_canvas'), |
||||
instance: null, |
||||
memory: null, |
||||
}; |
||||
|
||||
var HEAP32, HEAPU8, HEAPU16, HEAPU32, HEAPF32; |
||||
var WASM_MEMORY, WASM_HEAP; |
||||
var WASM_HEAP_MAX = 256*1024*1024; //max 256MB
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): log and abort functions
|
||||
//--------------------------------------------------------------------------------
|
||||
function log_error(code, msg) |
||||
{ |
||||
console.log('ERROR (' + code + '): ' + msg + '\n'); |
||||
} |
||||
|
||||
var ABORT = false; |
||||
function abort(code, msg) |
||||
{ |
||||
ABORT = true; |
||||
log_error(code, msg); |
||||
throw 'abort'; |
||||
} |
||||
|
||||
const utf8Decoder = new TextDecoder('utf-8'); |
||||
|
||||
function console_log(ptr, len) |
||||
{ |
||||
console.log(utf8Decoder.decode(new Uint8Array(APP.memory.buffer, ptr, len))); |
||||
} |
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
//NOTE(martin): Memory interaction
|
||||
//--------------------------------------------------------------------------------
|
||||
function MemorySetBufferViews() |
||||
{ |
||||
var buf = APP.memory.buffer; |
||||
HEAP32 = new Int32Array(buf); |
||||
HEAPU8 = new Uint8Array(buf); |
||||
HEAPU16 = new Uint16Array(buf); |
||||
HEAPU32 = new Uint32Array(buf); |
||||
HEAPF32 = new Float32Array(buf); |
||||
} |
||||
|
||||
function WriteHeapString(str, ptr, max_length) |
||||
{ |
||||
// Put a string from javascript onto the wasm memory heap (encoded as UTF8)
|
||||
if(!(0<max_length))return 0; |
||||
for(var e=str,r=HEAPU8,f=ptr,i=max_length,a=f,t=f+i-1,b=0;b<e.length;++b) |
||||
{ |
||||
var k=e.charCodeAt(b); |
||||
if(55296<=k&&k<=57343&&(k=65536+((1023&k)<<10)|1023&e.charCodeAt(++b)),k<=127){if(t<=f)break;r[f++]=k;} |
||||
else if(k<=2047){if(t<=f+1)break;r[f++]=192|k>>6,r[f++]=128|63&k;} |
||||
else if(k<=65535){if(t<=f+2)break;r[f++]=224|k>>12,r[f++]=128|k>>6&63,r[f++]=128|63&k;} |
||||
else if(k<=2097151){if(t<=f+3)break;r[f++]=240|k>>18,r[f++]=128|k>>12&63,r[f++]=128|k>>6&63,r[f++]=128|63&k;} |
||||
else if(k<=67108863){if(t<=f+4)break;r[f++]=248|k>>24,r[f++]=128|k>>18&63,r[f++]=128|k>>12&63,r[f++]=128|k>>6&63,r[f++]=128|63&k;} |
||||
else{if(t<=f+5)break;r[f++]=252|k>>30,r[f++]=128|k>>24&63,r[f++]=128|k>>18&63,r[f++]=128|k>>12&63,r[f++]=128|k>>6&63,r[f++]=128|63&k;} |
||||
} |
||||
return r[f]=0,f-a; |
||||
} |
||||
|
||||
function ReadHeapString(ptr, length) |
||||
{ |
||||
// Read a string from the wasm memory heap to javascript (decoded as UTF8)
|
||||
if (length === 0 || !ptr) return ''; |
||||
for (var hasUtf = 0, t, i = 0; !length || i != length; i++) |
||||
{ |
||||
t = HEAPU8[((ptr)+(i))>>0]; |
||||
if (t == 0 && !length) break; |
||||
hasUtf |= t; |
||||
} |
||||
if (!length) length = i; |
||||
if (hasUtf & 128) |
||||
{ |
||||
for(var r=HEAPU8,o=ptr,p=ptr+length,F=String.fromCharCode,e,f,i,n,C,t,a,g='';;) |
||||
{ |
||||
if(o==p||(e=r[o++],!e)) return g; |
||||
128&e?(f=63&r[o++],192!=(224&e)?(i=63&r[o++],224==(240&e)?e=(15&e)<<12|f<<6|i:(n=63&r[o++],240==(248&e)?e=(7&e)<<18|f<<12|i<<6|n:(C=63&r[o++],248==(252&e)?e=(3&e)<<24|f<<18|i<<12|n<<6|C:(t=63&r[o++],e=(1&e)<<30|f<<24|i<<18|n<<12|C<<6|t))),65536>e?g+=F(e):(a=e-65536,g+=F(55296|a>>10,56320|1023&a))):g+=F((31&e)<<6|f)):g+=F(e); |
||||
} |
||||
} |
||||
// split up into chunks, because .apply on a huge string can overflow the stack
|
||||
for (var ret = '', curr; length > 0; ptr += 1024, length -= 1024) |
||||
ret += String.fromCharCode.apply(String, HEAPU8.subarray(ptr, ptr + Math.min(length, 1024))); |
||||
return ret; |
||||
} |
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
//NOTE(martin): WebGL bindings
|
||||
//------------------------------------------------------------------------------------------------
|
||||
var GLctx = null; |
||||
var GLcounter = 1; |
||||
var GLbuffers = []; |
||||
var GLprograms = []; |
||||
var GLframebuffers = []; |
||||
var GLtextures = []; |
||||
var GLuniforms = []; |
||||
var GLshaders = []; |
||||
var GLprogramInfos = {}; |
||||
var GLpackAlignment = 4; |
||||
var GLunpackAlignment = 4; |
||||
var GLMINI_TEMP_BUFFER_SIZE = 256; |
||||
var GLminiTempBuffer = null; |
||||
var GLminiTempBufferViews = [0]; |
||||
|
||||
function GLsetupContext(canvas, attr) |
||||
{ |
||||
var attr = { majorVersion: 2, minorVersion: 0, antialias: false, alpha: false }; |
||||
var errorInfo = ''; |
||||
try |
||||
{ |
||||
let onContextCreationError = function(event) { errorInfo = event.statusMessage || errorInfo; }; |
||||
canvas.addEventListener('webglcontextcreationerror', onContextCreationError, false); |
||||
try { GLctx = canvas.getContext('webgl2', attr); } |
||||
finally { canvas.removeEventListener('webglcontextcreationerror', onContextCreationError, false); } |
||||
if (!GLctx) throw 'Could not create context'; |
||||
} |
||||
catch (e) { abort('WEBGL', e + (errorInfo ? ' (' + errorInfo + ')' : '')); } |
||||
|
||||
|
||||
var exts = GLctx.getSupportedExtensions(); |
||||
if (exts && exts.length > 0) |
||||
{ |
||||
// These are the 'safe' feature-enabling extensions that don't add any performance impact related to e.g. debugging, and
|
||||
// should be enabled by default so that client GLES2/GL code will not need to go through extra hoops to get its stuff working.
|
||||
// As new extensions are ratified at http://www.khronos.org/registry/webgl/extensions/ , feel free to add your new extensions
|
||||
// here, as long as they don't produce a performance impact for users that might not be using those extensions.
|
||||
// E.g. debugging-related extensions should probably be off by default.
|
||||
var W = 'WEBGL_', O = 'OES_', E = 'EXT_', T = 'texture_', C = 'compressed_'+T; |
||||
var automaticallyEnabledExtensions = [ // Khronos ratified WebGL extensions ordered by number (no debug extensions):
|
||||
O+T+'float', O+T+'half_float', O+'standard_derivatives', |
||||
O+'vertex_array_object', W+C+'s3tc', W+'depth_texture', |
||||
O+'element_index_uint', E+T+'filter_anisotropic', E+'frag_depth', |
||||
W+'draw_buffers', 'ANGLE_instanced_arrays', O+T+'float_linear', |
||||
O+T+'half_float_linear', E+'blend_minmax', E+'shader_texture_lod', |
||||
// Community approved WebGL extensions ordered by number:
|
||||
W+C+'pvrtc', E+'color_buffer_half_float', W+'color_buffer_float', |
||||
E+'sRGB', W+C+'etc1', E+'disjoint_timer_query', |
||||
W+C+'etc', W+C+'astc', E+'color_buffer_float', |
||||
W+C+'s3tc_srgb', E+'disjoint_timer_query_webgl2']; |
||||
exts.forEach(function(ext) |
||||
{ |
||||
if (automaticallyEnabledExtensions.indexOf(ext) != -1) |
||||
{ |
||||
// Calling .getExtension enables that extension permanently, no need to store the return value to be enabled.
|
||||
GLctx.getExtension(ext); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
return true; |
||||
}; |
||||
|
||||
var GLlastError; |
||||
|
||||
function GLBindImports(env) |
||||
{ |
||||
GLminiTempBuffer = new Float32Array(GLMINI_TEMP_BUFFER_SIZE); |
||||
for (var i = 0; i < GLMINI_TEMP_BUFFER_SIZE; i++) GLminiTempBufferViews[i] = GLminiTempBuffer.subarray(0, i+1); |
||||
|
||||
function getNewId(table) |
||||
{ |
||||
var ret = GLcounter++; |
||||
for (var i = table.length; i < ret; i++) table[i] = null; |
||||
return ret; |
||||
} |
||||
function getSource(shader, count, string, length) |
||||
{ |
||||
var source = ''; |
||||
for (var i = 0; i < count; ++i) |
||||
{ |
||||
var frag; |
||||
if (length) |
||||
{ |
||||
var len = HEAP32[(((length)+(i*4))>>2)]; |
||||
if (len < 0) frag = ReadHeapString(HEAP32[(((string)+(i*4))>>2)]); |
||||
else frag = ReadHeapString(HEAP32[(((string)+(i*4))>>2)], len); |
||||
} |
||||
else frag = ReadHeapString(HEAP32[(((string)+(i*4))>>2)]); |
||||
source += frag; |
||||
} |
||||
return source; |
||||
} |
||||
function populateUniformTable(program) |
||||
{ |
||||
var p = GLprograms[program]; |
||||
GLprogramInfos[program] = |
||||
{ |
||||
uniforms: {}, |
||||
maxUniformLength: 0, // This is eagerly computed below, since we already enumerate all uniforms anyway.
|
||||
maxAttributeLength: -1, // This is lazily computed and cached, computed when/if first asked, '-1' meaning not computed yet.
|
||||
maxUniformBlockNameLength: -1 // Lazily computed as well
|
||||
}; |
||||
|
||||
var ptable = GLprogramInfos[program]; |
||||
var utable = ptable.uniforms; |
||||
|
||||
// A program's uniform table maps the string name of an uniform to an integer location of that uniform.
|
||||
// The global GLuniforms map maps integer locations to WebGLUniformLocations.
|
||||
var numUniforms = GLctx.getProgramParameter(p, GLctx.ACTIVE_UNIFORMS); |
||||
for (var i = 0; i < numUniforms; ++i) |
||||
{ |
||||
var u = GLctx.getActiveUniform(p, i); |
||||
|
||||
var name = u.name; |
||||
ptable.maxUniformLength = Math.max(ptable.maxUniformLength, name.length+1); |
||||
|
||||
// Strip off any trailing array specifier we might have got, e.g. '[0]'.
|
||||
if (name.indexOf(']', name.length-1) !== -1) |
||||
{ |
||||
var ls = name.lastIndexOf('['); |
||||
name = name.slice(0, ls); |
||||
} |
||||
|
||||
// Optimize memory usage slightly: If we have an array of uniforms, e.g. 'vec3 colors[3];', then
|
||||
// only store the string 'colors' in utable, and 'colors[0]', 'colors[1]' and 'colors[2]' will be parsed as 'colors'+i.
|
||||
// Note that for the GLuniforms table, we still need to fetch the all WebGLUniformLocations for all the indices.
|
||||
var loc = GLctx.getUniformLocation(p, name); |
||||
|
||||
if (loc != null) |
||||
{ |
||||
var id = getNewId(GLuniforms); |
||||
utable[name] = [u.size, id]; |
||||
GLuniforms[id] = loc; |
||||
|
||||
for (var j = 1; j < u.size; ++j) |
||||
{ |
||||
var n = name + '['+j+']'; |
||||
loc = GLctx.getUniformLocation(p, n); |
||||
id = getNewId(GLuniforms); |
||||
|
||||
GLuniforms[id] = loc; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
function GLrecordError(err) |
||||
{ |
||||
if (!GLlastError) GLlastError = err; |
||||
} |
||||
|
||||
env.glActiveTexture = function(x0) { GLctx.activeTexture(x0); }; |
||||
env.glAttachShader = function(program, shader) { GLctx.attachShader(GLprograms[program], GLshaders[shader]); }; |
||||
env.glBindAttribLocation = function(program, index, name) { GLctx.bindAttribLocation(GLprograms[program], index, ReadHeapString(name)); }; |
||||
env.glBindBuffer = function(target, buffer) { GLctx.bindBuffer(target, buffer ? GLbuffers[buffer] : null); }; |
||||
env.glBindFramebuffer = function(target, framebuffer) { GLctx.bindFramebuffer(target, framebuffer ? GLframebuffers[framebuffer] : null); }; |
||||
env.glBindTexture = function(target, texture) { GLctx.bindTexture(target, texture ? GLtextures[texture] : null); }; |
||||
env.glBlendFunc = function(x0, x1) { GLctx.blendFunc(x0, x1); }; |
||||
env.glBlendFuncSeparate = function(x0, x1, x2, x3) { GLctx.blendFuncSeparate(x0, x1, x2, x3); } |
||||
env.glBlendColor = function(x0, x1, x2, x3) { GLctx.blendColor(x0, x1, x2, x3); } |
||||
env.glBlendEquation = function(x0) { GLctx.blendEquation(x0); } |
||||
env.glBlendEquationSeparate = function(x0, x1) { GLctx.blendEquationSeparate(x0, x1); } |
||||
|
||||
env.glBufferData = function(target, size, data, usage) |
||||
{ |
||||
if (!data) GLctx.bufferData(target, size, usage); |
||||
else GLctx.bufferData(target, HEAPU8.subarray(data, data+size), usage); |
||||
}; |
||||
|
||||
env.glBufferSubData = function(target, offset, size, data) { GLctx.bufferSubData(target, offset, HEAPU8.subarray(data, data+size)); }; |
||||
env.glCheckFramebufferStatus = function(target) { return(GLctx.checkFramebufferStatus(target));}; |
||||
env.glClear = function(x0) { GLctx.clear(x0); }; |
||||
env.glClearColor = function(x0, x1, x2, x3) { GLctx.clearColor(x0, x1, x2, x3); }; |
||||
env.glColorMask = function(red, green, blue, alpha) { GLctx.colorMask(!!red, !!green, !!blue, !!alpha); }; |
||||
env.glCompileShader = function(shader) { |
||||
GLctx.compileShader(GLshaders[shader]); |
||||
var message = GLctx.getShaderInfoLog(GLshaders[shader]); |
||||
if(message.length > 0) |
||||
{ |
||||
throw message; |
||||
} |
||||
}; |
||||
|
||||
env.glCreateProgram = function() |
||||
{ |
||||
var id = getNewId(GLprograms); |
||||
var program = GLctx.createProgram(); |
||||
program.name = id; |
||||
GLprograms[id] = program; |
||||
return id; |
||||
}; |
||||
|
||||
env.glCreateShader = function(shaderType) |
||||
{ |
||||
var id = getNewId(GLshaders); |
||||
GLshaders[id] = GLctx.createShader(shaderType); |
||||
return id; |
||||
}; |
||||
|
||||
env.glDeleteBuffers = function(n, buffers) |
||||
{ |
||||
for (var i = 0; i < n; i++) |
||||
{ |
||||
var id = HEAP32[(((buffers)+(i*4))>>2)]; |
||||
var buffer = GLbuffers[id]; |
||||
|
||||
// From spec: "glDeleteBuffers silently ignores 0's and names that do not correspond to existing buffer objects."
|
||||
if (!buffer) continue; |
||||
|
||||
GLctx.deleteBuffer(buffer); |
||||
buffer.name = 0; |
||||
GLbuffers[id] = null; |
||||
} |
||||
}; |
||||
|
||||
env.glDeleteFramebuffers = function(n, framebuffers) |
||||
{ |
||||
for (var i = 0; i < n; ++i) |
||||
{ |
||||
var id = HEAP32[(((framebuffers)+(i*4))>>2)]; |
||||
var framebuffer = GLframebuffers[id]; |
||||
if (!framebuffer) continue; // GL spec: "glDeleteFramebuffers silently ignores 0s and names that do not correspond to existing framebuffer objects".
|
||||
GLctx.deleteFramebuffer(framebuffer); |
||||
framebuffer.name = 0; |
||||
GLframebuffers[id] = null; |
||||
} |
||||
}; |
||||
|
||||
env.glDeleteProgram = function(id) |
||||
{ |
||||
if (!id) return; |
||||
var program = GLprograms[id]; |
||||
if (!program) |
||||
{ |
||||
// glDeleteProgram actually signals an error when deleting a nonexisting object, unlike some other GL delete functions.
|
||||
GLrecordError(0x0501); // GL_INVALID_VALUE
|
||||
return; |
||||
} |
||||
GLctx.deleteProgram(program); |
||||
program.name = 0; |
||||
GLprograms[id] = null; |
||||
GLprogramInfos[id] = null; |
||||
}; |
||||
|
||||
env.glDeleteShader = function(id) |
||||
{ |
||||
if (!id) return; |
||||
var shader = GLshaders[id]; |
||||
if (!shader) |
||||
{ |
||||
// glDeleteShader actually signals an error when deleting a nonexisting object, unlike some other GL delete functions.
|
||||
GLrecordError(0x0501); // GL_INVALID_VALUE
|
||||
return; |
||||
} |
||||
GLctx.deleteShader(shader); |
||||
GLshaders[id] = null; |
||||
}; |
||||
|
||||
env.glDeleteTextures = function(n, textures) |
||||
{ |
||||
for (var i = 0; i < n; i++) |
||||
{ |
||||
var id = HEAP32[(((textures)+(i*4))>>2)]; |
||||
var texture = GLtextures[id]; |
||||
if (!texture) continue; // GL spec: "glDeleteTextures silently ignores 0s and names that do not correspond to existing textures".
|
||||
GLctx.deleteTexture(texture); |
||||
texture.name = 0; |
||||
GLtextures[id] = null; |
||||
} |
||||
}; |
||||
|
||||
env.glDepthFunc = function(x0) { GLctx.depthFunc(x0); }; |
||||
env.glDepthMask = function(flag) { GLctx.depthMask(!!flag); }; |
||||
env.glDetachShader = function(program, shader) { GLctx.detachShader(GLprograms[program], GLshaders[shader]); }; |
||||
|
||||
env.glDisable = function(x0) { GLctx.disable(x0); }; |
||||
env.glDisableVertexAttribArray = function(index) { GLctx.disableVertexAttribArray(index); }; |
||||
env.glDrawArrays = function(mode, first, count) { GLctx.drawArrays(mode, first, count); }; |
||||
env.glDrawElements = function(mode, count, type, indices) { GLctx.drawElements(mode, count, type, indices); }; |
||||
env.glEnable = function(x0) { GLctx.enable(x0); }; |
||||
env.glEnableVertexAttribArray = function(index) { GLctx.enableVertexAttribArray(index); }; |
||||
env.glFramebufferTexture2D = function(target, attachment, textarget, texture, level) { GLctx.framebufferTexture2D(target, attachment, textarget, GLtextures[texture], level); }; |
||||
|
||||
env.glGenBuffers = function(n, buffers) |
||||
{ |
||||
for (var i = 0; i < n; i++) |
||||
{ |
||||
var buffer = GLctx.createBuffer(); |
||||
if (!buffer) |
||||
{ |
||||
GLrecordError(0x0502); // GL_INVALID_OPERATION
|
||||
while(i < n) HEAP32[(((buffers)+(i++*4))>>2)]=0; |
||||
return; |
||||
} |
||||
var id = getNewId(GLbuffers); |
||||
buffer.name = id; |
||||
GLbuffers[id] = buffer; |
||||
HEAP32[(((buffers)+(i*4))>>2)]=id; |
||||
} |
||||
}; |
||||
|
||||
env.glGenFramebuffers = function(n, ids) |
||||
{ |
||||
for (var i = 0; i < n; ++i) |
||||
{ |
||||
var framebuffer = GLctx.createFramebuffer(); |
||||
if (!framebuffer) |
||||
{ |
||||
GLrecordError(0x0502); // GL_INVALID_OPERATION
|
||||
while(i < n) HEAP32[(((ids)+(i++*4))>>2)]=0; |
||||
return; |
||||
} |
||||
var id = getNewId(GLframebuffers); |
||||
framebuffer.name = id; |
||||
GLframebuffers[id] = framebuffer; |
||||
HEAP32[(((ids)+(i*4))>>2)] = id; |
||||
} |
||||
}; |
||||
|
||||
env.glGenTextures = function(n, textures) |
||||
{ |
||||
for (var i = 0; i < n; i++) |
||||
{ |
||||
var texture = GLctx.createTexture(); |
||||
if (!texture) |
||||
{ |
||||
// GLES + EGL specs don't specify what should happen here, so best to issue an error and create IDs with 0.
|
||||
GLrecordError(0x0502); // GL_INVALID_OPERATION
|
||||
while(i < n) HEAP32[(((textures)+(i++*4))>>2)]=0; |
||||
return; |
||||
} |
||||
var id = getNewId(GLtextures); |
||||
texture.name = id; |
||||
GLtextures[id] = texture; |
||||
HEAP32[(((textures)+(i*4))>>2)]=id; |
||||
} |
||||
}; |
||||
|
||||
env.glGetActiveUniform = function(program, index, bufSize, length, size, type, name) |
||||
{ |
||||
program = GLprograms[program]; |
||||
var info = GLctx.getActiveUniform(program, index); |
||||
if (!info) return; // If an error occurs, nothing will be written to length, size, type and name.
|
||||
|
||||
if (bufSize > 0 && name) |
||||
{ |
||||
var numBytesWrittenExclNull = WriteHeapString(info.name, name, bufSize); |
||||
if (length) HEAP32[((length)>>2)]=numBytesWrittenExclNull; |
||||
} else { |
||||
if (length) HEAP32[((length)>>2)]=0; |
||||
} |
||||
|
||||
if (size) HEAP32[((size)>>2)]=info.size; |
||||
if (type) HEAP32[((type)>>2)]=info.type; |
||||
}; |
||||
|
||||
env.glGetAttribLocation = function(program, name) |
||||
{ |
||||
program = GLprograms[program]; |
||||
name = ReadHeapString(name); |
||||
return GLctx.getAttribLocation(program, name); |
||||
}; |
||||
|
||||
function webGLGet(name_, p, type) |
||||
{ |
||||
// Guard against user passing a null pointer.
|
||||
// Note that GLES2 spec does not say anything about how passing a null pointer should be treated.
|
||||
// Testing on desktop core GL 3, the application crashes on glGetIntegerv to a null pointer, but
|
||||
// better to report an error instead of doing anything random.
|
||||
if (!p) { GLrecordError(0x0501); return; } // GL_INVALID_VALUE
|
||||
|
||||
var ret = undefined; |
||||
switch(name_) |
||||
{ |
||||
// Handle a few trivial GLES values
|
||||
case 0x8DFA: ret = 1; break; // GL_SHADER_COMPILER
|
||||
case 0x8DF8: // GL_SHADER_BINARY_FORMATS
|
||||
if (type !== 'Integer' && type !== 'Integer64') GLrecordError(0x0500); // GL_INVALID_ENUM
|
||||
return; // Do not write anything to the out pointer, since no binary formats are supported.
|
||||
case 0x8DF9: ret = 0; break; // GL_NUM_SHADER_BINARY_FORMATS
|
||||
case 0x86A2: // GL_NUM_COMPRESSED_TEXTURE_FORMATS
|
||||
// WebGL doesn't have GL_NUM_COMPRESSED_TEXTURE_FORMATS (it's obsolete since GL_COMPRESSED_TEXTURE_FORMATS returns a JS array that can be queried for length),
|
||||
// so implement it ourselves to allow C++ GLES2 code get the length.
|
||||
var formats = GLctx.getParameter(0x86A3); // GL_COMPRESSED_TEXTURE_FORMATS
|
||||
ret = formats.length; |
||||
break; |
||||
} |
||||
|
||||
if (ret === undefined) |
||||
{ |
||||
var result = GLctx.getParameter(name_); |
||||
switch (typeof(result)) |
||||
{ |
||||
case 'number': |
||||
ret = result; |
||||
break; |
||||
case 'boolean': |
||||
ret = result ? 1 : 0; |
||||
break; |
||||
case 'string': |
||||
GLrecordError(0x0500); // GL_INVALID_ENUM
|
||||
return; |
||||
case 'object': |
||||
if (result === null) |
||||
{ |
||||
// null is a valid result for some (e.g., which buffer is bound - perhaps nothing is bound), but otherwise
|
||||
// can mean an invalid name_, which we need to report as an error
|
||||
switch(name_) |
||||
{ |
||||
case 0x8894: // ARRAY_BUFFER_BINDING
|
||||
case 0x8B8D: // CURRENT_PROGRAM
|
||||
case 0x8895: // ELEMENT_ARRAY_BUFFER_BINDING
|
||||
case 0x8CA6: // FRAMEBUFFER_BINDING
|
||||
case 0x8CA7: // RENDERBUFFER_BINDING
|
||||
case 0x8069: // TEXTURE_BINDING_2D
|
||||
case 0x8514: // TEXTURE_BINDING_CUBE_MAP
|
||||
ret = 0; |
||||
break; |
||||
default: |
||||
GLrecordError(0x0500); // GL_INVALID_ENUM
|
||||
return; |
||||
} |
||||
} |
||||
else if (result instanceof Float32Array || result instanceof Uint32Array || result instanceof Int32Array || result instanceof Array) |
||||
{ |
||||
for (var i = 0; i < result.length; ++i) { |
||||
switch (type) |
||||
{ |
||||
case 'Integer': HEAP32[(((p)+(i*4))>>2)]=result[i]; break; |
||||
case 'Float': HEAPF32[(((p)+(i*4))>>2)]=result[i]; break; |
||||
case 'Boolean': HEAPU8[(((p)+(i))>>0)]=result[i] ? 1 : 0; break; |
||||
default: abort('WEBGL', 'internal glGet error, bad type: ' + type); |
||||
} |
||||
} |
||||
return; |
||||
} |
||||
else if (result instanceof WebGLBuffer || result instanceof WebGLProgram || result instanceof WebGLFramebuffer || result instanceof WebGLRenderbuffer || result instanceof WebGLTexture) |
||||
{ |
||||
ret = result.name | 0; |
||||
} |
||||
else |
||||
{ |
||||
GLrecordError(0x0500); // GL_INVALID_ENUM
|
||||
return; |
||||
} |
||||
break; |
||||
default: |
||||
GLrecordError(0x0500); // GL_INVALID_ENUM
|
||||
return; |
||||
} |
||||
} |
||||
|
||||
switch (type) |
||||
{ |
||||
case 'Integer64': (tempI64 = [ret>>>0,(tempDouble=ret,(+(Math.abs(tempDouble))) >= 1.0 ? (tempDouble > 0.0 ? ((Math.min((+(Math.floor((tempDouble)/4294967296.0))), 4294967295.0))|0)>>>0 : (~~((+(Math.ceil((tempDouble - +(((~~(tempDouble)))>>>0))/4294967296.0)))))>>>0) : 0)],HEAP32[((p)>>2)]=tempI64[0],HEAP32[(((p)+(4))>>2)]=tempI64[1]); break; |
||||
case 'Integer': HEAP32[((p)>>2)] = ret; break; |
||||
case 'Float': HEAPF32[((p)>>2)] = ret; break; |
||||
case 'Boolean': HEAPU8[((p)>>0)] = ret ? 1 : 0; break; |
||||
default: abort('WEBGL', 'internal glGet error, bad type: ' + type); |
||||
} |
||||
} |
||||
|
||||
env.glGetError = function() |
||||
{ |
||||
if (GLlastError) |
||||
{ |
||||
var e = GLlastError; |
||||
GLlastError = 0; |
||||
return e; |
||||
} |
||||
return GLctx.getError(); |
||||
}; |
||||
|
||||
env.glGetIntegerv = function(name_, p) |
||||
{ |
||||
webGLGet(name_, p, 'Integer'); |
||||
}; |
||||
|
||||
env.glGetProgramInfoLog = function(program, maxLength, length, infoLog) |
||||
{ |
||||
var log = GLctx.getProgramInfoLog(GLprograms[program]); |
||||
if (log === null) log = '(unknown error)'; |
||||
if (maxLength > 0 && infoLog) |
||||
{ |
||||
var numBytesWrittenExclNull = WriteHeapString(log, infoLog, maxLength); |
||||
if (length) HEAP32[((length)>>2)]=numBytesWrittenExclNull; |
||||
} |
||||
else if (length) HEAP32[((length)>>2)]=0; |
||||
}; |
||||
|
||||
env.glGetProgramiv = function(program, pname, p) |
||||
{ |
||||
if (!p) |
||||
{ |
||||
// GLES2 specification does not specify how to behave if p is a null pointer. Since calling this function does not make sense
|
||||
// if p == null, issue a GL error to notify user about it.
|
||||
GLrecordError(0x0501); // GL_INVALID_VALUE
|
||||
return; |
||||
} |
||||
|
||||
if (program >= GLcounter) |
||||
{ |
||||
GLrecordError(0x0501); // GL_INVALID_VALUE
|
||||
return; |
||||
} |
||||
|
||||
var ptable = GLprogramInfos[program]; |
||||
if (!ptable) |
||||
{ |
||||
GLrecordError(0x0502); //GL_INVALID_OPERATION
|
||||
return; |
||||
} |
||||
|
||||
if (pname == 0x8B84) // GL_INFO_LOG_LENGTH
|
||||
{ |
||||
var log = GLctx.getProgramInfoLog(GLprograms[program]); |
||||
if (log === null) log = '(unknown error)'; |
||||
HEAP32[((p)>>2)] = log.length + 1; |
||||
} |
||||
else if (pname == 0x8B87) //GL_ACTIVE_UNIFORM_MAX_LENGTH
|
||||
{ |
||||
HEAP32[((p)>>2)] = ptable.maxUniformLength; |
||||
} |
||||
else if (pname == 0x8B8A) //GL_ACTIVE_ATTRIBUTE_MAX_LENGTH
|
||||
{ |
||||
if (ptable.maxAttributeLength == -1) |
||||
{ |
||||
program = GLprograms[program]; |
||||
var numAttribs = GLctx.getProgramParameter(program, GLctx.ACTIVE_ATTRIBUTES); |
||||
ptable.maxAttributeLength = 0; // Spec says if there are no active attribs, 0 must be returned.
|
||||
for (var i = 0; i < numAttribs; ++i) |
||||
{ |
||||
var activeAttrib = GLctx.getActiveAttrib(program, i); |
||||
ptable.maxAttributeLength = Math.max(ptable.maxAttributeLength, activeAttrib.name.length+1); |
||||
} |
||||
} |
||||
HEAP32[((p)>>2)] = ptable.maxAttributeLength; |
||||
} |
||||
else if (pname == 0x8A35) //GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH
|
||||
{ |
||||
if (ptable.maxUniformBlockNameLength == -1) |
||||
{ |
||||
program = GLprograms[program]; |
||||
var numBlocks = GLctx.getProgramParameter(program, GLctx.ACTIVE_UNIFORM_BLOCKS); |
||||
ptable.maxUniformBlockNameLength = 0; |
||||
for (var i = 0; i < numBlocks; ++i) |
||||
{ |
||||
var activeBlockName = GLctx.getActiveUniformBlockName(program, i); |
||||
ptable.maxUniformBlockNameLength = Math.max(ptable.maxUniformBlockNameLength, activeBlockName.length+1); |
||||
} |
||||
} |
||||
HEAP32[((p)>>2)] = ptable.maxUniformBlockNameLength; |
||||
} |
||||
else |
||||
{ |
||||
HEAP32[((p)>>2)] = GLctx.getProgramParameter(GLprograms[program], pname); |
||||
} |
||||
}; |
||||
|
||||
env.glGetShaderInfoLog = function(shader, maxLength, length, infoLog) |
||||
{ |
||||
var log = GLctx.getShaderInfoLog(GLshaders[shader]); |
||||
if (log === null) log = '(unknown error)'; |
||||
if (maxLength > 0 && infoLog) |
||||
{ |
||||
var numBytesWrittenExclNull = WriteHeapString(log, infoLog, maxLength); |
||||
if (length) HEAP32[((length)>>2)] = numBytesWrittenExclNull; |
||||
} |
||||
else if (length) HEAP32[((length)>>2)] = 0; |
||||
}; |
||||
|
||||
env.glGetShaderiv = function(shader, pname, p) |
||||
{ |
||||
if (!p) |
||||
{ |
||||
// GLES2 specification does not specify how to behave if p is a null pointer. Since calling this function does not make sense
|
||||
// if p == null, issue a GL error to notify user about it.
|
||||
GLrecordError(0x0501); // GL_INVALID_VALUE
|
||||
return; |
||||
} |
||||
if (pname == 0x8B84) // GL_INFO_LOG_LENGTH
|
||||
{ |
||||
var log = GLctx.getShaderInfoLog(GLshaders[shader]); |
||||
if (log === null) log = '(unknown error)'; |
||||
HEAP32[((p)>>2)] = log.length + 1; |
||||
} |
||||
else if (pname == 0x8B88) // GL_SHADER_SOURCE_LENGTH
|
||||
{ |
||||
var source = GLctx.getShaderSource(GLshaders[shader]); |
||||
var sourceLength = (source === null || source.length == 0) ? 0 : source.length + 1; |
||||
HEAP32[((p)>>2)] = sourceLength; |
||||
} |
||||
else HEAP32[((p)>>2)] = GLctx.getShaderParameter(GLshaders[shader], pname); |
||||
}; |
||||
|
||||
env.glGetUniformLocation = function(program, name) |
||||
{ |
||||
name = ReadHeapString(name); |
||||
|
||||
var arrayOffset = 0; |
||||
if (name.indexOf(']', name.length-1) !== -1) |
||||
{ |
||||
// If user passed an array accessor "[index]", parse the array index off the accessor.
|
||||
var ls = name.lastIndexOf('['); |
||||
var arrayIndex = name.slice(ls+1, -1); |
||||
if (arrayIndex.length > 0) |
||||
{ |
||||
arrayOffset = parseInt(arrayIndex); |
||||
if (arrayOffset < 0) return -1; |
||||
} |
||||
name = name.slice(0, ls); |
||||
} |
||||
|
||||
var ptable = GLprogramInfos[program]; |
||||
if (!ptable){ console.log("ptable == null"); return -1}; |
||||
var utable = ptable.uniforms; |
||||
var uniformInfo = utable[name]; // returns pair [ dimension_of_uniform_array, uniform_location ]
|
||||
if (uniformInfo && arrayOffset < uniformInfo[0]) |
||||
{ |
||||
// Check if user asked for an out-of-bounds element, i.e. for 'vec4 colors[3];' user could ask for 'colors[10]' which should return -1.
|
||||
return uniformInfo[1] + arrayOffset; |
||||
} |
||||
return -1; |
||||
}; |
||||
|
||||
env.glLineWidth = function(x0) { GLctx.lineWidth(x0); }; |
||||
|
||||
env.glLinkProgram = function(program) |
||||
{ |
||||
GLctx.linkProgram(GLprograms[program]); |
||||
GLprogramInfos[program] = null; // uniforms no longer keep the same names after linking
|
||||
populateUniformTable(program); |
||||
}; |
||||
|
||||
env.glPixelStorei = function(pname, param) |
||||
{ |
||||
if (pname == 0x0D05) GLpackAlignment = param; //GL_PACK_ALIGNMENT
|
||||
else if (pname == 0x0cf5) GLunpackAlignment = param; //GL_UNPACK_ALIGNMENT
|
||||
GLctx.pixelStorei(pname, param); |
||||
}; |
||||
|
||||
function webGLGetTexPixelData(type, format, width, height, pixels, internalFormat) |
||||
{ |
||||
var sizePerPixel; |
||||
var numChannels; |
||||
switch(format) |
||||
{ |
||||
case 0x1906: case 0x1909: case 0x1902: numChannels = 1; break; //GL_ALPHA, GL_LUMINANCE, GL_DEPTH_COMPONENT
|
||||
case 0x190A: numChannels = 2; break; //GL_LUMINANCE_ALPHA
|
||||
case 0x1907: case 0x8C40: numChannels = 3; break; //GL_RGB, GL_SRGB_EXT
|
||||
case 0x1908: case 0x8C42: numChannels = 4; break; //GL_RGBA, GL_SRGB_ALPHA_EXT
|
||||
default: GLrecordError(0x0500); return null; //GL_INVALID_ENUM
|
||||
} |
||||
switch (type) |
||||
{ |
||||
case 0x1401: sizePerPixel = numChannels*1; break; //GL_UNSIGNED_BYTE
|
||||
case 0x1403: case 0x8D61: sizePerPixel = numChannels*2; break; //GL_UNSIGNED_SHORT, GL_HALF_FLOAT_OES
|
||||
case 0x1405: case 0x1406: sizePerPixel = numChannels*4; break; //GL_UNSIGNED_INT, GL_FLOAT
|
||||
case 0x84FA: sizePerPixel = 4; break; //GL_UNSIGNED_INT_24_8_WEBGL/GL_UNSIGNED_INT_24_8
|
||||
case 0x8363: case 0x8033: case 0x8034: sizePerPixel = 2; break; //GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1
|
||||
default: GLrecordError(0x0500); return null; //GL_INVALID_ENUM
|
||||
} |
||||
|
||||
function roundedToNextMultipleOf(x, y) { return Math.floor((x + y - 1) / y) * y; } |
||||
var plainRowSize = width * sizePerPixel; |
||||
var alignedRowSize = roundedToNextMultipleOf(plainRowSize, GLunpackAlignment); |
||||
var bytes = (height <= 0 ? 0 : ((height - 1) * alignedRowSize + plainRowSize)); |
||||
|
||||
switch(type) |
||||
{ |
||||
case 0x1401: return HEAPU8.subarray((pixels),(pixels+bytes)); //GL_UNSIGNED_BYTE
|
||||
case 0x1406: return HEAPF32.subarray((pixels)>>2,(pixels+bytes)>>2); //GL_FLOAT
|
||||
case 0x1405: case 0x84FA: return HEAPU32.subarray((pixels)>>2,(pixels+bytes)>>2); //GL_UNSIGNED_INT, GL_UNSIGNED_INT_24_8_WEBGL/GL_UNSIGNED_INT_24_8
|
||||
case 0x1403: case 0x8363: case 0x8033: case 0x8034: case 0x8D61: return HEAPU16.subarray((pixels)>>1,(pixels+bytes)>>1); //GL_UNSIGNED_SHORT, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_4_4_4_4, GL_UNSIGNED_SHORT_5_5_5_1, GL_HALF_FLOAT_OES
|
||||
default: GLrecordError(0x0500); return null; //GL_INVALID_ENUM
|
||||
} |
||||
} |
||||
|
||||
env.glReadPixels = function(x, y, width, height, format, type, pixels) |
||||
{ |
||||
var pixelData = webGLGetTexPixelData(type, format, width, height, pixels, format); |
||||
if (!pixelData) return GLrecordError(0x0500); // GL_INVALID_ENUM
|
||||
GLctx.readPixels(x, y, width, height, format, type, pixelData); |
||||
}; |
||||
|
||||
env.glScissor = function(x0, x1, x2, x3) { GLctx.scissor(x0, x1, x2, x3) }; |
||||
|
||||
env.glShaderSource = function(shader, count, string, length) |
||||
{ |
||||
var source = getSource(shader, count, string, length); |
||||
GLctx.shaderSource(GLshaders[shader], source); |
||||
}; |
||||
|
||||
env.glTexImage2D = function(target, level, internalFormat, width, height, border, format, type, pixels) |
||||
{ |
||||
var pixelData = null; |
||||
if (pixels) pixelData = webGLGetTexPixelData(type, format, width, height, pixels, internalFormat); |
||||
GLctx.texImage2D(target, level, internalFormat, width, height, border, format, type, pixelData); |
||||
}; |
||||
|
||||
env.glTexParameteri = function(x0, x1, x2) |
||||
{ |
||||
GLctx.texParameteri(x0, x1, x2); |
||||
}; |
||||
|
||||
env.glTexSubImage2D = function(target, level, xoffset, yoffset, width, height, format, type, pixels) |
||||
{ |
||||
var pixelData = null; |
||||
if (pixels) pixelData = webGLGetTexPixelData(type, format, width, height, pixels, 0); |
||||
GLctx.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixelData); |
||||
}; |
||||
|
||||
env.glUniform1f = function(loc, v0) { GLctx.uniform1f(GLuniforms[loc], v0); }; |
||||
env.glUniform1i = function(loc, v0) { GLctx.uniform1i(GLuniforms[loc], v0); }; |
||||
env.glUniform2f = function(loc, v0, v1) { GLctx.uniform2f(GLuniforms[loc], v0, v1); }; |
||||
env.glUniform3f = function(loc, v0, v1, v2) { GLctx.uniform3f(GLuniforms[loc], v0, v1, v2); }; |
||||
|
||||
env.glUniform3fv = function(loc, count, value) |
||||
{ |
||||
var view; |
||||
if (3*count <= GLMINI_TEMP_BUFFER_SIZE) |
||||
{ |
||||
// avoid allocation when uploading few enough uniforms
|
||||
view = GLminiTempBufferViews[3*count-1]; |
||||
for (var ptr = value>>2, i = 0; i != 3*count; i++) |
||||
{ |
||||
view[i] = HEAPF32[ptr+i]; |
||||
} |
||||
} |
||||
else view = HEAPF32.subarray((value)>>2,(value+count*12)>>2); |
||||
GLctx.uniform3fv(GLuniforms[loc], view); |
||||
}; |
||||
|
||||
env.glUniform4f = function(loc, v0, v1, v2, v3) { GLctx.uniform4f(GLuniforms[loc], v0, v1, v2, v3); }; |
||||
|
||||
env.glUniformMatrix4fv = function(loc, count, transpose, value) |
||||
{ |
||||
count<<=4; |
||||
var view; |
||||
if (count <= GLMINI_TEMP_BUFFER_SIZE) |
||||
{ |
||||
// avoid allocation when uploading few enough uniforms
|
||||
view = GLminiTempBufferViews[count-1]; |
||||
for (var ptr = value>>2, i = 0; i != count; i += 4) |
||||
{ |
||||
view[i ] = HEAPF32[ptr+i ]; |
||||
view[i+1] = HEAPF32[ptr+i+1]; |
||||
view[i+2] = HEAPF32[ptr+i+2]; |
||||
view[i+3] = HEAPF32[ptr+i+3]; |
||||
} |
||||
} |
||||
else view = HEAPF32.subarray((value)>>2,(value+count*4)>>2); |
||||
GLctx.uniformMatrix4fv(GLuniforms[loc], !!transpose, view); |
||||
}; |
||||
|
||||
env.glUseProgram = function(program) { GLctx.useProgram(program ? GLprograms[program] : null); }; |
||||
env.glVertexAttrib4f = function(x0, x1, x2, x3, x4) { GLctx.vertexAttrib4f(x0, x1, x2, x3, x4); }; |
||||
env.glVertexAttrib4fv = function(index, v) { GLctx.vertexAttrib4f(index, HEAPF32[v>>2], HEAPF32[v+4>>2], HEAPF32[v+8>>2], HEAPF32[v+12>>2]); }; |
||||
env.glVertexAttribPointer = function(index, size, type, normalized, stride, ptr) { GLctx.vertexAttribPointer(index, size, type, !!normalized, stride, ptr); }; |
||||
|
||||
env.glViewport = function(x0, x1, x2, x3) { GLctx.viewport(x0, x1, x2, x3); }; |
||||
|
||||
var initTime; |
||||
|
||||
env.WAJS_SetupCanvas = function(width, height) |
||||
{ |
||||
var cnvs = APP.canvas; |
||||
cnvs.width = width; |
||||
cnvs.height = height; |
||||
cnvs.height = cnvs.clientHeight; |
||||
cnvs.width = cnvs.clientWidth; |
||||
|
||||
if (!GLsetupContext(cnvs)) return; |
||||
|
||||
initTime = Date.now(); |
||||
|
||||
var draw_func_ex = function() { if (ABORT) return; window.requestAnimationFrame(draw_func_ex); APP.instance.exports.WAFNDraw(); }; |
||||
window.requestAnimationFrame(draw_func_ex); |
||||
}; |
||||
|
||||
env.WAJS_GetTime = function(type) { return Date.now() - initTime; }; |
||||
} |
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
//NOTE(martin): Fill environments with functions
|
||||
//------------------------------------------------------------------------------------------------
|
||||
|
||||
const env = {}; |
||||
|
||||
env.console_log = console_log; |
||||
env.cosf = Math.cos; |
||||
env.sinf = Math.sin; |
||||
env.tanf = Math.tan; |
||||
env.sqrtf = Math.sqrt; |
||||
env.floorf = Math.floor; |
||||
|
||||
env.canvas_width = function(){return(APP.canvas.width);}; |
||||
env.canvas_height = function(){return(APP.canvas.height);}; |
||||
env.window_width = function(){return(window.innerWidth);}; |
||||
env.window_height = function(){return(window.innerHeight);}; |
||||
|
||||
GLBindImports(env); |
||||
|
||||
//------------------------------------------------------------------------------------------------
|
||||
//NOTE(martin): wasm instantiation
|
||||
//------------------------------------------------------------------------------------------------
|
||||
async function runWasm(canvasSize) |
||||
{ |
||||
try |
||||
{ |
||||
if(WebAssembly.instantiateStreaming) |
||||
{ |
||||
const response = fetch('./main.wasm'); |
||||
({ instance: APP.instance } = await WebAssembly.instantiateStreaming(response, { env })); |
||||
} |
||||
else |
||||
{ |
||||
const response = await fetch('./main.wasm'); |
||||
const bytes = await response.arrayBuffer(); |
||||
({ instance: APP.instance } = await WebAssembly.instantiate(bytes, { env })); |
||||
} |
||||
} |
||||
catch(e) |
||||
{ |
||||
console.log(`error instancing wasm: ${e}`); |
||||
return; |
||||
} |
||||
|
||||
APP.memory = APP.instance.exports.memory; |
||||
MemorySetBufferViews(); |
||||
|
||||
APP.canvas.addEventListener('dblclick', APP.instance.exports.reset); |
||||
APP.canvas.addEventListener('mouseup', (ev) => APP.instance.exports.mouse_up()); |
||||
APP.canvas.addEventListener('mousedown', (ev) => APP.instance.exports.mouse_down(ev.offsetX, ev.offsetY)); |
||||
APP.canvas.addEventListener('mousemove', (ev) => APP.instance.exports.mouse_move(ev.offsetX, ev.offsetY, ev.movementX, ev.movementY)); |
||||
|
||||
if(APP.instance.exports.init) |
||||
{ |
||||
var result = APP.instance.exports.init(canvasSize) |
||||
} |
||||
else |
||||
{ |
||||
console.log('no exported init function'); |
||||
} |
||||
} |
||||
|
||||
console.log("Hello world, from js"); |
||||
runWasm(800); |
||||
@ -0,0 +1,11 @@ |
||||
On macOS, homebrew version of llvm must be used instead of xcode's one: |
||||
|
||||
> brew install llvm |
||||
|
||||
Have to run a local http server to serve files: |
||||
|
||||
> python3 -m http.server |
||||
|
||||
|
||||
brew llvm toolchain seems to be broken and does not find open gl header files, and trying to include them from the framework path in /applications/xcode/ etc creates a bunch of other errors, so the workaround was to download mesa openGL headers, and tweak glext.h so that it includes stdint.h instead of inttypes.h (because brew's llvm can't seem to find this one). This was annoying as fuck. |
||||
Zig cc seem to have everything needed but doesn't recognize --export-all and such, so I could'nt find a way to use it. |
||||
@ -0,0 +1,28 @@ |
||||
#!/bin/bash |
||||
|
||||
for file in ./src/shaders/* ; do |
||||
|
||||
NAME=$(basename $file) |
||||
NAME=${NAME%".glsl"} |
||||
HEADER="$NAME"_shader.h |
||||
PTR_NAME="$NAME"_shader_src |
||||
DEF_GUARD=`echo "$NAME" | tr '[:lower:]' '[:upper:]'` |
||||
DEF_GUARD=_"$DEF_GUARD"_H_ |
||||
|
||||
echo -e \ |
||||
"//*************************************************\n"\ |
||||
"// $HEADER\n"\ |
||||
"//*************************************************\n"\ |
||||
"#ifndef $DEF_GUARD\n"\ |
||||
"#define $DEF_GUARD\n"\ |
||||
" const char* $PTR_NAME = " > src/"$HEADER" |
||||
|
||||
cat $file | ( while IFS= read line ; do |
||||
echo " \"$line\n\"" >> src/"$HEADER" |
||||
done ) |
||||
|
||||
echo -e \ |
||||
";\n"\ |
||||
"#endif // $DEF_GUARD\n" >> src/"$HEADER"; |
||||
|
||||
done |
||||
@ -0,0 +1,852 @@ |
||||
/************************************************************//**
|
||||
* |
||||
* @file: main.cpp |
||||
* @author: Martin Fouilleul |
||||
* @date: 27/02/2022 |
||||
* @revision: |
||||
* |
||||
*****************************************************************/ |
||||
|
||||
#include<GLES3/gl3.h> |
||||
|
||||
#include<stdbool.h> |
||||
|
||||
#define STB_SPRINTF_IMPLEMENTATION |
||||
#include"stb_sprintf.h" |
||||
|
||||
#include"blit_vertex_shader.h" |
||||
#include"blit_fragment_shader.h" |
||||
#include"common_vertex_shader.h" |
||||
#include"advect_shader.h" |
||||
#include"divergence_shader.h" |
||||
#include"jacobi_step_shader.h" |
||||
#include"multigrid_restrict_residual_shader.h" |
||||
#include"multigrid_correct_shader.h" |
||||
#include"subtract_pressure_shader.h" |
||||
#include"splat_shader.h" |
||||
|
||||
void* memcpy(void* dst, void* src, unsigned long n) |
||||
{ |
||||
for(unsigned long i=0; i<n; i++) |
||||
{ |
||||
((char*)dst)[i] = ((char*)src)[i]; |
||||
} |
||||
return(dst); |
||||
} |
||||
|
||||
//----------------------------------------------------------------
|
||||
// Functions defined in loader.js
|
||||
//----------------------------------------------------------------
|
||||
extern void WAJS_SetupCanvas(int width, int height); |
||||
extern unsigned int WAJS_GetTime(); |
||||
|
||||
#define minimum(x, y) (((x) < (y))? (x) : (y)) |
||||
#define maximum(x, y) (((x) > (y))? (x) : (y)) |
||||
|
||||
#define square(x) ((x)*(x)) |
||||
|
||||
float fabs(float x) |
||||
{ |
||||
return(x>0 ? x : -x); |
||||
} |
||||
|
||||
#define PI 3.14159 |
||||
#define EULER 2.71828 |
||||
#define SQRT2 1.41421 |
||||
|
||||
extern float cosf(float x); |
||||
extern float sinf(float x); |
||||
extern float tanf(float x); |
||||
extern float sqrtf(float x); |
||||
|
||||
extern float floorf(float x); |
||||
|
||||
extern void console_log(const char* ptr, int len); |
||||
|
||||
extern float canvas_width(); |
||||
extern float canvas_height(); |
||||
extern float window_width(); |
||||
extern float window_height(); |
||||
|
||||
//----------------------------------------------------------------
|
||||
//NOTE(martin): c string helpers
|
||||
//----------------------------------------------------------------
|
||||
int string_len(const char* ptr) |
||||
{ |
||||
int len = 0; |
||||
while(ptr[len] != '\0') |
||||
{ |
||||
len++; |
||||
} |
||||
return(len); |
||||
} |
||||
|
||||
void console_log_cstring(const char* ptr) |
||||
{ |
||||
console_log(ptr, string_len(ptr)); |
||||
} |
||||
|
||||
void console_log_fmt(const char* fmt, ...) |
||||
{ |
||||
va_list ap; |
||||
va_start(ap, fmt); |
||||
|
||||
char buffer[4096]; |
||||
|
||||
int len = stbsp_vsnprintf(buffer, 4096, fmt, ap); |
||||
console_log(buffer, len); |
||||
|
||||
va_end(ap); |
||||
} |
||||
|
||||
//----------------------------------------------------------------
|
||||
//NOTE(martin): GL vertex struct and identifiers
|
||||
//----------------------------------------------------------------
|
||||
typedef struct Vertex { float x, y; } Vertex; |
||||
|
||||
typedef struct advect_program |
||||
{ |
||||
GLuint prog; |
||||
|
||||
GLint pos; |
||||
GLint src; |
||||
GLint velocity; |
||||
GLint delta; |
||||
GLint dissipation; |
||||
|
||||
} advect_program; |
||||
|
||||
typedef struct div_program |
||||
{ |
||||
GLuint prog; |
||||
GLint pos; |
||||
GLint src; |
||||
|
||||
} div_program; |
||||
|
||||
typedef struct jacobi_program |
||||
{ |
||||
GLuint prog; |
||||
GLint pos; |
||||
GLint xTex; |
||||
GLint bTex; |
||||
|
||||
} jacobi_program; |
||||
|
||||
typedef struct multigrid_restrict_residual_program |
||||
{ |
||||
GLuint prog; |
||||
GLint pos; |
||||
GLint xTex; |
||||
GLint bTex; |
||||
|
||||
} multigrid_restrict_residual_program; |
||||
|
||||
typedef struct multigrid_correct_program |
||||
{ |
||||
GLuint prog; |
||||
GLint pos; |
||||
GLint src; |
||||
GLint error; |
||||
GLint invGridSize; |
||||
|
||||
} multigrid_correct_program; |
||||
|
||||
typedef struct subtract_program |
||||
{ |
||||
GLuint prog; |
||||
|
||||
GLint pos; |
||||
GLint src; |
||||
GLint pressure; |
||||
GLint invGridSize; |
||||
|
||||
} subtract_program; |
||||
|
||||
typedef struct blit_program |
||||
{ |
||||
GLuint prog; |
||||
|
||||
GLint pos; |
||||
GLint mvp; |
||||
GLint tex; |
||||
} blit_program; |
||||
|
||||
typedef struct splat_program |
||||
{ |
||||
GLuint prog; |
||||
|
||||
GLint pos; |
||||
GLint src; |
||||
GLint splatPos; |
||||
GLint splatColor; |
||||
GLint radius; |
||||
GLint additive; |
||||
GLint blending; |
||||
|
||||
} splat_program; |
||||
|
||||
typedef struct frame_buffer |
||||
{ |
||||
GLuint textures[2]; |
||||
GLuint fbos[2]; |
||||
} frame_buffer; |
||||
|
||||
advect_program advectProgram; |
||||
div_program divProgram; |
||||
jacobi_program jacobiProgram; |
||||
multigrid_restrict_residual_program multigridRestrictResidualProgram; |
||||
multigrid_correct_program multigridCorrectProgram; |
||||
|
||||
subtract_program subtractProgram; |
||||
splat_program splatProgram; |
||||
blit_program blitProgram; |
||||
|
||||
|
||||
frame_buffer colorBuffer; |
||||
frame_buffer velocityBuffer; |
||||
|
||||
const int MULTIGRID_COUNT = 4; |
||||
frame_buffer pressureBuffer[4]; |
||||
frame_buffer divBuffer[4]; |
||||
|
||||
GLuint vertexBuffer; |
||||
|
||||
//----------------------------------------------------------------
|
||||
//NOTE(martin): initialization
|
||||
//----------------------------------------------------------------
|
||||
|
||||
GLuint compile_shader(const char* vs, const char* fs) |
||||
{ |
||||
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); |
||||
glShaderSource(vertexShader, 1, &vs, 0); |
||||
glCompileShader(vertexShader); |
||||
|
||||
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); |
||||
glShaderSource(fragmentShader, 1, &fs, 0); |
||||
glCompileShader(fragmentShader); |
||||
|
||||
GLuint prog = glCreateProgram(); |
||||
glAttachShader(prog, vertexShader); |
||||
glAttachShader(prog, fragmentShader); |
||||
glLinkProgram(prog); |
||||
|
||||
return(prog); |
||||
} |
||||
|
||||
void init_advect(advect_program* program) |
||||
{ |
||||
console_log_fmt("compiling advect..."); |
||||
program->prog = compile_shader(common_vertex_shader_src, advect_shader_src); |
||||
program->pos = glGetAttribLocation(program->prog, "pos"); |
||||
program->src = glGetUniformLocation(program->prog, "src"); |
||||
program->velocity = glGetUniformLocation(program->prog, "velocity"); |
||||
program->delta = glGetUniformLocation(program->prog, "delta"); |
||||
program->dissipation = glGetUniformLocation(program->prog, "dissipation"); |
||||
} |
||||
|
||||
void init_div(div_program* program) |
||||
{ |
||||
console_log_fmt("compiling div..."); |
||||
program->prog = compile_shader(common_vertex_shader_src, divergence_shader_src); |
||||
program->pos = glGetAttribLocation(program->prog, "pos"); |
||||
program->src = glGetUniformLocation(program->prog, "src"); |
||||
} |
||||
|
||||
void init_jacobi(jacobi_program* program) |
||||
{ |
||||
console_log_fmt("compiling jacobi..."); |
||||
program->prog = compile_shader(common_vertex_shader_src, jacobi_step_shader_src); |
||||
program->pos = glGetAttribLocation(program->prog, "pos"); |
||||
program->xTex = glGetUniformLocation(program->prog, "xTex"); |
||||
program->bTex = glGetUniformLocation(program->prog, "bTex"); |
||||
} |
||||
|
||||
void init_multigrid_restrict_residual(multigrid_restrict_residual_program* program) |
||||
{ |
||||
console_log_fmt("compiling multigrid restrict residual..."); |
||||
program->prog = compile_shader(common_vertex_shader_src, multigrid_restrict_residual_shader_src); |
||||
program->pos = glGetAttribLocation(program->prog, "pos"); |
||||
program->xTex = glGetUniformLocation(program->prog, "xTex"); |
||||
program->bTex = glGetUniformLocation(program->prog, "bTex"); |
||||
} |
||||
|
||||
void init_multigrid_correct(multigrid_correct_program* program) |
||||
{ |
||||
console_log_fmt("compiling multigrid correct..."); |
||||
program->prog = compile_shader(common_vertex_shader_src, multigrid_correct_shader_src); |
||||
program->pos = glGetAttribLocation(program->prog, "pos"); |
||||
program->src = glGetUniformLocation(program->prog, "src"); |
||||
program->error = glGetUniformLocation(program->prog, "error"); |
||||
program->invGridSize = glGetUniformLocation(program->prog, "invGridSize"); |
||||
} |
||||
|
||||
void init_subtract(subtract_program* program) |
||||
{ |
||||
console_log_fmt("compiling subtract..."); |
||||
program->prog = compile_shader(common_vertex_shader_src, subtract_pressure_shader_src); |
||||
program->pos = glGetAttribLocation(program->prog, "pos"); |
||||
program->src = glGetUniformLocation(program->prog, "src"); |
||||
program->pressure = glGetUniformLocation(program->prog, "pressure"); |
||||
program->invGridSize = glGetUniformLocation(program->prog, "invGridSize"); |
||||
} |
||||
|
||||
void init_splat(splat_program* program) |
||||
{ |
||||
console_log_fmt("compiling splat..."); |
||||
program->prog = compile_shader(common_vertex_shader_src, splat_shader_src); |
||||
program->pos = glGetAttribLocation(program->prog, "pos"); |
||||
program->src = glGetUniformLocation(program->prog, "src"); |
||||
program->splatPos = glGetUniformLocation(program->prog, "splatPos"); |
||||
program->splatColor = glGetUniformLocation(program->prog, "splatColor"); |
||||
program->radius = glGetUniformLocation(program->prog, "radius"); |
||||
program->additive = glGetUniformLocation(program->prog, "additive"); |
||||
program->blending = glGetUniformLocation(program->prog, "blending"); |
||||
} |
||||
|
||||
void init_blit(blit_program* program) |
||||
{ |
||||
console_log_fmt("compiling blit..."); |
||||
program->prog = compile_shader(blit_vertex_shader_src, blit_fragment_shader_src); |
||||
program->pos = glGetAttribLocation(program->prog, "pos"); |
||||
program->mvp = glGetUniformLocation(program->prog, "mvp"); |
||||
program->tex = glGetUniformLocation(program->prog, "tex"); |
||||
} |
||||
|
||||
GLuint create_texture(int width, int height, GLenum internalFormat, GLenum format, GLenum type, char* initData) |
||||
{ |
||||
GLuint texture; |
||||
glGenTextures(1, &texture); |
||||
glBindTexture(GL_TEXTURE_2D, texture); |
||||
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, initData); |
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
||||
|
||||
return(texture); |
||||
} |
||||
|
||||
GLuint create_fbo(GLuint texture) |
||||
{ |
||||
GLuint fbo; |
||||
glGenFramebuffers(1, &fbo); |
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo); |
||||
glBindTexture(GL_TEXTURE_2D, texture); |
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); |
||||
glClear(GL_COLOR_BUFFER_BIT); |
||||
return(fbo); |
||||
} |
||||
|
||||
void init_frame_buffer(frame_buffer* framebuffer, |
||||
int width, |
||||
int height, |
||||
GLenum internalFormat, |
||||
GLenum format, |
||||
GLenum type, |
||||
char* initData) |
||||
{ |
||||
for(int i=0; i<2; i++) |
||||
{ |
||||
framebuffer->textures[i] = create_texture(width, height, internalFormat, format, type, initData); |
||||
framebuffer->fbos[i] = create_fbo(framebuffer->textures[i]); |
||||
} |
||||
|
||||
GLenum err = glCheckFramebufferStatus(GL_FRAMEBUFFER); |
||||
if(err != GL_FRAMEBUFFER_COMPLETE) |
||||
{ |
||||
console_log_fmt("Frame buffer incomplete, %i", err); |
||||
} |
||||
} |
||||
|
||||
void frame_buffer_swap(frame_buffer* buffer) |
||||
{ |
||||
GLuint tmp = buffer->fbos[0]; |
||||
buffer->fbos[0] = buffer->fbos[1]; |
||||
buffer->fbos[1] = tmp; |
||||
|
||||
tmp = buffer->textures[0]; |
||||
buffer->textures[0] = buffer->textures[1]; |
||||
buffer->textures[1] = tmp; |
||||
} |
||||
|
||||
//----------------------------------------------------------------
|
||||
//NOTE(martin): entry point
|
||||
//----------------------------------------------------------------
|
||||
/*
|
||||
#define texWidth 512 |
||||
#define texHeight 512 |
||||
*/ |
||||
#define texWidth (256) |
||||
#define texHeight (256) |
||||
|
||||
float colorInitData[texWidth][texHeight][4] = {0}; |
||||
float velocityInitData[texWidth][texHeight][4] = {0}; |
||||
|
||||
const float EPSILON = 1., |
||||
INV_GRID_SIZE = 1./(float)texWidth, |
||||
DELTA = 1./120.; |
||||
|
||||
const GLenum TEX_INTERNAL_FORMAT = GL_RGBA32F; |
||||
const GLenum TEX_FORMAT = GL_RGBA; |
||||
const GLenum TEX_TYPE = GL_FLOAT; |
||||
|
||||
void reset_texture(GLuint texture, float width, float height, char* initData) |
||||
{ |
||||
glBindTexture(GL_TEXTURE_2D, texture); |
||||
glTexImage2D(GL_TEXTURE_2D, 0, TEX_INTERNAL_FORMAT, width, height, 0, TEX_FORMAT, TEX_TYPE, initData); |
||||
} |
||||
|
||||
void reset() |
||||
{ |
||||
console_log_fmt("reset"); |
||||
|
||||
reset_texture(colorBuffer.textures[0], texWidth, texHeight, (char*)colorInitData); |
||||
reset_texture(colorBuffer.textures[1], texWidth, texHeight, (char*)colorInitData); |
||||
reset_texture(velocityBuffer.textures[0], texWidth, texHeight, (char*)velocityInitData); |
||||
reset_texture(velocityBuffer.textures[1], texWidth, texHeight, (char*)velocityInitData); |
||||
|
||||
int gridFactor = 1; |
||||
for(int i=0; i<MULTIGRID_COUNT; i++) |
||||
{ |
||||
reset_texture(pressureBuffer[i].textures[0], texWidth/gridFactor, texHeight/gridFactor, 0); |
||||
reset_texture(pressureBuffer[i].textures[1], texWidth/gridFactor, texHeight/gridFactor, 0); |
||||
|
||||
gridFactor *= 2; |
||||
} |
||||
} |
||||
|
||||
typedef struct mouse_input |
||||
{ |
||||
float x; |
||||
float y; |
||||
float deltaX; |
||||
float deltaY; |
||||
bool down; |
||||
|
||||
} mouse_input; |
||||
|
||||
mouse_input mouseInput = {0}; |
||||
|
||||
void mouse_down(float x, float y) |
||||
{ |
||||
mouseInput.down = true; |
||||
mouseInput.x = x; |
||||
mouseInput.y = y; |
||||
} |
||||
|
||||
void mouse_up() |
||||
{ |
||||
mouseInput.down = false; |
||||
} |
||||
|
||||
void mouse_move(float x, float y, float deltaX, float deltaY) |
||||
{ |
||||
mouseInput.x = x; |
||||
mouseInput.y = y; |
||||
mouseInput.deltaX = deltaX; |
||||
mouseInput.deltaY = deltaY; |
||||
} |
||||
|
||||
void init_color_checker() |
||||
{ |
||||
for(int i=0; i<texHeight; i++) |
||||
{ |
||||
for(int j=0; j<texWidth; j++) |
||||
{ |
||||
float u = j/(float)texWidth; |
||||
float v = i/(float)texWidth; |
||||
float value = ((int)(u*10)%2) == ((int)(v*10)%2) ? 1. : 0.; |
||||
|
||||
for(int k = 0; k<3; k++) |
||||
{ |
||||
colorInitData[i][j][k] = value; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void init_velocity_vortex() |
||||
{ |
||||
for(int i=0; i<texHeight; i++) |
||||
{ |
||||
for(int j=0; j<texWidth; j++) |
||||
{ |
||||
float x = 2*j/(float)texWidth - 1; |
||||
float y = 2*i/(float)texWidth - 1; |
||||
velocityInitData[i][j][0] = sinf(2*PI*y); |
||||
velocityInitData[i][j][1] = sinf(2*PI*x); |
||||
} |
||||
} |
||||
} |
||||
|
||||
float testDiv[texWidth/2][texWidth/2][4]; |
||||
|
||||
int init(float canvasSize) |
||||
{ |
||||
console_log_cstring("Hello, world (from C)"); |
||||
|
||||
console_log_fmt("Setting up canvas (%f, %f)\n", canvasSize, canvasSize); |
||||
|
||||
WAJS_SetupCanvas(canvasSize, canvasSize); |
||||
|
||||
// init programs
|
||||
init_advect(&advectProgram); |
||||
init_div(&divProgram); |
||||
init_jacobi(&jacobiProgram); |
||||
init_multigrid_restrict_residual(&multigridRestrictResidualProgram); |
||||
init_multigrid_correct(&multigridCorrectProgram); |
||||
|
||||
init_subtract(&subtractProgram); |
||||
init_splat(&splatProgram); |
||||
init_blit(&blitProgram); |
||||
|
||||
// init frame buffers
|
||||
console_log_fmt("create color buffer"); |
||||
init_frame_buffer(&colorBuffer, texWidth, texHeight, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, 0); |
||||
console_log_fmt("create velocity buffer"); |
||||
init_frame_buffer(&velocityBuffer, texWidth, texHeight, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, 0); |
||||
|
||||
int gridFactor = 1; |
||||
for(int i=0; i<MULTIGRID_COUNT; i++) |
||||
{ |
||||
console_log_fmt("create div buffer %i", i); |
||||
init_frame_buffer(&divBuffer[i], texWidth/gridFactor, texHeight/gridFactor, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, 0); |
||||
console_log_fmt("create pressure buffer %i", i); |
||||
init_frame_buffer(&pressureBuffer[i], texWidth/gridFactor, texHeight/gridFactor, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, 0); |
||||
gridFactor *= 2; |
||||
} |
||||
|
||||
// init vertex buffer
|
||||
Vertex vertices[6] = |
||||
{ |
||||
{-1, -1}, |
||||
{ 1, -1}, |
||||
{ 1, 1}, |
||||
{-1, -1}, |
||||
{ 1, 1}, |
||||
{ -1, 1} |
||||
}; |
||||
|
||||
glGenBuffers(1, &vertexBuffer); |
||||
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); |
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); |
||||
|
||||
//WARN: we assume blitProgram.pos == advectProgram.pos, is there a situation where it wouldn't be true??
|
||||
glEnableVertexAttribArray(blitProgram.pos); |
||||
glVertexAttribPointer(blitProgram.pos, 2, TEX_TYPE, GL_FALSE, sizeof(Vertex), (void*)0); |
||||
|
||||
for(int i=0; i<texWidth/2; i++) |
||||
{ |
||||
for(int j=0; j<texHeight/2; j++) |
||||
{ |
||||
testDiv[i][j][0] = 0.5 + 0.5*cosf(j/100.*3.14159 + i/100.*1.2139); |
||||
} |
||||
} |
||||
|
||||
return(0); |
||||
} |
||||
|
||||
void apply_splat(float splatPosX, float splatPosY, float splatVelX, float splatVelY, float r, float g, float b) |
||||
{ |
||||
glUseProgram(splatProgram.prog); |
||||
|
||||
// force
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, velocityBuffer.fbos[1]); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); |
||||
glUniform1i(splatProgram.src, 0); |
||||
|
||||
glUniform2f(splatProgram.splatPos, splatPosX, splatPosY); |
||||
glUniform3f(splatProgram.splatColor, splatVelX, splatVelY, 0); |
||||
glUniform1f(splatProgram.additive, 1); |
||||
glUniform1f(splatProgram.blending, 0); |
||||
|
||||
glUniform1f(splatProgram.radius, 0.01); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
|
||||
frame_buffer_swap(&velocityBuffer); |
||||
|
||||
// dye
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, colorBuffer.fbos[1]); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, colorBuffer.textures[0]); |
||||
glUniform1i(splatProgram.src, 0); |
||||
|
||||
glUniform2f(splatProgram.splatPos, splatPosX, splatPosY); |
||||
glUniform3f(splatProgram.splatColor, r, g, b); |
||||
glUniform1f(splatProgram.additive, 0); |
||||
glUniform1f(splatProgram.blending, 1); |
||||
glUniform1f(splatProgram.radius, 0.01); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
|
||||
frame_buffer_swap(&colorBuffer); |
||||
|
||||
} |
||||
|
||||
void multigrid_smooth(frame_buffer* x, frame_buffer* b, float invGridSize, int iterationCount) |
||||
{ |
||||
glUseProgram(jacobiProgram.prog); |
||||
|
||||
for(int i=0; i<iterationCount; i++) |
||||
{ |
||||
glBindFramebuffer(GL_FRAMEBUFFER, x->fbos[1]); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, x->textures[0]); |
||||
glUniform1i(jacobiProgram.xTex, 0); |
||||
|
||||
glActiveTexture(GL_TEXTURE1); |
||||
glBindTexture(GL_TEXTURE_2D, b->textures[0]); |
||||
glUniform1i(jacobiProgram.bTex, 1); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
|
||||
frame_buffer_swap(x); |
||||
} |
||||
} |
||||
|
||||
void multigrid_coarsen_residual(frame_buffer* output, frame_buffer* x, frame_buffer* b, float invFineGridSize) |
||||
{ |
||||
//NOTE: compute residual and downsample to coarser grid, put result in coarser buffer
|
||||
glUseProgram(multigridRestrictResidualProgram.prog); |
||||
glBindFramebuffer(GL_FRAMEBUFFER, output->fbos[1]); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, x->textures[0]); |
||||
glUniform1i(multigridRestrictResidualProgram.xTex, 0); |
||||
|
||||
glActiveTexture(GL_TEXTURE1); |
||||
glBindTexture(GL_TEXTURE_2D, b->textures[0]); |
||||
glUniform1i(multigridRestrictResidualProgram.bTex, 1); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
|
||||
frame_buffer_swap(output); |
||||
} |
||||
|
||||
void multigrid_prolongate_and_correct(frame_buffer* x, frame_buffer* error, float invFineGridSize) |
||||
{ |
||||
//NOTE: correct finer pressure
|
||||
glUseProgram(multigridCorrectProgram.prog); |
||||
glBindFramebuffer(GL_FRAMEBUFFER, x->fbos[1]); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, x->textures[0]); |
||||
glUniform1i(multigridCorrectProgram.src, 0); |
||||
|
||||
glActiveTexture(GL_TEXTURE1); |
||||
glBindTexture(GL_TEXTURE_2D, error->textures[0]); |
||||
glUniform1i(multigridCorrectProgram.error, 1); |
||||
|
||||
glUniform1f(multigridCorrectProgram.invGridSize, invFineGridSize); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
|
||||
frame_buffer_swap(x); |
||||
} |
||||
|
||||
void multigrid_clear(frame_buffer* error) |
||||
{ |
||||
glBindFramebuffer(GL_FRAMEBUFFER, error->fbos[0]); |
||||
glClear(GL_COLOR_BUFFER_BIT); |
||||
} |
||||
|
||||
// This function is called by loader.js every frame
|
||||
void WAFNDraw() |
||||
{ |
||||
float t = WAJS_GetTime()/1000.; |
||||
float aspectRatio = canvas_width()/canvas_height(); |
||||
|
||||
static float frameT = 0; |
||||
frameT += 1./60.; |
||||
|
||||
glViewport(0, 0, texWidth, texHeight); |
||||
|
||||
//NOTE: advect velocity thru itself
|
||||
glUseProgram(advectProgram.prog); |
||||
glBindFramebuffer(GL_FRAMEBUFFER, velocityBuffer.fbos[1]); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); |
||||
glUniform1i(advectProgram.src, 0); |
||||
|
||||
glActiveTexture(GL_TEXTURE1); |
||||
glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); |
||||
glUniform1i(advectProgram.velocity, 1); |
||||
|
||||
glUniform1f(advectProgram.delta, DELTA); |
||||
glUniform1f(advectProgram.dissipation, 0.01); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
|
||||
frame_buffer_swap(&velocityBuffer); |
||||
|
||||
//NOTE: apply force and dye
|
||||
if(mouseInput.down && (mouseInput.deltaX || mouseInput.deltaY)) |
||||
{ |
||||
float canvasWidth = canvas_width(); |
||||
float canvasHeight = canvas_height(); |
||||
|
||||
float splatPosX = mouseInput.x/canvasWidth; |
||||
float splatPosY = 1 - mouseInput.y/canvasHeight; |
||||
|
||||
float splatVelX = 10000.*DELTA*mouseInput.deltaX/canvasWidth; |
||||
float splatVelY = -10000.*DELTA*mouseInput.deltaY/canvasWidth; |
||||
|
||||
float intensity = 100*sqrtf(square(mouseInput.deltaX/canvasWidth) + square(mouseInput.deltaY/canvasHeight)); |
||||
|
||||
float r = intensity * (sinf(2*PI*0.1*t) + 1); |
||||
float g = intensity * (cosf(2*PI*0.1/EULER*t + 654) + 1); |
||||
float b = intensity * (sinf(2*PI*0.1/SQRT2*t + 937) + 1); |
||||
|
||||
apply_splat(splatPosX, splatPosY, splatVelX, splatVelY, r, g, b); |
||||
|
||||
mouseInput.deltaX = 0; |
||||
mouseInput.deltaY = 0; |
||||
} |
||||
|
||||
//DEBUG
|
||||
//*
|
||||
if((int)(frameT*3/2.) % 6 == 0) |
||||
{ |
||||
float dirX = 0.2*cosf(2*3.141516*frameT*7.26); |
||||
float dirY = 0.3+0.1*sinf(frameT); |
||||
apply_splat(0.5, 0.1, dirX, dirY, 1.5, 1., 0.1); |
||||
} |
||||
//*/
|
||||
|
||||
//NOTE: compute divergence of advected velocity
|
||||
glUseProgram(divProgram.prog); |
||||
glBindFramebuffer(GL_FRAMEBUFFER, divBuffer[0].fbos[1]); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); |
||||
glUniform1i(divProgram.src, 0); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
|
||||
frame_buffer_swap(&divBuffer[0]); |
||||
|
||||
//NOTE: compute pressure
|
||||
|
||||
#if 0 |
||||
multigrid_clear(&pressureBuffer[0]); |
||||
multigrid_smooth(&pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, 500); |
||||
#else |
||||
//*
|
||||
multigrid_clear(&pressureBuffer[0]); |
||||
|
||||
for(int i=0; i<4; i++) |
||||
{ |
||||
multigrid_smooth(&pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, 2); |
||||
//*
|
||||
multigrid_coarsen_residual(&divBuffer[1], &pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE); |
||||
|
||||
multigrid_clear(&pressureBuffer[1]); |
||||
multigrid_smooth(&pressureBuffer[1], &divBuffer[1], 2*INV_GRID_SIZE, 2); |
||||
multigrid_coarsen_residual(&divBuffer[2], &pressureBuffer[1], &divBuffer[1], 2*INV_GRID_SIZE); |
||||
|
||||
multigrid_clear(&pressureBuffer[2]); |
||||
multigrid_smooth(&pressureBuffer[2], &divBuffer[2], 4*INV_GRID_SIZE, 60); |
||||
|
||||
multigrid_prolongate_and_correct(&pressureBuffer[1], &pressureBuffer[2], 2*INV_GRID_SIZE); |
||||
multigrid_smooth(&pressureBuffer[1], &divBuffer[1], 2*INV_GRID_SIZE, 8); |
||||
|
||||
multigrid_prolongate_and_correct(&pressureBuffer[0], &pressureBuffer[1], INV_GRID_SIZE); |
||||
//*/
|
||||
multigrid_smooth(&pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, 4); |
||||
} |
||||
/*/
|
||||
multigrid_clear(&pressureBuffer[0]); |
||||
multigrid_smooth(&pressureBuffer[0], &pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, 10); |
||||
|
||||
multigrid_coarsen_residual(&divBuffer[1], &pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE); |
||||
|
||||
multigrid_clear(&pressureBuffer[1]); |
||||
multigrid_smooth(&pressureBuffer[1], &pressureBuffer[1], &divBuffer[1], 2*INV_GRID_SIZE, 400); |
||||
|
||||
multigrid_prolongate_and_correct(&pressureBuffer[0], &pressureBuffer[0], &pressureBuffer[1], INV_GRID_SIZE); |
||||
|
||||
multigrid_smooth(&pressureBuffer[0], &pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, 10); |
||||
//*/
|
||||
#endif |
||||
|
||||
//NOTE: subtract pressure gradient to advected velocity
|
||||
glUseProgram(subtractProgram.prog); |
||||
glBindFramebuffer(GL_FRAMEBUFFER, velocityBuffer.fbos[1]); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); |
||||
glUniform1i(subtractProgram.src, 0); |
||||
|
||||
glActiveTexture(GL_TEXTURE1); |
||||
glBindTexture(GL_TEXTURE_2D, pressureBuffer[0].textures[0]); |
||||
glUniform1i(subtractProgram.pressure, 1); |
||||
|
||||
glUniform1f(subtractProgram.invGridSize, INV_GRID_SIZE); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
|
||||
frame_buffer_swap(&velocityBuffer); |
||||
|
||||
//NOTE: Advect color through corrected velocity field
|
||||
glUseProgram(advectProgram.prog); |
||||
glBindFramebuffer(GL_FRAMEBUFFER, colorBuffer.fbos[1]); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, colorBuffer.textures[0]); |
||||
glUniform1i(advectProgram.src, 0); |
||||
|
||||
glActiveTexture(GL_TEXTURE1); |
||||
glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); |
||||
glUniform1i(advectProgram.velocity, 1); |
||||
|
||||
glUniform1f(advectProgram.delta, DELTA); |
||||
|
||||
glUniform1f(advectProgram.dissipation, 0.5); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
|
||||
frame_buffer_swap(&colorBuffer); |
||||
|
||||
//*
|
||||
//NOTE: Blit color texture to screen
|
||||
|
||||
float displayMatrix[16] = { |
||||
1/aspectRatio, 0, 0, 0, |
||||
0, 1, 0, 0, |
||||
0, 0, 1, 0, |
||||
0, 0, 0, 1 }; |
||||
|
||||
glViewport(0, 0, canvas_width(), canvas_height()); |
||||
|
||||
glUseProgram(blitProgram.prog); |
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, colorBuffer.textures[0]); |
||||
glUniform1i(blitProgram.tex, 0); |
||||
|
||||
glUniformMatrix4fv(blitProgram.mvp, 1, GL_FALSE, displayMatrix); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
/*/
|
||||
//NOTE: Blit velocity to screen
|
||||
glViewport(0, 0, window_width(), window_height()); |
||||
glUseProgram(blitProgram.prog); |
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0); |
||||
|
||||
glActiveTexture(GL_TEXTURE0); |
||||
glBindTexture(GL_TEXTURE_2D, velocityBuffer.textures[0]); |
||||
glUniform1i(blitProgram.tex, 0); |
||||
|
||||
glUniformMatrix4fv(blitProgram.mvp, 1, GL_FALSE, displayMatrix); |
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6); |
||||
//*/
|
||||
} |
||||
Binary file not shown.
@ -0,0 +1,19 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
precision highp sampler2D; |
||||
|
||||
in vec2 texCoord; |
||||
out vec4 fragColor; |
||||
|
||||
uniform sampler2D src; |
||||
uniform sampler2D velocity; |
||||
uniform float delta; |
||||
uniform float dissipation; |
||||
|
||||
void main() |
||||
{ |
||||
vec2 u = texture(velocity, texCoord).xy; |
||||
vec2 samplePos = texCoord - delta * u; |
||||
fragColor = texture(src, samplePos) / (1. + dissipation*delta); |
||||
} |
||||
@ -0,0 +1,15 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
precision highp sampler2D; |
||||
|
||||
in vec2 texCoord; |
||||
out vec4 fragColor; |
||||
|
||||
uniform sampler2D tex; |
||||
|
||||
void main() |
||||
{ |
||||
fragColor = texture(tex, texCoord); |
||||
fragColor.a = 1.0; |
||||
} |
||||
@ -0,0 +1,14 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
|
||||
in vec2 pos; |
||||
out vec2 texCoord; |
||||
|
||||
uniform mat4 mvp; |
||||
|
||||
void main() |
||||
{ |
||||
texCoord = 0.5*(pos + vec2(1,1)); |
||||
gl_Position = mvp * vec4(pos, 0, 1); |
||||
} |
||||
@ -0,0 +1,12 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
|
||||
in vec2 pos; |
||||
out vec2 texCoord; |
||||
|
||||
void main() |
||||
{ |
||||
texCoord = 0.5*(pos + vec2(1,1)); |
||||
gl_Position = vec4(pos, 0, 1); |
||||
} |
||||
@ -0,0 +1,43 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
precision highp sampler2D; |
||||
|
||||
in vec2 texCoord; |
||||
out vec4 fragColor; |
||||
|
||||
uniform sampler2D src; |
||||
|
||||
float ux(ivec2 coord) |
||||
{ |
||||
return(texelFetch(src, coord, 0).x); |
||||
} |
||||
|
||||
float uy(ivec2 coord) |
||||
{ |
||||
return(texelFetch(src, coord, 0).y); |
||||
} |
||||
|
||||
void main() |
||||
{ |
||||
ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy)); |
||||
|
||||
ivec2 vr = pixelCoord + ivec2(1, 0); |
||||
ivec2 vl = pixelCoord - ivec2(1, 0); |
||||
ivec2 vt = pixelCoord + ivec2(0, 1); |
||||
ivec2 vb = pixelCoord - ivec2(0, 1); |
||||
|
||||
float r = ux(vr); |
||||
float l = ux(vl); |
||||
float t = uy(vt); |
||||
float b = uy(vb); |
||||
|
||||
/* |
||||
vec2 c = texelFetch(src, pixelCoord, 0).xy; |
||||
if(vr.x >= textureSize(src, 0).x) { r = -c.x; } |
||||
if(vl.x < 0) { l = -c.x; } |
||||
if(vt.y >= textureSize(src, 0).y) { t = -c.y; } |
||||
if(vb.y < 0) { b = -c.y; } |
||||
*/ |
||||
fragColor = vec4(-(r - l + t - b)/2., 0, 0, 1); |
||||
} |
||||
@ -0,0 +1,43 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
precision highp sampler2D; |
||||
|
||||
in vec2 texCoord; |
||||
out vec4 fragColor; |
||||
|
||||
uniform sampler2D xTex; |
||||
uniform sampler2D bTex; |
||||
|
||||
float x(ivec2 coord) |
||||
{ |
||||
if( coord.x <= 0 |
||||
|| coord.x > textureSize(xTex, 0).x |
||||
|| coord.y <= 0 |
||||
|| coord.y > textureSize(xTex, 0).y) |
||||
{ |
||||
return(0.); |
||||
} |
||||
return(texelFetch(xTex, coord, 0).x); |
||||
} |
||||
|
||||
float b(ivec2 coord) |
||||
{ |
||||
return(texelFetch(bTex, coord, 0).x); |
||||
} |
||||
|
||||
void main() |
||||
{ |
||||
ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy)); |
||||
|
||||
ivec2 vr = pixelCoord + ivec2(1, 0); |
||||
ivec2 vl = pixelCoord - ivec2(1, 0); |
||||
ivec2 vt = pixelCoord + ivec2(0, 1); |
||||
ivec2 vb = pixelCoord - ivec2(0, 1); |
||||
|
||||
float w = 0.8; |
||||
float standardJacobi = (x(vl) + x(vr) + x(vt) + x(vb) + b(pixelCoord))/4.; |
||||
float weightedJacobi = w*standardJacobi + (1.-w)*x(pixelCoord); |
||||
|
||||
fragColor = vec4(weightedJacobi, 0, 0, 1); |
||||
} |
||||
@ -0,0 +1,34 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
precision highp sampler2D; |
||||
|
||||
in vec2 texCoord; |
||||
out vec4 fragColor; |
||||
|
||||
uniform sampler2D src; |
||||
uniform sampler2D error; |
||||
uniform float invGridSize; |
||||
|
||||
float e(ivec2 coord) |
||||
{ |
||||
return(texelFetch(error, coord, 0).x); |
||||
} |
||||
|
||||
void main() |
||||
{ |
||||
ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy)); |
||||
vec2 coarseCoord = vec2(pixelCoord)/2.; |
||||
vec2 offset = fract(coarseCoord); |
||||
|
||||
ivec2 bl = ivec2(floor(coarseCoord)); |
||||
ivec2 br = bl + ivec2(1, 0); |
||||
ivec2 tl = bl + ivec2(0, 1); |
||||
ivec2 tr = bl + ivec2(1, 1); |
||||
|
||||
float topLerp = (1.-offset.x)*e(tl)+ offset.x*e(tr); |
||||
float bottomLerp = (1.-offset.x)*e(bl) + offset.x*e(br); |
||||
float bilerpError = (1.-offset.y)*bottomLerp + offset.y*topLerp; |
||||
|
||||
fragColor = vec4(texture(src, texCoord).x + bilerpError, 0, 0, 1); |
||||
} |
||||
@ -0,0 +1,61 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
precision highp sampler2D; |
||||
|
||||
in vec2 texCoord; |
||||
out vec4 fragColor; |
||||
|
||||
uniform sampler2D xTex; |
||||
uniform sampler2D bTex; |
||||
|
||||
float x(ivec2 coord) |
||||
{ |
||||
if( coord.x <= 0 |
||||
|| coord.x > textureSize(xTex, 0).x |
||||
|| coord.y <= 0 |
||||
|| coord.y > textureSize(xTex, 0).y) |
||||
{ |
||||
return(0.); |
||||
} |
||||
return(texelFetch(xTex, coord, 0).x); |
||||
} |
||||
|
||||
float b(ivec2 coord) |
||||
{ |
||||
if( coord.x <= 0 |
||||
|| coord.x > textureSize(bTex, 0).x |
||||
|| coord.y <= 0 |
||||
|| coord.y > textureSize(bTex, 0).y) |
||||
{ |
||||
return(0.); |
||||
} |
||||
return(texelFetch(bTex, coord, 0).x); |
||||
} |
||||
|
||||
float residual(ivec2 coord) |
||||
{ |
||||
ivec2 vr = coord + ivec2(1, 0); |
||||
ivec2 vl = coord - ivec2(1, 0); |
||||
ivec2 vt = coord + ivec2(0, 1); |
||||
ivec2 vb = coord - ivec2(0, 1); |
||||
|
||||
return((x(vl) + x(vr) + x(vt) + x(vb) + b(coord) - 4.*x(coord))*4.); |
||||
} |
||||
|
||||
void main() |
||||
{ |
||||
ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy)); |
||||
|
||||
float restricted = residual(2*pixelCoord + ivec2(-1, -1)) |
||||
+ residual(2*pixelCoord + ivec2(1, -1)) |
||||
+ residual(2*pixelCoord + ivec2(1, 1)) |
||||
+ residual(2*pixelCoord + ivec2(-1, 1)) |
||||
+ 2.*residual(2*pixelCoord + ivec2(-1, 0)) |
||||
+ 2.*residual(2*pixelCoord + ivec2(1, 0)) |
||||
+ 2.*residual(2*pixelCoord + ivec2(0, -1)) |
||||
+ 2.*residual(2*pixelCoord + ivec2(0, 1)) |
||||
+ 4.*residual(2*pixelCoord); |
||||
restricted /= 16.; |
||||
fragColor = vec4(restricted, 0, 0, 1); |
||||
} |
||||
@ -0,0 +1,45 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
precision highp sampler2D; |
||||
|
||||
in vec2 texCoord; |
||||
out vec4 fragColor; |
||||
|
||||
uniform sampler2D src; |
||||
uniform vec2 splatPos; |
||||
uniform vec3 splatColor; |
||||
uniform float radius; |
||||
uniform float additive; |
||||
uniform float blending; |
||||
|
||||
void main() |
||||
{ |
||||
float d2 = dot(texCoord - splatPos, texCoord - splatPos); |
||||
float intensity = exp(-10.*d2/radius); |
||||
|
||||
float n = 119.*gl_FragCoord.x + 107.*gl_FragCoord.y + 113.; |
||||
n = fract(n*fract(n/2.7182818)); |
||||
intensity += intensity*abs(cos(2.*3.14159*n)); |
||||
|
||||
//NOTE: the force is added in the additive part. |
||||
vec2 force = splatColor.xy; |
||||
force.x += 0.2*sin(2.*3.14159*fract(n/1.144213)); |
||||
force.y += 0.2*cos(2.*3.14159*fract(n/1.144213)); |
||||
|
||||
{ |
||||
vec2 u = force/length(force); |
||||
|
||||
vec2 v = texCoord - splatPos; |
||||
vec2 norm = v - dot(v, u)*u; |
||||
float dFromAxis = length(norm); |
||||
|
||||
force += 30.*norm; |
||||
} |
||||
|
||||
vec3 u = texture(src, texCoord).xyz; |
||||
vec3 uAdd = u + intensity*vec3(force, 0); |
||||
vec3 uBlend = u*(1.-intensity) + intensity * splatColor; |
||||
|
||||
fragColor = vec4(uAdd*additive + uBlend*blending, 1); |
||||
} |
||||
@ -0,0 +1,40 @@ |
||||
#version 300 es |
||||
|
||||
precision highp float; |
||||
precision highp sampler2D; |
||||
|
||||
in vec2 texCoord; |
||||
out vec4 fragColor; |
||||
|
||||
uniform sampler2D src; |
||||
uniform sampler2D pressure; |
||||
uniform float invGridSize; |
||||
|
||||
vec2 u(ivec2 coord) |
||||
{ |
||||
return(texelFetch(src, coord, 0).xy); |
||||
} |
||||
|
||||
float p(ivec2 coord) |
||||
{ |
||||
if(coord.x < 0) { coord.x = 0; } |
||||
if(coord.x >= textureSize(pressure, 0).x) { coord.x = textureSize(pressure, 0).x-1; } |
||||
if(coord.y < 0) { coord.y = 0; } |
||||
if(coord.y >= textureSize(pressure, 0).y) { coord.y = textureSize(pressure, 0).y-1; } |
||||
|
||||
return(texelFetch(pressure, coord, 0).x); |
||||
} |
||||
|
||||
void main() |
||||
{ |
||||
ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy)); |
||||
|
||||
ivec2 vr = pixelCoord + ivec2(1, 0); |
||||
ivec2 vl = pixelCoord - ivec2(1, 0); |
||||
ivec2 vt = pixelCoord + ivec2(0, 1); |
||||
ivec2 vb = pixelCoord - ivec2(0, 1); |
||||
|
||||
vec2 gradP = vec2(p(vr) - p(vl), p(vt) - p(vb))/2.; |
||||
|
||||
fragColor = vec4(u(pixelCoord) - gradP, 0, 1); |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,8 @@ |
||||
import http.server |
||||
|
||||
request_handler = http.server.SimpleHTTPRequestHandler |
||||
request_handler.extensions_map['.wasm'] = 'application/wasm' |
||||
|
||||
server_address = ('0.0.0.0', 8000) |
||||
httpd = http.server.HTTPServer(server_address, request_handler) |
||||
httpd.serve_forever() |
||||
Loading…
Reference in new issue