From 450b5bb08dfaf413843ad2c2a5a0035fd5bcdd2c Mon Sep 17 00:00:00 2001 From: Artsiom Trubchyk Date: Wed, 13 Sep 2023 21:47:04 +0300 Subject: [PATCH] Initial. --- .clang-format | 8 + .gitattributes | 53 + .github/ISSUE_TEMPLATE/bug_report.md | 38 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/workflows/main.yml | 47 + .gitignore | 10 + .lua-format | 3 + .prettierrc | 2 + LICENSE.md | 121 ++ README.md | 58 + example/assets/defold-white-logo.svg | 15 + example/main.collection | 58 + example/main.script | 25 + example/table_util.lua | 45 + example/webgl_memory.go | 15 + example/webgl_memory.gui | 80 ++ example/webgl_memory.gui_script | 17 + game.project | 33 + manifest.private.der | Bin 0 -> 634 bytes manifest.public.der | Bin 0 -> 162 bytes webgl_memory/api/webgl_memory.script_api | 11 + webgl_memory/ext.manifest | 1 + webgl_memory/lib/web/lib_webgl_memory.js | 54 + .../manifests/web/engine_template.html | 5 + webgl_memory/res/web/webgl-memory.js | 1145 +++++++++++++++++ webgl_memory/src/extension.cpp | 52 + 26 files changed, 1916 insertions(+) create mode 100644 .clang-format create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 .lua-format create mode 100644 .prettierrc create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 example/assets/defold-white-logo.svg create mode 100644 example/main.collection create mode 100644 example/main.script create mode 100644 example/table_util.lua create mode 100644 example/webgl_memory.go create mode 100644 example/webgl_memory.gui create mode 100644 example/webgl_memory.gui_script create mode 100644 game.project create mode 100644 manifest.private.der create mode 100644 manifest.public.der create mode 100644 webgl_memory/api/webgl_memory.script_api create mode 100644 webgl_memory/ext.manifest create mode 100644 webgl_memory/lib/web/lib_webgl_memory.js create mode 100644 webgl_memory/manifests/web/engine_template.html create mode 100644 webgl_memory/res/web/webgl-memory.js create mode 100644 webgl_memory/src/extension.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..58da39f --- /dev/null +++ b/.clang-format @@ -0,0 +1,8 @@ +--- +BasedOnStyle: LLVM +ColumnLimit: 120 +IncludeBlocks: Regroup +IndentWidth: 4 +TabWidth: 4 + +... diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9e11e8a --- /dev/null +++ b/.gitattributes @@ -0,0 +1,53 @@ +# Defold Protocol Buffer Text Files (https://github.com/github/linguist/issues/5091) +*.animationset linguist-language=JSON5 gitlab-language=protobuf +*.atlas linguist-language=JSON5 gitlab-language=protobuf +*.camera linguist-language=JSON5 gitlab-language=protobuf +*.collection linguist-language=JSON5 gitlab-language=protobuf +*.collectionfactory linguist-language=JSON5 gitlab-language=protobuf +*.collectionproxy linguist-language=JSON5 gitlab-language=protobuf +*.collisionobject linguist-language=JSON5 gitlab-language=protobuf +*.cubemap linguist-language=JSON5 gitlab-language=protobuf +*.display_profiles linguist-language=JSON5 gitlab-language=protobuf +*.factory linguist-language=JSON5 gitlab-language=protobuf +*.font linguist-language=JSON5 gitlab-language=protobuf +*.gamepads linguist-language=JSON5 gitlab-language=protobuf +*.go linguist-language=JSON5 gitlab-language=protobuf +*.gui linguist-language=JSON5 gitlab-language=protobuf +*.input_binding linguist-language=JSON5 gitlab-language=protobuf +*.label linguist-language=JSON5 gitlab-language=protobuf +*.material linguist-language=JSON5 gitlab-language=protobuf +*.mesh linguist-language=JSON5 gitlab-language=protobuf +*.model linguist-language=JSON5 gitlab-language=protobuf +*.particlefx linguist-language=JSON5 gitlab-language=protobuf +*.render linguist-language=JSON5 gitlab-language=protobuf +*.sound linguist-language=JSON5 gitlab-language=protobuf +*.sprite linguist-language=JSON5 gitlab-language=protobuf +*.spinemodel linguist-language=JSON5 gitlab-language=protobuf +*.spinescene linguist-language=JSON5 gitlab-language=protobuf +*.texture_profiles linguist-language=JSON5 gitlab-language=protobuf +*.tilemap linguist-language=JSON5 gitlab-language=protobuf +*.tilesource linguist-language=JSON5 gitlab-language=protobuf + +# Defold JSON Files +*.buffer linguist-language=JSON gitlab-language=json + +# Defold GLSL Shaders +*.fp text eol=lf linguist-language=GLSL gitlab-language=glsl +*.vp text eol=lf linguist-language=GLSL gitlab-language=glsl + +# Defold Lua Files +*.editor_script text eol=lf linguist-language=Lua gitlab-language=lua +*.render_script text eol=lf linguist-language=Lua gitlab-language=lua +*.script text eol=lf linguist-language=Lua gitlab-language=lua +*.gui_script text eol=lf linguist-language=Lua gitlab-language=lua + +# Defold Project Settings +*.project linguist-language=INI gitlab-language=ini + +# EOL Settings +*.cpp text eol=lf +*.html text eol=lf +*.json text eol=lf +*.lua text eol=lf +*.md text eol=lf +*.yml text eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..bd70a21 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,47 @@ +name: Build example + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v3 + + - name: Install Java ♨️ + uses: actions/setup-java@v3 + with: + distribution: "temurin" + java-version: "17" + + - name: Build 🔧 + env: + DEFOLD_BOB_SHA1: ${{ secrets.DEFOLD_BOB_SHA1 }} + run: | + lsb_release -a + + mkdir -p build/bundle + + # Download the latest bob.jar + BOB_SHA1=${DEFOLD_BOB_SHA1:-$(curl -s 'https://d.defold.com/stable/info.json' | jq -r .sha1)} + BOB_LOCAL_SHA1=$((java -jar build/bundle/bob.jar --version | cut -d' ' -f6) || true) + if [ "${BOB_LOCAL_SHA1}" != "${BOB_SHA1}" ]; then wget --progress=dot:mega -O build/bundle/bob.jar "https://d.defold.com/archive/${BOB_SHA1}/bob/bob.jar"; fi + java -jar build/bundle/bob.jar --version + + # Build the project + java -jar build/bundle/bob.jar --email a@b.com --auth 123 --texture-compression true --bundle-output build/bundle/js-web --platform js-web --archive --variant debug resolve build bundle + + # Move to the public directory + mv build/bundle/js-web/* build/bundle/public + + - name: Deploy to Pages 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages + folder: build/bundle/public + if: github.ref == 'refs/heads/main' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a32d29f --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/.internal +/build +.externalToolBuilders +.DS_Store +Thumbs.db +.lock-wscript +*.pyc +.project +.cproject +builtins \ No newline at end of file diff --git a/.lua-format b/.lua-format new file mode 100644 index 0000000..c81cf20 --- /dev/null +++ b/.lua-format @@ -0,0 +1,3 @@ +column_limit: 120 +keep_simple_control_block_one_line: false +keep_simple_function_one_line: false diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..0c06786 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,2 @@ +tabWidth: 4 +printWidth: 120 diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.md b/README.md new file mode 100644 index 0000000..267b90e --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# WebGL memory tracker for Defold + +A quick and easy way to find out the GPU and CPU memory usage of your Defold game on HTML5 platform. + +So, this is an adaptation into a native extension of the great [WebGL-Memory](https://github.com/greggman/webgl-memory) script for the Defold engine. You add the extension to your project, then you can ask how much WebGL memory and resources you're using. It's important to keep in mind that WebGL-Memory tries to approximate the data, so the statistics may not be accurate. + +As a bonus, this extension adds the size of the heap used by the game. + +## Quick Start + +Add this project as a [Defold library dependency](http://www.defold.com/manuals/libraries/). Open your `game.project` file and in the dependencies field under project add: + +https://github.com/indiesoftby/defold-webgl-memory/archive/main.zip + +Request for the data: + +```lua +if webgl_memory then + local info = webgl_memory.get_info() + print("GPU memory used in total: " .. math.floor(info.memory.total / 1024 / 1024) .. " MB") + print("CPU heap size: " .. math.floor(info.memory.wasmheap / 1024 / 1024) .. " MB") +end +``` + +## Advanced Usage + +When you call `webgl_memory.get_info()`, the result is: + +```lua +{ + memory = { + buffer = , + texture = , + renderbuffer = , + drawingbuffer = , + total = , + wasmheap = , -- is injected by the extension and equals to `Module.HEAP8.length`. + }, + resources = { + buffer = , + renderbuffer = , + program = , + query = , + sampler = , + shader = , + sync = , + texture = , + transformFeedback = , + vertexArray = , + } +} +``` + +## Credits + +This project is licensed under the terms of the CC0 1.0 Universal license. It's developed and supported by [@aglitchman](https://github.com/aglitchman). + +It includes includes a release version of WebGL-Memory, license MIT. diff --git a/example/assets/defold-white-logo.svg b/example/assets/defold-white-logo.svg new file mode 100644 index 0000000..2b37a37 --- /dev/null +++ b/example/assets/defold-white-logo.svg @@ -0,0 +1,15 @@ + + + + + diff --git a/example/main.collection b/example/main.collection new file mode 100644 index 0000000..be8d819 --- /dev/null +++ b/example/main.collection @@ -0,0 +1,58 @@ +name: "main" +scale_along_z: 0 +embedded_instances { + id: "main" + data: "components {\n" + " id: \"main\"\n" + " component: \"/example/main.script\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + " property_decls {\n" + " }\n" + "}\n" + "embedded_components {\n" + " id: \"webgl_memory\"\n" + " type: \"factory\"\n" + " data: \"prototype: \\\"/example/webgl_memory.go\\\"\\n" + "load_dynamically: false\\n" + "dynamic_prototype: false\\n" + "\"\n" + " position {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " }\n" + " rotation {\n" + " x: 0.0\n" + " y: 0.0\n" + " z: 0.0\n" + " w: 1.0\n" + " }\n" + "}\n" + "" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale3 { + x: 1.0 + y: 1.0 + z: 1.0 + } +} diff --git a/example/main.script b/example/main.script new file mode 100644 index 0000000..492091b --- /dev/null +++ b/example/main.script @@ -0,0 +1,25 @@ +function init(self) + msg.post(".", "acquire_input_focus") + + self.wm_ui = factory.create("#webgl_memory") + + if webgl_memory then + local info = webgl_memory.get_info() + print("GPU memory used in total: " .. math.floor(info.memory.total / 1024 / 1024) .. " MB") + print("CPU heap size: " .. math.floor(info.memory.wasmheap / 1024 / 1024) .. " MB") + end +end + +function final(self) +end + +function on_input(self, action_id, action) + if action_id == hash("key_space") and action.pressed then + if self.wm_ui then + go.delete(self.wm_ui) + self.wm_ui = nil + else + self.wm_ui = factory.create("#webgl_memory") + end + end +end diff --git a/example/table_util.lua b/example/table_util.lua new file mode 100644 index 0000000..93db7bb --- /dev/null +++ b/example/table_util.lua @@ -0,0 +1,45 @@ +local M = {} + +local TAB = " " + +function M.val_to_str(v, indent) + if "string" == type(v) then + v = string.gsub(v, "\n", "\\n") + if string.match(string.gsub(v, "[^'\"]", ""), '^"+$') then + return "'" .. v .. "'" + end + return '"' .. string.gsub(v, '"', '\\"') .. '"' + else + return "table" == type(v) and M.tostring(v, indent) or tostring(v) + end +end + +function M.key_to_str(k) + if "string" == type(k) and string.match(k, "^[_%a][_%a%d]*$") then + return k + else + return "[" .. M.val_to_str(k) .. "]" + end +end + +function M.tostring(tbl, indent) + indent = indent or "" + if "table" ~= type(tbl) then return tostring(tbl) end + local result, done, keys = {}, {}, {} + for k, v in ipairs(tbl) do + table.insert(result, M.val_to_str(v)) + done[k] = true + end + for k, _ in pairs(tbl) do + table.insert(keys, k) + end + table.sort(keys) + for _, k in ipairs(keys) do + if not done[k] then + table.insert(result, indent .. TAB .. M.key_to_str(k) .. " = " .. M.val_to_str(tbl[k], indent .. TAB)) + end + end + return "{\n" .. table.concat(result, ",\n") .. "\n" .. indent .. "}" +end + +return M diff --git a/example/webgl_memory.go b/example/webgl_memory.go new file mode 100644 index 0000000..b5ec6e4 --- /dev/null +++ b/example/webgl_memory.go @@ -0,0 +1,15 @@ +components { + id: "gui" + component: "/example/webgl_memory.gui" + position { + x: 0.0 + y: 0.0 + z: 0.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } +} diff --git a/example/webgl_memory.gui b/example/webgl_memory.gui new file mode 100644 index 0000000..8c40f5e --- /dev/null +++ b/example/webgl_memory.gui @@ -0,0 +1,80 @@ +script: "/example/webgl_memory.gui_script" +fonts { + name: "system_font" + font: "/builtins/fonts/system_font.font" +} +background_color { + x: 0.0 + y: 0.0 + z: 0.0 + w: 0.0 +} +nodes { + position { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + rotation { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + scale { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + size { + x: 640.0 + y: 640.0 + z: 0.0 + w: 1.0 + } + color { + x: 1.0 + y: 1.0 + z: 1.0 + w: 1.0 + } + type: TYPE_TEXT + blend_mode: BLEND_MODE_ALPHA + text: "" + font: "system_font" + id: "text" + xanchor: XANCHOR_LEFT + yanchor: YANCHOR_BOTTOM + pivot: PIVOT_SW + outline { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + shadow { + x: 0.0 + y: 0.0 + z: 0.0 + w: 1.0 + } + adjust_mode: ADJUST_MODE_FIT + line_break: true + layer: "" + inherit_alpha: true + alpha: 1.0 + outline_alpha: 1.0 + shadow_alpha: 1.0 + template_node_child: false + text_leading: 1.0 + text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT +max_nodes: 512 diff --git a/example/webgl_memory.gui_script b/example/webgl_memory.gui_script new file mode 100644 index 0000000..43a1489 --- /dev/null +++ b/example/webgl_memory.gui_script @@ -0,0 +1,17 @@ +local table_util = require("example.table_util") + +function init(self) + self.text_node = gui.get_node("text") +end + +function final(self) +end + +function update(self, dt) + if webgl_memory then + local info = webgl_memory.get_info() + gui.set_text(self.text_node, table_util.tostring(info)) + else + gui.set_text(self.text_node, "") + end +end diff --git a/game.project b/game.project new file mode 100644 index 0000000..db2618c --- /dev/null +++ b/game.project @@ -0,0 +1,33 @@ +[bootstrap] +main_collection = /example/main.collectionc + +[script] +shared_state = 1 + +[display] +width = 960 +height = 640 +high_dpi = 1 + +[android] +input_method = HiddenInputField + +[project] +title = ext_webgl_memory_example +developer = Indiesoft LLC + +[library] +include_dirs = webgl_memory + +[render] +clear_color_alpha = 1.0 + +[html5] +heap_size = 32 +cssfile = /builtins/manifests/web/dark_theme.css +scale_mode = fit +splash_image = /example/assets/defold-white-logo.svg + +[input] +game_binding = /builtins/input/all.input_bindingc + diff --git a/manifest.private.der b/manifest.private.der new file mode 100644 index 0000000000000000000000000000000000000000..58d09cf33d07b5c42db84bb910f3f586374cc67b GIT binary patch literal 634 zcmV-=0)_oBf&z8|0RS)!1_>&LNQUpU@(FLTmk_A0)c@5fmX$UD}#W) z`VFR-XFdy`e7NyV=OaHS5fv1~i4s8J z8JSMl)PK1_47hEosVl3@xf0f|l^Vo1-_@MJ*ndjH73LY2j828eNC)})X@#?1TXeo| z@OYV6E_yFG6SErB-+TU{0s{d60Rn-53GF&K9{$%fnj7Qqq||CjhNeV`lfaDP4Up}3 z^u1|bbvtoYdgw0ih)olkeJZD$FP|n;ZUXeitZ>>N5&(O3+C_d5R-5;?ssfFC!lJ&! zKoqo%_L<2v3eTen{voQSNMrOqi3CYof}HIa)!1HTz=22nWYlL)Lmck%&s!D!0zm+z zz3jT8=)*OE;N4e84R6J9p?&s|Cq&l(=jhr&z1UG8?~tSz4{b%L{5~*17S%Gpra-Lh)?r6~}X^0*UJru(HSDU-sbc-3RAELkz-*AT5 zhq2i!TCJP|V^S=61tbjN>YY98FVA+3lPv;3GLeoN7%8O6M(osNmkry}TDSbCbQJ!- zl&YITYt65j@25H0^!&d`6Tcz^Ji&Eh6YPY1to^w$$!k8ckHF6r0zm-2idKZ2{906f zH`*fZ;c1Bt4Q!<8G58RHYCqWh#d(s1oRj#0@v zSbxO=Kt1AW6G<<+2vUm2DYa}X`<|Odk}hU7Bq_K;b*KZf97*caWf4iKz_$L= Uym+W`C-9L;7+9eu(pP&)vasVYh5!Hn literal 0 HcmV?d00001 diff --git a/manifest.public.der b/manifest.public.der new file mode 100644 index 0000000000000000000000000000000000000000..94c91e296e3717567b41668b40fc6d103fe0d9a7 GIT binary patch literal 162 zcmV;T0A2qufuAr91_>&LNQUFdy`e7NyV=OaHS5fv1~i4s8J8JSMl)PK1_ z47hEosVl3@xf0f|l^Vo1-_@MJ*ndjH73LY2j828eNC)})X@#?1TXeo|@OYV6E_yFG Q6SErB-+TU{0s{d60nA8C1ONa4 literal 0 HcmV?d00001 diff --git a/webgl_memory/api/webgl_memory.script_api b/webgl_memory/api/webgl_memory.script_api new file mode 100644 index 0000000..4a78d86 --- /dev/null +++ b/webgl_memory/api/webgl_memory.script_api @@ -0,0 +1,11 @@ +- name: webgl_memory + type: table + desc: C++ helper functions + members: + + - name: get_info + type: function + description: Returns a table with memory info data + returns: + - name: result + type: table diff --git a/webgl_memory/ext.manifest b/webgl_memory/ext.manifest new file mode 100644 index 0000000..2452c71 --- /dev/null +++ b/webgl_memory/ext.manifest @@ -0,0 +1 @@ +name: "webgl_memory" diff --git a/webgl_memory/lib/web/lib_webgl_memory.js b/webgl_memory/lib/web/lib_webgl_memory.js new file mode 100644 index 0000000..5cb245c --- /dev/null +++ b/webgl_memory/lib/web/lib_webgl_memory.js @@ -0,0 +1,54 @@ +var LibraryWebGLMemory = { + $WebGLMemory: { + warnShown: false, + + cstringify: function (obj) { + var str = JSON.stringify(obj); + var cstr = allocate(intArrayFromString(str), "i8", ALLOC_NORMAL); + return cstr; + }, + + addHeapSize: function (obj) { + obj.memory.wasmheap = Module.HEAP8.length; + return obj; + } + }, + + WebGLMemory_GetInfo: function (param, subparam) { + var ext = Module.ctx.getExtension("GMAN_webgl_memory"); + if (ext) { + return WebGLMemory.cstringify(WebGLMemory.addHeapSize(ext.getMemoryInfo())); + } else { + if (!WebGLMemory.warnShown) { + WebGLMemory.warnShown = true; + console.warn( + "Unable to get the `GMAN_webgl_memory` extension - webgl-memory.js hasn't been loaded yet?" + ); + } + return WebGLMemory.cstringify(WebGLMemory.addHeapSize({ + memory: { + buffer: 0, + texture: 0, + renderbuffer: 0, + drawingbuffer: 0, + total: 0, + }, + resources: { + buffer: 0, + renderbuffer: 0, + program: 0, + query: 0, + sampler: 0, + shader: 0, + sync: 0, + texture: 0, + transformFeedback: 0, + vertexArray: 0, + }, + })); + } + }, +}; + +autoAddDeps(LibraryWebGLMemory, "$WebGLMemory"); +mergeInto(LibraryManager.library, LibraryWebGLMemory); diff --git a/webgl_memory/manifests/web/engine_template.html b/webgl_memory/manifests/web/engine_template.html new file mode 100644 index 0000000..2cf62e4 --- /dev/null +++ b/webgl_memory/manifests/web/engine_template.html @@ -0,0 +1,5 @@ + + + + + diff --git a/webgl_memory/res/web/webgl-memory.js b/webgl_memory/res/web/webgl-memory.js new file mode 100644 index 0000000..5fbd1cc --- /dev/null +++ b/webgl_memory/res/web/webgl-memory.js @@ -0,0 +1,1145 @@ +/* webgl-memory@1.0.15, license MIT */ +(function (factory) { + typeof define === 'function' && define.amd ? define(factory) : + factory(); +}((function () { 'use strict'; + + /* PixelFormat */ + const ALPHA = 0x1906; + const RGB = 0x1907; + const RGBA = 0x1908; + const LUMINANCE = 0x1909; + const LUMINANCE_ALPHA = 0x190A; + const DEPTH_COMPONENT = 0x1902; + const DEPTH_STENCIL = 0x84F9; + + const R8 = 0x8229; + const R8_SNORM = 0x8F94; + const R16F = 0x822D; + const R32F = 0x822E; + const R8UI = 0x8232; + const R8I = 0x8231; + const RG16UI = 0x823A; + const RG16I = 0x8239; + const RG32UI = 0x823C; + const RG32I = 0x823B; + const RG8 = 0x822B; + const RG8_SNORM = 0x8F95; + const RG16F = 0x822F; + const RG32F = 0x8230; + const RG8UI = 0x8238; + const RG8I = 0x8237; + const R16UI = 0x8234; + const R16I = 0x8233; + const R32UI = 0x8236; + const R32I = 0x8235; + const RGB8 = 0x8051; + const SRGB8 = 0x8C41; + const RGB565 = 0x8D62; + const RGB8_SNORM = 0x8F96; + const R11F_G11F_B10F = 0x8C3A; + const RGB9_E5 = 0x8C3D; + const RGB16F = 0x881B; + const RGB32F = 0x8815; + const RGB8UI = 0x8D7D; + const RGB8I = 0x8D8F; + const RGB16UI = 0x8D77; + const RGB16I = 0x8D89; + const RGB32UI = 0x8D71; + const RGB32I = 0x8D83; + const RGBA8 = 0x8058; + const SRGB8_ALPHA8 = 0x8C43; + const RGBA8_SNORM = 0x8F97; + const RGB5_A1 = 0x8057; + const RGBA4 = 0x8056; + const RGB10_A2 = 0x8059; + const RGBA16F = 0x881A; + const RGBA32F = 0x8814; + const RGBA8UI = 0x8D7C; + const RGBA8I = 0x8D8E; + const RGB10_A2UI = 0x906F; + const RGBA16UI = 0x8D76; + const RGBA16I = 0x8D88; + const RGBA32I = 0x8D82; + const RGBA32UI = 0x8D70; + + const DEPTH_COMPONENT16 = 0x81A5; + const DEPTH_COMPONENT24 = 0x81A6; + const DEPTH_COMPONENT32F = 0x8CAC; + const DEPTH32F_STENCIL8 = 0x8CAD; + const DEPTH24_STENCIL8 = 0x88F0; + const UNSIGNED_BYTE = 0x1401; + const UNSIGNED_SHORT = 0x1403; + const UNSIGNED_INT = 0x1405; + const FLOAT = 0x1406; + const UNSIGNED_SHORT_4_4_4_4 = 0x8033; + const UNSIGNED_SHORT_5_5_5_1 = 0x8034; + const UNSIGNED_SHORT_5_6_5 = 0x8363; + const HALF_FLOAT = 0x140B; + const HALF_FLOAT_OES = 0x8D61; // Thanks Khronos for making this different >:( + + const SRGB_ALPHA_EXT = 0x8C42; + + /** + * @typedef {Object} TextureFormatDetails + * @property {number} textureFormat format to pass texImage2D and similar functions. + * @property {boolean} colorRenderable true if you can render to this format of texture. + * @property {boolean} textureFilterable true if you can filter the texture, false if you can ony use `NEAREST`. + * @property {number[]} type Array of possible types you can pass to texImage2D and similar function + * @property {Object.} bytesPerElementMap A map of types to bytes per element + * @private + */ + + let s_textureInternalFormatInfo; + function getTextureInternalFormatInfo(internalFormat) { + if (!s_textureInternalFormatInfo) { + // NOTE: these properties need unique names so we can let Uglify mangle the name. + const t = {}; + // unsized formats + t[ALPHA] = { bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT], }; + t[LUMINANCE] = { bytesPerElement: [1, 2, 2, 4], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT], }; + t[LUMINANCE_ALPHA] = { bytesPerElement: [2, 4, 4, 8], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT], }; + t[RGB] = { bytesPerElement: [3, 6, 6, 12, 2], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT, UNSIGNED_SHORT_5_6_5], }; + t[RGBA] = { bytesPerElement: [4, 8, 8, 16, 2, 2], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT, UNSIGNED_SHORT_4_4_4_4, UNSIGNED_SHORT_5_5_5_1], }; + t[SRGB_ALPHA_EXT] = { bytesPerElement: [4, 8, 8, 16, 2, 2], type: [UNSIGNED_BYTE, HALF_FLOAT, HALF_FLOAT_OES, FLOAT, UNSIGNED_SHORT_4_4_4_4, UNSIGNED_SHORT_5_5_5_1], }; + t[DEPTH_COMPONENT] = { bytesPerElement: [2, 4], type: [UNSIGNED_INT, UNSIGNED_SHORT], }; + t[DEPTH_STENCIL] = { bytesPerElement: [4], }; + + // sized formats + t[R8] = { bytesPerElement: [1], }; + t[R8_SNORM] = { bytesPerElement: [1], }; + t[R16F] = { bytesPerElement: [2], }; + t[R32F] = { bytesPerElement: [4], }; + t[R8UI] = { bytesPerElement: [1], }; + t[R8I] = { bytesPerElement: [1], }; + t[R16UI] = { bytesPerElement: [2], }; + t[R16I] = { bytesPerElement: [2], }; + t[R32UI] = { bytesPerElement: [4], }; + t[R32I] = { bytesPerElement: [4], }; + t[RG8] = { bytesPerElement: [2], }; + t[RG8_SNORM] = { bytesPerElement: [2], }; + t[RG16F] = { bytesPerElement: [4], }; + t[RG32F] = { bytesPerElement: [8], }; + t[RG8UI] = { bytesPerElement: [2], }; + t[RG8I] = { bytesPerElement: [2], }; + t[RG16UI] = { bytesPerElement: [4], }; + t[RG16I] = { bytesPerElement: [4], }; + t[RG32UI] = { bytesPerElement: [8], }; + t[RG32I] = { bytesPerElement: [8], }; + t[RGB8] = { bytesPerElement: [3], }; + t[SRGB8] = { bytesPerElement: [3], }; + t[RGB565] = { bytesPerElement: [2], }; + t[RGB8_SNORM] = { bytesPerElement: [3], }; + t[R11F_G11F_B10F] = { bytesPerElement: [4], }; + t[RGB9_E5] = { bytesPerElement: [4], }; + t[RGB16F] = { bytesPerElement: [6], }; + t[RGB32F] = { bytesPerElement: [12], }; + t[RGB8UI] = { bytesPerElement: [3], }; + t[RGB8I] = { bytesPerElement: [3], }; + t[RGB16UI] = { bytesPerElement: [6], }; + t[RGB16I] = { bytesPerElement: [6], }; + t[RGB32UI] = { bytesPerElement: [12], }; + t[RGB32I] = { bytesPerElement: [12], }; + t[RGBA8] = { bytesPerElement: [4], }; + t[SRGB8_ALPHA8] = { bytesPerElement: [4], }; + t[RGBA8_SNORM] = { bytesPerElement: [4], }; + t[RGB5_A1] = { bytesPerElement: [2], }; + t[RGBA4] = { bytesPerElement: [2], }; + t[RGB10_A2] = { bytesPerElement: [4], }; + t[RGBA16F] = { bytesPerElement: [8], }; + t[RGBA32F] = { bytesPerElement: [16], }; + t[RGBA8UI] = { bytesPerElement: [4], }; + t[RGBA8I] = { bytesPerElement: [4], }; + t[RGB10_A2UI] = { bytesPerElement: [4], }; + t[RGBA16UI] = { bytesPerElement: [8], }; + t[RGBA16I] = { bytesPerElement: [8], }; + t[RGBA32I] = { bytesPerElement: [16], }; + t[RGBA32UI] = { bytesPerElement: [16], }; + // Sized Internal + t[DEPTH_COMPONENT16] = { bytesPerElement: [2], }; + t[DEPTH_COMPONENT24] = { bytesPerElement: [4], }; + t[DEPTH_COMPONENT32F] = { bytesPerElement: [4], }; + t[DEPTH24_STENCIL8] = { bytesPerElement: [4], }; + t[DEPTH32F_STENCIL8] = { bytesPerElement: [4], }; + + s_textureInternalFormatInfo = t; + } + return s_textureInternalFormatInfo[internalFormat]; + } + + function makeComputeBlockRectSizeFunction(blockWidth, blockHeight, bytesPerBlock) { + return function(width, height, depth) { + const blocksAcross = (width + blockWidth - 1) / blockWidth | 0; + const blocksDown = (height + blockHeight - 1) / blockHeight | 0; + return blocksAcross * blocksDown * bytesPerBlock * depth; + } + } + + function makeComputePaddedRectSizeFunction(minWidth, minHeight, divisor) { + return function(width, height, depth) { + return (Math.max(width, minWidth) * Math.max(height, minHeight) / divisor | 0) * depth; + } + } + + // WEBGL_compressed_texture_s3tc + const COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0; + const COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1; + const COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; + const COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3; + // WEBGL_compressed_texture_etc1 + const COMPRESSED_RGB_ETC1_WEBGL = 0x8D64; + // WEBGL_compressed_texture_pvrtc + const COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00; + const COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01; + const COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02; + const COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03; + // WEBGL_compressed_texture_etc + const COMPRESSED_R11_EAC = 0x9270; + const COMPRESSED_SIGNED_R11_EAC = 0x9271; + const COMPRESSED_RG11_EAC = 0x9272; + const COMPRESSED_SIGNED_RG11_EAC = 0x9273; + const COMPRESSED_RGB8_ETC2 = 0x9274; + const COMPRESSED_SRGB8_ETC2 = 0x9275; + const COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276; + const COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277; + const COMPRESSED_RGBA8_ETC2_EAC = 0x9278; + const COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279; + // WEBGL_compressed_texture_astc + const COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0; + const COMPRESSED_RGBA_ASTC_5x4_KHR = 0x93B1; + const COMPRESSED_RGBA_ASTC_5x5_KHR = 0x93B2; + const COMPRESSED_RGBA_ASTC_6x5_KHR = 0x93B3; + const COMPRESSED_RGBA_ASTC_6x6_KHR = 0x93B4; + const COMPRESSED_RGBA_ASTC_8x5_KHR = 0x93B5; + const COMPRESSED_RGBA_ASTC_8x6_KHR = 0x93B6; + const COMPRESSED_RGBA_ASTC_8x8_KHR = 0x93B7; + const COMPRESSED_RGBA_ASTC_10x5_KHR = 0x93B8; + const COMPRESSED_RGBA_ASTC_10x6_KHR = 0x93B9; + const COMPRESSED_RGBA_ASTC_10x8_KHR = 0x93BA; + const COMPRESSED_RGBA_ASTC_10x10_KHR = 0x93BB; + const COMPRESSED_RGBA_ASTC_12x10_KHR = 0x93BC; + const COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD; + const COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0; + const COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR = 0x93D1; + const COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR = 0x93D2; + const COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR = 0x93D3; + const COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR = 0x93D4; + const COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR = 0x93D5; + const COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR = 0x93D6; + const COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR = 0x93D7; + const COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR = 0x93D8; + const COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR = 0x93D9; + const COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR = 0x93DA; + const COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR = 0x93DB; + const COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR = 0x93DC; + const COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD; + // WEBGL_compressed_texture_s3tc_srgb + const COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C; + const COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D; + const COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E; + const COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F; + // EXT_texture_compression_bptc + const COMPRESSED_RGBA_BPTC_UNORM_EXT = 0x8E8C; + const COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT = 0x8E8D; + const COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT = 0x8E8E; + const COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT = 0x8E8F; + // EXT_texture_compression_rgtc + const COMPRESSED_RED_RGTC1_EXT = 0x8DBB; + const COMPRESSED_SIGNED_RED_RGTC1_EXT = 0x8DBC; + const COMPRESSED_RED_GREEN_RGTC2_EXT = 0x8DBD; + const COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT = 0x8DBE; + + const compressedTextureFunctions = new Map([ + [ COMPRESSED_RGB_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ], + [ COMPRESSED_RGBA_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ], + [ COMPRESSED_RGBA_S3TC_DXT3_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ], + [ COMPRESSED_RGBA_S3TC_DXT5_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ], + + [ COMPRESSED_RGB_ETC1_WEBGL, makeComputeBlockRectSizeFunction(4, 4, 8) ], + + [ COMPRESSED_RGB_PVRTC_4BPPV1_IMG, makeComputePaddedRectSizeFunction(8, 8, 2) ], + [ COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, makeComputePaddedRectSizeFunction(8, 8, 2) ], + [ COMPRESSED_RGB_PVRTC_2BPPV1_IMG, makeComputePaddedRectSizeFunction(16, 8, 4) ], + [ COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, makeComputePaddedRectSizeFunction(16, 8, 4) ], + + [ COMPRESSED_R11_EAC, makeComputeBlockRectSizeFunction(4, 4, 8) ], + [ COMPRESSED_SIGNED_R11_EAC, makeComputeBlockRectSizeFunction(4, 4, 8) ], + [ COMPRESSED_RGB8_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ], + [ COMPRESSED_SRGB8_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ], + [ COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ], + [ COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, makeComputeBlockRectSizeFunction(4, 4, 8) ], + + [ COMPRESSED_RG11_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ], + [ COMPRESSED_SIGNED_RG11_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ], + [ COMPRESSED_RGBA8_ETC2_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, makeComputeBlockRectSizeFunction(4, 4, 16) ], + + [ COMPRESSED_RGBA_ASTC_4x4_KHR, makeComputeBlockRectSizeFunction(4, 4, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, makeComputeBlockRectSizeFunction(4, 4, 16) ], + [ COMPRESSED_RGBA_ASTC_5x4_KHR, makeComputeBlockRectSizeFunction(5, 4, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR, makeComputeBlockRectSizeFunction(5, 4, 16) ], + [ COMPRESSED_RGBA_ASTC_5x5_KHR, makeComputeBlockRectSizeFunction(5, 5, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR, makeComputeBlockRectSizeFunction(5, 5, 16) ], + [ COMPRESSED_RGBA_ASTC_6x5_KHR, makeComputeBlockRectSizeFunction(6, 5, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR, makeComputeBlockRectSizeFunction(6, 5, 16) ], + [ COMPRESSED_RGBA_ASTC_6x6_KHR, makeComputeBlockRectSizeFunction(6, 6, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR, makeComputeBlockRectSizeFunction(6, 6, 16) ], + [ COMPRESSED_RGBA_ASTC_8x5_KHR, makeComputeBlockRectSizeFunction(8, 5, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR, makeComputeBlockRectSizeFunction(8, 5, 16) ], + [ COMPRESSED_RGBA_ASTC_8x6_KHR, makeComputeBlockRectSizeFunction(8, 6, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR, makeComputeBlockRectSizeFunction(8, 6, 16) ], + [ COMPRESSED_RGBA_ASTC_8x8_KHR, makeComputeBlockRectSizeFunction(8, 8, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR, makeComputeBlockRectSizeFunction(8, 8, 16) ], + [ COMPRESSED_RGBA_ASTC_10x5_KHR, makeComputeBlockRectSizeFunction(10, 5, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR, makeComputeBlockRectSizeFunction(10, 5, 16) ], + [ COMPRESSED_RGBA_ASTC_10x6_KHR, makeComputeBlockRectSizeFunction(10, 6, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR, makeComputeBlockRectSizeFunction(10, 6, 16) ], + [ COMPRESSED_RGBA_ASTC_10x8_KHR, makeComputeBlockRectSizeFunction(10, 8, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR, makeComputeBlockRectSizeFunction(10, 8, 16) ], + [ COMPRESSED_RGBA_ASTC_10x10_KHR, makeComputeBlockRectSizeFunction(10, 10, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR, makeComputeBlockRectSizeFunction(10, 10, 16) ], + [ COMPRESSED_RGBA_ASTC_12x10_KHR, makeComputeBlockRectSizeFunction(12, 10, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR, makeComputeBlockRectSizeFunction(12, 10, 16) ], + [ COMPRESSED_RGBA_ASTC_12x12_KHR, makeComputeBlockRectSizeFunction(12, 12, 16) ], + [ COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR, makeComputeBlockRectSizeFunction(12, 12, 16) ], + + [ COMPRESSED_SRGB_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ], + [ COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, makeComputeBlockRectSizeFunction(4, 4, 8) ], + [ COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ], + [ COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, makeComputeBlockRectSizeFunction(4, 4, 16) ], + + [ COMPRESSED_RGBA_BPTC_UNORM_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ], + [ COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ], + [ COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ], + [ COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ], + + [ COMPRESSED_RED_RGTC1_EXT, makeComputeBlockRectSizeFunction( 4, 4, 8 ) ], + [ COMPRESSED_SIGNED_RED_RGTC1_EXT, makeComputeBlockRectSizeFunction( 4, 4, 8 ) ], + [ COMPRESSED_RED_GREEN_RGTC2_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ], + [ COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT, makeComputeBlockRectSizeFunction( 4, 4, 16 ) ], + ]); + + /** + * Gets the number of bytes per element for a given internalFormat / type + * @param {number} internalFormat The internalFormat parameter from texImage2D etc.. + * @param {number} type The type parameter for texImage2D etc.. + * @return {number} the number of bytes per element for the given internalFormat, type combo + * @memberOf module:twgl/textures + */ + function getBytesPerElementForInternalFormat(internalFormat, type) { + const info = getTextureInternalFormatInfo(internalFormat); + if (!info) { + throw "unknown internal format"; + } + if (info.type) { + const ndx = info.type.indexOf(type); + if (ndx < 0) { + throw new Error(`unsupported type ${type} for internalformat ${internalFormat}`); + } + return info.bytesPerElement[ndx]; + } + return info.bytesPerElement[0]; + } + + function getBytesForMipUncompressed(internalFormat, width, height, depth, type) { + const bytesPerElement = getBytesPerElementForInternalFormat(internalFormat, type); + return width * height * depth * bytesPerElement; + } + + function getBytesForMip(internalFormat, width, height, depth, type) { + const fn = compressedTextureFunctions.get(internalFormat); + return fn ? fn(width, height, depth) : getBytesForMipUncompressed(internalFormat, width, height, depth, type); + } + + function isTypedArray(v) { + return v && v.buffer && v.buffer instanceof ArrayBuffer; + } + + function isBufferSource(v) { + return isTypedArray(v) || v instanceof ArrayBuffer; + } + + function getDrawingbufferInfo(gl) { + return { + samples: gl.getParameter(gl.SAMPLES) || 1, + depthBits: gl.getParameter(gl.DEPTH_BITS), + stencilBits: gl.getParameter(gl.STENCIL_BITS), + contextAttributes: gl.getContextAttributes(), + }; + } + + function computeDepthStencilSize(drawingBufferInfo) { + const {depthBits, stencilBits} = drawingBufferInfo; + const depthSize = (depthBits + stencilBits + 7) / 8 | 0; + return depthSize === 3 ? 4 : depthSize; + } + + function computeDrawingbufferSize(gl, drawingBufferInfo) { + if (gl.isContextLost()) { + return 0; + } + const {samples} = drawingBufferInfo; + // this will need to change for hi-color support + const colorSize = 4; + const size = gl.drawingBufferWidth * gl.drawingBufferHeight; + const depthStencilSize = computeDepthStencilSize(drawingBufferInfo); + return size * colorSize + size * samples * colorSize + size * depthStencilSize; + } + + // I know this is not a full check + function isNumber(v) { + return typeof v === 'number'; + } + + /* + The MIT License (MIT) + + Copyright (c) 2021 Gregg Tavares + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + /* global console */ + /* global WebGL2RenderingContext */ + /* global WebGLUniformLocation */ + + //------------ [ from https://github.com/KhronosGroup/WebGLDeveloperTools ] + + /* + ** Copyright (c) 2012 The Khronos Group Inc. + ** + ** Permission is hereby granted, free of charge, to any person obtaining a + ** copy of this software and/or associated documentation files (the + ** "Materials"), to deal in the Materials without restriction, including + ** without limitation the rights to use, copy, modify, merge, publish, + ** distribute, sublicense, and/or sell copies of the Materials, and to + ** permit persons to whom the Materials are furnished to do so, subject to + ** the following conditions: + ** + ** The above copyright notice and this permission notice shall be included + ** in all copies or substantial portions of the Materials. + ** + ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + */ + + + const augmentedSet = new Set(); + + /** + * Given a WebGL context replaces all the functions with wrapped functions + * that call gl.getError after every command + * + * @param {WebGLRenderingContext|Extension} ctx The webgl context to wrap. + * @param {string} nameOfClass (eg, webgl, webgl2, OES_texture_float) + */ + function augmentAPI(ctx, nameOfClass, options = {}) { + + if (augmentedSet.has(ctx)) { + return ctx; + } + augmentedSet.add(ctx); + + const origGLErrorFn = options.origGLErrorFn || ctx.getError; + + function createSharedState(ctx) { + const drawingBufferInfo = getDrawingbufferInfo(ctx); + const sharedState = { + baseContext: ctx, + config: options, + apis: { + // custom extension + gman_webgl_memory: { + ctx: { + getMemoryInfo() { + const drawingbuffer = computeDrawingbufferSize(ctx, drawingBufferInfo); + return { + memory: { + ...memory, + drawingbuffer, + total: drawingbuffer + memory.buffer + memory.texture + memory.renderbuffer, + }, + resources: { + ...resources, + } + }; + }, + }, + }, + }, + resources: {}, + memory: { + texture: 0, + buffer: 0, + renderbuffer: 0, + }, + bindings: new Map(), + defaultVertexArray: {}, + webglObjectToMemory: new Map(), + }; + + const unRestorableAPIs = new Set([ + 'webgl', + 'webgl2', + 'webgl_lose_context', + ]); + + function resetSharedState() { + sharedState.bindings.clear(); + sharedState.webglObjectToMemory.clear(); + sharedState.webglObjectToMemory.set(sharedState.defaultVertexArray, {}); + sharedState.currentVertexArray = sharedState.defaultVertexArray; + [sharedState.resources, sharedState.memory].forEach(function(obj) { + for (let prop in obj) { + obj[prop] = 0; + } + }); + } + + function handleContextLost() { + // Issues: + // * all resources are lost. + // Solution: handled by resetSharedState + // * all functions are no-op + // Solutions: + // * swap all functions for noop + // (not so easy because some functions return values) + // * wrap all functions is a isContextLost check forwarder + // (slow? and same as above) + // * have each function manually check for context lost + // (simple but repetitive) + // * all extensions are lost + // Solution: For these we go through and restore all the functions + // on each extension + resetSharedState(); + sharedState.isContextLost = true; + + // restore all original functions for extensions since + // user will have to get new extensions. + for (const [name, {ctx, origFuncs}] of [...Object.entries(sharedState.apis)]) { + if (!unRestorableAPIs.has(name) && origFuncs) { + augmentedSet.delete(ctx); + for (const [funcName, origFn] of Object.entries(origFuncs)) { + ctx[funcName] = origFn; + } + delete apis[name]; + } + } + } + + function handleContextRestored() { + sharedState.isContextLost = false; + } + + if (ctx.canvas) { + ctx.canvas.addEventListener('webglcontextlost', handleContextLost); + ctx.canvas.addEventListener('webglcontextrestored', handleContextRestored); + } + + resetSharedState(); + return sharedState; + } + + const sharedState = options.sharedState || createSharedState(ctx); + options.sharedState = sharedState; + + const { + apis, + baseContext, + bindings, + config, + memory, + resources, + webglObjectToMemory, + } = sharedState; + + const origFuncs = {}; + + function noop() { + } + + function makeCreateWrapper(ctx, typeName, _funcName) { + const funcName = _funcName || `create${typeName[0].toUpperCase()}${typeName.substr(1)}`; + if (!ctx[funcName]) { + return; + } + resources[typeName] = 0; + return function(ctx, funcName, args, webglObj) { + if (sharedState.isContextLost) { + return null; + } + ++resources[typeName]; + webglObjectToMemory.set(webglObj, { + size: 0, + }); + }; + } + + function makeDeleteWrapper(typeName, fn = noop, _funcName) { + const funcName = _funcName || `delete${typeName[0].toUpperCase()}${typeName.substr(1)}`; + if (!ctx[funcName]) { + return; + } + return function(ctx, funcName, args) { + if (sharedState.isContextLost) { + return; + } + const [obj] = args; + const info = webglObjectToMemory.get(obj); + if (info) { + --resources[typeName]; + fn(obj, info); + // TODO: handle resource counts + webglObjectToMemory.delete(obj); + } + }; + } + + function updateRenderbuffer(target, samples, internalFormat, width, height) { + if (sharedState.isContextLost) { + return; + } + const obj = bindings.get(target); + if (!obj) { + throw new Error(`no renderbuffer bound to ${target}`); + } + const info = webglObjectToMemory.get(obj); + if (!info) { + throw new Error(`unknown renderbuffer ${obj}`); + } + + const bytesForMip = getBytesForMip(internalFormat, width, height, 1); + const newSize = bytesForMip * samples; + + memory.renderbuffer -= info.size; + info.size = newSize; + memory.renderbuffer += newSize; + } + + const ELEMENT_ARRAY_BUFFER = 0x8893; + + const UNSIGNED_BYTE = 0x1401; + const TEXTURE_CUBE_MAP = 0x8513; + const TEXTURE_2D_ARRAY = 0x8C1A; + const TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515; + const TEXTURE_CUBE_MAP_NEGATIVE_X = 0x8516; + const TEXTURE_CUBE_MAP_POSITIVE_Y = 0x8517; + const TEXTURE_CUBE_MAP_NEGATIVE_Y = 0x8518; + const TEXTURE_CUBE_MAP_POSITIVE_Z = 0x8519; + const TEXTURE_CUBE_MAP_NEGATIVE_Z = 0x851A; + + const TEXTURE_BASE_LEVEL = 0x813C; + const TEXTURE_MAX_LEVEL = 0x813D; + + const cubemapTargets = new Set([ + TEXTURE_CUBE_MAP_POSITIVE_X, + TEXTURE_CUBE_MAP_NEGATIVE_X, + TEXTURE_CUBE_MAP_POSITIVE_Y, + TEXTURE_CUBE_MAP_NEGATIVE_Y, + TEXTURE_CUBE_MAP_POSITIVE_Z, + TEXTURE_CUBE_MAP_NEGATIVE_Z, + ]); + + function isCubemapFace(target) { + return cubemapTargets.has(target); + } + + function getTextureInfo(target) { + target = isCubemapFace(target) ? TEXTURE_CUBE_MAP : target; + const obj = bindings.get(target); + if (!obj) { + throw new Error(`no texture bound to ${target}`); + } + const info = webglObjectToMemory.get(obj); + if (!info) { + throw new Error(`unknown texture ${obj}`); + } + return info; + } + + function updateMipLevel(info, target, level, internalFormat, width, height, depth, type) { + const oldSize = info.size; + const newMipSize = getBytesForMip(internalFormat, width, height, depth, type); + + const faceNdx = isCubemapFace(target) + ? target - TEXTURE_CUBE_MAP_POSITIVE_X + : 0; + + info.mips = info.mips || []; + info.mips[level] = info.mips[level] || []; + const mipFaceInfo = info.mips[level][faceNdx] || {}; + info.size -= mipFaceInfo.size || 0; + + mipFaceInfo.size = newMipSize; + mipFaceInfo.internalFormat = internalFormat; + mipFaceInfo.type = type; + mipFaceInfo.width = width; + mipFaceInfo.height = height; + mipFaceInfo.depth = depth; + + info.mips[level][faceNdx] = mipFaceInfo; + info.size += newMipSize; + + memory.texture -= oldSize; + memory.texture += info.size; + } + + function updateTexStorage(target, levels, internalFormat, width, height, depth) { + const info = getTextureInfo(target); + const numFaces = target === TEXTURE_CUBE_MAP ? 6 : 1; + const baseFaceTarget = target === TEXTURE_CUBE_MAP ? TEXTURE_CUBE_MAP_POSITIVE_X : target; for (let level = 0; level < levels; ++level) { + for (let face = 0; face < numFaces; ++face) { + updateMipLevel(info, baseFaceTarget + face, level, internalFormat, width, height, depth); + } + width = Math.ceil(Math.max(width / 2, 1)); + height = Math.ceil(Math.max(height / 2, 1)); + depth = target === TEXTURE_2D_ARRAY ? depth : Math.ceil(Math.max(depth / 2, 1)); + } + } + + function handleBindVertexArray(gl, funcName, args) { + if (sharedState.isContextLost) { + return; + } + const [va] = args; + sharedState.currentVertexArray = va ? va : sharedState.defaultVertexArray; + } + + function handleBufferBinding(target, obj) { + if (sharedState.isContextLost) { + return; + } + switch (target) { + case ELEMENT_ARRAY_BUFFER: + const info = webglObjectToMemory.get(sharedState.currentVertexArray); + info.elementArrayBuffer = obj; + break; + default: + bindings.set(target, obj); + break; + } + } + + const preChecks = {}; + const postChecks = { + // WebGL1 + // void bufferData(GLenum target, GLsizeiptr size, GLenum usage); + // void bufferData(GLenum target, [AllowShared] BufferSource? srcData, GLenum usage); + // WebGL2: + // void bufferData(GLenum target, [AllowShared] ArrayBufferView srcData, GLenum usage, GLuint srcOffset, + // optional GLuint length = 0); + bufferData(gl, funcName, args) { + if (sharedState.isContextLost) { + return; + } + const [target, src, /* usage */, srcOffset = 0, length = undefined] = args; + let obj; + switch (target) { + case ELEMENT_ARRAY_BUFFER: + { + const info = webglObjectToMemory.get(sharedState.currentVertexArray); + obj = info.elementArrayBuffer; + } + break; + default: + obj = bindings.get(target); + break; + } + if (!obj) { + throw new Error(`no buffer bound to ${target}`); + } + let newSize = 0; + if (length !== undefined) { + newSize = length * src.BYTES_PER_ELEMENT; + } else if (isBufferSource(src)) { + newSize = src.byteLength; + } else if (isNumber(src)) { + newSize = src; + } else { + throw new Error(`unsupported bufferData src type ${src}`); + } + + const info = webglObjectToMemory.get(obj); + if (!info) { + throw new Error(`unknown buffer ${obj}`); + } + + memory.buffer -= info.size; + info.size = newSize; + memory.buffer += newSize; + }, + + bindVertexArray: handleBindVertexArray, + bindVertexArrayOES: handleBindVertexArray, + + bindBuffer(gl, funcName, args) { + const [target, obj] = args; + handleBufferBinding(target, obj); + }, + + bindBufferBase(gl, funcName, args) { + const [target, ndx, obj] = args; + handleBufferBinding(target, obj); + }, + + bindBufferRange(gl, funcName, args) { + const [target, ndx, obj, offset, size] = args; + handleBufferBinding(target, obj); + }, + + bindRenderbuffer(gl, funcName, args) { + if (sharedState.isContextLost) { + return; + } + const [target, obj] = args; + bindings.set(target, obj); + }, + + bindTexture(gl, funcName, args) { + if (sharedState.isContextLost) { + return; + } + const [target, obj] = args; + bindings.set(target, obj); + }, + + // void gl.copyTexImage2D(target, level, internalformat, x, y, width, height, border); + copyTexImage2D(ctx, funcName, args) { + if (sharedState.isContextLost) { + return; + } + const [target, level, internalFormat, x, y, width, height, border] = args; + const info = getTextureInfo(target); + updateMipLevel(info, target, level, internalFormat, width, height, 1, UNSIGNED_BYTE); + }, + + createBuffer: makeCreateWrapper(ctx, 'buffer'), + createFramebuffer: makeCreateWrapper(ctx, 'framebuffer'), + createRenderbuffer: makeCreateWrapper(ctx, 'renderbuffer'), + createProgram: makeCreateWrapper(ctx, 'program'), + createQuery: makeCreateWrapper(ctx, 'query'), + createShader: makeCreateWrapper(ctx, 'shader'), + createSampler: makeCreateWrapper(ctx, 'sampler'), + createTexture: makeCreateWrapper(ctx, 'texture'), + createTransformFeedback: makeCreateWrapper(ctx, 'transformFeedback'), + createVertexArray: makeCreateWrapper(ctx, 'vertexArray'), + createVertexArrayOES: makeCreateWrapper(ctx, 'vertexArray', 'createVertexArrayOES'), + + // WebGL 1: + // void gl.compressedTexImage2D(target, level, internalformat, width, height, border, ArrayBufferView? pixels); + // + // Additionally available in WebGL 2: + // read from buffer bound to gl.PIXEL_UNPACK_BUFFER + // void gl.compressedTexImage2D(target, level, internalformat, width, height, border, GLsizei imageSize, GLintptr offset); + // void gl.compressedTexImage2D(target, level, internalformat, width, height, border, + // ArrayBufferView srcData, optional srcOffset, optional srcLengthOverride); + compressedTexImage2D(ctx, funcName, args) { + if (sharedState.isContextLost) { + return; + } + const [target, level, internalFormat, width, height] = args; + const info = getTextureInfo(target); + updateMipLevel(info, target, level, internalFormat, width, height, 1, UNSIGNED_BYTE); + }, + + // read from buffer bound to gl.PIXEL_UNPACK_BUFFER + // void gl.compressedTexImage3D(target, level, internalformat, width, height, depth, border, GLsizei imageSize, GLintptr offset); + // void gl.compressedTexImage3D(target, level, internalformat, width, height, depth, border, + // ArrayBufferView srcData, optional srcOffset, optional srcLengthOverride); + compressedTexImage3D(ctx, funcName, args) { + if (sharedState.isContextLost) { + return; + } + const [target, level, internalFormat, width, height, depth] = args; + const info = getTextureInfo(target); + updateMipLevel(info, target, level, internalFormat, width, height, depth, UNSIGNED_BYTE); + }, + + deleteBuffer: makeDeleteWrapper('buffer', function(obj, info) { + memory.buffer -= info.size; + }), + deleteFramebuffer: makeDeleteWrapper('framebuffer'), + deleteProgram: makeDeleteWrapper('program'), + deleteQuery: makeDeleteWrapper('query'), + deleteRenderbuffer: makeDeleteWrapper('renderbuffer', function(obj, info) { + memory.renderbuffer -= info.size; + }), + deleteSampler: makeDeleteWrapper('sampler'), + deleteShader: makeDeleteWrapper('shader'), + deleteSync: makeDeleteWrapper('sync'), + deleteTexture: makeDeleteWrapper('texture', function(obj, info) { + memory.texture -= info.size; + }), + deleteTransformFeedback: makeDeleteWrapper('transformFeedback'), + deleteVertexArray: makeDeleteWrapper('vertexArray'), + deleteVertexArrayOES: makeDeleteWrapper('vertexArray', noop, 'deleteVertexArrayOES'), + + fenceSync: function(ctx) { + if (sharedState.isContextLost) { + return; + } + if (!ctx.fenceSync) { + return; + } + resources.sync = 0; + return function(ctx, funcName, args, webglObj) { + ++resources.sync; + + webglObjectToMemory.set(webglObj, { + size: 0, + }); + }; + }(ctx), + + generateMipmap(ctx, funcName, args) { + if (sharedState.isContextLost) { + return; + } + const [target] = args; + const info = getTextureInfo(target); + const baseMipNdx = info.parameters ? info.parameters.get(TEXTURE_BASE_LEVEL) || 0 : 0; + const maxMipNdx = info.parameters ? info.parameters.get(TEXTURE_MAX_LEVEL) || 1024 : 1024; + let {width, height, depth, internalFormat, type} = info.mips[baseMipNdx][0]; + let level = baseMipNdx + 1; + + const numFaces = target === TEXTURE_CUBE_MAP ? 6 : 1; + const baseFaceTarget = target === TEXTURE_CUBE_MAP ? TEXTURE_CUBE_MAP_POSITIVE_X : target; while (level <= maxMipNdx && !(width === 1 && height === 1 && (depth === 1 || target === TEXTURE_2D_ARRAY))) { + width = Math.ceil(Math.max(width / 2, 1)); + height = Math.ceil(Math.max(height / 2, 1)); + depth = target === TEXTURE_2D_ARRAY ? depth : Math.ceil(Math.max(depth / 2, 1)); + for (let face = 0; face < numFaces; ++face) { + updateMipLevel(info, baseFaceTarget + face, level, internalFormat, width, height, depth, type); + } + ++level; + } + }, + + getSupportedExtensions(ctx, funcName, args, result) { + if (sharedState.isContextLost) { + return; + } + result.push('GMAN_webgl_memory'); + }, + + // void gl.renderbufferStorage(target, internalFormat, width, height); + // gl.RGBA4: 4 red bits, 4 green bits, 4 blue bits 4 alpha bits. + // gl.RGB565: 5 red bits, 6 green bits, 5 blue bits. + // gl.RGB5_A1: 5 red bits, 5 green bits, 5 blue bits, 1 alpha bit. + // gl.DEPTH_COMPONENT16: 16 depth bits. + // gl.STENCIL_INDEX8: 8 stencil bits. + // gl.DEPTH_STENCIL + renderbufferStorage(ctx, funcName, args) { + const [target, internalFormat, width, height] = args; + updateRenderbuffer(target, 1, internalFormat, width, height); + }, + + // void gl.renderbufferStorageMultisample(target, samples, internalFormat, width, height); + renderbufferStorageMultisample(ctx, funcName, args) { + const [target, samples, internalFormat, width, height] = args; + updateRenderbuffer(target, samples, internalFormat, width, height); + }, + + texImage2D(ctx, funcName, args) { + if (sharedState.isContextLost) { + return; + } + // WebGL1: + // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView? pixels); + // void gl.texImage2D(target, level, internalformat, format, type, ImageData? pixels); + // void gl.texImage2D(target, level, internalformat, format, type, HTMLImageElement? pixels); + // void gl.texImage2D(target, level, internalformat, format, type, HTMLCanvasElement? pixels); + // void gl.texImage2D(target, level, internalformat, format, type, HTMLVideoElement? pixels); + // void gl.texImage2D(target, level, internalformat, format, type, ImageBitmap? pixels// ); + + // WebGL2: + // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, GLintptr offset); + // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLCanvasElement source); + // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLImageElement source); + // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, HTMLVideoElement source); + // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageBitmap source); + // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ImageData source); + // void gl.texImage2D(target, level, internalformat, width, height, border, format, type, ArrayBufferView srcData, srcOffset); + let [target, level, internalFormat] = args; + let width; + let height; + let type; + if (args.length == 6) { + const src = args[5]; + width = src.width; + height = src.height; + type = args[4]; + } else { + width = args[3]; + height = args[4]; + type = args[7]; + } + + const info = getTextureInfo(target); + updateMipLevel(info, target, level, internalFormat, width, height, 1, type); + }, + + // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, GLintptr offset); + // + // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, HTMLCanvasElement source); + // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, HTMLImageElement source); + // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, HTMLVideoElement source); + // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ImageBitmap source); + // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ImageData source); + // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ArrayBufferView? srcData); + // void gl.texImage3D(target, level, internalformat, width, height, depth, border, format, type, ArrayBufferView srcData, srcOffset); + + texImage3D(ctx, funcName, args) { + if (sharedState.isContextLost) { + return; + } + let [target, level, internalFormat, width, height, depth, border, format, type] = args; + const info = getTextureInfo(target); + updateMipLevel(info, target, level, internalFormat, width, height, depth, type); + }, + + texParameteri(ctx, funcName, args) { + if (sharedState.isContextLost) { + return; + } + let [target, pname, value] = args; + const info = getTextureInfo(target); + info.parameters = info.parameters || new Map(); + info.parameters.set(pname, value); + }, + + // void gl.texStorage2D(target, levels, internalformat, width, height); + texStorage2D(ctx, funcName, args) { + let [target, levels, internalFormat, width, height] = args; + updateTexStorage(target, levels, internalFormat, width, height, 1); + }, + + // void gl.texStorage3D(target, levels, internalformat, width, height, depth); + texStorage3D(ctx, funcName, args) { + let [target, levels, internalFormat, width, height, depth] = args; + updateTexStorage(target, levels, internalFormat, width, height, depth); + }, + }; + + const extraWrappers = { + getExtension(ctx, propertyName) { + if (sharedState.isContextLost) { + return null; + } + const origFn = ctx[propertyName]; + ctx[propertyName] = function(...args) { + const extensionName = args[0].toLowerCase(); + const api = apis[extensionName]; + if (api) { + return api.ctx; + } + const ext = origFn.call(ctx, ...args); + if (ext) { + augmentAPI(ext, extensionName, {...options, origGLErrorFn}); + } + return ext; + }; + }, + }; + + // Makes a function that calls a WebGL function and then calls getError. + function makeErrorWrapper(ctx, funcName) { + const origFn = ctx[funcName]; + const preCheck = preChecks[funcName] || noop; + const postCheck = postChecks[funcName] || noop; + if (preCheck === noop && postChecks === noop) { + return; + } + ctx[funcName] = function(...args) { + preCheck(ctx, funcName, args); + const result = origFn.call(ctx, ...args); + postCheck(ctx, funcName, args, result); + return result; + }; + const extraWrapperFn = extraWrappers[funcName]; + if (extraWrapperFn) { + extraWrapperFn(ctx, funcName, origGLErrorFn); + } + } + + // Wrap each function + for (const propertyName in ctx) { + if (typeof ctx[propertyName] === 'function') { + origFuncs[propertyName] = ctx[propertyName]; + makeErrorWrapper(ctx, propertyName); + } + } + + apis[nameOfClass.toLowerCase()] = { ctx, origFuncs }; + } + + /* + The MIT License (MIT) + + Copyright (c) 2021 Gregg Tavares + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + /* global console */ + /* global document */ + /* global HTMLCanvasElement */ + /* global OffscreenCanvas */ + + function wrapGetContext(Ctor) { + const oldFn = Ctor.prototype.getContext; + Ctor.prototype.getContext = function(type, ...args) { + const ctx = oldFn.call(this, type, ...args); + // Using bindTexture to see if it's WebGL. Could check for instanceof WebGLRenderingContext + // but that might fail if wrapped by debugging extension + if (ctx && ctx.bindTexture) { + const config = {}; + augmentAPI(ctx, type, config); + ctx.getExtension('GMAN_webgl_memory'); + } + return ctx; + }; + } + + if (typeof HTMLCanvasElement !== 'undefined') { + wrapGetContext(HTMLCanvasElement); + } + if (typeof OffscreenCanvas !== 'undefined') { + wrapGetContext(OffscreenCanvas); + } + +}))); diff --git a/webgl_memory/src/extension.cpp b/webgl_memory/src/extension.cpp new file mode 100644 index 0000000..971f099 --- /dev/null +++ b/webgl_memory/src/extension.cpp @@ -0,0 +1,52 @@ + +#include + +#if defined(DM_PLATFORM_HTML5) +extern "C" +{ + const char* WebGLMemory_GetInfo(); +} + +static int GetInfo(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + const char *json = WebGLMemory_GetInfo(); + dmScript::JsonToLua(L, json, strlen(json)); + free((void*)json); + return 1; +} + +// Functions exposed to Lua +static const luaL_reg Ext_methods[] = { + { "get_info", GetInfo }, + /* Sentinel: */ + { NULL, NULL } +}; + +static void LuaInit(lua_State* L) +{ + int top = lua_gettop(L); + + // Register lua names + luaL_register(L, "webgl_memory", Ext_methods); + + lua_pop(L, 1); + assert(top == lua_gettop(L)); +} +#endif + +static dmExtension::Result InitializeExt(dmExtension::Params* params) +{ +#if defined(DM_PLATFORM_HTML5) + LuaInit(params->m_L); +#endif + + return dmExtension::RESULT_OK; +} + +static dmExtension::Result FinalizeExt(dmExtension::Params* params) +{ + return dmExtension::RESULT_OK; +} + +DM_DECLARE_EXTENSION(webgl_memory, "webgl_memory", 0, 0, InitializeExt, 0, 0, FinalizeExt)