From e4d9955e83b552ad9e5e28e6584625925eafc354 Mon Sep 17 00:00:00 2001 From: Martin Fouilleul Date: Thu, 3 Aug 2023 11:37:32 +0200 Subject: [PATCH] [gles] expose GLES surface and GLES API to orca apps. - Allow orca app to request either Canvas or GLES surfaces - Add bounds check specifications to the json bindings spec format and to the bindings generator scripts. - Generate GLES API json bindings spec from gl.xml - Remove APIs that can't be bound with current wasm memory model (ie buffer mapping) - Manually link remaining APIs, except glGetString - Add fluid simulation sample - Add abort messages for wasm loading and runtime fatal errors - Adapt orca build tool to generate GLES json spec from gl.xml - Adapt glesTriangle and fluid samples build scripts to new orca build tool --- .gitignore | 4 + build.bat | 62 - build.sh | 97 -- milepost | 2 +- samples/fluid/.gitignore | 3 + samples/fluid/build.bat | 33 + samples/fluid/build.sh | 35 + samples/fluid/icon.png | Bin 0 -> 171792 bytes samples/fluid/src/glsl_shaders.h | 551 ++++++++ samples/fluid/src/main.c | 928 +++++++++++++ samples/fluid/src/shaders/advect.glsl | 56 + .../fluid/src/shaders/blit_div_fragment.glsl | 34 + .../fluid/src/shaders/blit_div_vertex.glsl | 14 + samples/fluid/src/shaders/blit_fragment.glsl | 15 + .../src/shaders/blit_residue_fragment.glsl | 65 + samples/fluid/src/shaders/blit_vertex.glsl | 18 + samples/fluid/src/shaders/common_vertex.glsl | 12 + samples/fluid/src/shaders/divergence.glsl | 41 + samples/fluid/src/shaders/jacobi_step.glsl | 55 + .../fluid/src/shaders/multigrid_correct.glsl | 53 + .../shaders/multigrid_restrict_residual.glsl | 61 + samples/fluid/src/shaders/splat.glsl | 29 + .../fluid/src/shaders/subtract_pressure.glsl | 47 + samples/glesTriangle/.gitignore | 3 + samples/glesTriangle/build.bat | 17 + samples/glesTriangle/build.sh | 33 + samples/glesTriangle/src/main.c | 114 ++ samples/pong/src/main.c | 5 +- samples/ui/src/main.c | 4 +- scripts/bindgen.py | 120 +- scripts/bundle.py | 2 + scripts/dev.py | 7 + scripts/gles_gen.py | 315 +++++ scripts/reg_modified.py | 1175 +++++++++++++++++ sdk/orca.h | 12 + src/canvas_api.json | 21 +- src/core_api.json | 56 - src/gles_api.json | 383 ------ src/gles_api_bind_manual.c | 1132 ++++++++++++++++ src/main.c | 257 ++-- src/manual_gles_api.c | 29 - src/orca_app.h | 11 +- 42 files changed, 5158 insertions(+), 753 deletions(-) delete mode 100644 build.bat delete mode 100755 build.sh create mode 100644 samples/fluid/.gitignore create mode 100644 samples/fluid/build.bat create mode 100755 samples/fluid/build.sh create mode 100644 samples/fluid/icon.png create mode 100644 samples/fluid/src/glsl_shaders.h create mode 100644 samples/fluid/src/main.c create mode 100644 samples/fluid/src/shaders/advect.glsl create mode 100644 samples/fluid/src/shaders/blit_div_fragment.glsl create mode 100644 samples/fluid/src/shaders/blit_div_vertex.glsl create mode 100644 samples/fluid/src/shaders/blit_fragment.glsl create mode 100644 samples/fluid/src/shaders/blit_residue_fragment.glsl create mode 100644 samples/fluid/src/shaders/blit_vertex.glsl create mode 100644 samples/fluid/src/shaders/common_vertex.glsl create mode 100644 samples/fluid/src/shaders/divergence.glsl create mode 100644 samples/fluid/src/shaders/jacobi_step.glsl create mode 100644 samples/fluid/src/shaders/multigrid_correct.glsl create mode 100644 samples/fluid/src/shaders/multigrid_restrict_residual.glsl create mode 100644 samples/fluid/src/shaders/splat.glsl create mode 100644 samples/fluid/src/shaders/subtract_pressure.glsl create mode 100644 samples/glesTriangle/.gitignore create mode 100644 samples/glesTriangle/build.bat create mode 100755 samples/glesTriangle/build.sh create mode 100644 samples/glesTriangle/src/main.c create mode 100644 scripts/gles_gen.py create mode 100644 scripts/reg_modified.py delete mode 100644 src/gles_api.json create mode 100644 src/gles_api_bind_manual.c delete mode 100644 src/manual_gles_api.c diff --git a/.gitignore b/.gitignore index 5df72cc..5524392 100644 --- a/.gitignore +++ b/.gitignore @@ -17,11 +17,15 @@ build Debug/* +scripts/__pycache__ +src/gles_api.json src/bindgen_core_api.c src/bindgen_gles_api.c sdk/io_stubs.c sdk/orca_surface.c +sdk/gl31.h *bind_gen.c +gles_gen.log .vscode/launch.json .vscode/settings.json diff --git a/build.bat b/build.bat deleted file mode 100644 index ccd88f3..0000000 --- a/build.bat +++ /dev/null @@ -1,62 +0,0 @@ -@echo off - -set target=%1% -if "%~1%" == "" set target=orca - -if not exist bin mkdir bin -if not exist bin\obj mkdir bin\obj - -if %target% == wasm3 ( - echo building wasm3 - - set wasm3_includes=/I .\ext\wasm3\source - set wasm3_sources=/I .\ext\wasm3\source\*.c - - for %%f in ( .\ext\wasm3\source\*.c ) do ( - cl /nologo /Zi /Zc:preprocessor /O2 /c /Fo:bin\obj\%%~nf.obj %wasm3_includes% %%f - ) - lib /nologo /out:bin\wasm3.lib bin\obj\*.obj -) - -if %target% == milepost ( - echo building milepost - cd milepost - build.bat - cd .. -) - -if %target% == orca ( - echo building orca - - ::copy libraries - copy milepost\bin\milepost.dll bin - copy milepost\bin\milepost.dll.lib bin - - ::generate wasm3 api bindings - python3 scripts\bindgen.py core src\core_api.json^ - --wasm3-bindings src\core_api_bind_gen.c - - python3 scripts\bindgen.py gles src\gles_api.json^ - --wasm3-bindings src\gles_api_bind_gen.c - - python3 scripts\bindgen.py canvas src\canvas_api.json^ - --guest-stubs sdk\orca_surface.c^ - --guest-include graphics.h^ - --wasm3-bindings src\canvas_api_bind_gen.c - - python3 scripts\bindgen.py clock src\clock_api.json^ - --guest-stubs sdk\orca_clock.c^ - --guest-include platform_clock.h^ - --wasm3-bindings src\clock_api_bind_gen.c - - python3 scripts\bindgen.py io^ - src\io_api.json^ - --guest-stubs sdk\io_stubs.c^ - --wasm3-bindings src\io_api_bind_gen.c - - ::compile orca - set INCLUDES=/I src /I sdk /I ext\wasm3\source /I milepost\src /I milepost\ext - set LIBS=/LIBPATH:bin milepost.dll.lib wasm3.lib - - cl /Zi /Zc:preprocessor /std:c11 /experimental:c11atomics %INCLUDES% src\main.c /link %LIBS% /out:bin\orca.exe -) diff --git a/build.sh b/build.sh deleted file mode 100755 index 6c84383..0000000 --- a/build.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash - -set -exo pipefail - -target="$1" - -if [ -z $target ] ; then - target='orca' -fi -target=$(echo $target | tr '[:upper:]' '[:lower:]') - -if [ ! \( -e bin \) ] ; then - mkdir ./bin -fi - -if [ ! \( -e resources \) ] ; then - mkdir ./resources -fi - - -if [ $target = milepost ] ; then - echo "building milepost" - pushd milepost > /dev/null - ./build.sh lib "$2" - popd > /dev/null - -elif [ $target = wasm3 ] ; then - - echo "building wasm3" - mkdir ./bin/obj - for file in ./ext/wasm3/source/*.c ; do - name=$(basename $file) - name=${name/.c/.o} - clang -c -g -O2 -foptimize-sibling-calls -Wno-extern-initializer -Dd_m3VerboseErrorMessages -o ./bin/obj/$name -I./ext/wasm3/source $file - done - ar -rcs ./bin/libwasm3.a ./bin/obj/*.o - rm -rf ./bin/obj - -elif [ $target = orca ] ; then - echo "building orca" - - if [ ! \( -e milepost/bin/libmilepost.dylib \) -o ! \( -e milepost/bin/mtl_renderer.metallib \) ] ; then - ./build.sh milepost - fi - - if [ ! \( -e ./bin/libwasm3.a \) ] ; then - ./build.sh wasm3 - fi - - # copy libraries - cp milepost/bin/mtl_renderer.metallib bin/ - cp milepost/bin/libmilepost.dylib bin/ - cp milepost/bin/libGLESv2.dylib bin/ - cp milepost/bin/libEGL.dylib bin/ - - INCLUDES="-Isrc -Isdk -Imilepost/src -Imilepost/src/util -Imilepost/src/platform -Iext/wasm3/source -Imilepost/ext/" - LIBS="-Lbin -lmilepost -lwasm3" - FLAGS="-g -DLOG_COMPILE_DEBUG -mmacos-version-min=10.15.4 -maes" - - # generate wasm3 api bindings - - python3 ./scripts/bindgen.py core \ - src/core_api.json \ - --wasm3-bindings ./src/core_api_bind_gen.c - - python3 ./scripts/bindgen.py gles \ - src/gles_api.json \ - --wasm3-bindings ./src/gles_api_bind_gen.c - - python3 ./scripts/bindgen.py canvas \ - src/canvas_api.json \ - --guest-stubs sdk/orca_surface.c \ - --guest-include graphics.h \ - --wasm3-bindings ./src/canvas_api_bind_gen.c - - python3 ./scripts/bindgen.py clock \ - src/clock_api.json \ - --guest-stubs sdk/orca_clock.c \ - --guest-include platform_clock.h \ - --wasm3-bindings ./src/clock_api_bind_gen.c - - python3 ./scripts/bindgen.py io \ - src/io_api.json \ - --guest-stubs sdk/io_stubs.c \ - --wasm3-bindings ./src/io_api_bind_gen.c - - # compile orca - clang $FLAGS $INCLUDES $LIBS -o bin/orca src/main.c - - # fix libs imports - install_name_tool -change "./bin/libmilepost.dylib" "@rpath/libmilepost.dylib" bin/orca - install_name_tool -add_rpath "@executable_path/" bin/orca - - -else - echo "unknown build target $target" -fi diff --git a/milepost b/milepost index c103c00..d01dc83 160000 --- a/milepost +++ b/milepost @@ -1 +1 @@ -Subproject commit c103c001f7c8e780602193f22360201810802438 +Subproject commit d01dc832fbbc7b1e720476178a2c49a737717e0f diff --git a/samples/fluid/.gitignore b/samples/fluid/.gitignore new file mode 100644 index 0000000..8840e08 --- /dev/null +++ b/samples/fluid/.gitignore @@ -0,0 +1,3 @@ +Fluid +profile.dtrace +profile.spall diff --git a/samples/fluid/build.bat b/samples/fluid/build.bat new file mode 100644 index 0000000..c01c970 --- /dev/null +++ b/samples/fluid/build.bat @@ -0,0 +1,33 @@ +@echo off + +:: compile wasm module +set wasmFlags=--target=wasm32^ + --no-standard-libraries ^ + -fno-builtin ^ + -Wl,--no-entry ^ + -Wl,--export-dynamic ^ + -g ^ + -O2 ^ + -mbulk-memory ^ + -D__ORCA__ ^ + -isystem ..\..\cstdlib\include -I ..\..\sdk -I..\..\milepost\ext -I ..\..\milepost -I ..\..\milepost\src + +set shaders=src/shaders/advect.glsl^ + src/shaders/blit_div_fragment.glsl^ + src/shaders/blit_div_vertex.glsl^ + src/shaders/blit_fragment.glsl^ + src/shaders/blit_residue_fragment.glsl^ + src/shaders/blit_vertex.glsl^ + src/shaders/common_vertex.glsl^ + src/shaders/divergence.glsl^ + src/shaders/jacobi_step.glsl^ + src/shaders/multigrid_correct.glsl^ + src/shaders/multigrid_restrict_residual.glsl^ + src/shaders/splat.glsl^ + src/shaders/subtract_pressure.glsl + +call python3 ../../milepost/scripts/embed_text.py --prefix=glsl_ --output src/glsl_shaders.h %shaders% + +clang %wasmFlags% -o .\module.wasm ..\..\sdk\orca.c ..\..\cstdlib\src\*.c src\main.c + +python3 ..\..\scripts\mkapp.py --orca-dir ..\.. --icon icon.png --name Fluid module.wasm diff --git a/samples/fluid/build.sh b/samples/fluid/build.sh new file mode 100755 index 0000000..c2439c3 --- /dev/null +++ b/samples/fluid/build.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -euo pipefail + +if [[ -x /usr/local/opt/llvm/bin/clang ]]; then + CLANG=/usr/local/opt/llvm/bin/clang +elif [[ -x /opt/homebrew/opt/llvm/bin/clang ]]; then + CLANG=/opt/homebrew/opt/llvm/bin/clang +else + echo "Could not find Homebrew clang; this script will probably not work." + CLANG=clang +fi + +STDLIB_DIR=../../cstdlib +ORCA_SDK_DIR=../../sdk +MILEPOST_DIR=../../milepost + +python3 ../../milepost/scripts/embed_text.py --prefix=glsl_ --output src/glsl_shaders.h src/shaders/*.glsl + +wasmFlags="--target=wasm32 \ + --no-standard-libraries \ + -fno-builtin \ + -Wl,--no-entry \ + -Wl,--export-dynamic \ + -g \ + -O2 \ + -mbulk-memory \ + -D__ORCA__ \ + -I $STDLIB_DIR/include \ + -I $ORCA_SDK_DIR \ + -I $MILEPOST_DIR/ext -I $MILEPOST_DIR -I $MILEPOST_DIR/src" + +$CLANG $wasmFlags -o ./module.wasm ../../sdk/orca.c ../../cstdlib/src/*.c src/main.c + +orca bundle --orca-dir ../.. --icon icon.png --name Fluid module.wasm diff --git a/samples/fluid/icon.png b/samples/fluid/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ebfa188517c064d8b55049d1644e1f8dfe4d2528 GIT binary patch literal 171792 zcmeFZ`9GB3`#65j3^SG)lzpF4QFevwqs3CTl8~K*?EB70$WBCMnNbQ6LfIKh*0Mx) zX6$4e>kI~8z25KF^Zk0fAD_?nANYRf`r*FrbFTeyo$EgLea@L^Jsouf9Tyz{00>Qu zTlWC~e2Rhr8tCa|$8Yf9^g?B)s;vqDUlQplR@A5WNE?m&+5ix62>{T~0pKqQdI>=$B>>331^{-q%$j?OrycgTMw(BwwSg}qW* z=I8A8mjF=olRrhBZN03Je$GxVp7MT5=l?|^e~SNY7C(>t7m1gn(s?6oJ>*SS4_l

JRH8hKGZV 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%0Dm=(a(t9Eejkcp-23fo~ezVnjya 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 J(K)B+{{ZUANJ0Pr literal 0 HcmV?d00001 diff --git a/samples/fluid/src/glsl_shaders.h b/samples/fluid/src/glsl_shaders.h new file mode 100644 index 0000000..a15bb11 --- /dev/null +++ b/samples/fluid/src/glsl_shaders.h @@ -0,0 +1,551 @@ +/********************************************************************* +* +* file: glsl_shaders.h +* note: string literals auto-generated by embed_text.py +* date: 08/082023 +* +**********************************************************************/ +#ifndef __GLSL_SHADERS_H__ +#define __GLSL_SHADERS_H__ + + +//NOTE: string imported from src/shaders/advect.glsl +const char* glsl_advect = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D src;\n" +"uniform sampler2D velocity;\n" +"uniform float delta;\n" +"uniform float dissipation;\n" +"\n" +"vec2 u(ivec2 coord)\n" +"{\n" +" return(texelFetch(velocity, coord, 0).xy);\n" +"}\n" +"\n" +"vec4 q(ivec2 coord)\n" +"{\n" +" if( coord.x < 0\n" +" || coord.x >= textureSize(src, 0).x\n" +" || coord.y < 0\n" +" || coord.y >= textureSize(src, 0).y)\n" +" {\n" +" return(vec4(0.));\n" +" }\n" +" return(texelFetch(src, coord, 0));\n" +"}\n" +"\n" +"vec4 bilerpSrc(vec2 pos)\n" +"{\n" +" vec2 offset = fract(pos);\n" +"\n" +" ivec2 bl = ivec2(floor(pos));\n" +"\n" +" ivec2 br = bl + ivec2(1, 0);\n" +" ivec2 tl = bl + ivec2(0, 1);\n" +" ivec2 tr = bl + ivec2(1, 1);\n" +"\n" +" vec4 lerpTop = (1.-offset.x)*q(tl) + offset.x*q(tr);\n" +" vec4 lerpBottom = (1.-offset.x)*q(bl) + offset.x*q(br);\n" +" vec4 result = (1.-offset.y)*lerpBottom + offset.y*lerpTop;\n" +"\n" +" return(result);\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" float texWidth = float(textureSize(velocity, 0).x);\n" +"\n" +" ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy));\n" +"\n" +" vec2 samplePos = vec2(pixelCoord) - texWidth * delta * u(pixelCoord);\n" +" fragColor = bilerpSrc(samplePos) / (1. + dissipation*delta);\n" +"}\n"; + +//NOTE: string imported from src/shaders/blit_div_fragment.glsl +const char* glsl_blit_div_fragment = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D tex;\n" +"\n" +"vec3 color_map(float v)\n" +"{\n" +" float logv = log(abs(v))/log(10.0);\n" +" float f = floor(logv + 7.0);\n" +" float i = floor(4.0*(logv + 7.0 - f));\n" +"\n" +" if(f < 0.0) return vec3(0.0);\n" +" if(f < 1.0) return mix(vec3(1.0, 0.0, 0.0), vec3(1.0), i/4.0);\n" +" if(f < 2.0) return mix(vec3(0.0, 1.0, 0.0), vec3(1.0), i/4.0);\n" +" if(f < 3.0) return mix(vec3(0.0, 0.0, 1.0), vec3(1.0), i/4.0);\n" +" if(f < 4.0) return mix(vec3(1.0, 1.0, 0.0), vec3(1.0), i/4.0);\n" +" if(f < 5.0) return mix(vec3(1.0, 0.0, 1.0), vec3(1.0), i/4.0);\n" +" if(f < 6.0) return mix(vec3(0.0, 1.0, 1.0), vec3(1.0), i/4.0);\n" +" if(f < 7.0) return mix(vec3(1.0, 0.5, 0.0), vec3(1.0), i/4.0);\n" +" if(f < 8.0) return mix(vec3(1.0, 1.0, 1.0), vec3(1.0), i/4.0);\n" +" return vec3(1.0);\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" ivec2 pixelCoord = ivec2(floor(texCoord.xy * vec2(textureSize(tex, 0).xy)));\n" +" float f = texelFetch(tex, pixelCoord, 0).x;\n" +" fragColor = vec4(color_map(f), 1.0);\n" +"}\n"; + +//NOTE: string imported from src/shaders/blit_div_vertex.glsl +const char* glsl_blit_div_vertex = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"\n" +"in vec2 pos;\n" +"out vec2 texCoord;\n" +"\n" +"uniform mat4 mvp;\n" +"\n" +"void main()\n" +"{\n" +" texCoord = 0.5*(pos + vec2(1,1));\n" +" gl_Position = mvp * vec4(pos, 0, 1);\n" +"}\n"; + +//NOTE: string imported from src/shaders/blit_fragment.glsl +const char* glsl_blit_fragment = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D tex;\n" +"\n" +"void main()\n" +"{\n" +" fragColor = texture(tex, texCoord);\n" +" fragColor.a = 1.0;\n" +"}\n"; + +//NOTE: string imported from src/shaders/blit_residue_fragment.glsl +const char* glsl_blit_residue_fragment = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D xTex;\n" +"uniform sampler2D bTex;\n" +"\n" +"float x(ivec2 coord)\n" +"{\n" +" if( coord.x <= 0\n" +" || coord.x >= textureSize(xTex, 0).x\n" +" || coord.y <= 0\n" +" || coord.y >= textureSize(xTex, 0).y)\n" +" {\n" +" return(0.);\n" +" }\n" +" return(texelFetch(xTex, coord, 0).x);\n" +"}\n" +"\n" +"float b(ivec2 coord)\n" +"{\n" +" if( coord.x <= 0\n" +" || coord.x >= textureSize(bTex, 0).x\n" +" || coord.y <= 0\n" +" || coord.y >= textureSize(bTex, 0).y)\n" +" {\n" +" return(0.);\n" +" }\n" +" return(texelFetch(bTex, coord, 0).x);\n" +"}\n" +"\n" +"vec3 color_map(float v)\n" +"{\n" +" float logv = log(abs(v))/log(10.0);\n" +" float f = floor(logv + 7.0);\n" +" float i = floor(4.0*(logv + 7.0 - f));\n" +"\n" +" if(f < 0.0) return vec3(0.0);\n" +" if(f < 1.0) return mix(vec3(1.0, 0.0, 0.0), vec3(1.0), i/4.0);\n" +" if(f < 2.0) return mix(vec3(0.0, 1.0, 0.0), vec3(1.0), i/4.0);\n" +" if(f < 3.0) return mix(vec3(0.0, 0.0, 1.0), vec3(1.0), i/4.0);\n" +" if(f < 4.0) return mix(vec3(1.0, 1.0, 0.0), vec3(1.0), i/4.0);\n" +" if(f < 5.0) return mix(vec3(1.0, 0.0, 1.0), vec3(1.0), i/4.0);\n" +" if(f < 6.0) return mix(vec3(0.0, 1.0, 1.0), vec3(1.0), i/4.0);\n" +" if(f < 7.0) return mix(vec3(1.0, 0.5, 0.0), vec3(1.0), i/4.0);\n" +" if(f < 8.0) return mix(vec3(1.0, 1.0, 1.0), vec3(1.0), i/4.0);\n" +" return vec3(1.0);\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" ivec2 pixelCoord = ivec2(floor(texCoord.xy * vec2(textureSize(xTex, 0).xy)));\n" +"\n" +" float tl = x(pixelCoord + ivec2(-1, 1));\n" +" float tr = x(pixelCoord + ivec2(1, 1));\n" +" float bl = x(pixelCoord + ivec2(-1, -1));\n" +" float br = x(pixelCoord + ivec2(1, -1));\n" +"\n" +" float residue = b(pixelCoord) - (-tl - tr - bl - br + 4.*x(pixelCoord));\n" +" fragColor = vec4(color_map(residue), 1);\n" +"}\n"; + +//NOTE: string imported from src/shaders/blit_vertex.glsl +const char* glsl_blit_vertex = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"\n" +"in vec2 pos;\n" +"out vec2 texCoord;\n" +"\n" +"uniform mat4 mvp;\n" +"uniform ivec2 gridSize;\n" +"\n" +"void main()\n" +"{\n" +" float margin = 32.;\n" +" float ratio = 1. - 2.*margin/float(gridSize.x);\n" +"\n" +" texCoord = margin/float(gridSize.x) + ratio*(0.5*(pos + vec2(1,1)));\n" +" gl_Position = mvp * vec4(pos, 0, 1);\n" +"}\n"; + +//NOTE: string imported from src/shaders/common_vertex.glsl +const char* glsl_common_vertex = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"\n" +"in vec2 pos;\n" +"out vec2 texCoord;\n" +"\n" +"void main()\n" +"{\n" +" texCoord = 0.5*(pos + vec2(1,1));\n" +" gl_Position = vec4(pos, 0, 1);\n" +"}\n"; + +//NOTE: string imported from src/shaders/divergence.glsl +const char* glsl_divergence = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D src;\n" +"\n" +"vec2 u(ivec2 coord)\n" +"{\n" +" return(texelFetch(src, coord, 0).xy);\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy));\n" +"\n" +" if( pixelCoord.x <= 0\n" +" || pixelCoord.x >= textureSize(src, 0).x\n" +" || pixelCoord.y <= 0\n" +" || pixelCoord.y >= textureSize(src, 0).y)\n" +" {\n" +" fragColor = vec4(0, 0, 0, 1);\n" +" }\n" +" else\n" +" {\n" +" vec2 tl = u(pixelCoord + ivec2(-1, 0));\n" +" vec2 tr = u(pixelCoord);\n" +" vec2 bl = u(pixelCoord + ivec2(-1, -1));\n" +" vec2 br = u(pixelCoord + ivec2(0, -1));\n" +"\n" +" float r = (tr.x + br.x)/2.;\n" +" float l = (tl.x + bl.x)/2.;\n" +" float t = (tl.y + tr.y)/2.;\n" +" float b = (bl.y + br.y)/2.;\n" +"\n" +" fragColor = vec4(-2.*(r - l + t - b), 0, 0, 1);\n" +" }\n" +"}\n"; + +//NOTE: string imported from src/shaders/jacobi_step.glsl +const char* glsl_jacobi_step = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D xTex;\n" +"uniform sampler2D bTex;\n" +"\n" +"float x(ivec2 coord)\n" +"{\n" +" if( coord.x <= 0\n" +" || coord.x >= textureSize(xTex, 0).x\n" +" || coord.y <= 0\n" +" || coord.y >= textureSize(xTex, 0).y)\n" +" {\n" +" return(0.);\n" +" }\n" +" return(texelFetch(xTex, coord, 0).x);\n" +"}\n" +"\n" +"float b(ivec2 coord)\n" +"{\n" +" if( coord.x <= 0\n" +" || coord.x >= textureSize(bTex, 0).x\n" +" || coord.y <= 0\n" +" || coord.y >= textureSize(bTex, 0).y)\n" +" {\n" +" return(0.);\n" +" }\n" +" return(texelFetch(bTex, coord, 0).x);\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy));\n" +"\n" +" if( pixelCoord.x <= 0\n" +" || pixelCoord.y <= 0)\n" +" {\n" +" fragColor = vec4(0, 0, 0, 1);\n" +" }\n" +" else\n" +" {\n" +" float tl = x(pixelCoord + ivec2(-1, 1));\n" +" float tr = x(pixelCoord + ivec2(1, 1));\n" +" float bl = x(pixelCoord + ivec2(-1, -1));\n" +" float br = x(pixelCoord + ivec2(1, -1));\n" +"\n" +" float jacobi = (tl + tr + bl + br + b(pixelCoord))/4.;\n" +" fragColor = vec4(jacobi, 0, 0, 1);\n" +" }\n" +"}\n"; + +//NOTE: string imported from src/shaders/multigrid_correct.glsl +const char* glsl_multigrid_correct = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D src;\n" +"uniform sampler2D error;\n" +"uniform float invGridSize;\n" +"\n" +"float e(ivec2 coord)\n" +"{\n" +" if( coord.x <= 0\n" +" || coord.x >= textureSize(error, 0).x\n" +" || coord.y <= 0\n" +" || coord.y >= textureSize(error, 0).y)\n" +" {\n" +" return(0.);\n" +" }\n" +" return(texelFetch(error, coord, 0).x);\n" +"}\n" +"\n" +"float p(ivec2 coord)\n" +"{\n" +" if( coord.x <= 0\n" +" || coord.x >= textureSize(src, 0).x\n" +" || coord.y <= 0\n" +" || coord.y >= textureSize(src, 0).y)\n" +" {\n" +" return(0.);\n" +" }\n" +" return(texelFetch(src, coord, 0).x);\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy));\n" +" vec2 coarseCoord = vec2(pixelCoord)/2.;\n" +" vec2 offset = fract(coarseCoord);\n" +"\n" +" ivec2 bl = ivec2(floor(coarseCoord));\n" +" ivec2 br = bl + ivec2(1, 0);\n" +" ivec2 tl = bl + ivec2(0, 1);\n" +" ivec2 tr = bl + ivec2(1, 1);\n" +"\n" +" float topLerp = (1.-offset.x)*e(tl)+ offset.x*e(tr);\n" +" float bottomLerp = (1.-offset.x)*e(bl) + offset.x*e(br);\n" +" float bilerpError = (1.-offset.y)*bottomLerp + offset.y*topLerp;\n" +"\n" +" fragColor = vec4(p(pixelCoord) + bilerpError, 0, 0, 1);\n" +"}\n"; + +//NOTE: string imported from src/shaders/multigrid_restrict_residual.glsl +const char* glsl_multigrid_restrict_residual = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D xTex;\n" +"uniform sampler2D bTex;\n" +"\n" +"float x(ivec2 coord)\n" +"{\n" +" if( coord.x <= 0\n" +" || coord.x >= textureSize(xTex, 0).x\n" +" || coord.y <= 0\n" +" || coord.y >= textureSize(xTex, 0).y)\n" +" {\n" +" return(0.);\n" +" }\n" +" return(texelFetch(xTex, coord, 0).x);\n" +"}\n" +"\n" +"float b(ivec2 coord)\n" +"{\n" +" if( coord.x <= 0\n" +" || coord.x >= textureSize(bTex, 0).x\n" +" || coord.y <= 0\n" +" || coord.y >= textureSize(bTex, 0).y)\n" +" {\n" +" return(0.);\n" +" }\n" +" return(texelFetch(bTex, coord, 0).x);\n" +"}\n" +"\n" +"float residual(ivec2 coord)\n" +"{\n" +" ivec2 vr = coord + ivec2(1, 0);\n" +" ivec2 vl = coord - ivec2(1, 0);\n" +" ivec2 vt = coord + ivec2(0, 1);\n" +" ivec2 vb = coord - ivec2(0, 1);\n" +"\n" +" return((x(vl) + x(vr) + x(vt) + x(vb) + b(coord) - 4.*x(coord))*4.);\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy));\n" +"\n" +" float restricted = residual(2*pixelCoord + ivec2(-1, -1))\n" +" + residual(2*pixelCoord + ivec2(1, -1))\n" +" + residual(2*pixelCoord + ivec2(1, 1))\n" +" + residual(2*pixelCoord + ivec2(-1, 1))\n" +" + 2.*residual(2*pixelCoord + ivec2(-1, 0))\n" +" + 2.*residual(2*pixelCoord + ivec2(1, 0))\n" +" + 2.*residual(2*pixelCoord + ivec2(0, -1))\n" +" + 2.*residual(2*pixelCoord + ivec2(0, 1))\n" +" + 4.*residual(2*pixelCoord);\n" +" restricted /= 16.;\n" +" fragColor = vec4(restricted, 0, 0, 1);\n" +"}\n"; + +//NOTE: string imported from src/shaders/splat.glsl +const char* glsl_splat = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D src;\n" +"uniform vec2 splatPos;\n" +"uniform vec3 splatColor;\n" +"uniform float radius;\n" +"uniform float additive;\n" +"uniform float blending;\n" +"\n" +"uniform float randomize;\n" +"\n" +"void main()\n" +"{\n" +" float d2 = dot(texCoord - splatPos, texCoord - splatPos);\n" +" float intensity = exp(-10.*d2/radius);\n" +" vec2 force = splatColor.xy;\n" +"\n" +" vec3 u = texture(src, texCoord).xyz;\n" +" vec3 uAdd = u + intensity*splatColor.xyz;\n" +" vec3 uBlend = u*(1.-intensity) + intensity * splatColor;\n" +"\n" +" fragColor = vec4(uAdd*additive + uBlend*blending, 1);\n" +"}\n"; + +//NOTE: string imported from src/shaders/subtract_pressure.glsl +const char* glsl_subtract_pressure = +"#version 300 es\n" +"\n" +"precision highp float;\n" +"precision highp sampler2D;\n" +"\n" +"in vec2 texCoord;\n" +"out vec4 fragColor;\n" +"\n" +"uniform sampler2D src;\n" +"uniform sampler2D pressure;\n" +"uniform float invGridSize;\n" +"\n" +"vec2 u(ivec2 coord)\n" +"{\n" +" return(texelFetch(src, coord, 0).xy);\n" +"}\n" +"\n" +"float p(ivec2 coord)\n" +"{\n" +" if( coord.x <= 0\n" +" || coord.x >= textureSize(pressure, 0).x\n" +" || coord.y <= 0\n" +" || coord.y >= textureSize(pressure, 0).y)\n" +" {\n" +" return(0.);\n" +" }\n" +" return(texelFetch(pressure, coord, 0).x);\n" +"}\n" +"\n" +"void main()\n" +"{\n" +" ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy));\n" +"\n" +" float tl = p(pixelCoord + ivec2(0, 1));\n" +" float tr = p(pixelCoord + ivec2(1, 1));\n" +" float bl = p(pixelCoord);\n" +" float br = p(pixelCoord + ivec2(1, 0));\n" +"\n" +" float r = (tr + br)/2.;\n" +" float l = (tl + bl)/2.;\n" +" float t = (tl + tr)/2.;\n" +" float b = (bl + br)/2.;\n" +"\n" +" vec2 gradP = vec2(r - l, t - b);\n" +"\n" +" fragColor = vec4(u(pixelCoord) - gradP, 0, 1);\n" +"}\n"; + +#endif // __GLSL_SHADERS_H__ diff --git a/samples/fluid/src/main.c b/samples/fluid/src/main.c new file mode 100644 index 0000000..fe17f4e --- /dev/null +++ b/samples/fluid/src/main.c @@ -0,0 +1,928 @@ +/************************************************************//** +* +* @file: main.cpp +* @author: Martin Fouilleul +* @date: 27/02/2022 +* @revision: +* +*****************************************************************/ + +#include"orca.h" +#include"glsl_shaders.h" + +//---------------------------------------------------------------- +//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 blit_residue_program +{ + GLuint prog; + + GLint pos; + GLint mvp; + GLint xTex; + GLint bTex; +} blit_residue_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 gridSize; + GLint tex; +} blit_program; + +typedef struct splat_program +{ + GLuint prog; + + GLint pos; + GLint src; + GLint splatPos; + GLint splatColor; + GLint radius; + GLint additive; + GLint blending; + GLint randomize; + +} 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; +blit_program blitDivProgram; +blit_residue_program blitResidueProgram; + +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); + + + //TODO errors + int status = 0; + glGetProgramiv(prog, GL_LINK_STATUS, &status); + if(status != GL_TRUE) + { + log_error("program failed to link: "); + int logSize = 0; + glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logSize); + + mem_arena_scope scratch = mem_scratch_begin(); + char* log = mem_arena_alloc(scratch.arena, logSize); + + glGetProgramInfoLog(prog, logSize, 0, log); + log_error("%s\n", log); + + mem_scratch_end(scratch); + } + + int err = glGetError(); + if(err) + { + log_error("gl error %i\n", err); + } + + return(prog); +} + +void init_advect(advect_program* program) +{ + log_info("compiling advect..."); + program->prog = compile_shader(glsl_common_vertex, glsl_advect); + 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) +{ + log_info("compiling div..."); + program->prog = compile_shader(glsl_common_vertex, glsl_divergence); + program->pos = glGetAttribLocation(program->prog, "pos"); + program->src = glGetUniformLocation(program->prog, "src"); +} + +void init_jacobi(jacobi_program* program) +{ + log_info("compiling jacobi..."); + program->prog = compile_shader(glsl_common_vertex, glsl_jacobi_step); + 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) +{ + log_info("compiling multigrid restrict residual..."); + program->prog = compile_shader(glsl_common_vertex, glsl_multigrid_restrict_residual); + 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) +{ + log_info("compiling multigrid correct..."); + program->prog = compile_shader(glsl_common_vertex, glsl_multigrid_correct); + 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) +{ + log_info("compiling subtract..."); + program->prog = compile_shader(glsl_common_vertex, glsl_subtract_pressure); + 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) +{ + log_info("compiling splat..."); + program->prog = compile_shader(glsl_common_vertex, glsl_splat); + 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"); + program->randomize = glGetUniformLocation(program->prog, "randomize"); +} + +void init_blit(blit_program* program) +{ + log_info("compiling blit..."); + program->prog = compile_shader(glsl_blit_vertex, glsl_blit_fragment); + program->pos = glGetAttribLocation(program->prog, "pos"); + program->mvp = glGetUniformLocation(program->prog, "mvp"); + program->tex = glGetUniformLocation(program->prog, "tex"); + program->gridSize = glGetUniformLocation(program->prog, "gridSize"); +} + +void init_blit_div(blit_program* program) +{ + log_info("compiling blit div..."); + program->prog = compile_shader(glsl_blit_div_vertex, glsl_blit_div_fragment); + program->pos = glGetAttribLocation(program->prog, "pos"); + program->mvp = glGetUniformLocation(program->prog, "mvp"); + program->tex = glGetUniformLocation(program->prog, "tex"); +} + +void init_blit_residue(blit_residue_program* program) +{ + log_info("compiling blit residue..."); + program->prog = compile_shader(glsl_blit_div_vertex, glsl_blit_residue_fragment); + program->pos = glGetAttribLocation(program->prog, "pos"); + program->mvp = glGetUniformLocation(program->prog, "mvp"); + program->xTex = glGetUniformLocation(program->prog, "xTex"); + program->bTex = glGetUniformLocation(program->prog, "bTex"); +} + + +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); + 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) + { + log_info("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 (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; + +#define square(x) ((x)*(x)) + +/* +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); +} + +static bool resetCmd = false; + +void reset() +{ +// resetCmd = true; + log_info("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; ifbos[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); +} + +void input_splat(float t) +{ + //NOTE: apply force and dye + if(mouseInput.down && (mouseInput.deltaX || mouseInput.deltaY)) + { + // account for margin + float margin = 32; + + float offset = margin/texWidth; + float ratio = 1 - 2*margin/texWidth; + + float splatPosX = (mouseInput.x/frameWidth)*ratio + offset; + float splatPosY = (1 - mouseInput.y/frameHeight)*ratio + offset; + + float splatVelX = (10000.*DELTA*mouseInput.deltaX/frameWidth)*ratio; + float splatVelY = (-10000.*DELTA*mouseInput.deltaY/frameWidth)*ratio; + + float intensity = 100*sqrtf(square(ratio*mouseInput.deltaX/frameWidth) + square(ratio*mouseInput.deltaY/frameHeight)); + + float r = intensity * (sinf(2*M_PI*0.1*t) + 1); + float g = 0.5*intensity * (cosf(2*M_PI*0.1/M_E*t + 654) + 1); + float b = intensity * (sinf(2*M_PI*0.1/M_SQRT2*t + 937) + 1); + + float radius = 0.005; + + apply_splat(splatPosX, splatPosY, radius, splatVelX, splatVelY, r, g, b, false); + + mouseInput.deltaX = 0; + mouseInput.deltaY = 0; + } +} + +float testDiv[texWidth/2][texWidth/2][4]; + +mg_surface surface; + +ORCA_EXPORT void OnInit() +{ + log_info("Hello, world (from C)"); + + surface = mg_surface_gles(); + mg_surface_prepare(surface); + +// init_color_checker(); +// init_velocity_vortex(); + + // init programs + init_advect(&advectProgram); + init_div(&divProgram); + init_jacobi(&jacobiProgram); + init_multigrid_restrict_residual(&multigridRestrictResidualProgram); + init_multigrid_correct(&multigridCorrectProgram); + init_blit_residue(&blitResidueProgram); + + init_subtract(&subtractProgram); + init_splat(&splatProgram); + init_blit(&blitProgram); + init_blit_div(&blitDivProgram); + + // init frame buffers + log_info("create color buffer"); + init_frame_buffer(&colorBuffer, texWidth, texHeight, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, (char*)colorInitData); + log_info("create velocity buffer"); + init_frame_buffer(&velocityBuffer, texWidth, texHeight, TEX_INTERNAL_FORMAT, TEX_FORMAT, TEX_TYPE, (char*)velocityInitData); + + int gridFactor = 1; + for(int i=0; i= 0.5) + { + splat = false; + splatDir++; + splatDir = splatDir % 3; + } + float dirX = 0; + float dirY = 0; + if(splatDir == 0) + { + dirX = 0; + dirY = 0.3; + } + if(splatDir == 1) + { + dirX = 0.3; + dirY = 0; + } + if(splatDir == 2) + { + dirX = 0.2121; + dirY = 0.2121; + } + apply_splat(0.5, 0.5, dirX, dirY, 1.5, 1., 0.1, false); + } + resetCmd = false; + + if(frameCount>20) + { + return; + } + frameCount++; +*/ + + input_splat(t); + + + //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 + glBindFramebuffer(GL_FRAMEBUFFER, pressureBuffer[0].fbos[1]); + glClear(GL_COLOR_BUFFER_BIT); + + #if 0 + multigrid_clear(&pressureBuffer[0]); + jacobi_solve(&pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, texWidth*texHeight); + #else + multigrid_clear(&pressureBuffer[0]); + + for(int i=0; i<1; i++) + { + jacobi_solve(&pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, 2); + multigrid_coarsen_residual(&divBuffer[1], &pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE); + + multigrid_clear(&pressureBuffer[1]); + jacobi_solve(&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]); + jacobi_solve(&pressureBuffer[2], &divBuffer[2], 4*INV_GRID_SIZE, 30); + + multigrid_prolongate_and_correct(&pressureBuffer[1], &pressureBuffer[2], 2*INV_GRID_SIZE); + jacobi_solve(&pressureBuffer[1], &divBuffer[1], 2*INV_GRID_SIZE, 8); + + multigrid_prolongate_and_correct(&pressureBuffer[0], &pressureBuffer[1], INV_GRID_SIZE); + jacobi_solve(&pressureBuffer[0], &divBuffer[0], INV_GRID_SIZE, 4); + } + #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.001); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + frame_buffer_swap(&colorBuffer); + + //NOTE: Blit color texture to screen + + //NOTE: blit residue to screen + glViewport(0, 0, frameWidth, frameHeight); + + float displayMatrix[16] = { + 1/aspectRatio, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 }; + +/* + glUseProgram(blitResidueProgram.prog); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, pressureBuffer[0].textures[0]); + glUniform1i(blitResidueProgram.xTex, 0); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, divBuffer[0].textures[0]); + glUniform1i(blitResidueProgram.bTex, 1); + + glUniformMatrix4fv(blitResidueProgram.mvp, 1, GL_FALSE, displayMatrix); + + glDrawArrays(GL_TRIANGLES, 0, 6); +//*/ +//* + glUseProgram(blitProgram.prog); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, colorBuffer.textures[0]); + glUniform1i(blitProgram.tex, 0); + + glUniform2i(blitProgram.gridSize, texWidth, texHeight); + + glUniformMatrix4fv(blitProgram.mvp, 1, GL_FALSE, displayMatrix); + + glDrawArrays(GL_TRIANGLES, 0, 6); +/*/ + + //NOTE: recompute divergence of (corrected) 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: Blit divergence to screen + glViewport(0, 0, canvas_width(), canvas_height()); + glUseProgram(blitDivProgram.prog); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, divBuffer[0].textures[0]); + glUniform1i(blitDivProgram.tex, 0); + + glUniformMatrix4fv(blitDivProgram.mvp, 1, GL_FALSE, displayMatrix); + + glDrawArrays(GL_TRIANGLES, 0, 6); + +//*/ + + mg_surface_present(surface); +} diff --git a/samples/fluid/src/shaders/advect.glsl b/samples/fluid/src/shaders/advect.glsl new file mode 100644 index 0000000..28cbd2c --- /dev/null +++ b/samples/fluid/src/shaders/advect.glsl @@ -0,0 +1,56 @@ +#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; + +vec2 u(ivec2 coord) +{ + return(texelFetch(velocity, coord, 0).xy); +} + +vec4 q(ivec2 coord) +{ + if( coord.x < 0 + || coord.x >= textureSize(src, 0).x + || coord.y < 0 + || coord.y >= textureSize(src, 0).y) + { + return(vec4(0.)); + } + return(texelFetch(src, coord, 0)); +} + +vec4 bilerpSrc(vec2 pos) +{ + vec2 offset = fract(pos); + + ivec2 bl = ivec2(floor(pos)); + + ivec2 br = bl + ivec2(1, 0); + ivec2 tl = bl + ivec2(0, 1); + ivec2 tr = bl + ivec2(1, 1); + + vec4 lerpTop = (1.-offset.x)*q(tl) + offset.x*q(tr); + vec4 lerpBottom = (1.-offset.x)*q(bl) + offset.x*q(br); + vec4 result = (1.-offset.y)*lerpBottom + offset.y*lerpTop; + + return(result); +} + +void main() +{ + float texWidth = float(textureSize(velocity, 0).x); + + ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy)); + + vec2 samplePos = vec2(pixelCoord) - texWidth * delta * u(pixelCoord); + fragColor = bilerpSrc(samplePos) / (1. + dissipation*delta); +} diff --git a/samples/fluid/src/shaders/blit_div_fragment.glsl b/samples/fluid/src/shaders/blit_div_fragment.glsl new file mode 100644 index 0000000..2beaac8 --- /dev/null +++ b/samples/fluid/src/shaders/blit_div_fragment.glsl @@ -0,0 +1,34 @@ +#version 300 es + +precision highp float; +precision highp sampler2D; + +in vec2 texCoord; +out vec4 fragColor; + +uniform sampler2D tex; + +vec3 color_map(float v) +{ + float logv = log(abs(v))/log(10.0); + float f = floor(logv + 7.0); + float i = floor(4.0*(logv + 7.0 - f)); + + if(f < 0.0) return vec3(0.0); + if(f < 1.0) return mix(vec3(1.0, 0.0, 0.0), vec3(1.0), i/4.0); + if(f < 2.0) return mix(vec3(0.0, 1.0, 0.0), vec3(1.0), i/4.0); + if(f < 3.0) return mix(vec3(0.0, 0.0, 1.0), vec3(1.0), i/4.0); + if(f < 4.0) return mix(vec3(1.0, 1.0, 0.0), vec3(1.0), i/4.0); + if(f < 5.0) return mix(vec3(1.0, 0.0, 1.0), vec3(1.0), i/4.0); + if(f < 6.0) return mix(vec3(0.0, 1.0, 1.0), vec3(1.0), i/4.0); + if(f < 7.0) return mix(vec3(1.0, 0.5, 0.0), vec3(1.0), i/4.0); + if(f < 8.0) return mix(vec3(1.0, 1.0, 1.0), vec3(1.0), i/4.0); + return vec3(1.0); +} + +void main() +{ + ivec2 pixelCoord = ivec2(floor(texCoord.xy * vec2(textureSize(tex, 0).xy))); + float f = texelFetch(tex, pixelCoord, 0).x; + fragColor = vec4(color_map(f), 1.0); +} diff --git a/samples/fluid/src/shaders/blit_div_vertex.glsl b/samples/fluid/src/shaders/blit_div_vertex.glsl new file mode 100644 index 0000000..f69dc76 --- /dev/null +++ b/samples/fluid/src/shaders/blit_div_vertex.glsl @@ -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); +} diff --git a/samples/fluid/src/shaders/blit_fragment.glsl b/samples/fluid/src/shaders/blit_fragment.glsl new file mode 100644 index 0000000..4e905a3 --- /dev/null +++ b/samples/fluid/src/shaders/blit_fragment.glsl @@ -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; +} diff --git a/samples/fluid/src/shaders/blit_residue_fragment.glsl b/samples/fluid/src/shaders/blit_residue_fragment.glsl new file mode 100644 index 0000000..f630f41 --- /dev/null +++ b/samples/fluid/src/shaders/blit_residue_fragment.glsl @@ -0,0 +1,65 @@ +#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); +} + +vec3 color_map(float v) +{ + float logv = log(abs(v))/log(10.0); + float f = floor(logv + 7.0); + float i = floor(4.0*(logv + 7.0 - f)); + + if(f < 0.0) return vec3(0.0); + if(f < 1.0) return mix(vec3(1.0, 0.0, 0.0), vec3(1.0), i/4.0); + if(f < 2.0) return mix(vec3(0.0, 1.0, 0.0), vec3(1.0), i/4.0); + if(f < 3.0) return mix(vec3(0.0, 0.0, 1.0), vec3(1.0), i/4.0); + if(f < 4.0) return mix(vec3(1.0, 1.0, 0.0), vec3(1.0), i/4.0); + if(f < 5.0) return mix(vec3(1.0, 0.0, 1.0), vec3(1.0), i/4.0); + if(f < 6.0) return mix(vec3(0.0, 1.0, 1.0), vec3(1.0), i/4.0); + if(f < 7.0) return mix(vec3(1.0, 0.5, 0.0), vec3(1.0), i/4.0); + if(f < 8.0) return mix(vec3(1.0, 1.0, 1.0), vec3(1.0), i/4.0); + return vec3(1.0); +} + +void main() +{ + ivec2 pixelCoord = ivec2(floor(texCoord.xy * vec2(textureSize(xTex, 0).xy))); + + float tl = x(pixelCoord + ivec2(-1, 1)); + float tr = x(pixelCoord + ivec2(1, 1)); + float bl = x(pixelCoord + ivec2(-1, -1)); + float br = x(pixelCoord + ivec2(1, -1)); + + float residue = b(pixelCoord) - (-tl - tr - bl - br + 4.*x(pixelCoord)); + fragColor = vec4(color_map(residue), 1); +} diff --git a/samples/fluid/src/shaders/blit_vertex.glsl b/samples/fluid/src/shaders/blit_vertex.glsl new file mode 100644 index 0000000..d397a76 --- /dev/null +++ b/samples/fluid/src/shaders/blit_vertex.glsl @@ -0,0 +1,18 @@ +#version 300 es + +precision highp float; + +in vec2 pos; +out vec2 texCoord; + +uniform mat4 mvp; +uniform ivec2 gridSize; + +void main() +{ + float margin = 32.; + float ratio = 1. - 2.*margin/float(gridSize.x); + + texCoord = margin/float(gridSize.x) + ratio*(0.5*(pos + vec2(1,1))); + gl_Position = mvp * vec4(pos, 0, 1); +} diff --git a/samples/fluid/src/shaders/common_vertex.glsl b/samples/fluid/src/shaders/common_vertex.glsl new file mode 100644 index 0000000..1dd2519 --- /dev/null +++ b/samples/fluid/src/shaders/common_vertex.glsl @@ -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); +} diff --git a/samples/fluid/src/shaders/divergence.glsl b/samples/fluid/src/shaders/divergence.glsl new file mode 100644 index 0000000..8b23781 --- /dev/null +++ b/samples/fluid/src/shaders/divergence.glsl @@ -0,0 +1,41 @@ +#version 300 es + +precision highp float; +precision highp sampler2D; + +in vec2 texCoord; +out vec4 fragColor; + +uniform sampler2D src; + +vec2 u(ivec2 coord) +{ + return(texelFetch(src, coord, 0).xy); +} + +void main() +{ + ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy)); + + if( pixelCoord.x <= 0 + || pixelCoord.x >= textureSize(src, 0).x + || pixelCoord.y <= 0 + || pixelCoord.y >= textureSize(src, 0).y) + { + fragColor = vec4(0, 0, 0, 1); + } + else + { + vec2 tl = u(pixelCoord + ivec2(-1, 0)); + vec2 tr = u(pixelCoord); + vec2 bl = u(pixelCoord + ivec2(-1, -1)); + vec2 br = u(pixelCoord + ivec2(0, -1)); + + float r = (tr.x + br.x)/2.; + float l = (tl.x + bl.x)/2.; + float t = (tl.y + tr.y)/2.; + float b = (bl.y + br.y)/2.; + + fragColor = vec4(-2.*(r - l + t - b), 0, 0, 1); + } +} diff --git a/samples/fluid/src/shaders/jacobi_step.glsl b/samples/fluid/src/shaders/jacobi_step.glsl new file mode 100644 index 0000000..35418b9 --- /dev/null +++ b/samples/fluid/src/shaders/jacobi_step.glsl @@ -0,0 +1,55 @@ +#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); +} + +void main() +{ + ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy)); + + if( pixelCoord.x <= 0 + || pixelCoord.y <= 0) + { + fragColor = vec4(0, 0, 0, 1); + } + else + { + float tl = x(pixelCoord + ivec2(-1, 1)); + float tr = x(pixelCoord + ivec2(1, 1)); + float bl = x(pixelCoord + ivec2(-1, -1)); + float br = x(pixelCoord + ivec2(1, -1)); + + float jacobi = (tl + tr + bl + br + b(pixelCoord))/4.; + fragColor = vec4(jacobi, 0, 0, 1); + } +} diff --git a/samples/fluid/src/shaders/multigrid_correct.glsl b/samples/fluid/src/shaders/multigrid_correct.glsl new file mode 100644 index 0000000..1d59ec7 --- /dev/null +++ b/samples/fluid/src/shaders/multigrid_correct.glsl @@ -0,0 +1,53 @@ +#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) +{ + if( coord.x <= 0 + || coord.x >= textureSize(error, 0).x + || coord.y <= 0 + || coord.y >= textureSize(error, 0).y) + { + return(0.); + } + return(texelFetch(error, coord, 0).x); +} + +float p(ivec2 coord) +{ + if( coord.x <= 0 + || coord.x >= textureSize(src, 0).x + || coord.y <= 0 + || coord.y >= textureSize(src, 0).y) + { + return(0.); + } + return(texelFetch(src, 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(p(pixelCoord) + bilerpError, 0, 0, 1); +} diff --git a/samples/fluid/src/shaders/multigrid_restrict_residual.glsl b/samples/fluid/src/shaders/multigrid_restrict_residual.glsl new file mode 100644 index 0000000..bea8b9c --- /dev/null +++ b/samples/fluid/src/shaders/multigrid_restrict_residual.glsl @@ -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); +} diff --git a/samples/fluid/src/shaders/splat.glsl b/samples/fluid/src/shaders/splat.glsl new file mode 100644 index 0000000..34a1347 --- /dev/null +++ b/samples/fluid/src/shaders/splat.glsl @@ -0,0 +1,29 @@ +#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; + +uniform float randomize; + +void main() +{ + float d2 = dot(texCoord - splatPos, texCoord - splatPos); + float intensity = exp(-10.*d2/radius); + vec2 force = splatColor.xy; + + vec3 u = texture(src, texCoord).xyz; + vec3 uAdd = u + intensity*splatColor.xyz; + vec3 uBlend = u*(1.-intensity) + intensity * splatColor; + + fragColor = vec4(uAdd*additive + uBlend*blending, 1); +} diff --git a/samples/fluid/src/shaders/subtract_pressure.glsl b/samples/fluid/src/shaders/subtract_pressure.glsl new file mode 100644 index 0000000..7905df9 --- /dev/null +++ b/samples/fluid/src/shaders/subtract_pressure.glsl @@ -0,0 +1,47 @@ +#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 >= textureSize(pressure, 0).x + || coord.y <= 0 + || coord.y >= textureSize(pressure, 0).y) + { + return(0.); + } + return(texelFetch(pressure, coord, 0).x); +} + +void main() +{ + ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy)); + + float tl = p(pixelCoord + ivec2(0, 1)); + float tr = p(pixelCoord + ivec2(1, 1)); + float bl = p(pixelCoord); + float br = p(pixelCoord + ivec2(1, 0)); + + float r = (tr + br)/2.; + float l = (tl + bl)/2.; + float t = (tl + tr)/2.; + float b = (bl + br)/2.; + + vec2 gradP = vec2(r - l, t - b); + + fragColor = vec4(u(pixelCoord) - gradP, 0, 1); +} diff --git a/samples/glesTriangle/.gitignore b/samples/glesTriangle/.gitignore new file mode 100644 index 0000000..4c0e970 --- /dev/null +++ b/samples/glesTriangle/.gitignore @@ -0,0 +1,3 @@ +Triangle +profile.dtrace +profile.spall diff --git a/samples/glesTriangle/build.bat b/samples/glesTriangle/build.bat new file mode 100644 index 0000000..add38e3 --- /dev/null +++ b/samples/glesTriangle/build.bat @@ -0,0 +1,17 @@ +@echo off + +:: compile wasm module +set wasmFlags=--target=wasm32^ + --no-standard-libraries ^ + -fno-builtin ^ + -Wl,--no-entry ^ + -Wl,--export-dynamic ^ + -g ^ + -O2 ^ + -mbulk-memory ^ + -D__ORCA__ ^ + -isystem ..\..\cstdlib\include -I ..\..\sdk -I..\..\milepost\ext -I ..\..\milepost -I ..\..\milepost\src + +clang %wasmFlags% -o .\module.wasm ..\..\sdk\orca.c ..\..\cstdlib\src\*.c src\main.c + +python3 ..\..\scripts\mkapp.py --orca-dir ..\.. --name Triangle module.wasm diff --git a/samples/glesTriangle/build.sh b/samples/glesTriangle/build.sh new file mode 100755 index 0000000..32f59df --- /dev/null +++ b/samples/glesTriangle/build.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -euo pipefail + +if [[ -x /usr/local/opt/llvm/bin/clang ]]; then + CLANG=/usr/local/opt/llvm/bin/clang +elif [[ -x /opt/homebrew/opt/llvm/bin/clang ]]; then + CLANG=/opt/homebrew/opt/llvm/bin/clang +else + echo "Could not find Homebrew clang; this script will probably not work." + CLANG=clang +fi + +STDLIB_DIR=../../cstdlib +ORCA_SDK_DIR=../../sdk +MILEPOST_DIR=../../milepost + +wasmFlags="--target=wasm32 \ + --no-standard-libraries \ + -fno-builtin \ + -Wl,--no-entry \ + -Wl,--export-dynamic \ + -g \ + -O2 \ + -mbulk-memory \ + -D__ORCA__ \ + -I $STDLIB_DIR/include \ + -I $ORCA_SDK_DIR \ + -I $MILEPOST_DIR/ext -I $MILEPOST_DIR -I $MILEPOST_DIR/src" + +$CLANG $wasmFlags -o ./module.wasm ../../sdk/orca.c ../../cstdlib/src/*.c src/main.c + +orca bundle --orca-dir ../.. --name Triangle module.wasm diff --git a/samples/glesTriangle/src/main.c b/samples/glesTriangle/src/main.c new file mode 100644 index 0000000..c0b0dd6 --- /dev/null +++ b/samples/glesTriangle/src/main.c @@ -0,0 +1,114 @@ +#include +#include +#include + +#include + +vec2 frameSize = {100, 100}; + +mg_surface surface; + +unsigned int program; + +const char* vshaderSource = + "attribute vec4 vPosition;\n" + "uniform mat4 transform;\n" + "void main()\n" + "{\n" + " gl_Position = transform*vPosition;\n" + "}\n"; + +const char* fshaderSource = + "precision mediump float;\n" + "void main()\n" + "{\n" + " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" + "}\n"; + +void compile_shader(GLuint shader, const char* source) +{ + glShaderSource(shader, 1, &source, 0); + glCompileShader(shader); + + int err = glGetError(); + if(err) + { + log_info("gl error"); + } +} + +char* ORCA_IMPORT(orca_mem_grow)(u64 size); + +ORCA_EXPORT void OnInit(void) +{ + surface = mg_surface_gles(); + mg_surface_prepare(surface); + + const char* extensions = (const char*)glGetString(GL_EXTENSIONS); + log_info("GLES extensions: %s\n", extensions); + + int extensionCount = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount); + for(int i=0; i= (char*)_mem) && (((char*)'+ argName +' - (char*)_mem) < m3_GetMemorySize(runtime)), "parameter \''+argName+'\' is out of bounds");\n' + s += '\t\tORCA_ASSERT((char*)' + argName + ' + ' + + proc = argLen.get('proc') + if proc != None: + s += proc + '(runtime, ' + lenProcArgs = argLen['args'] + for i, lenProcArg in enumerate(lenProcArgs): + s += lenProcArg + if i < len(lenProcArgs)-1: + s += ', ' + s += ')' + else: + components = argLen.get('components') + countArg = argLen.get('count') + + if components != None: + s += str(components) + if countArg != None: + s += '*' + if countArg != None: + s += countArg + + if typeCName.endswith('**') or (typeCName.startswith('void') == False and typeCName.startswith('const void') == False): + s += '*sizeof('+typeCName[:-1]+')' + + s += ' <= ((char*)_mem + m3_GetMemorySize(runtime)), "parameter \''+argName+'\' overflows wasm memory");\n' + s += '\t}\n' + + s += '\t' + + if retTag == 'i': + s += '*((i32*)&_sp[0]) = (i32)' + elif retTag == 'I': + s += '*((i64*)&_sp[0]) = (i64)' + elif retTag == 'f': + s += '*((f32*)&_sp[0]) = (f32)' + elif retTag == 'F': + s += '*((f64*)&_sp[0]) = (f64)' + elif retTag == 'S': + retTypeName = decl['ret']['name'] + retTypeCName = decl['ret'].get('cname', retTypeName) + s += '*(' + retTypeCName + '*)((char*)_mem + *(i32*)&_sp[0]) = ' + elif retTag == 'p': + print("Warning: " + name + ": pointer return type not supported yet") + + s += cname + '(' + + for i, arg in enumerate(decl['args']): + s += arg['name'] + if i+1 < len(decl['args']): s += ', ' @@ -158,8 +214,9 @@ def bindgen(apiName, spec, **kwargs): print(s, file=host_bindings) # link function - s = 'int bindgen_link_' + apiName + '_api(IM3Module module)\n{\n\t' - s += 'M3Result res;\n' + s = 'int bindgen_link_' + apiName + '_api(IM3Module module)\n{\n' + s += ' M3Result res;\n' + s += ' int ret = 0;\n' for decl in data: name = decl['name'] @@ -185,11 +242,14 @@ def bindgen(apiName, spec, **kwargs): m3Sig += ')' - s += '\tres = m3_LinkRawFunction(module, "*", "' + name + '", "' + m3Sig + '", ' + cname + '_stub);\n' - s += '\tif(res != m3Err_none && res != m3Err_functionLookupFailed) { log_error("error: %s\\n", res); return(-1); }\n\n' + s += ' res = m3_LinkRawFunction(module, "*", "' + name + '", "' + m3Sig + '", ' + cname + '_stub);\n' + s += ' if(res != m3Err_none && res != m3Err_functionLookupFailed)\n' + s += ' {\n' + s += ' log_error("Couldn\'t link function ' + name + ' (%s)\\n", res);\n' + s += ' ret = -1;\n' + s += ' }\n\n' - - s += '\treturn(0);\n}\n' + s += '\treturn(ret);\n}\n' print(s, file=host_bindings) @@ -213,7 +273,7 @@ if __name__ == "__main__": wasm3_bindings_path = args.wasm3_bindings if wasm3_bindings_path == None: wasm3_bindings_path = 'bindgen_' + apiName + '_wasm3_bindings.c' - + bindgen(apiName, spec, guest_stubs=guest_stubs_path, guest_include=args.guest_include, diff --git a/scripts/bundle.py b/scripts/bundle.py index 1cd4791..7fe9e9a 100644 --- a/scripts/bundle.py +++ b/scripts/bundle.py @@ -171,6 +171,8 @@ def macos_make_app(args): icon.icns NSHighResolutionCapable True + MetalCaptureEnabled + """ diff --git a/scripts/dev.py b/scripts/dev.py index 571b9d0..7f258ab 100644 --- a/scripts/dev.py +++ b/scripts/dev.py @@ -10,6 +10,7 @@ from zipfile import ZipFile from . import checksum from .bindgen import bindgen +from .gles_gen import gles_gen from .log import * from .utils import pushd, removeall @@ -374,6 +375,12 @@ def gen_all_bindings(): bindgen("core", "src/core_api.json", wasm3_bindings="src/core_api_bind_gen.c", ) + + gles_gen("milepost/ext/gl.xml", + "src/gles_api.json", + "sdk/gl31.h" + ) + bindgen("gles", "src/gles_api.json", wasm3_bindings="src/gles_api_bind_gen.c", ) diff --git a/scripts/gles_gen.py b/scripts/gles_gen.py new file mode 100644 index 0000000..cb3377e --- /dev/null +++ b/scripts/gles_gen.py @@ -0,0 +1,315 @@ +from .reg_modified import * +import xml.etree.ElementTree as et +from argparse import ArgumentParser + +# remove APIs that can't be sandboxed +removeProc = [ + "glMapBuffer", + "glMapBufferRange", + "glUnmapBuffer", + "glFlushMappedBufferRange", + "glGetBufferPointerv" +] + +def gen_gles_header(spec, filename): + # Generates the GLES header, wrapping gl functions + # prototypes in ORCA_IMPORT() macro + + gles2through31Pat = '2\.[0-9]|3\.[01]' + allVersions = '.*' + + genOpts = CGeneratorOptions( + filename=filename, + apiname='gles2', + profile='common', + versions=gles2through31Pat, + emitversions=allVersions, + protectProto=False, + procMacro='ORCA_IMPORT', + removeProc = removeProc) + + reg = Registry() + tree = et.parse(spec) + reg.loadElementTree(tree) + + logFile = open('./gles_gen.log', 'w') + gen = COutputGenerator(diagFile=logFile) + reg.setGenerator(gen) + reg.apiGen(genOpts) + + logFile.close() + + +def get_bindgen_tag_for_type(typeName): + typeToTags = { + "void": "v", + + "GLenum": "i", + "GLbitfield": "i", + + "GLboolean": "i", + "GLbyte": "i", + "GLubyte": "i", + "GLchar": "i", + + "GLshort": "i", + "GLushort": "i", + "GLhalf": "i", + "GLhalfARB": "i", + + "GLuint": "i", + "GLint": "i", + "GLclampx": "i", + "GLsizei": "i", + "GLfixed": "i", + + "GLintptr": "i", + "GLsizeiptr": "i", + + "GLuint64": "I", + "GLint64": "I", + + "GLfloat": "f", + "GLclampf": "f", + + "GLdouble": "F", + "GLclampd": "F", + + #NOTE: we treat sync objects as opaque 64bit values + #TODO we should _also_ make sure that Wasm code treat them as 64bit values + "GLsync": "I" + } + + if typeName[len(typeName)-1] == '*': + return "p" + else: + tag = typeToTags.get(typeName) + return tag + + +def gen_compsize_len_entry(name, argName, compsizeArgs): + + entry = '\t\t\t"len": {\n' + entry += '\t\t\t\t"proc": "orca_'+ name +'_'+argName+'_length",\n' + entry += '\t\t\t\t"args": [' + + for i, compsizeArg in enumerate(compsizeArgs): + entry += '"' + compsizeArg + '"' + if i < len(compsizeArgs)-1: + entry += ', ' + entry += ']\n' + entry += '\t\t\t}' + return entry + +def gen_argcount_len_entry(name, argName, tokens): + + entry = '\t\t\t"len": {' + if len(tokens) == 2: + if tokens[1].isnumeric() == False: + print("Warning: function " + name + ": couldn't parse parameter '" + argName + "' lenght attribute") + entry += '"count": "' + tokens[0] + '", "components": '+ tokens[1] + elif len(tokens) == 1: + if tokens[0].isnumeric(): + entry += '"components":'+ tokens[0] + else: + entry += '"count": "'+ tokens[0] + '"' + else: + print("Warning: function " + name + ": couldn't parse parameter '" + argName + "' lenght attribute") + + entry += '}' + return entry + +def gen_gles_bindgen_json(spec, filename): + + # Gather gles 3.1 required functions + tree = et.parse(spec) + api = [] + + for feature in tree.iterfind('feature[@api="gles2"]'): + if float(feature.get('number')) > 3.1: + break + + for require in feature.iter('require'): + if require.get('profile') == 'compatibility': + continue + for command in require.iter('command'): + if command.get('name') not in removeProc: + api.append(command.get('name')) + + for remove in feature.iter('remove'): + for command in remove.iter('command'): + api.remove(command.get('name')) + + # put all GL commands in a dict + commands = dict() + commandsSpec = tree.find('./commands') + for command in commandsSpec.iter('command'): + name = command.find('proto/name') + commands[name.text] = command + + # TODO: Generate json descriptions for commands in api + + manualBind = [ + "glShaderSource", + "glGetVertexAttribPointerv", + "glVertexAttribPointer", + "glVertexAttribIPointer", + "glGetString", + "glGetStringi", + "glGetUniformIndices" + ] + + json = '[\n' + for name in api: + if name in manualBind: + continue + + command = commands.get(name) + if command == None: + print("Couldn't find definition for required command '" + name + "'") + exit(-1) + + proto = command.find("proto") + ptype = proto.find("ptype") + + retType = '' + if proto.text != None: + retType += proto.text + + if ptype != None: + if ptype.text != None: + retType += ptype.text + if ptype.tail != None: + retType += ptype.tail + + retType = retType.strip() + + retTag = get_bindgen_tag_for_type(retType) + if retTag == None: + print("Couldn't find tag for GL type '" + retType + "'") + exit(-1) + + entry = '{\n\t"name": "' + name + '",\n' + entry += '\t"cname": "' + name + '",\n' + entry += '\t"ret": { "name": "' + retType + '", "tag": "' + retTag + '"},\n' + + entry += '\t"args": [ ' + + # iterate through params + for param in command.iter('param'): + + argNode = param.find('name') + argName = argNode.text + + typeNode = param.find('ptype') + + typeName = '' + + if param.text != None: + typeName += param.text + + if typeNode != None: + if typeNode.text != None: + typeName += typeNode.text + if typeNode.tail != None: + typeName += typeNode.tail + + typeName = typeName.strip() + + if typeName.endswith('**'): + print("Warning: function " + name + ": parameter " + argName + " has 2 (or more) levels of indirection") + + typeTag = get_bindgen_tag_for_type(typeName) + + if typeTag == None: + print("Couldn't find tag for GL type '" + typeName + "' in function '"+ name +"'") + exit(-1) + + entry += '\n' + entry += '\t\t{\n\t\t\t"name": "'+ argName +'",\n' + entry += '\t\t\t"type": {"name": "'+ typeName +'", "tag": "'+ typeTag +'"}' + + lenString = param.get('len') + + nullStringProcWithNoLen = [ + "glBindAttribLocation", + "glGetAttribLocation", + "glGetUniformLocation" + ] + + drawIndirectProc = [ + "glDrawArraysIndirect", + "glDrawElementsIndirect" + ] + + if typeTag == "p": + if lenString == None: + if name in drawIndirectProc: + entry += ',\n' + entry += gen_compsize_len_entry(name, argName, ['indirect']) + elif name in nullStringProcWithNoLen: + entry += ',\n' + entry += gen_compsize_len_entry(name, argName, ['name']) + else: + print("Warning: function " + name + ": parameter " + argName + " has no len attribute") + + elif lenString != None: + entry += ',\n' + + tokens = lenString.split('*') + + if lenString.startswith("COMPSIZE"): + tmp = lenString + if tmp.startswith("COMPSIZE("): + tmp = tmp[len("COMPSIZE("):] + if tmp.endswith(")"): + tmp = tmp[:-1] + + compsizeArgs = list(filter(None, tmp.split(","))) + + if len(compsizeArgs) == 0: + # special case glGetUniformBlockIndex which isn't specified correctly in gl.xml + if name == 'glGetUniformBlockIndex': + compsizeArgs = ['uniformBlockName'] + + entry += gen_compsize_len_entry(name, argName, compsizeArgs) + + else: + entry += gen_argcount_len_entry(name, argName, tokens) + + entry += '\n\t\t},' + + entry = entry[:-1] + entry += '\n\t]\n}' + + json += entry + json += ',\n' + + json = json[:-2] + json += '\n]' + + # write json to jsonFile + f = open(filename, 'w') + f.write(json) + f.close() + +def gles_gen(spec, json, header): + gen_gles_header(spec, header) + gen_gles_bindgen_json(spec, json) + +#---------------------------------------- +# driver +#---------------------------------------- + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("-s", "--spec") + parser.add_argument("--header") + parser.add_argument("-j", "--json") + + args = parser.parse_args() + + glesHeader = args.header + jsonFile = args.json + + gles_gen(args.spec, jsonFile, glesHeader) diff --git a/scripts/reg_modified.py b/scripts/reg_modified.py new file mode 100644 index 0000000..5f4e769 --- /dev/null +++ b/scripts/reg_modified.py @@ -0,0 +1,1175 @@ +#!/usr/bin/python3 -i +# +# Copyright 2013-2020 The Khronos Group Inc. +# SPDX-License-Identifier: Apache-2.0 + +import io,os,re,string,sys +from xml import etree + +def write(*args, **kwargs): + file = kwargs.pop('file', sys.stdout) + end = kwargs.pop('end', '\n') + file.write(' '.join([str(arg) for arg in args])) + file.write(end) + +# noneStr - returns string argument, or "" if argument is None. +# Used in converting lxml Elements into text. +# str - string to convert +def noneStr(str): + if (str): + return str + else: + return "" + +# matchAPIProfile - returns whether an API and profile +# being generated matches an element's profile +# api - string naming the API to match +# profile - string naming the profile to match +# elem - Element which (may) have 'api' and 'profile' +# attributes to match to. +# If a tag is not present in the Element, the corresponding API +# or profile always matches. +# Otherwise, the tag must exactly match the API or profile. +# Thus, if 'profile' = core: +# with no attribute will match +# will match +# will not match +# Possible match conditions: +# Requested Element +# Profile Profile +# --------- -------- +# None None Always matches +# 'string' None Always matches +# None 'string' Does not match. Can't generate multiple APIs +# or profiles, so if an API/profile constraint +# is present, it must be asked for explicitly. +# 'string' 'string' Strings must match +# +# ** In the future, we will allow regexes for the attributes, +# not just strings, so that api="^(gl|gles2)" will match. Even +# this isn't really quite enough, we might prefer something +# like "gl(core)|gles1(common-lite)". +def matchAPIProfile(api, profile, elem): + """Match a requested API & profile name to a api & profile attributes of an Element""" + match = True + # Match 'api', if present + if ('api' in elem.attrib): + if (api == None): + raise UserWarning("No API requested, but 'api' attribute is present with value '" + + elem.get('api') + "'") + elif (api != elem.get('api')): + # Requested API doesn't match attribute + return False + if ('profile' in elem.attrib): + if (profile == None): + raise UserWarning("No profile requested, but 'profile' attribute is present with value '" + + elem.get('profile') + "'") + elif (profile != elem.get('profile')): + # Requested profile doesn't match attribute + return False + return True + +# BaseInfo - base class for information about a registry feature +# (type/group/enum/command/API/extension). +# required - should this feature be defined during header generation +# (has it been removed by a profile or version)? +# declared - has this feature been defined already? +# elem - lxml.etree Element for this feature +# resetState() - reset required/declared to initial values. Used +# prior to generating a new API interface. +class BaseInfo: + """Represents the state of a registry feature, used during API generation""" + def __init__(self, elem): + self.required = False + self.declared = False + self.elem = elem + def resetState(self): + self.required = False + self.declared = False + +# TypeInfo - registry information about a type. No additional state +# beyond BaseInfo is required. +class TypeInfo(BaseInfo): + """Represents the state of a registry type""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + +# GroupInfo - registry information about a group of related enums. +# enums - dictionary of enum names which are in the group +class GroupInfo(BaseInfo): + """Represents the state of a registry enumerant group""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.enums = {} + +# EnumInfo - registry information about an enum +# type - numeric type of the value of the tag +# ( '' for GLint, 'u' for GLuint, 'ull' for GLuint64 ) +class EnumInfo(BaseInfo): + """Represents the state of a registry enum""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.type = elem.get('type') + if (self.type == None): + self.type = '' + +# CmdInfo - registry information about a command +# glxtype - type of GLX protocol { None, 'render', 'single', 'vendor' } +# glxopcode - GLX protocol opcode { None, number } +# glxequiv - equivalent command at GLX dispatch level { None, string } +# vecequiv - equivalent vector form of a command taking multiple scalar args +# { None, string } +class CmdInfo(BaseInfo): + """Represents the state of a registry command""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.glxtype = None + self.glxopcode = None + self.glxequiv = None + self.vecequiv = None + +# FeatureInfo - registry information about an API +# or +# name - feature name string (e.g. 'GL_ARB_multitexture') +# number - feature version number (e.g. 1.2). +# features are unversioned and assigned version number 0. +# category - category, e.g. VERSION or ARB/KHR/OES/ETC/vendor +# emit - has this feature been defined already? +class FeatureInfo(BaseInfo): + """Represents the state of an API feature (version/extension)""" + def __init__(self, elem): + BaseInfo.__init__(self, elem) + self.name = elem.get('name') + # Determine element category (vendor). Only works + # for elements. + if (elem.tag == 'feature'): + self.category = 'VERSION' + self.number = elem.get('number') + else: + self.category = self.name.split('_', 2)[1] + self.number = "0" + self.emit = False + +# Primary sort key for regSortFeatures. +# Sorts by category of the feature name string: +# Core API features (those defined with a tag) +# ARB/KHR/OES (Khronos extensions) +# other (EXT/vendor extensions) +def regSortCategoryKey(feature): + if (feature.elem.tag == 'feature'): + return 0 + elif (feature.category == 'ARB' or + feature.category == 'KHR' or + feature.category == 'OES'): + return 1 + else: + return 2 + +# Secondary sort key for regSortFeatures. +# Sorts by extension name. +def regSortNameKey(feature): + return feature.name + +# Tertiary sort key for regSortFeatures. +# Sorts by feature version number. +# elements all have version number "0" +def regSortNumberKey(feature): + return feature.number + +# regSortFeatures - default sort procedure for features. +# Sorts by primary key of feature category, +# then by feature name within the category, +# then by version number +def regSortFeatures(featureList): + featureList.sort(key = regSortNumberKey) + featureList.sort(key = regSortNameKey) + featureList.sort(key = regSortCategoryKey) + +# GeneratorOptions - base class for options used during header production +# These options are target language independent, and used by +# Registry.apiGen() and by base OutputGenerator objects. +# +# Members +# filename - name of file to generate, or None to write to stdout. +# apiname - string matching 'apiname' attribute, e.g. 'gl'. +# profile - string specifying API profile , e.g. 'core', or None. +# versions - regex matching API versions to process interfaces for. +# Normally '.*' or '[0-9]\.[0-9]' to match all defined versions. +# emitversions - regex matching API versions to actually emit +# interfaces for (though all requested versions are considered +# when deciding which interfaces to generate). For GL 4.3 glext.h, +# this might be '1\.[2-5]|[2-4]\.[0-9]'. +# defaultExtensions - If not None, a string which must in its +# entirety match the pattern in the "supported" attribute of +# the . Defaults to None. Usually the same as apiname. +# addExtensions - regex matching names of additional extensions +# to include. Defaults to None. +# removeExtensions - regex matching names of extensions to +# remove (after defaultExtensions and addExtensions). Defaults +# to None. +# sortProcedure - takes a list of FeatureInfo objects and sorts +# them in place to a preferred order in the generated output. +# Default is core API versions, ARB/KHR/OES extensions, all +# other extensions, alphabetically within each group. +# The regex patterns can be None or empty, in which case they match +# nothing. +class GeneratorOptions: + """Represents options during header production from an API registry""" + def __init__(self, + filename = None, + apiname = None, + profile = None, + versions = '.*', + emitversions = '.*', + defaultExtensions = None, + addExtensions = None, + removeExtensions = None, + sortProcedure = regSortFeatures): + self.filename = filename + self.apiname = apiname + self.profile = profile + self.versions = self.emptyRegex(versions) + self.emitversions = self.emptyRegex(emitversions) + self.defaultExtensions = defaultExtensions + self.addExtensions = self.emptyRegex(addExtensions) + self.removeExtensions = self.emptyRegex(removeExtensions) + self.sortProcedure = sortProcedure + # + # Substitute a regular expression which matches no version + # or extension names for None or the empty string. + def emptyRegex(self,pat): + if (pat == None or pat == ''): + return '_nomatch_^' + else: + return pat + +# CGeneratorOptions - subclass of GeneratorOptions. +# +# Adds options used by COutputGenerator objects during C language header +# generation. +# +# Additional members +# prefixText - list of strings to prefix generated header with +# (usually a copyright statement + calling convention macros). +# protectFile - True if multiple inclusion protection should be +# generated (based on the filename) around the entire header. +# protectFeature - True if #ifndef..#endif protection should be +# generated around a feature interface in the header file. +# genFuncPointers - True if function pointer typedefs should be +# generated +# protectProto - Controls cpp protection around prototypes: +# False - no protection +# 'nonzero' - protectProtoStr must be defined to a nonzero value +# True - protectProtoStr must be defined +# protectProtoStr - #ifdef symbol to use around prototype +# declarations, if protected +# apicall - string to use for the function declaration prefix, +# such as APICALL on Windows. +# apientry - string to use for the calling convention macro, +# in typedefs, such as APIENTRY. +# apientryp - string to use for the calling convention macro +# in function pointer typedefs, such as APIENTRYP. +class CGeneratorOptions(GeneratorOptions): + """Represents options during C header production from an API registry""" + def __init__(self, + filename = None, + apiname = None, + profile = None, + versions = '.*', + emitversions = '.*', + defaultExtensions = None, + addExtensions = None, + removeExtensions = None, + sortProcedure = regSortFeatures, + prefixText = "", + genFuncPointers = True, + protectFile = True, + protectFeature = True, + protectProto = True, + protectProtoStr = True, + apicall = '', + apientry = '', + apientryp = '', + procMacro = '', + removeProc = []): + GeneratorOptions.__init__(self, filename, apiname, profile, + versions, emitversions, defaultExtensions, + addExtensions, removeExtensions, sortProcedure) + self.prefixText = prefixText + self.genFuncPointers = genFuncPointers + self.protectFile = protectFile + self.protectFeature = protectFeature + self.protectProto = protectProto + self.protectProtoStr = protectProtoStr + self.apicall = apicall + self.apientry = apientry + self.apientryp = apientryp + self.procMacro = procMacro + self.removeProc = removeProc + +# OutputGenerator - base class for generating API interfaces. +# Manages basic logic, logging, and output file control +# Derived classes actually generate formatted output. +# +# ---- methods ---- +# OutputGenerator(errFile, warnFile, diagFile) +# errFile, warnFile, diagFile - file handles to write errors, +# warnings, diagnostics to. May be None to not write. +# logMsg(level, *args) - log messages of different categories +# level - 'error', 'warn', or 'diag'. 'error' will also +# raise a UserWarning exception +# *args - print()-style arguments +# beginFile(genOpts) - start a new interface file +# genOpts - GeneratorOptions controlling what's generated and how +# endFile() - finish an interface file, closing it when done +# beginFeature(interface, emit) - write interface for a feature +# and tag generated features as having been done. +# interface - element for the / to generate +# emit - actually write to the header only when True +# endFeature() - finish an interface. +# genType(typeinfo,name) - generate interface for a type +# typeinfo - TypeInfo for a type +# genEnum(enuminfo, name) - generate interface for an enum +# enuminfo - EnumInfo for an enum +# name - enum name +# genCmd(cmdinfo) - generate interface for a command +# cmdinfo - CmdInfo for a command +class OutputGenerator: + """Generate specified API interfaces in a specific style, such as a C header""" + def __init__(self, + errFile = sys.stderr, + warnFile = sys.stderr, + diagFile = sys.stdout): + self.outFile = None + self.errFile = errFile + self.warnFile = warnFile + self.diagFile = diagFile + # Internal state + self.featureName = None + self.genOpts = None + # + # logMsg - write a message of different categories to different + # destinations. + # level - + # 'diag' (diagnostic, voluminous) + # 'warn' (warning) + # 'error' (fatal error - raises exception after logging) + # *args - print()-style arguments to direct to corresponding log + def logMsg(self, level, *args): + """Log a message at the given level. Can be ignored or log to a file""" + if (level == 'error'): + strfile = io.StringIO() + write('ERROR:', *args, file=strfile) + if (self.errFile != None): + write(strfile.getvalue(), file=self.errFile) + raise UserWarning(strfile.getvalue()) + elif (level == 'warn'): + if (self.warnFile != None): + write('WARNING:', *args, file=self.warnFile) + elif (level == 'diag'): + if (self.diagFile != None): + write('DIAG:', *args, file=self.diagFile) + else: + raise UserWarning( + '*** FATAL ERROR in Generator.logMsg: unknown level:' + level) + # + def beginFile(self, genOpts): + self.genOpts = genOpts + # + # Open specified output file. Not done in constructor since a + # Generator can be used without writing to a file. + if (self.genOpts.filename != None): + self.outFile = open(self.genOpts.filename, 'w') + else: + self.outFile = sys.stdout + def endFile(self): + self.errFile and self.errFile.flush() + self.warnFile and self.warnFile.flush() + self.diagFile and self.diagFile.flush() + self.outFile.flush() + if (self.outFile != sys.stdout and self.outFile != sys.stderr): + self.outFile.close() + self.genOpts = None + # + def beginFeature(self, interface, emit): + self.emit = emit + self.featureName = interface.get('name') + # If there's an additional 'protect' attribute in the feature, save it + self.featureExtraProtect = interface.get('protect') + def endFeature(self): + # Derived classes responsible for emitting feature + self.featureName = None + self.featureExtraProtect = None + # + # Type generation + def genType(self, typeinfo, name): + if (self.featureName == None): + raise UserWarning('Attempt to generate type', name, + 'when not in feature') + # + # Enumerant generation + def genEnum(self, enuminfo, name): + if (self.featureName == None): + raise UserWarning('Attempt to generate enum', name, + 'when not in feature') + # + # Command generation + def genCmd(self, cmd, name): + if (self.featureName == None): + raise UserWarning('Attempt to generate command', name, + 'when not in feature') + +# COutputGenerator - subclass of OutputGenerator. +# Generates C-language API interfaces. +# +# ---- methods ---- +# COutputGenerator(errFile, warnFile, diagFile) - args as for +# OutputGenerator. Defines additional internal state. +# makeCDecls(cmd) - return C prototype and function pointer typedef for a +# Element, as a list of two strings +# cmd - Element for the +# newline() - print a newline to the output file (utility function) +# ---- methods overriding base class ---- +# beginFile(genOpts) +# endFile() +# beginFeature(interface, emit) +# endFeature() +# genType(typeinfo,name) - generate interface for a type +# genEnum(enuminfo, name) +# genCmd(cmdinfo) +class COutputGenerator(OutputGenerator): + """Generate specified API interfaces in a specific style, such as a C header""" + def __init__(self, + errFile = sys.stderr, + warnFile = sys.stderr, + diagFile = sys.stdout): + OutputGenerator.__init__(self, errFile, warnFile, diagFile) + # Internal state - accumulators for different inner block text + self.typeBody = '' + self.enumBody = '' + self.cmdBody = '' + # + # makeCDecls - return C prototype and function pointer typedef for a + # command, as a two-element list of strings. + # cmd - Element containing a tag + def makeCDecls(self, cmd): + """Generate C function pointer typedef for Element""" + proto = cmd.find('proto') + params = cmd.findall('param') + # Begin accumulating prototype and typedef strings + pdecl = self.genOpts.apicall + + tdecl = 'typedef ' + # + # Insert the function return type/name. + # For prototypes, add APIENTRY macro before the name + # For typedefs, add (APIENTRYP ) around the name and + # use the PFNGLCMDNAMEPROC nameng convention. + # Done by walking the tree for element by element. + # lxml.etree has elem.text followed by (elem[i], elem[i].tail) + # for each child element and any following text + # Leading text + pdecl += noneStr(proto.text) + tdecl += noneStr(proto.text) + # For each child element, if it's a wrap in appropriate + # declaration. Otherwise append its contents and tail contents. + for elem in proto: + text = noneStr(elem.text) + tail = noneStr(elem.tail) + if (elem.tag == 'name'): + pdecl += self.genOpts.apientry + + if self.genOpts.procMacro != '': + pdecl += self.genOpts.procMacro + '(' + + pdecl += text + tail + + if self.genOpts.procMacro != '': + pdecl += ')' + + tdecl += '(' + self.genOpts.apientryp + 'PFN' + text.upper() + 'PROC' + tail + ')' + else: + pdecl += text + tail + tdecl += text + tail + # Now add the parameter declaration list, which is identical + # for prototypes and typedefs. Concatenate all the text from + # a node without the tags. No tree walking required + # since all tags are ignored. + n = len(params) + paramdecl = ' (' + if n > 0: + for i in range(0,n): + paramdecl += ''.join([t for t in params[i].itertext()]) + if (i < n - 1): + paramdecl += ', ' + else: + paramdecl += 'void' + paramdecl += ');\n'; + return [ pdecl + paramdecl, tdecl + paramdecl ] + # + def newline(self): + write('', file=self.outFile) + # + def beginFile(self, genOpts): + OutputGenerator.beginFile(self, genOpts) + # C-specific + # + # Multiple inclusion protection & C++ wrappers. + if (genOpts.protectFile and self.genOpts.filename): + headerSym = '__' + self.genOpts.apiname + '_' + re.sub('\.h', '_h_', os.path.basename(self.genOpts.filename)) + write('#ifndef', headerSym, file=self.outFile) + write('#define', headerSym, '1', file=self.outFile) + self.newline() + write('#ifdef __cplusplus', file=self.outFile) + write('extern "C" {', file=self.outFile) + write('#endif', file=self.outFile) + self.newline() + # + # User-supplied prefix text, if any (list of strings) + if (genOpts.prefixText): + for s in genOpts.prefixText: + write(s, file=self.outFile) + # + # Some boilerplate describing what was generated - this + # will probably be removed later since the extensions + # pattern may be very long. + write('/* Generated C header for:', file=self.outFile) + write(' * API:', genOpts.apiname, file=self.outFile) + if (genOpts.profile): + write(' * Profile:', genOpts.profile, file=self.outFile) + write(' * Versions considered:', genOpts.versions, file=self.outFile) + write(' * Versions emitted:', genOpts.emitversions, file=self.outFile) + write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile) + write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile) + write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile) + write(' */', file=self.outFile) + def endFile(self): + # C-specific + # Finish C++ wrapper and multiple inclusion protection + self.newline() + write('#ifdef __cplusplus', file=self.outFile) + write('}', file=self.outFile) + write('#endif', file=self.outFile) + if (self.genOpts.protectFile and self.genOpts.filename): + self.newline() + write('#endif', file=self.outFile) + # Finish processing in superclass + OutputGenerator.endFile(self) + def beginFeature(self, interface, emit): + # Start processing in superclass + OutputGenerator.beginFeature(self, interface, emit) + # C-specific + # Accumulate types, enums, function pointer typedefs, end function + # prototypes separately for this feature. They're only printed in + # endFeature(). + self.typeBody = '' + self.enumBody = '' + self.cmdPointerBody = '' + self.cmdBody = '' + def endFeature(self): + # C-specific + # Actually write the interface to the output file. + if (self.emit): + self.newline() + if (self.genOpts.protectFeature): + write('#ifndef', self.featureName, file=self.outFile) + write('#define', self.featureName, '1', file=self.outFile) + if (self.typeBody != ''): + write(self.typeBody, end='', file=self.outFile) + # + # Don't add additional protection for derived type declarations, + # which may be needed by other features later on. + if (self.featureExtraProtect != None): + write('#ifdef', self.featureExtraProtect, file=self.outFile) + if (self.enumBody != ''): + write(self.enumBody, end='', file=self.outFile) + if (self.genOpts.genFuncPointers and self.cmdPointerBody != ''): + write(self.cmdPointerBody, end='', file=self.outFile) + if (self.cmdBody != ''): + if (self.genOpts.protectProto == True): + prefix = '#ifdef ' + self.genOpts.protectProtoStr + '\n' + suffix = '#endif\n' + elif (self.genOpts.protectProto == 'nonzero'): + prefix = '#if ' + self.genOpts.protectProtoStr + '\n' + suffix = '#endif\n' + elif (self.genOpts.protectProto == False): + prefix = '' + suffix = '' + else: + self.gen.logMsg('warn', + '*** Unrecognized value for protectProto:', + self.genOpts.protectProto, + 'not generating prototype wrappers') + prefix = '' + suffix = '' + + write(prefix + self.cmdBody + suffix, end='', file=self.outFile) + if (self.featureExtraProtect != None): + write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile) + if (self.genOpts.protectFeature): + write('#endif /*', self.featureName, '*/', file=self.outFile) + # Finish processing in superclass + OutputGenerator.endFeature(self) + # + # Type generation + def genType(self, typeinfo, name): + OutputGenerator.genType(self, typeinfo, name) + # + # Replace tags with an APIENTRY-style string + # (from self.genOpts). Copy other text through unchanged. + # If the resulting text is an empty string, don't emit it. + typeElem = typeinfo.elem + s = noneStr(typeElem.text) + for elem in typeElem: + if (elem.tag == 'apientry'): + s += self.genOpts.apientry + noneStr(elem.tail) + else: + s += noneStr(elem.text) + noneStr(elem.tail) + if (len(s) > 0): + self.typeBody += s + '\n' + # + # Enumerant generation + def genEnum(self, enuminfo, name): + OutputGenerator.genEnum(self, enuminfo, name) + # + # EnumInfo.type is a C value suffix (e.g. u, ull) + self.enumBody += '#define ' + name.ljust(33) + ' ' + enuminfo.elem.get('value') + # + # Handle non-integer 'type' fields by using it as the C value suffix + t = enuminfo.elem.get('type') + if (t != '' and t != 'i'): + self.enumBody += enuminfo.type + self.enumBody += '\n' + # + # Command generation + def genCmd(self, cmdinfo, name): + if name in self.genOpts.removeProc: + return + OutputGenerator.genCmd(self, cmdinfo, name) + # + decls = self.makeCDecls(cmdinfo.elem) + self.cmdBody += decls[0] + if (self.genOpts.genFuncPointers): + self.cmdPointerBody += decls[1] + +# Registry - object representing an API registry, loaded from an XML file +# Members +# tree - ElementTree containing the root +# typedict - dictionary of TypeInfo objects keyed by type name +# groupdict - dictionary of GroupInfo objects keyed by group name +# enumdict - dictionary of EnumInfo objects keyed by enum name +# cmddict - dictionary of CmdInfo objects keyed by command name +# apidict - dictionary of Elements keyed by API name +# extensions - list of Elements +# extdict - dictionary of Elements keyed by extension name +# gen - OutputGenerator object used to write headers / messages +# genOpts - GeneratorOptions object used to control which +# fetures to write and how to format them +# emitFeatures - True to actually emit features for a version / extension, +# or False to just treat them as emitted +# Public methods +# loadElementTree(etree) - load registry from specified ElementTree +# loadFile(filename) - load registry from XML file +# setGenerator(gen) - OutputGenerator to use +# parseTree() - parse the registry once loaded & create dictionaries +# dumpReg(maxlen, filehandle) - diagnostic to dump the dictionaries +# to specified file handle (default stdout). Truncates type / +# enum / command elements to maxlen characters (default 80) +# generator(g) - specify the output generator object +# apiGen(apiname, genOpts) - generate API headers for the API type +# and profile specified in genOpts, but only for the versions and +# extensions specified there. +# apiReset() - call between calls to apiGen() to reset internal state +# validateGroups() - call to verify that each or +# with a 'group' attribute matches an actual existing group. +# Private methods +# addElementInfo(elem,info,infoName,dictionary) - add feature info to dict +# lookupElementInfo(fname,dictionary) - lookup feature info in dict +class Registry: + """Represents an API registry loaded from XML""" + def __init__(self): + self.tree = None + self.typedict = {} + self.groupdict = {} + self.enumdict = {} + self.cmddict = {} + self.apidict = {} + self.extensions = [] + self.extdict = {} + # A default output generator, so commands prior to apiGen can report + # errors via the generator object. + self.gen = OutputGenerator() + self.genOpts = None + self.emitFeatures = False + def loadElementTree(self, tree): + """Load ElementTree into a Registry object and parse it""" + self.tree = tree + self.parseTree() + def loadFile(self, file): + """Load an API registry XML file into a Registry object and parse it""" + self.tree = etree.parse(file) + self.parseTree() + def setGenerator(self, gen): + """Specify output generator object. None restores the default generator""" + self.gen = gen + # addElementInfo - add information about an element to the + # corresponding dictionary + # elem - ///// Element + # info - corresponding {Type|Group|Enum|Cmd|Feature}Info object + # infoName - 'type' / 'group' / 'enum' / 'command' / 'feature' / 'extension' + # dictionary - self.{type|group|enum|cmd|api|ext}dict + # If the Element has an 'api' attribute, the dictionary key is the + # tuple (name,api). If not, the key is the name. 'name' is an + # attribute of the Element + def addElementInfo(self, elem, info, infoName, dictionary): + if ('api' in elem.attrib): + key = (elem.get('name'),elem.get('api')) + else: + key = elem.get('name') + if key in dictionary: + self.gen.logMsg('warn', '*** Attempt to redefine', + infoName, 'with key:', key) + else: + dictionary[key] = info + # + # lookupElementInfo - find a {Type|Enum|Cmd}Info object by name. + # If an object qualified by API name exists, use that. + # fname - name of type / enum / command + # dictionary - self.{type|enum|cmd}dict + def lookupElementInfo(self, fname, dictionary): + key = (fname, self.genOpts.apiname) + if (key in dictionary): + # self.gen.logMsg('diag', 'Found API-specific element for feature', fname) + return dictionary[key] + elif (fname in dictionary): + # self.gen.logMsg('diag', 'Found generic element for feature', fname) + return dictionary[fname] + else: + return None + def parseTree(self): + """Parse the registry Element, once created""" + # This must be the Element for the root + self.reg = self.tree.getroot() + # + # Create dictionary of registry types from toplevel tags + # and add 'name' attribute to each tag (where missing) + # based on its element. + # + # There's usually one block; more are OK + # Required attributes: 'name' or nested tag contents + self.typedict = {} + for type in self.reg.findall('types/type'): + # If the doesn't already have a 'name' attribute, set + # it from contents of its tag. + if (type.get('name') == None): + type.attrib['name'] = type.find('name').text + self.addElementInfo(type, TypeInfo(type), 'type', self.typedict) + # + # Create dictionary of registry groups from toplevel tags. + # + # There's usually one block; more are OK. + # Required attributes: 'name' + self.groupdict = {} + for group in self.reg.findall('groups/group'): + self.addElementInfo(group, GroupInfo(group), 'group', self.groupdict) + # + # Create dictionary of registry enums from toplevel tags + # + # There are usually many tags in different namespaces, but + # these are functional namespaces of the values, while the actual + # enum names all share the dictionary. + # Required attributes: 'name', 'value' + self.enumdict = {} + for enum in self.reg.findall('enums/enum'): + self.addElementInfo(enum, EnumInfo(enum), 'enum', self.enumdict) + # + # Create dictionary of registry commands from tags + # and add 'name' attribute to each tag (where missing) + # based on its element. + # + # There's usually only one block; more are OK. + # Required attributes: 'name' or tag contents + self.cmddict = {} + for cmd in self.reg.findall('commands/command'): + # If the doesn't already have a 'name' attribute, set + # it from contents of its tag. + if (cmd.get('name') == None): + cmd.attrib['name'] = cmd.find('proto/name').text + ci = CmdInfo(cmd) + self.addElementInfo(cmd, ci, 'command', self.cmddict) + # + # Create dictionaries of API and extension interfaces + # from toplevel and tags. + # + self.apidict = {} + for feature in self.reg.findall('feature'): + ai = FeatureInfo(feature) + self.addElementInfo(feature, ai, 'feature', self.apidict) + self.extensions = self.reg.findall('extensions/extension') + self.extdict = {} + for feature in self.extensions: + ei = FeatureInfo(feature) + self.addElementInfo(feature, ei, 'extension', self.extdict) + def dumpReg(self, maxlen = 40, filehandle = sys.stdout): + """Dump all the dictionaries constructed from the Registry object""" + write('***************************************', file=filehandle) + write(' ** Dumping Registry contents **', file=filehandle) + write('***************************************', file=filehandle) + write('// Types', file=filehandle) + for name in self.typedict: + tobj = self.typedict[name] + write(' Type', name, '->', etree.tostring(tobj.elem)[0:maxlen], file=filehandle) + write('// Groups', file=filehandle) + for name in self.groupdict: + gobj = self.groupdict[name] + write(' Group', name, '->', etree.tostring(gobj.elem)[0:maxlen], file=filehandle) + write('// Enums', file=filehandle) + for name in self.enumdict: + eobj = self.enumdict[name] + write(' Enum', name, '->', etree.tostring(eobj.elem)[0:maxlen], file=filehandle) + write('// Commands', file=filehandle) + for name in self.cmddict: + cobj = self.cmddict[name] + write(' Command', name, '->', etree.tostring(cobj.elem)[0:maxlen], file=filehandle) + write('// APIs', file=filehandle) + for key in self.apidict: + write(' API Version ', key, '->', + etree.tostring(self.apidict[key].elem)[0:maxlen], file=filehandle) + write('// Extensions', file=filehandle) + for key in self.extdict: + write(' Extension', key, '->', + etree.tostring(self.extdict[key].elem)[0:maxlen], file=filehandle) + # write('***************************************', file=filehandle) + # write(' ** Dumping XML ElementTree **', file=filehandle) + # write('***************************************', file=filehandle) + # write(etree.tostring(self.tree.getroot(),pretty_print=True), file=filehandle) + # + # typename - name of type + # required - boolean (to tag features as required or not) + def markTypeRequired(self, typename, required): + """Require (along with its dependencies) or remove (but not its dependencies) a type""" + self.gen.logMsg('diag', '*** tagging type:', typename, '-> required =', required) + # Get TypeInfo object for tag corresponding to typename + type = self.lookupElementInfo(typename, self.typedict) + if (type != None): + # Tag required type dependencies as required. + # This DOES NOT un-tag dependencies in a tag. + # See comments in markRequired() below for the reason. + if (required and ('requires' in type.elem.attrib)): + depType = type.elem.get('requires') + self.gen.logMsg('diag', '*** Generating dependent type', + depType, 'for type', typename) + self.markTypeRequired(depType, required) + type.required = required + else: + self.gen.logMsg('warn', '*** type:', typename , 'IS NOT DEFINED') + # + # features - Element for or tag + # required - boolean (to tag features as required or not) + def markRequired(self, features, required): + """Require or remove features specified in the Element""" + self.gen.logMsg('diag', '*** markRequired (features = , required =', required, ')') + # Loop over types, enums, and commands in the tag + # @@ It would be possible to respect 'api' and 'profile' attributes + # in individual features, but that's not done yet. + for typeElem in features.findall('type'): + self.markTypeRequired(typeElem.get('name'), required) + for enumElem in features.findall('enum'): + name = enumElem.get('name') + self.gen.logMsg('diag', '*** tagging enum:', name, '-> required =', required) + enum = self.lookupElementInfo(name, self.enumdict) + if (enum != None): + enum.required = required + else: + self.gen.logMsg('warn', '*** enum:', name , 'IS NOT DEFINED') + for cmdElem in features.findall('command'): + name = cmdElem.get('name') + self.gen.logMsg('diag', '*** tagging command:', name, '-> required =', required) + cmd = self.lookupElementInfo(name, self.cmddict) + if (cmd != None): + cmd.required = required + # Tag all parameter types of this command as required. + # This DOES NOT remove types of commands in a + # tag, because many other commands may use the same type. + # We could be more clever and reference count types, + # instead of using a boolean. + if (required): + # Look for in entire tree, + # not just immediate children + for ptype in cmd.elem.findall('.//ptype'): + self.gen.logMsg('diag', '*** markRequired: command implicitly requires dependent type', ptype.text) + self.markTypeRequired(ptype.text, required) + else: + self.gen.logMsg('warn', '*** command:', name, 'IS NOT DEFINED') + # + # interface - Element for or , containing + # and tags + # api - string specifying API name being generated + # profile - string specifying API profile being generated + def requireAndRemoveFeatures(self, interface, api, profile): + """Process and tags for a or """ + # marks things that are required by this version/profile + for feature in interface.findall('require'): + if (matchAPIProfile(api, profile, feature)): + self.markRequired(feature,True) + # marks things that are removed by this version/profile + for feature in interface.findall('remove'): + if (matchAPIProfile(api, profile, feature)): + self.markRequired(feature,False) + # + # generateFeature - generate a single type / enum / command, + # and all its dependencies as needed. + # fname - name of feature (// + # ftype - type of feature, 'type' | 'enum' | 'command' + # dictionary - of *Info objects - self.{type|enum|cmd}dict + # genProc - bound function pointer for self.gen.gen{Type|Enum|Cmd} + def generateFeature(self, fname, ftype, dictionary, genProc): + f = self.lookupElementInfo(fname, dictionary) + if (f == None): + # No such feature. This is an error, but reported earlier + self.gen.logMsg('diag', '*** No entry found for feature', fname, + 'returning!') + return + # + # If feature isn't required, or has already been declared, return + if (not f.required): + self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(not required)') + return + if (f.declared): + self.gen.logMsg('diag', '*** Skipping', ftype, fname, '(already declared)') + return + # + # Pull in dependent type declaration(s) of the feature. + # For types, there may be one in the 'required' attribute of the element + # For commands, there may be many in tags within the element + # For enums, no dependencies are allowed (though perhasps if you + # have a uint64 enum, it should require GLuint64) + if (ftype == 'type'): + if ('requires' in f.elem.attrib): + depname = f.elem.get('requires') + self.gen.logMsg('diag', '*** Generating required dependent type', + depname) + self.generateFeature(depname, 'type', self.typedict, + self.gen.genType) + elif (ftype == 'command'): + for ptype in f.elem.findall('.//ptype'): + depname = ptype.text + self.gen.logMsg('diag', '*** Generating required parameter type', + depname) + self.generateFeature(depname, 'type', self.typedict, + self.gen.genType) + # + # Actually generate the type only if emitting declarations + if self.emitFeatures: + self.gen.logMsg('diag', '*** Emitting', ftype, 'decl for', fname) + genProc(f, fname) + else: + self.gen.logMsg('diag', '*** Skipping', ftype, fname, + '(not emitting this feature)') + # Always mark feature declared, as though actually emitted + f.declared = True + # + # generateRequiredInterface - generate all interfaces required + # by an API version or extension + # interface - Element for or + def generateRequiredInterface(self, interface): + """Generate required C interface for specified API version/extension""" + # + # Loop over all features inside all tags. + # tags are ignored (handled in pass 1). + for features in interface.findall('require'): + for t in features.findall('type'): + self.generateFeature(t.get('name'), 'type', self.typedict, + self.gen.genType) + for e in features.findall('enum'): + self.generateFeature(e.get('name'), 'enum', self.enumdict, + self.gen.genEnum) + for c in features.findall('command'): + self.generateFeature(c.get('name'), 'command', self.cmddict, + self.gen.genCmd) + # + # apiGen(genOpts) - generate interface for specified versions + # genOpts - GeneratorOptions object with parameters used + # by the Generator object. + def apiGen(self, genOpts): + """Generate interfaces for the specified API type and range of versions""" + # + self.gen.logMsg('diag', '*******************************************') + self.gen.logMsg('diag', ' Registry.apiGen file:', genOpts.filename, + 'api:', genOpts.apiname, + 'profile:', genOpts.profile) + self.gen.logMsg('diag', '*******************************************') + # + self.genOpts = genOpts + # Reset required/declared flags for all features + self.apiReset() + # + # Compile regexps used to select versions & extensions + regVersions = re.compile(self.genOpts.versions) + regEmitVersions = re.compile(self.genOpts.emitversions) + regAddExtensions = re.compile(self.genOpts.addExtensions) + regRemoveExtensions = re.compile(self.genOpts.removeExtensions) + # + # Get all matching API versions & add to list of FeatureInfo + features = [] + apiMatch = False + for key in self.apidict: + fi = self.apidict[key] + api = fi.elem.get('api') + if (api == self.genOpts.apiname): + apiMatch = True + if (regVersions.match(fi.number)): + # Matches API & version #s being generated. Mark for + # emission and add to the features[] list . + # @@ Could use 'declared' instead of 'emit'? + fi.emit = (regEmitVersions.match(fi.number) != None) + features.append(fi) + if (not fi.emit): + self.gen.logMsg('diag', '*** NOT tagging feature api =', api, + 'name =', fi.name, 'number =', fi.number, + 'for emission (does not match emitversions pattern)') + else: + self.gen.logMsg('diag', '*** NOT including feature api =', api, + 'name =', fi.name, 'number =', fi.number, + '(does not match requested versions)') + else: + self.gen.logMsg('diag', '*** NOT including feature api =', api, + 'name =', fi.name, + '(does not match requested API)') + if (not apiMatch): + self.gen.logMsg('warn', '*** No matching API versions found!') + # + # Get all matching extensions & add to the list. + # Start with extensions tagged with 'api' pattern matching the API + # being generated. Add extensions matching the pattern specified in + # regExtensions, then remove extensions matching the pattern + # specified in regRemoveExtensions + for key in self.extdict: + ei = self.extdict[key] + extName = ei.name + include = False + # + # Include extension if defaultExtensions is not None and if the + # 'supported' attribute matches defaultExtensions. The regexp in + # 'supported' must exactly match defaultExtensions, so bracket + # it with ^(pat)$. + pat = '^(' + ei.elem.get('supported') + ')$' + if (self.genOpts.defaultExtensions and + re.match(pat, self.genOpts.defaultExtensions)): + self.gen.logMsg('diag', '*** Including extension', + extName, "(defaultExtensions matches the 'supported' attribute)") + include = True + # + # Include additional extensions if the extension name matches + # the regexp specified in the generator options. This allows + # forcing extensions into an interface even if they're not + # tagged appropriately in the registry. + if (regAddExtensions.match(extName) != None): + self.gen.logMsg('diag', '*** Including extension', + extName, '(matches explicitly requested extensions to add)') + include = True + # Remove extensions if the name matches the regexp specified + # in generator options. This allows forcing removal of + # extensions from an interface even if they're tagged that + # way in the registry. + if (regRemoveExtensions.match(extName) != None): + self.gen.logMsg('diag', '*** Removing extension', + extName, '(matches explicitly requested extensions to remove)') + include = False + # + # If the extension is to be included, add it to the + # extension features list. + if (include): + ei.emit = True + features.append(ei) + else: + self.gen.logMsg('diag', '*** NOT including extension', + extName, '(does not match api attribute or explicitly requested extensions)') + # + # Sort the extension features list, if a sort procedure is defined + if (self.genOpts.sortProcedure): + self.genOpts.sortProcedure(features) + # + # Pass 1: loop over requested API versions and extensions tagging + # types/commands/features as required (in an block) or no + # longer required (in an block). It is possible to remove + # a feature in one version and restore it later by requiring it in + # a later version. + # If a profile other than 'None' is being generated, it must + # match the profile attribute (if any) of the and + # tags. + self.gen.logMsg('diag', '*** PASS 1: TAG FEATURES ********************************************') + for f in features: + self.gen.logMsg('diag', '*** PASS 1: Tagging required and removed features for', + f.name) + self.requireAndRemoveFeatures(f.elem, self.genOpts.apiname, self.genOpts.profile) + # + # Pass 2: loop over specified API versions and extensions printing + # declarations for required things which haven't already been + # generated. + self.gen.logMsg('diag', '*** PASS 2: GENERATE INTERFACES FOR FEATURES ************************') + self.gen.beginFile(self.genOpts) + for f in features: + self.gen.logMsg('diag', '*** PASS 2: Generating interface for', + f.name) + emit = self.emitFeatures = f.emit + if (not emit): + self.gen.logMsg('diag', '*** PASS 2: NOT declaring feature', + f.elem.get('name'), 'because it is not tagged for emission') + # Generate the interface (or just tag its elements as having been + # emitted, if they haven't been). + self.gen.beginFeature(f.elem, emit) + self.generateRequiredInterface(f.elem) + self.gen.endFeature() + self.gen.endFile() + # + # apiReset - use between apiGen() calls to reset internal state + # + def apiReset(self): + """Reset type/enum/command dictionaries before generating another API""" + for type in self.typedict: + self.typedict[type].resetState() + for enum in self.enumdict: + self.enumdict[enum].resetState() + for cmd in self.cmddict: + self.cmddict[cmd].resetState() + for cmd in self.apidict: + self.apidict[cmd].resetState() + # + # validateGroups - check that group= attributes match actual groups + # + def validateGroups(self): + """Validate group= attributes on and tags""" + # Keep track of group names not in tags + badGroup = {} + self.gen.logMsg('diag', '*** VALIDATING GROUP ATTRIBUTES ***') + for cmd in self.reg.findall('commands/command'): + proto = cmd.find('proto') + funcname = cmd.find('proto/name').text + if ('group' in proto.attrib.keys()): + group = proto.get('group') + # self.gen.logMsg('diag', '*** Command ', funcname, ' has return group ', group) + if (group not in self.groupdict.keys()): + # self.gen.logMsg('diag', '*** Command ', funcname, ' has UNKNOWN return group ', group) + if (group not in badGroup.keys()): + badGroup[group] = 1 + else: + badGroup[group] = badGroup[group] + 1 + for param in cmd.findall('param'): + pname = param.find('name') + if (pname != None): + pname = pname.text + else: + pname = type.get('name') + if ('group' in param.attrib.keys()): + group = param.get('group') + if (group not in self.groupdict.keys()): + # self.gen.logMsg('diag', '*** Command ', funcname, ' param ', pname, ' has UNKNOWN group ', group) + if (group not in badGroup.keys()): + badGroup[group] = 1 + else: + badGroup[group] = badGroup[group] + 1 + if (len(badGroup.keys()) > 0): + self.gen.logMsg('diag', '*** SUMMARY OF UNRECOGNIZED GROUPS ***') + for key in sorted(badGroup.keys()): + self.gen.logMsg('diag', ' ', key, ' occurred ', badGroup[key], ' times') diff --git a/sdk/orca.h b/sdk/orca.h index a093c2d..2c96e5b 100644 --- a/sdk/orca.h +++ b/sdk/orca.h @@ -19,10 +19,22 @@ #include"platform/platform_clock.h" #include"platform/platform_io.h" +#include"math.h" + +#include"graphics.h" +#include"gl31.h" + #if COMPILER_CLANG #define ORCA_EXPORT __attribute__((visibility("default"))) #else #error "Orca apps can only be compiled with clang for now" #endif + +mg_surface mg_surface_canvas(); +mg_surface mg_surface_gles(); + + + + #endif //__ORCA_H_ diff --git a/src/canvas_api.json b/src/canvas_api.json index 4bb301c..5c329df 100644 --- a/src/canvas_api.json +++ b/src/canvas_api.json @@ -36,12 +36,6 @@ {"name": "pixels", "type": {"name": "u8*", "tag": "p"}}] }, -{ - "name": "mg_surface_main", - "cname": "orca_surface_main", - "ret": {"name": "mg_surface", "tag": "S"}, - "args": [] -}, { "name": "mg_surface_prepare", "cname": "mg_surface_prepare", @@ -75,4 +69,17 @@ "type": {"name": "u32", "tag": "i"}}, {"name": "elements", "type": {"name": "mg_path_elt*", "tag": "p"}}] -}] +}, +{ + "name": "mg_surface_canvas", + "cname": "orca_surface_canvas", + "ret": {"name": "mg_surface", "tag": "S"}, + "args": [] +}, +{ + "name": "mg_surface_gles", + "cname": "orca_surface_gles", + "ret": {"name": "mg_surface", "tag": "S"}, + "args": [] +} +] diff --git a/src/core_api.json b/src/core_api.json index 061dfac..fe67f27 100644 --- a/src/core_api.json +++ b/src/core_api.json @@ -1,60 +1,4 @@ [ -{ - "name": "cosf", - "cname": "cosf", - "ret": {"name": "float", "tag": "f"}, - "args": [ {"name": "x", - "type": {"name": "float", "tag": "f"}}] -}, -{ - "name": "sinf", - "cname": "sinf", - "ret": {"name": "float", "tag": "f"}, - "args": [ {"name": "x", - "type": {"name": "float", "tag": "f"}}] -}, -{ - "name": "floorf", - "cname": "floorf", - "ret": {"name": "float", "tag": "f"}, - "args": [ {"name": "x", - "type": {"name": "float", "tag": "f"}}] -}, -{ - "name": "sqrtf", - "cname": "sqrtf", - "ret": {"name": "float", "tag": "f"}, - "args": [ {"name": "x", - "type": {"name": "float", "tag": "f"}}] -}, -{ - "name": "cos", - "cname": "cos", - "ret": {"name": "double", "tag": "F"}, - "args": [ {"name": "x", - "type": {"name": "double", "tag": "F"}}] -}, -{ - "name": "sin", - "cname": "sin", - "ret": {"name": "double", "tag": "F"}, - "args": [ {"name": "x", - "type": {"name": "double", "tag": "F"}}] -}, -{ - "name": "sqrt", - "cname": "sqrt", - "ret": {"name": "double", "tag": "F"}, - "args": [ {"name": "x", - "type": {"name": "double", "tag": "F"}}] -}, -{ - "name": "fabs", - "cname": "fabs", - "ret": {"name": "double", "tag": "F"}, - "args": [ {"name": "x", - "type": {"name": "double", "tag": "F"}}] -}, { "name": "orca_log", "cname": "orca_log", diff --git a/src/gles_api.json b/src/gles_api.json deleted file mode 100644 index fcd6982..0000000 --- a/src/gles_api.json +++ /dev/null @@ -1,383 +0,0 @@ -[ -{ - "name": "glCreateProgram", - "cname": "glCreateProgram", - "ret": {"name": "int", "tag": "i"}, - "args": [] -}, -{ - "name": "glCreateShader", - "cname": "glCreateShader", - "ret": {"name": "int", "tag": "i"}, - "args": [ {"name": "shaderType", - "type": {"name": "GLenum", "tag": "i"}}] -}, -{ - "name": "glCompileShader", - "cname": "glCompileShader", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "shader", - "type": {"name": "GLuint", "tag": "i"}}] -}, -{ - "name": "glAttachShader", - "cname": "glAttachShader", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "program", - "type": {"name": "GLuint", "tag": "i"}}, - {"name": "shader", - "type": {"name": "GLuint", "tag": "i"}} - ] -}, -{ - "name": "glLinkProgram", - "cname": "glLinkProgram", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "program", - "type": {"name": "GLuint", "tag": "i"}}] -}, -{ - "name": "glUseProgram", - "cname": "glUseProgram", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "program", - "type": {"name": "GLuint", "tag": "i"}}] -}, -{ - "name": "glGetError", - "cname": "glGetError", - "ret": {"name": "GLenum", "tag": "i"}, - "args": [] -}, -{ - "name": "glClearColor", - "cname": "glClearColor", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "red", - "type": {"name": "GLfloat", "tag": "f"}}, - {"name": "green", - "type": {"name": "GLfloat", "tag": "f"}}, - {"name": "blue", - "type": {"name": "GLfloat", "tag": "f"}}, - {"name": "alpha", - "type": {"name": "GLfloat", "tag": "f"}} - ] -}, -{ - "name": "glClear", - "cname": "glClear", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "mask", - "type": {"name": "GLbitfield", "tag": "i"}}] -}, -{ - "name": "glViewport", - "cname": "glViewport", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "x", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "y", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "width", - "type": {"name": "GLsizei", "tag": "i"}}, - {"name": "height", - "type": {"name": "GLsizei", "tag": "i"}} - ] -}, -{ - "name": "glGetShaderiv", - "cname": "glGetShaderiv", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "shader", - "type": {"name": "GLuint", "tag": "i"}}, - {"name": "pname", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "params", - "type": {"name": "GLint*", "tag": "p"}} - ] -}, -{ - "name": "glGetShaderInfoLog", - "cname": "glGetShaderInfoLog", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "shader", - "type": {"name": "GLuint", "tag": "i"}}, - {"name": "maxLength", - "type": {"name": "GLsizei", "tag": "i"}}, - {"name": "length", - "type": {"name": "GLsizei", "tag": "p"}}, - {"name": "infoLog", - "type": {"name": "GLchar", "tag": "p"}} - ] -}, -{ - "name": "glBindFramebuffer", - "cname": "glBindFramebuffer", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "target", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "framebuffer", - "type": {"name": "GLuint", "tag": "i"}} - ] -}, -{ - "name": "glBindTexture", - "cname": "glBindTexture", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "target", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "texture", - "type": {"name": "GLuint", "tag": "i"}} - ] -}, -{ - "name": "glActiveTexture", - "cname": "glActiveTexture", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "texture", - "type": {"name": "GLuint", "tag": "i"}}] -}, -{ - "name": "glGenBuffers", - "cname": "glGenBuffers", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "n", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "buffers", - "type": {"name": "GLuint*", "tag": "p"}} - ] -}, -{ - "name": "glGenTextures", - "cname": "glGenTextures", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "n", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "textures", - "type": {"name": "GLuint*", "tag": "p"}} - ] -}, -{ - "name": "glGenFramebuffers", - "cname": "glGenFramebuffers", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "n", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "framebuffers", - "type": {"name": "GLuint*", "tag": "p"}} - ] -}, -{ - "name": "glFramebufferTexture2D", - "cname": "glFramebufferTexture2D", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "target", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "attachment", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "textarget", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "texture", - "type": {"name": "GLuint", "tag": "i"}}, - {"name": "level", - "type": {"name": "GLint", "tag": "i"}} - ] -}, -{ - "name": "glCheckFramebufferStatus", - "cname": "glCheckFramebufferStatus", - "ret": {"name": "GLenum", "tag": "i"}, - "args": [ {"name": "target", - "type": {"name": "GLenum", "tag": "i"}}] -}, -{ - "name": "glTexImage2D", - "cname": "glTexImage2D", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "target", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "level", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "internalformat", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "width", - "type": {"name": "GLsizei", "tag": "i"}}, - {"name": "height", - "type": {"name": "GLsizei", "tag": "i"}}, - {"name": "border", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "format", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "type", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "data", - "type": {"name": "void*", "tag": "p"}} - ] -}, -{ - "name": "glTexParameteri", - "cname": "glTexParameteri", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "target", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "pname", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "param", - "type": {"name": "GLint", "tag": "i"}} - ] -}, -{ - "name": "glBindBuffer", - "cname": "glBindBuffer", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "target", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "buffer", - "type": {"name": "GLuint", "tag": "i"}} - ] -}, -{ - "name": "glBufferData", - "cname": "glBufferData", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "target", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "size", - "type": {"name": "GLsizeiptr", "tag": "i"}}, - {"name": "data", - "type": {"name": "void*", "tag": "p"}}, - {"name": "usage", - "type": {"name": "GLenum", "tag": "i"}} - ] -}, -{ - "name": "glUniform1i", - "cname": "glUniform1i", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "location", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "v0", - "type": {"name": "GLint", "tag": "i"}} - ] -}, -{ - "name": "glUniform2i", - "cname": "glUniform2i", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "location", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "v0", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "v1", - "type": {"name": "GLint", "tag": "i"}} - ] -}, -{ - "name": "glUniform1f", - "cname": "glUniform1f", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "location", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "v0", - "type": {"name": "GLfloat", "tag": "f"}} - ] -}, -{ - "name": "glUniform2f", - "cname": "glUniform2f", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "location", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "v0", - "type": {"name": "GLfloat", "tag": "f"}}, - {"name": "v1", - "type": {"name": "GLfloat", "tag": "f"}} - ] -}, -{ - "name": "glUniform3f", - "cname": "glUniform3f", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "location", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "v0", - "type": {"name": "GLfloat", "tag": "f"}}, - {"name": "v1", - "type": {"name": "GLfloat", "tag": "f"}}, - {"name": "v2", - "type": {"name": "GLfloat", "tag": "f"}} - ] -}, -{ - "name": "glUniformMatrix4fv", - "cname": "glUniformMatrix4fv", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "location", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "count", - "type": {"name": "GLsizei", "tag": "i"}}, - {"name": "transpose", - "type": {"name": "GLboolean", "tag": "i"}}, - {"name": "value", - "type": {"name": "GLfloat*", "tag": "p"}} - ] -}, -{ - "name": "glGetAttribLocation", - "cname": "glGetAttribLocation", - "ret": {"name": "GLint", "tag": "i"}, - "args": [ {"name": "program", - "type": {"name": "GLuint", "tag": "i"}}, - {"name": "name", - "type": {"name": "GLchar*", "tag": "p"}} - ] -}, -{ - "name": "glGetUniformLocation", - "cname": "glGetUniformLocation", - "ret": {"name": "GLint", "tag": "i"}, - "args": [ {"name": "program", - "type": {"name": "GLuint", "tag": "i"}}, - {"name": "name", - "type": {"name": "GLchar*", "tag": "p"}} - ] -}, - -{ - "name": "glVertexAttribPointer", - "cname": "glVertexAttribPointer", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "index", - "type": {"name": "GLuint", "tag": "i"}}, - {"name": "size", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "type", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "normalized", - "type": {"name": "GLboolean", "tag": "i"}}, - {"name": "stride", - "type": {"name": "GLsizei", "tag": "i"}}, - {"name": "pointer", - "type": {"name": "void*", "tag": "p"}} - ] -}, -{ - "name": "glEnableVertexAttribArray", - "cname": "glEnableVertexAttribArray", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "index", - "type": {"name": "GLuint", "tag": "i"}}] -}, -{ - "name": "glDrawArrays", - "cname": "glDrawArrays", - "ret": {"name": "void", "tag": "v"}, - "args": [ {"name": "mode", - "type": {"name": "GLenum", "tag": "i"}}, - {"name": "first", - "type": {"name": "GLint", "tag": "i"}}, - {"name": "count", - "type": {"name": "GLsizei", "tag": "i"}} - ] -} -] diff --git a/src/gles_api_bind_manual.c b/src/gles_api_bind_manual.c new file mode 100644 index 0000000..a9bdfea --- /dev/null +++ b/src/gles_api_bind_manual.c @@ -0,0 +1,1132 @@ + +//------------------------------------------------------------------------ +// Manual pointer size checking functions +//------------------------------------------------------------------------ + +u64 orca_gles_check_cstring(IM3Runtime runtime, const char* ptr) +{ + uint32_t memorySize = 0; + char* memory = (char*)m3_GetMemory(runtime, &memorySize, 0); + + //NOTE: Here we are guaranteed that ptr is in [ memory ; memory + memorySize [ + // hence (memory + memorySize) - ptr is representable by size_t and <= memorySize + size_t maxLen = (memory + memorySize) - ptr; + + u64 len = strnlen(ptr, maxLen); + + if(len == maxLen) + { + //NOTE: string overflows wasm memory, return a length that will trigger the bounds check + len = maxLen + 1; + } + return(len+1); //include null-terminator +} + +u64 orca_gl_type_size(GLenum type) +{ + u64 size = 8; + switch(type) + { + case GL_UNSIGNED_BYTE: + case GL_BYTE: + size = sizeof(GLbyte); + break; + + case GL_UNSIGNED_SHORT: + case GL_SHORT: + case GL_HALF_FLOAT: + size = sizeof(GLshort); + break; + + case GL_UNSIGNED_INT: + case GL_INT: + case GL_FIXED: + case GL_INT_2_10_10_10_REV: + case GL_UNSIGNED_INT_2_10_10_10_REV: + size = sizeof(GLint); + break; + + case GL_FLOAT: + size = sizeof(GLfloat); + break; + + case GL_DOUBLE: + size = sizeof(GLdouble); + break; + + default: + ORCA_ASSERT(0, "unknown GLenum type %i", type); + } + + return(size); +} + +u64 orca_gl_format_count(GLenum format) +{ + u64 count = 4; + switch(format) + { + case GL_RED: + case GL_RED_INTEGER: + case GL_DEPTH_COMPONENT: + case GL_STENCIL_INDEX: + case GL_LUMINANCE: + case GL_ALPHA: + count = 1; + break; + + case GL_RG: + case GL_RG_INTEGER: + case GL_DEPTH_STENCIL: + case GL_LUMINANCE_ALPHA: + count = 2; + break; + + case GL_RGB: + case GL_RGB_INTEGER: + count = 3; + break; + + case GL_RGBA: + case GL_RGBA_INTEGER: + count = 4; + break; + + default: + ORCA_ASSERT(0, "unknow GLenum format %i", format); + } + + return(count); +} + +typedef struct orca_gles_impl_limits +{ + bool init; + int maxDrawBuffers; + int numCompressedTextureFormats; + int numProgramBinaryFormats; + int numShaderBinaryFormats; + //... +} orca_gles_impl_limits; + +orca_gles_impl_limits __orcaGLESImplLimits = {0}; + +u64 orca_glGet_data_length(GLenum pname) +{ + if(!__orcaGLESImplLimits.init) + { + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &__orcaGLESImplLimits.maxDrawBuffers); + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &__orcaGLESImplLimits.numCompressedTextureFormats); + glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &__orcaGLESImplLimits.numProgramBinaryFormats); + glGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &__orcaGLESImplLimits.numShaderBinaryFormats); + } + u64 count = 8; + + if(pname >= GL_DRAW_BUFFER0 && pname < GL_DRAW_BUFFER0 + __orcaGLESImplLimits.maxDrawBuffers) + { + count = 1; + } + else + { + switch(pname) + { + case GL_ACTIVE_TEXTURE: + case GL_ALPHA_BITS: + case GL_ARRAY_BUFFER_BINDING: + case GL_BLEND: + case GL_BLEND_DST_ALPHA: + case GL_BLEND_DST_RGB: + case GL_BLEND_EQUATION_ALPHA: + case GL_BLEND_EQUATION_RGB: + case GL_BLEND_SRC_ALPHA: + case GL_BLEND_SRC_RGB: + case GL_BLUE_BITS: + case GL_CONTEXT_FLAGS: + case GL_CONTEXT_ROBUST_ACCESS: + case GL_COPY_READ_BUFFER_BINDING: + case GL_COPY_WRITE_BUFFER_BINDING: + case GL_CULL_FACE: + case GL_CULL_FACE_MODE: + case GL_CURRENT_PROGRAM: + case GL_DEBUG_GROUP_STACK_DEPTH: + case GL_DEBUG_LOGGED_MESSAGES: + case GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH: + case GL_DEPTH_BITS: + case GL_DEPTH_CLEAR_VALUE: + case GL_DEPTH_FUNC: + case GL_DEPTH_TEST: + case GL_DEPTH_WRITEMASK: + case GL_DISPATCH_INDIRECT_BUFFER_BINDING: + case GL_DITHER: + case GL_DRAW_FRAMEBUFFER_BINDING: + case GL_ELEMENT_ARRAY_BUFFER_BINDING: + case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS: + case GL_FRAGMENT_SHADER_DERIVATIVE_HINT: + case GL_FRONT_FACE: + case GL_GENERATE_MIPMAP_HINT: + case GL_GREEN_BITS: + case GL_IMAGE_BINDING_LAYERED: + case GL_IMPLEMENTATION_COLOR_READ_FORMAT: + case GL_IMPLEMENTATION_COLOR_READ_TYPE: + case GL_LAYER_PROVOKING_VERTEX: + case GL_LINE_WIDTH: + case GL_MAJOR_VERSION: + case GL_MAX_3D_TEXTURE_SIZE: + case GL_MAX_ARRAY_TEXTURE_LAYERS: + case GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS: + case GL_MAX_COLOR_ATTACHMENTS: + + case GL_MAX_COMBINED_ATOMIC_COUNTERS: + case GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS: + case GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS: + case GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS: + case GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS: + case GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS: + case GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS: + case GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS: + case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: + case GL_MAX_COMBINED_UNIFORM_BLOCKS: + case GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS: + case GL_MAX_COMPUTE_ATOMIC_COUNTERS: + case GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS: + case GL_MAX_COMPUTE_IMAGE_UNIFORMS: + case GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS: + case GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS: + case GL_MAX_COMPUTE_UNIFORM_BLOCKS: + case GL_MAX_COMPUTE_UNIFORM_COMPONENTS: + case GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS: + case GL_MAX_COMPUTE_WORK_GROUP_COUNT: + case GL_MAX_COMPUTE_WORK_GROUP_SIZE: + case GL_MAX_CUBE_MAP_TEXTURE_SIZE: + case GL_MAX_DEBUG_GROUP_STACK_DEPTH: + case GL_MAX_DEBUG_LOGGED_MESSAGES: + case GL_MAX_DEBUG_MESSAGE_LENGTH: + case GL_MAX_DEPTH_TEXTURE_SAMPLES: + case GL_MAX_DRAW_BUFFERS: + case GL_MAX_ELEMENT_INDEX: + case GL_MAX_ELEMENTS_INDICES: + case GL_MAX_ELEMENTS_VERTICES: + case GL_MAX_FRAGMENT_ATOMIC_COUNTERS: + case GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS: + case GL_MAX_FRAGMENT_IMAGE_UNIFORMS: + case GL_MAX_FRAGMENT_INPUT_COMPONENTS: + case GL_MAX_FRAGMENT_INTERPOLATION_OFFSET: + case GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS: + case GL_MAX_FRAGMENT_UNIFORM_BLOCKS: + case GL_MAX_FRAGMENT_UNIFORM_COMPONENTS: + case GL_MAX_FRAGMENT_UNIFORM_VECTORS: + case GL_MAX_FRAMEBUFFER_HEIGHT: + case GL_MAX_FRAMEBUFFER_LAYERS: + case GL_MAX_FRAMEBUFFER_SAMPLES: + case GL_MAX_FRAMEBUFFER_WIDTH: + case GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS: + case GL_MAX_GEOMETRY_ATOMIC_COUNTERS: + case GL_MAX_GEOMETRY_IMAGE_UNIFORMS: + case GL_MAX_GEOMETRY_INPUT_COMPONENTS: + case GL_MAX_GEOMETRY_OUTPUT_COMPONENTS: + case GL_MAX_GEOMETRY_OUTPUT_VERTICES: + case GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS: + case GL_MAX_GEOMETRY_SHADER_INVOCATIONS: + case GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS: + case GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS: + case GL_MAX_GEOMETRY_UNIFORM_BLOCKS: + case GL_MAX_GEOMETRY_UNIFORM_COMPONENTS: + case GL_MAX_INTEGER_SAMPLES: + case GL_MAX_LABEL_LENGTH: + case GL_MAX_PROGRAM_TEXEL_OFFSET: + case GL_MAX_RENDERBUFFER_SIZE: + case GL_MAX_SAMPLE_MASK_WORDS: + case GL_MAX_SAMPLES: + case GL_MAX_SERVER_WAIT_TIMEOUT: + case GL_MAX_SHADER_STORAGE_BLOCK_SIZE: + case GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS: + case GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS: + case GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS: + case GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS: + case GL_MAX_TESS_CONTROL_INPUT_COMPONENTS: + case GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS: + case GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS: + case GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS: + case GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS: + case GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS: + case GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS: + case GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS: + case GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS: + case GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS: + case GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS: + case GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS: + case GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS: + case GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS: + case GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS: + case GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS: + case GL_MAX_TESS_GEN_LEVEL: + case GL_MAX_TESS_PATCH_COMPONENTS: + case GL_MAX_TEXTURE_BUFFER_SIZE: + case GL_MAX_TEXTURE_IMAGE_UNITS: + case GL_MAX_TEXTURE_LOD_BIAS: + case GL_MAX_TEXTURE_SIZE: + case GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS: + case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS: + case GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS: + case GL_MAX_UNIFORM_BLOCK_SIZE: + case GL_MAX_UNIFORM_BUFFER_BINDINGS: + case GL_MAX_UNIFORM_LOCATIONS: + case GL_MAX_VARYING_COMPONENTS: + case GL_MAX_VARYING_VECTORS: + case GL_MAX_VERTEX_ATOMIC_COUNTERS: + case GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS: + case GL_MAX_VERTEX_ATTRIB_BINDINGS: + case GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET: + case GL_MAX_VERTEX_ATTRIBS: + case GL_MAX_VERTEX_IMAGE_UNIFORMS: + case GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS: + case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: + case GL_MAX_VERTEX_OUTPUT_COMPONENTS: + case GL_MAX_VERTEX_UNIFORM_BLOCKS: + case GL_MAX_VERTEX_UNIFORM_COMPONENTS: + case GL_MAX_VERTEX_UNIFORM_VECTORS: + case GL_MIN_FRAGMENT_INTERPOLATION_OFFSET: + case GL_MIN_PROGRAM_TEXEL_OFFSET: + case GL_MIN_SAMPLE_SHADING_VALUE: + case GL_MINOR_VERSION: + case GL_NUM_COMPRESSED_TEXTURE_FORMATS: + case GL_NUM_EXTENSIONS: + case GL_NUM_PROGRAM_BINARY_FORMATS: + case GL_NUM_SHADER_BINARY_FORMATS: + case GL_PACK_ALIGNMENT: + case GL_PACK_ROW_LENGTH: + case GL_PACK_SKIP_PIXELS: + case GL_PACK_SKIP_ROWS: + case GL_PATCH_VERTICES: + case GL_PIXEL_PACK_BUFFER_BINDING: + case GL_PIXEL_UNPACK_BUFFER_BINDING: + case GL_POLYGON_OFFSET_FACTOR: + case GL_POLYGON_OFFSET_FILL: + case GL_POLYGON_OFFSET_UNITS: + case GL_PRIMITIVE_RESTART_FIXED_INDEX: + case GL_PROGRAM_PIPELINE_BINDING: + case GL_RASTERIZER_DISCARD: + case GL_READ_BUFFER: + case GL_READ_FRAMEBUFFER_BINDING: + case GL_RED_BITS: + case GL_RENDERBUFFER_BINDING: + case GL_RESET_NOTIFICATION_STRATEGY: + case GL_SAMPLE_ALPHA_TO_COVERAGE: + case GL_SAMPLE_BUFFERS: + case GL_SAMPLE_COVERAGE: + case GL_SAMPLE_COVERAGE_INVERT: + case GL_SAMPLE_COVERAGE_VALUE: + case GL_SAMPLE_MASK_VALUE: + case GL_SAMPLE_SHADING: + case GL_SAMPLER_BINDING: + case GL_SAMPLES: + case GL_SCISSOR_TEST: + case GL_SHADER_COMPILER: + case GL_SHADER_STORAGE_BUFFER_BINDING: + case GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT: + case GL_SHADER_STORAGE_BUFFER_SIZE: + case GL_SHADER_STORAGE_BUFFER_START: + case GL_STENCIL_BACK_FAIL: + case GL_STENCIL_BACK_FUNC: + case GL_STENCIL_BACK_PASS_DEPTH_FAIL: + case GL_STENCIL_BACK_PASS_DEPTH_PASS: + case GL_STENCIL_BACK_REF: + case GL_STENCIL_BACK_VALUE_MASK: + case GL_STENCIL_BACK_WRITEMASK: + case GL_STENCIL_BITS: + case GL_STENCIL_CLEAR_VALUE: + case GL_STENCIL_FAIL: + case GL_STENCIL_FUNC: + case GL_STENCIL_PASS_DEPTH_FAIL: + case GL_STENCIL_PASS_DEPTH_PASS: + case GL_STENCIL_REF: + case GL_STENCIL_TEST: + case GL_STENCIL_VALUE_MASK: + case GL_STENCIL_WRITEMASK: + case GL_SUBPIXEL_BITS: + case GL_TEXTURE_BINDING_2D: + case GL_TEXTURE_BINDING_2D_ARRAY: + case GL_TEXTURE_BINDING_3D: + case GL_TEXTURE_BINDING_BUFFER: + case GL_TEXTURE_BINDING_CUBE_MAP: + case GL_TEXTURE_BINDING_2D_MULTISAMPLE: + case GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY: + case GL_TEXTURE_BINDING_CUBE_MAP_ARRAY: + case GL_TEXTURE_BUFFER_BINDING: + case GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT: + case GL_TRANSFORM_FEEDBACK_BINDING: + case GL_TRANSFORM_FEEDBACK_ACTIVE: + case GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: + case GL_TRANSFORM_FEEDBACK_PAUSED: + case GL_TRANSFORM_FEEDBACK_BUFFER_SIZE: + case GL_TRANSFORM_FEEDBACK_BUFFER_START: + case GL_UNIFORM_BUFFER_BINDING: + case GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT: + case GL_UNIFORM_BUFFER_SIZE: + case GL_UNIFORM_BUFFER_START: + case GL_UNPACK_ALIGNMENT: + case GL_UNPACK_IMAGE_HEIGHT: + case GL_UNPACK_ROW_LENGTH: + case GL_UNPACK_SKIP_IMAGES: + case GL_UNPACK_SKIP_PIXELS: + case GL_UNPACK_SKIP_ROWS: + case GL_VERTEX_ARRAY_BINDING: + case GL_VERTEX_BINDING_DIVISOR: + case GL_VERTEX_BINDING_OFFSET: + case GL_VERTEX_BINDING_STRIDE: + case GL_TEXTURE_2D: + case GL_TEXTURE_3D: + count = 1; + break; + + case GL_ALIASED_LINE_WIDTH_RANGE: + case GL_ALIASED_POINT_SIZE_RANGE: + case GL_DEPTH_RANGE: + case GL_MAX_VIEWPORT_DIMS: + case GL_MULTISAMPLE_LINE_WIDTH_RANGE: + count = 2; + break; + + case GL_BLEND_COLOR: + case GL_COLOR_CLEAR_VALUE: + case GL_COLOR_WRITEMASK: + case GL_SCISSOR_BOX: + case GL_VIEWPORT: + count = 4; + break; + + case GL_PRIMITIVE_BOUNDING_BOX: + count = 8; + break; + + case GL_COMPRESSED_TEXTURE_FORMATS: + count = __orcaGLESImplLimits.numCompressedTextureFormats; + break; + + case GL_PROGRAM_BINARY_FORMATS: + count = __orcaGLESImplLimits.numProgramBinaryFormats; + break; + + case GL_SHADER_BINARY_FORMATS: + count = __orcaGLESImplLimits.numShaderBinaryFormats; + break; + + default: + ORCA_ASSERT(0, "unknown GLenum pname %i", pname); + break; + } + } + return(count); +} + +u64 orca_glDrawElements_indices_length(IM3Runtime runtime, GLsizei count, GLenum type) +{ + return(orca_gl_type_size(type)*count); +} + +u64 orca_glGetBooleanv_data_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glGet_data_length(pname)); +} + +u64 orca_glGetBufferParameteriv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} +u64 orca_glGetFloatv_data_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glGet_data_length(pname)); +} +u64 orca_glGetFramebufferAttachmentParameteriv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} +u64 orca_glGetIntegerv_data_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glGet_data_length(pname)); +} +u64 orca_glGetProgramiv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} +u64 orca_glGetRenderbufferParameteriv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} +u64 orca_glGetShaderiv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} + +u64 orca_glTexParameter_params_length_generic(GLenum pname) +{ + u64 count = 4; + if(pname == GL_TEXTURE_BORDER_COLOR) + { + count = 4; + } + else + { + count = 1; + } + return(count); +} + +u64 orca_glGetTexParameterfv_params_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glTexParameter_params_length_generic(pname)); +} + +u64 orca_glGetTexParameteriv_params_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glTexParameter_params_length_generic(pname)); +} + +u64 orca_glReadPixels_pixels_length(IM3Runtime runtime, GLenum format, GLenum type, GLsizei width, GLsizei height) +{ + u64 count = width*height*orca_gl_type_size(type)*orca_gl_format_count(format); + return(count); +} +u64 orca_glTexImage2D_pixels_length(IM3Runtime runtime, GLenum format, GLenum type, GLsizei width, GLsizei height) +{ + u64 count = width*height*orca_gl_type_size(type)*orca_gl_format_count(format); + return(count); +} + +u64 orca_glTexParameterfv_params_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glTexParameter_params_length_generic(pname)); +} +u64 orca_glTexParameteriv_params_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glTexParameter_params_length_generic(pname)); +} +u64 orca_glTexSubImage2D_pixels_length(IM3Runtime runtime, GLenum format, GLenum type, GLsizei width, GLsizei height) +{ + u64 count = width*height*orca_gl_type_size(type)*orca_gl_format_count(format); + return(count); +} + +u64 orca_glDrawRangeElements_indices_length(IM3Runtime runtime, GLsizei count, GLenum type) +{ + return(count*orca_gl_type_size(type)); +} +u64 orca_glTexImage3D_pixels_length(IM3Runtime runtime, GLenum format, GLenum type, GLsizei width, GLsizei height, GLsizei depth) +{ + u64 count = width*height*depth*orca_gl_type_size(type)*orca_gl_format_count(format); + return(count); +} +u64 orca_glTexSubImage3D_pixels_length(IM3Runtime runtime, GLenum format, GLenum type, GLsizei width, GLsizei height, GLsizei depth) +{ + u64 count = width*height*depth*orca_gl_type_size(type)*orca_gl_format_count(format); + return(count); +} +u64 orca_glGetQueryiv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} +u64 orca_glGetQueryObjectuiv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} +u64 orca_glGetIntegeri_v_data_length(IM3Runtime runtime, GLenum target) +{ + return(orca_glGet_data_length(target)); +} +u64 orca_glVertexAttribIPointer_pointer_length(IM3Runtime runtime, GLint size, GLenum type, GLsizei stride) +{ + //WARN: pointer param of glVertexAttribPointer is actually treated as an offset, + // so, we don't need to check if this points to valid memory ?? + return(0); +} + +u64 orca_glClearBuffer_value_length_generic(GLenum buffer) +{ + u64 count = 4; + switch(buffer) + { + case GL_COLOR: + count = 4; + break; + + case GL_DEPTH: + case GL_STENCIL: + count = 1; + break; + + default: + ORCA_ASSERT(0, "invalid buffer enum for glClearBuffer()"); + } + return(count); +} + +u64 orca_glClearBufferiv_value_length(IM3Runtime runtime, GLenum buffer) +{ + return(orca_glClearBuffer_value_length_generic(buffer)); +} +u64 orca_glClearBufferuiv_value_length(IM3Runtime runtime, GLenum buffer) +{ + return(orca_glClearBuffer_value_length_generic(buffer)); +} +u64 orca_glClearBufferfv_value_length(IM3Runtime runtime, GLenum buffer) +{ + return(orca_glClearBuffer_value_length_generic(buffer)); +} + +u64 orca_glGetUniformIndices_uniformIndices_length(IM3Runtime runtime, GLsizei uniformCount) +{ + return(uniformCount); +} +u64 orca_glGetActiveUniformsiv_params_length(IM3Runtime runtime, GLsizei uniformCount, GLenum pname) +{ + return(uniformCount); +} + +u64 orca_glGetActiveUniformBlockiv_params_length(IM3Runtime runtime, GLuint program, GLuint uniformBlockIndex, GLenum pname) +{ + u64 count; + if(pname == GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES) + { + GLint param; + glGetActiveUniformBlockiv(program, uniformBlockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, ¶m); + count = param; + } + else + { + count = 1; + } + return(count); +} +u64 orca_glDrawElementsInstanced_indices_length(IM3Runtime runtime, GLsizei count, GLenum type) +{ + return(count*orca_gl_type_size(type)); +} +u64 orca_glGetInteger64v_data_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glGet_data_length(pname)); +} +u64 orca_glGetInteger64i_v_data_length(IM3Runtime runtime, GLenum target) +{ + return(orca_glGet_data_length(target)); +} +u64 orca_glGetBufferParameteri64v_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} + +u64 orca_glSamplerParameter_param_length_generic(GLenum pname) +{ + //NOTE: same as texture parameter pnames + return(orca_glTexParameter_params_length_generic(pname)); +} + +u64 orca_glSamplerParameteriv_param_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glSamplerParameter_param_length_generic(pname)); +} +u64 orca_glSamplerParameterfv_param_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glSamplerParameter_param_length_generic(pname)); +} +u64 orca_glGetSamplerParameteriv_params_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glSamplerParameter_param_length_generic(pname)); +} +u64 orca_glGetSamplerParameterfv_params_length(IM3Runtime runtime, GLenum pname) +{ + return(orca_glSamplerParameter_param_length_generic(pname)); +} +u64 orca_glGetFramebufferParameteriv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} +u64 orca_glGetProgramInterfaceiv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} + +u64 orca_glGetProgramPipelineiv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} +u64 orca_glGetBooleani_v_data_length(IM3Runtime runtime, GLenum target) +{ + return(orca_glSamplerParameter_param_length_generic(target)); +} +u64 orca_glGetMultisamplefv_val_length(IM3Runtime runtime, GLenum pname) +{ + return(2); +} +u64 orca_glGetTexLevelParameteriv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} +u64 orca_glGetTexLevelParameterfv_params_length(IM3Runtime runtime, GLenum pname) +{ + //NOTE: all pnames return a single value in 3.1 + return(1); +} + +//------------------------------------------------------------------------ +// Uniforms size checking +//------------------------------------------------------------------------ + +u64 orca_glGetUniform_params_length_generic(GLuint program, GLint location) +{ + //NOTE: This is super stupid but we can't get the size (or index) of a uniform directly from its location, + // so we have to iterate through all uniforms... + GLint maxUniformName = 0; + glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniformName); + + int uniformCount = 0; + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniformCount); + + mem_arena_scope scratch = mem_scratch_begin(); + char* name = mem_arena_alloc(scratch.arena, maxUniformName+1); + + u64 count = 0; + bool found = false; + + for(int i=0; i= (char*)_mem) && (((char*)pointer - (char*)_mem) < m3_GetMemorySize(runtime)), + "parameter 'pointer' is out of bounds"); + ORCA_ASSERT((char*)pointer + sizeof(i32) <= ((char*)_mem + m3_GetMemorySize(runtime)), + "parameter 'pointer' overflows wasm memory"); + } + void* rawPointer = 0; + glGetVertexAttribPointerv(index, pname, &rawPointer); + + //NOTE: pointer is actually a _byte offset_ into a GPU buffer. So we do _not_ convert it to a wasm pointer, + // but we need to truncate it to u32 size... + //WARN: can OpenGL return a byte offset > UINT_MAX ? + *pointer = (i32)(intptr_t)rawPointer; + return(0); +} + +const void* glVertexAttribPointer_stub(IM3Runtime runtime, IM3ImportContext _ctx, uint64_t* _sp, void* _mem) +{ + GLuint index = *(u32*)&_sp[0]; + GLint size = *(i32*)&_sp[1]; + GLenum type = *(i32*)&_sp[2]; + GLboolean normalized = (GLboolean)*(i32*)&_sp[3]; + GLsizei stride = *(i32*)&_sp[4]; + + //NOTE: pointer is interpreted as an offset if there's a non-null buffer bound to GL_ARRAY_BUFFER, + // or as a pointer otherwise. Since there's no way of checking the length of client vertex arrays, + // we just disable those. + + GLint boundBuffer = 0; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &boundBuffer); + + if(boundBuffer != 0) + { + //NOTE: don't do bounds checking since pointer is really an offset in a GPU buffer + const void* pointer = (void*)(intptr_t)*(u32*)&_sp[5]; + + glVertexAttribPointer(index, size, type, normalized, stride, pointer); + } + else + { + //NOTE: we crash here before letting ANGLE crash because vertex attrib pointer is not set + ORCA_ASSERT("Calling glVertexAttribPointer with a GL_ARRAY_BUFFER binding of 0 is unsafe and disabled in Orca."); + } + return(0); +} + +const void* glVertexAttribIPointer_stub(IM3Runtime runtime, IM3ImportContext _ctx, uint64_t* _sp, void* _mem) +{ + GLuint index = *(u32*)&_sp[0]; + GLint size = *(i32*)&_sp[1]; + GLenum type = *(i32*)&_sp[2]; + GLsizei stride = *(i32*)&_sp[3]; + + //NOTE: pointer is interpreted as an offset if there's a non-null buffer bound to GL_ARRAY_BUFFER, + // or as a pointer otherwise. Since there's no way of checking the length of client vertex arrays, + // we just disable those. + + GLint boundBuffer = 0; + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &boundBuffer); + + if(boundBuffer != 0) + { + //NOTE: don't do bounds checking since pointer is really an offset in a GPU buffer + const void* pointer = (void*)(intptr_t)*(u32*)&_sp[4]; + + glVertexAttribIPointer(index, size, type, stride, pointer); + } + else + { + ORCA_ASSERT(0, "Calling glVertexAttribIPointer with a GL_ARRAY_BUFFER binding of 0 is unsafe and disabled in Orca."); + } + return(0); +} + +const void* glGetUniformIndices_stub(IM3Runtime runtime, IM3ImportContext _ctx, uint64_t* _sp, void* _mem) +{ + GLuint program = (GLuint)*(i32*)&_sp[0]; + GLsizei uniformCount = (GLsizei)*(i32*)&_sp[1]; + u32* uniformNames = (u32*)((char*)_mem + *(u32*)&_sp[2]); + GLuint * uniformIndices = (GLuint *)((char*)_mem + *(u32*)&_sp[3]); + + u64 memorySize = m3_GetMemorySize(runtime); + //NOTE: check size of uniformNames + { + ORCA_ASSERT(((char*)uniformNames >= (char*)_mem) && (((char*)uniformNames - (char*)_mem) < memorySize), + "parameter 'uniformNames' is out of bounds"); + ORCA_ASSERT((char*)uniformNames + uniformCount * sizeof(u32) <= ((char*)_mem + memorySize), + "parameter 'uniformNames' overflows wasm memory"); + } + //NOTE: check each individual uniformNames + mem_arena_scope scratch = mem_scratch_begin(); + + char** uniformNamesRaw = mem_arena_alloc_array(scratch.arena, char*, uniformCount); + for(int i=0; i= (char*)_mem && (raw - (char*)_mem) < memorySize, "uniformName[%i] is out of bounds", i); + + u64 len = orca_gles_check_cstring(runtime, raw); + + ORCA_ASSERT(raw + len <= ((char*)_mem + memorySize), "uniformName[%i] overflows wasm memory", i); + + uniformNamesRaw[i] = raw; + } + + //NOTE: check size of uniformIndices + { + ORCA_ASSERT(((char*)uniformIndices >= (char*)_mem) && (((char*)uniformIndices - (char*)_mem) < memorySize), + "parameter 'uniformIndices' is out of bounds"); + ORCA_ASSERT((char*)uniformIndices + uniformCount * sizeof(GLuint) <= ((char*)_mem + memorySize), + "parameter 'uniformIndices' overflows wasm memory"); + } + + glGetUniformIndices(program, uniformCount, (const GLchar* const*)uniformNamesRaw, uniformIndices); + + mem_scratch_end(scratch); + return(0); +} + +typedef struct orca_gl_getstring_entry +{ + u32 offset; + u32 len; +} orca_gl_getstring_entry; + +GLenum ORCA_GL_GETSTRING_NAMES[] = { + GL_EXTENSIONS, + GL_VENDOR, + GL_RENDERER, + GL_VERSION, + GL_SHADING_LANGUAGE_VERSION +}; + +enum { + ORCA_GL_GETSTRING_ENTRY_COUNT = sizeof(ORCA_GL_GETSTRING_NAMES)/sizeof(GLenum) +}; + +typedef struct orca_gl_getstring_info +{ + bool init; + + orca_gl_getstring_entry entries[ORCA_GL_GETSTRING_ENTRY_COUNT]; + + u32 indexedEntryCount; + orca_gl_getstring_entry* indexedEntries; + +} orca_gl_getstring_info; + +orca_gl_getstring_info __orcaGLGetStringInfo = {0}; + +void orca_gl_getstring_init(orca_gl_getstring_info* info, char* memory) +{ + u32 totalSize = 0; + const char* strings[ORCA_GL_GETSTRING_ENTRY_COUNT] = {0}; + + for(int i=0; ientries[i].len = strlen(strings[i]) + 1; + totalSize += info->entries[i].len; + } + } + + glGetIntegerv(GL_NUM_EXTENSIONS, (GLint*)&info->indexedEntryCount); + mem_arena_scope scratch = mem_scratch_begin(); + const char** extensions = mem_arena_alloc(scratch.arena, info->indexedEntryCount); + + //NOTE: we will hold this until program terminates + info->indexedEntries = malloc_array(orca_gl_getstring_entry, info->indexedEntryCount); + + for(int i=0; iindexedEntryCount; i++) + { + extensions[i] = (const char*)glGetStringi(GL_EXTENSIONS, i); + if(extensions[i]) + { + info->indexedEntries[i].len = strlen(extensions[i])+1; + totalSize += info->indexedEntries[i].len; + } + } + + u32 wasmIndex = orca_mem_grow(totalSize); + + for(int i=0; ientries[i].offset = wasmIndex; + memcpy(memory + wasmIndex, strings[i], info->entries[i].len); + + wasmIndex += info->entries[i].len; + } + } + + for(int i=0; iindexedEntryCount; i++) + { + if(extensions[i]) + { + info->indexedEntries[i].offset = wasmIndex; + memcpy(memory + wasmIndex, extensions[i], info->indexedEntries[i].len); + wasmIndex += info->indexedEntries[i].len; + } + } + + mem_scratch_end(scratch); + + info->init = true; +} + +const void* glGetString_stub(IM3Runtime runtime, IM3ImportContext _ctx, uint64_t* _sp, void* _mem) +{ + if(!__orcaGLGetStringInfo.init) + { + uint32_t memorySize = 0; + char* memory = (char*)m3_GetMemory(runtime, &memorySize, 0); + orca_gl_getstring_init(&__orcaGLGetStringInfo, memory); + } + + GLenum name = (GLenum)*(i32*)&_sp[1]; + *(u32*)&_sp[0] = 0; + + for(int i=0; isurface = mg_surface_create_for_window(data->window, data->api); + + //NOTE: this will be called on main thread, so we need to deselect the surface here, + // and reselect it on the orca thread + mg_surface_deselect(); + + return(0); +} + +mg_surface orca_surface_canvas(void) +{ + orca_surface_create_data data = { + .surface = mg_surface_nil(), + .window = __orcaApp.window, + .api = MG_CANVAS + }; + + mp_dispatch_on_main_thread_sync(__orcaApp.window, orca_surface_callback, (void*)&data); + mg_surface_prepare(data.surface); + return(data.surface); +} + +mg_surface orca_surface_gles(void) +{ + orca_surface_create_data data = { + .surface = mg_surface_nil(), + .window = __orcaApp.window, + .api = MG_GLES + }; + + mp_dispatch_on_main_thread_sync(__orcaApp.window, orca_surface_callback, (void*)&data); + mg_surface_prepare(data.surface); + return(data.surface); } void orca_surface_render_commands(mg_surface surface, @@ -302,8 +380,25 @@ void orca_runtime_init(orca_runtime* runtime) #include"clock_api_bind_gen.c" #include"io_api_bind_gen.c" +#include"gles_api_bind_manual.c" #include"gles_api_bind_gen.c" -#include"manual_gles_api.c" + + +void orca_wasm3_abort(IM3Runtime runtime, M3Result res, const char* file, const char* function, int line, const char* msg) +{ + M3ErrorInfo errInfo = {0}; + m3_GetErrorInfo(runtime, &errInfo); + if(errInfo.message && res == errInfo.result) + { + orca_abort_fmt(file, function, line, "%s: %s (%s)", msg, res, errInfo.message); + } + else + { + orca_abort_fmt(file, function, line, "%s: %s", msg, res); + } +} + +#define ORCA_WASM3_ABORT(runtime, err, msg) orca_wasm3_abort(runtime, err, __FILE__, __FUNCTION__, __LINE__, msg) i32 orca_runloop(void* user) { @@ -318,16 +413,7 @@ i32 orca_runloop(void* user) FILE* file = fopen(modulePath.ptr, "rb"); if(!file) { - log_error("Couldn't load wasm module at %s\n", modulePath.ptr); - - const char* options[] = {"OK"}; - mp_alert_popup("Error", - "The application couldn't load: web assembly module not found", - 1, - options); - - mp_request_quit(); - return(-1); + ORCA_ABORT("The application couldn't load: web assembly module not found"); } fseek(file, 0, SEEK_END); @@ -343,41 +429,44 @@ i32 orca_runloop(void* user) app->runtime.m3Env = m3_NewEnvironment(); app->runtime.m3Runtime = m3_NewRuntime(app->runtime.m3Env, stackSize, NULL); - m3_RuntimeSetMemoryCallbacks(app->runtime.m3Runtime, wasm_memory_resize_callback, wasm_memory_free_callback, &app->runtime.wasmMemory); //NOTE: host memory will be freed when runtime is freed. + m3_RuntimeSetMemoryCallbacks(app->runtime.m3Runtime, wasm_memory_resize_callback, wasm_memory_free_callback, &app->runtime.wasmMemory); - //TODO check errors - m3_ParseModule(app->runtime.m3Env, &app->runtime.m3Module, (u8*)app->runtime.wasmBytecode.ptr, app->runtime.wasmBytecode.len); - m3_LoadModule(app->runtime.m3Runtime, app->runtime.m3Module); + M3Result res = m3_ParseModule(app->runtime.m3Env, &app->runtime.m3Module, (u8*)app->runtime.wasmBytecode.ptr, app->runtime.wasmBytecode.len); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "The application couldn't parse its web assembly module"); + } + + res = m3_LoadModule(app->runtime.m3Runtime, app->runtime.m3Module); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "The application couldn't load its web assembly module into the runtime"); + } m3_SetModuleName(app->runtime.m3Module, bundleNameCString); mem_arena_clear(mem_scratch()); //NOTE: bind orca APIs - bindgen_link_core_api(app->runtime.m3Module); - bindgen_link_canvas_api(app->runtime.m3Module); - bindgen_link_clock_api(app->runtime.m3Module); - bindgen_link_io_api(app->runtime.m3Module); - bindgen_link_gles_api(app->runtime.m3Module); - manual_link_gles_api(app->runtime.m3Module); + { + int err = 0; + err |= bindgen_link_core_api(app->runtime.m3Module); + err |= bindgen_link_canvas_api(app->runtime.m3Module); + err |= bindgen_link_clock_api(app->runtime.m3Module); + err |= bindgen_link_io_api(app->runtime.m3Module); + err |= bindgen_link_gles_api(app->runtime.m3Module); + err |= manual_link_gles_api(app->runtime.m3Module); + if(err) + { + ORCA_ABORT("The application couldn't link one or more functions to its web assembly module (see console log for more information)"); + } + } //NOTE: compile - M3Result res = m3_CompileModule(app->runtime.m3Module); + res = m3_CompileModule(app->runtime.m3Module); if(res) { - M3ErrorInfo errInfo = {0}; - m3_GetErrorInfo(app->runtime.m3Runtime, &errInfo); - - log_error("wasm error: %s\n", errInfo.message); - - const char* options[] = {"OK"}; - mp_alert_popup("Error", - "The application couldn't load: can't compile web assembly module", - 1, - options); - - mp_request_quit(); - return(-1); + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "The application couldn't compile its web assembly module"); } //NOTE: Find and type check event handlers. @@ -451,28 +540,15 @@ i32 orca_runloop(void* user) app->rootDir = cmp.handle; } - //NOTE: prepare GL surface - mg_surface_prepare(app->surface); - IM3Function* exports = app->runtime.exports; //NOTE: call init handler if(exports[G_EXPORT_ON_INIT]) { - M3Result err = m3_Call(exports[G_EXPORT_ON_INIT], 0, 0); - if(err != NULL) + M3Result res = m3_Call(exports[G_EXPORT_ON_INIT], 0, 0); + if(res) { - log_error("runtime error: %s\n", err); - - str8 msg = str8_pushf(mem_scratch(), "Runtime error: %s\n", err); - const char* options[] = {"OK"}; - mp_alert_popup("Error", - msg.ptr, - 1, - options); - - mp_request_quit(); - return(-1); + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); } } @@ -482,7 +558,11 @@ i32 orca_runloop(void* user) u32 width = (u32)content.w; u32 height = (u32)content.h; const void* args[2] = {&width, &height}; - m3_Call(exports[G_EXPORT_FRAME_RESIZE], 2, args); + M3Result res = m3_Call(exports[G_EXPORT_FRAME_RESIZE], 2, args); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } } ui_set_context(&app->debugOverlay.ui); @@ -505,7 +585,11 @@ i32 orca_runloop(void* user) memcpy(eventPtr, event, sizeof(*event)); const void* args[1] = {&app->runtime.rawEventOffset}; - m3_Call(exports[G_EXPORT_RAW_EVENT], 1, args); + M3Result res = m3_Call(exports[G_EXPORT_RAW_EVENT], 1, args); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } #else log_error("OnRawEvent() is not supported on big endian platforms"); #endif @@ -527,7 +611,11 @@ i32 orca_runloop(void* user) u32 width = (u32)event->move.content.w; u32 height = (u32)event->move.content.h; const void* args[2] = {&width, &height}; - m3_Call(exports[G_EXPORT_FRAME_RESIZE], 2, args); + M3Result res = m3_Call(exports[G_EXPORT_FRAME_RESIZE], 2, args); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } } } break; @@ -539,7 +627,11 @@ i32 orca_runloop(void* user) { int key = event->key.code; const void* args[1] = {&key}; - m3_Call(exports[G_EXPORT_MOUSE_DOWN], 1, args); + M3Result res = m3_Call(exports[G_EXPORT_MOUSE_DOWN], 1, args); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } } } else @@ -548,7 +640,11 @@ i32 orca_runloop(void* user) { int key = event->key.code; const void* args[1] = {&key}; - m3_Call(exports[G_EXPORT_MOUSE_UP], 1, args); + M3Result res = m3_Call(exports[G_EXPORT_MOUSE_UP], 1, args); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } } } } break; @@ -558,7 +654,11 @@ i32 orca_runloop(void* user) if(exports[G_EXPORT_MOUSE_MOVE]) { const void* args[4] = {&event->mouse.x, &event->mouse.y, &event->mouse.deltaX, &event->mouse.deltaY}; - m3_Call(exports[G_EXPORT_MOUSE_MOVE], 4, args); + M3Result res = m3_Call(exports[G_EXPORT_MOUSE_MOVE], 4, args); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } } } break; @@ -576,7 +676,11 @@ i32 orca_runloop(void* user) if(exports[G_EXPORT_KEY_DOWN]) { const void* args[1] = {&event->key.code}; - m3_Call(exports[G_EXPORT_KEY_DOWN], 1, args); + M3Result res = m3_Call(exports[G_EXPORT_KEY_DOWN], 1, args); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } } } else if(event->key.action == MP_KEY_RELEASE) @@ -584,7 +688,11 @@ i32 orca_runloop(void* user) if(exports[G_EXPORT_KEY_UP]) { const void* args[1] = {&event->key.code}; - m3_Call(exports[G_EXPORT_KEY_UP], 1, args); + M3Result res = m3_Call(exports[G_EXPORT_KEY_UP], 1, args); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } } } } break; @@ -732,8 +840,11 @@ i32 orca_runloop(void* user) if(exports[G_EXPORT_FRAME_REFRESH]) { - mg_surface_prepare(app->surface); - m3_Call(exports[G_EXPORT_FRAME_REFRESH], 0, 0); + M3Result res = m3_Call(exports[G_EXPORT_FRAME_REFRESH], 0, 0); + if(res) + { + ORCA_WASM3_ABORT(app->runtime.m3Runtime, res, "Runtime error"); + } } if(app->debugOverlay.show) @@ -761,10 +872,6 @@ int main(int argc, char** argv) mp_rect windowRect = {.x = 100, .y = 100, .w = 810, .h = 610}; app->window = mp_window_create(windowRect, "orca", 0); - app->surface = mg_surface_create_for_window(app->window, MG_CANVAS); - app->canvas = mg_canvas_create(); - mg_surface_swap_interval(app->surface, 1); - app->debugOverlay.show = false; app->debugOverlay.surface = mg_surface_create_for_window(app->window, MG_CANVAS); app->debugOverlay.canvas = mg_canvas_create(); @@ -784,11 +891,6 @@ int main(int argc, char** argv) for(int i=0; i<3; i++) { - mg_surface_prepare(app->surface); - mg_canvas_set_current(app->canvas); - mg_render(app->surface, app->canvas); - mg_surface_present(app->surface); - mg_surface_prepare(app->debugOverlay.surface); mg_canvas_set_current(app->debugOverlay.canvas); mg_render(app->debugOverlay.surface, app->debugOverlay.canvas); @@ -812,9 +914,6 @@ int main(int argc, char** argv) mp_thread_join(runloopThread, NULL); - mg_canvas_destroy(app->canvas); - mg_surface_destroy(app->surface); - mg_canvas_destroy(app->debugOverlay.canvas); mg_surface_destroy(app->debugOverlay.surface); diff --git a/src/manual_gles_api.c b/src/manual_gles_api.c deleted file mode 100644 index e7a52ce..0000000 --- a/src/manual_gles_api.c +++ /dev/null @@ -1,29 +0,0 @@ - -const void* glShaderSource_stub(IM3Runtime runtime, IM3ImportContext _ctx, uint64_t * _sp, void * _mem) -{ - i32 shader = *(i32*)&_sp[0]; - i32 count = *(i32*)&_sp[1]; - i32 stringArrayOffset = *(i32*)&_sp[2]; - i32 lengthArrayOffset = *(i32*)&_sp[3]; - - int* stringOffsetArray = (int*)((char*)_mem + stringArrayOffset); - const char** stringArray = (const char**)mem_arena_alloc_array(mem_scratch(), char*, count); - for(int i=0; i